diff --git a/.docker/Dockerfile b/.docker/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..97b8fdf2b5ae43dc96726e16ea21a2c6a1883fdb --- /dev/null +++ b/.docker/Dockerfile @@ -0,0 +1,37 @@ +FROM debian:bookworm +RUN apt-get update && \ + apt-get install \ + curl \ + git \ + openjdk-17-jdk-headless \ + python3-autopep8 \ + python3-pip \ + python3-pytest \ + python3-sphinx \ + -y +RUN pip3 install --break-system-packages pylint recommonmark sphinx-rtd-theme tox +COPY .docker/wait-for-it.sh /wait-for-it.sh +ARG PYLIB +ADD https://gitlab.indiscale.com/api/v4/projects/97/repository/commits/${PYLIB} \ + pylib_version.json +RUN git clone https://gitlab.indiscale.com/caosdb/src/caosdb-pylib.git && \ + cd caosdb-pylib && git checkout ${PYLIB} && pip3 install --break-system-packages . +ARG ADVANCED +ADD https://gitlab.indiscale.com/api/v4/projects/104/repository/commits/${ADVANCED} \ + advanced_version.json +RUN git clone https://gitlab.indiscale.com/caosdb/src/caosdb-advanced-user-tools.git && \ + cd caosdb-advanced-user-tools && git checkout ${ADVANCED} && pip3 install --break-system-packages .[h5-crawler] +COPY . /git + +# Delete .git because it is huge. +RUN rm -r /git/.git + +RUN cd /git/ && pip3 install --break-system-packages . + +WORKDIR /git/integrationtests +# wait for server, +CMD /wait-for-it.sh caosdb-server:10443 -t 500 -- \ + # ... install pycaosdb.ini the server-side scripts + cp /git/.docker/sss_pycaosdb.ini /scripting/home/.pycaosdb.ini && \ + # ... and run tests + pytest-3 . diff --git a/.docker/cert.sh b/.docker/cert.sh new file mode 100755 index 0000000000000000000000000000000000000000..628ba8dd9cc19f85a515a75cebd03b8981337bfd --- /dev/null +++ b/.docker/cert.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# ** header v3.0 +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2019 Daniel Hornung, Göttingen +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# ** end header + + +# Creates a directory `cert` and certificates in this directory. +# +# The hostname for which the certificate is created can be changed by setting +# the environment variable CAOSHOSTNAME. +# +# ## Overview of variables ## +# +# - CAOSHOSTNAME :: Hostname for the key (localhost) +# - KEYPW :: Password for the key (default ist CaosDBSecret) +# - KEYSTOREPW :: Password for the key store (same as KEYPW) +function cert() { + mkdir -p cert + cd cert + KEYPW="${KEYPW:-CaosDBSecret}" + CAOSHOSTNAME="${CAOSHOSTNAME:-localhost}" + KEYSTOREPW="${KEYPW:-}" + # NOTE: KEYPW and KEYSTOREPW are the same, due to Java limitations. + KEYPW="${KEYPW}" openssl genrsa -aes256 -out caosdb.key.pem \ + -passout env:KEYPW 2048 + # Certificate is for localhost + KEYPW="${KEYPW}" openssl req -new -x509 -key caosdb.key.pem \ + -out caosdb.cert.pem -passin env:KEYPW \ + -subj "/C=/ST=/L=/O=example/OU=example/CN=${CAOSHOSTNAME}" \ + -days 365 \ + -addext "subjectAltName = DNS:${CAOSHOSTNAME}" \ + -addext "certificatePolicies = 1.2.3.4" + KEYPW="${KEYPW}" KEYSTOREPW="$KEYSTOREPW" openssl pkcs12 -export \ + -inkey caosdb.key.pem -in caosdb.cert.pem -out all-certs.pkcs12 \ + -passin env:KEYPW -passout env:KEYPW + + keytool -importkeystore -srckeystore all-certs.pkcs12 -srcstoretype PKCS12 \ + -deststoretype pkcs12 -destkeystore caosdb.jks \ + -srcstorepass "${KEYPW}" \ + -destkeypass "${KEYPW}" -deststorepass "$KEYSTOREPW" + echo "Certificates successfuly created." +} + +cert diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..02ccac5c48e039a3374a0d169f3b355f897e45fc --- /dev/null +++ b/.docker/docker-compose.yml @@ -0,0 +1,43 @@ +version: '3.7' +services: + sqldb: + image: mariadb:10.4 + environment: + MYSQL_ROOT_PASSWORD: caosdb1234 + networks: + - caosnet + caosdb-server: + image: "$CI_REGISTRY/caosdb/src/caosdb-deploy:$CAOSDB_TAG" + user: 999:999 + depends_on: + - sqldb + networks: + - caosnet + volumes: + - type: bind + source: ./cert + target: /opt/caosdb/cert + - type: bind + source: "../integrationtests/test_data/extroot" + target: /opt/caosdb/mnt/extroot + - type: volume + source: scripting + target: /opt/caosdb/git/caosdb-server/scripting + - type: volume + source: authtoken + target: /opt/caosdb/git/caosdb-server/authtoken + ports: + # - "from_outside:from_inside" + - "10443:10443" + - "10080:10080" + environment: + DEBUG: 1 + CAOSDB_CONFIG_AUTHTOKEN_CONFIG: "conf/core/authtoken.example.yaml" + CAOSDB_CONFIG_TRANSACTION_BENCHMARK_ENABLED: "TRUE" + CAOSDB_CONFIG__CAOSDB_INTEGRATION_TEST_SUITE_KEY: 10b128cf8a1372f30aa3697466bb55e76974e0c16a599bb44ace88f19c8f61e2 +volumes: + scripting: + authtoken: +networks: + caosnet: + driver: bridge diff --git a/.docker/run.sh b/.docker/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..b0e1a716f28516b83043fb3fdb6594515a0bafd4 --- /dev/null +++ b/.docker/run.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +docker-compose -f tester.yml run tester +rv=$? +echo $rv > result diff --git a/.docker/sss_pycaosdb.ini b/.docker/sss_pycaosdb.ini new file mode 100644 index 0000000000000000000000000000000000000000..de2867f8dc66b3e81f10f35e40c36f9cb8591604 --- /dev/null +++ b/.docker/sss_pycaosdb.ini @@ -0,0 +1,9 @@ +; this is the pycaosdb.ini for the server-side-scripting home. +[Connection] +url = https://caosdb-server:10443 +cacert = /opt/caosdb/cert/caosdb.cert.pem +debug = 0 +timeout = 5000 + +[Misc] +sendmail = /usr/local/bin/sendmail_to_file diff --git a/.docker/tester.yml b/.docker/tester.yml new file mode 100644 index 0000000000000000000000000000000000000000..83db879c6072bfdea7b3212c833116b96bb54d0c --- /dev/null +++ b/.docker/tester.yml @@ -0,0 +1,26 @@ +version: '3.7' +services: + tester: + image: "$CI_REGISTRY_IMAGE" + networks: + - docker_caosnet + volumes: + - type: bind + source: ./cert + target: /cert + - type: volume + source: extroot + target: /extroot + - type: volume + source: scripting + target: /scripting + - type: volume + source: authtoken + target: /authtoken +networks: + docker_caosnet: + external: true +volumes: + scripting: + extroot: + authtoken: diff --git a/.docker/tester_pycaosdb.ini b/.docker/tester_pycaosdb.ini new file mode 100644 index 0000000000000000000000000000000000000000..2159dec250b3dcb2f16043d12bdbe73675e4d75c --- /dev/null +++ b/.docker/tester_pycaosdb.ini @@ -0,0 +1,31 @@ +; pycaosdb.ini for pytest test suites. + +[IntegrationTests] +; location of the scripting bin dir which is used for the test scripts from the +; server's perspective. +test_server_side_scripting.bin_dir.server = scripting/bin-debug/ +; location of the scripting bin dir which is used for the test scripts from the +; pyinttest's perspective. +test_server_side_scripting.bin_dir.local = /scripting/bin-debug/ + +; location of the files from the pyinttest perspective +test_files.test_insert_files_in_dir.local = /extroot/test_insert_files_in_dir/ +; location of the files from the caosdb_servers perspective +test_files.test_insert_files_in_dir.server = /opt/caosdb/mnt/extroot/test_insert_files_in_dir/ + +; location of the one-time tokens from the pyinttest's perspective +test_authentication.admin_token_crud = /authtoken/admin_token_crud.txt +test_authentication.admin_token_expired = /authtoken/admin_token_expired.txt +test_authentication.admin_token_3_attempts = /authtoken/admin_token_3_attempts.txt + + +[Connection] +url = https://caosdb-server:10443/ +username = admin +cacert = /cert/caosdb.cert.pem +debug = 0 + +password_method = plain +password = caosdb + +timeout = 500 diff --git a/.docker/wait-for-it.sh b/.docker/wait-for-it.sh new file mode 100755 index 0000000000000000000000000000000000000000..d69e99f1f13257b559dce2433de0515379663efa --- /dev/null +++ b/.docker/wait-for-it.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# License: +# From https://github.com/vishnubob/wait-for-it +# The MIT License (MIT) +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + WAITFORIT_BUSYTIMEFLAG="-t" + +else + WAITFORIT_ISBUSY=0 + WAITFORIT_BUSYTIMEFLAG="" +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..43301651fac17dc7a71dd736f9f54259bd56cafc --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*~ +.coverage +__pycache__ +.tox +*.egg-info +venv/ +build +/.env/ +/src/doc/_apidoc/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..1944b7062da4fab02dffe185215115e039514d92 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,216 @@ +# +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2018 Research Group Biomedical Physics, +# Max-Planck-Institute for Dynamics and Self-Organization Göttingen +# Copyright (C) 2019 Henrik tom Wörden +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +variables: + CI_REGISTRY_IMAGE: $CI_REGISTRY/caosdb/src/linkahead-python-package-template/testenv:$CI_COMMIT_REF_NAME + CI_REGISTRY_IMAGE_BASE: $CI_REGISTRY/caosdb/src/caosdb-pyinttest/base:latest + +stages: + - info + - setup + - cert + - style + - test + - deploy + +.env: &env + - echo "Pipeline triggered by $TRIGGERED_BY_REPO@$TRIGGERED_BY_REF ($TRIGGERED_BY_HASH)" + - echo "CI_REGISTRY_IMAGE_BASE = $CI_REGISTRY_IMAGE_BASE" + - echo "CI_REGISTRY_IMAGE = $CI_REGISTRY_IMAGE" + - echo "CAOSDB_TAG = $CAOSDB_TAG" + - echo "REFTAG = $REFTAG" + - echo "F_BRANCH = $F_BRANCH" + - echo "CI_COMMIT_REF_NAME = $CI_COMMIT_REF_NAME" + - ls -lah /image-cache/ + + - F_BRANCH=${F_BRANCH:-$CI_COMMIT_REF_NAME} + - echo $F_BRANCH + - if [[ "$REFTAG" == "" ]] ; then + if [[ "$F_BRANCH" == "dev" ]] ; then + REFTAG=dev; + fi; + fi + - REFTAG=${REFTAG:-dev_F_${F_BRANCH}} + + - echo $F_BRANCH + + - if [[ "$CAOSDB_TAG" == "" ]]; then + CAOSDB_TAG=${REFTAG}; + fi + - echo $CAOSDB_TAG + +info: + tags: [cached-dind] + image: docker:20.10 + stage: info + needs: [] + script: + - *env + +unittest_py3.11: + tags: [cached-dind] + stage: test + image: $CI_REGISTRY_IMAGE + script: + - python3 -c "import sys; assert sys.version.startswith('3.11')" + - tox + +unittest_py3.8: + tags: [cached-dind] + stage: test + image: python:3.8 + script: &python_test_script + # install dependencies + - pip install pytest pytest-cov + - pip install . + # actual test + - pytest --cov=linkahead_python_package_template -vv ./unittests + +unittest_py3.9: + tags: [cached-dind] + stage: test + image: python:3.11 + script: *python_test_script + +unittest_py3.10: + tags: [cached-dind] + stage: test + image: python:3.10 + script: *python_test_script + +unittest_py3.12: + tags: [cached-dind] + stage: test + image: python:3.12 + script: *python_test_script + +unittest_py3.13: + allow_failure: true + tags: [cached-dind] + stage: test + image: python:3.13-rc + script: *python_test_script + +build-testenv: + tags: [cached-dind] + image: docker:20.10 + stage: setup + timeout: 2h + only: + - schedules + - web + - pushes + needs: [] + script: + - df -h + - command -v wget + - if [ -z "$PYLIB" ]; then + if echo "$CI_COMMIT_REF_NAME" | grep -c "^f-" ; then + echo "Check if pylib has branch $CI_COMMIT_REF_NAME" ; + if wget https://gitlab.indiscale.com/api/v4/projects/97/repository/branches/${CI_COMMIT_REF_NAME} ; then + PYLIB=$CI_COMMIT_REF_NAME ; + fi; + fi; + fi; + - PYLIB=${PYLIB:-dev} + - echo $PYLIB + + - if [ -z "$ADVANCED" ]; then + if echo "$CI_COMMIT_REF_NAME" | grep -c "^f-" ; then + echo "Check if advanced user tools have branch $CI_COMMIT_REF_NAME" ; + if wget https://gitlab.indiscale.com/api/v4/projects/104/repository/branches/${CI_COMMIT_REF_NAME} ; then + ADVANCED=$CI_COMMIT_REF_NAME ; + fi; + fi; + fi; + - ADVANCED=${ADVANCED:-dev} + - echo $ADVANCED + + - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY + # use here general latest or specific branch latest... + - docker build + --build-arg PYLIB=${PYLIB} + --build-arg ADVANCED=${ADVANCED:dev} + --file .docker/Dockerfile + -t $CI_REGISTRY_IMAGE . + - docker push $CI_REGISTRY_IMAGE + - docker save $CI_REGISTRY_IMAGE > /image-cache/linkahead-python-package-template-testenv-${CI_COMMIT_REF_NAME}.tar + +cert: + tags: [docker] + stage: cert + image: $CI_REGISTRY_IMAGE + needs: + - job: build-testenv + optional: true + artifacts: + paths: + - .docker/cert/ + expire_in: 1 week + script: + - cd .docker + - CAOSHOSTNAME=caosdb-server ./cert.sh + +code-style: + tags: [docker] + stage: style + image: $CI_REGISTRY_IMAGE + needs: + - job: build-testenv + optional: true + script: + - autopep8 -r --diff --exit-code . + allow_failure: true + +pylint: + tags: [docker] + stage: style + image: $CI_REGISTRY_IMAGE + needs: + - job: build-testenv + optional: true + allow_failure: true + script: + - pylint --unsafe-load-any-extension=y -d all -e E,F src/linkahead_python_package_template + +# Build the sphinx documentation and make it ready for deployment by Gitlab Pages +# Special job for serving a static website. See https://docs.gitlab.com/ee/ci/yaml/README.html#pages +# Based on: https://gitlab.indiscale.com/caosdb/src/caosdb-pylib/-/ci/editor?branch_name=main +pages_prepare: &pages_prepare + tags: [ cached-dind ] + stage: deploy + needs: + - job: build-testenv + image: $CI_REGISTRY_IMAGE + only: + refs: + - /^release-.*$/i + script: + - echo "Deploying documentation" + - make doc + - cp -r build/doc/html public + artifacts: + paths: + - public +pages: + <<: *pages_prepare + only: + refs: + - main diff --git a/README.md b/README.md index 9134834df5f52e225585d15da6736246dfa8cd6b..c37d2e63b2ad4e65ba5524a3fed7116f011e6c39 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,24 @@ This repo contains all the custom code related to the loan management in LinkAhead. It is to be checked out as a custom-directory and needs to be specified in your profile.yml. + +## Usage +Mount the custom folder by adding it to the `profile.yml`. E.g. +``` + custom: + # Customizations for the demo server are stored here; e.g. the tour + - "./custom" # typical folder for instance specific customization + - "./loan/loan-custom" # adds the loan module +``` + +Also, you need to add the python package to the scripting interface in the +profile: +``` +TODO +``` + + + +## Integration tests +Run `pytest .` in the integration tests folder + diff --git a/caosdb-server/scripting/bin/test/loan_management/box_loan.py b/caosdb-server/scripting/bin/test/loan_management/box_loan.py deleted file mode 120000 index 034b4c7dfca3b7d5c0e6ac80ad830c176658934a..0000000000000000000000000000000000000000 --- a/caosdb-server/scripting/bin/test/loan_management/box_loan.py +++ /dev/null @@ -1 +0,0 @@ -../../loan_management/box_loan.py \ No newline at end of file diff --git a/caosdb-server/scripting/bin/test/loan_management/manual_return.py b/caosdb-server/scripting/bin/test/loan_management/manual_return.py deleted file mode 120000 index f0eab4f92842389daf8a57e71480f8238d93e421..0000000000000000000000000000000000000000 --- a/caosdb-server/scripting/bin/test/loan_management/manual_return.py +++ /dev/null @@ -1 +0,0 @@ -../../loan_management/manual_return.py \ No newline at end of file diff --git a/caosdb-server/scripting/bin/test/loan_management/request_loan.py b/caosdb-server/scripting/bin/test/loan_management/request_loan.py deleted file mode 120000 index 0c060509fbb552f9e92d8b890acbbdb1db3e2e7c..0000000000000000000000000000000000000000 --- a/caosdb-server/scripting/bin/test/loan_management/request_loan.py +++ /dev/null @@ -1 +0,0 @@ -../../loan_management/request_loan.py \ No newline at end of file diff --git a/caosdb-server/scripting/bin/test/loan_management/request_return.py b/caosdb-server/scripting/bin/test/loan_management/request_return.py deleted file mode 120000 index b080e7193350a231c118aa9d493966ca5ffe45d8..0000000000000000000000000000000000000000 --- a/caosdb-server/scripting/bin/test/loan_management/request_return.py +++ /dev/null @@ -1 +0,0 @@ -../../loan_management/request_return.py \ No newline at end of file diff --git a/caosdb-server/scripting/bin/test/loan_management/test_box_loan.py b/caosdb-server/scripting/bin/test/loan_management/test_box_loan.py deleted file mode 100644 index a1b17f253e8014b855a9462d3e8d3dc30cc7e1dc..0000000000000000000000000000000000000000 --- a/caosdb-server/scripting/bin/test/loan_management/test_box_loan.py +++ /dev/null @@ -1,99 +0,0 @@ -from os.path import abspath, dirname, join -from pytest import raises -from caosdb import get_connection, configure_connection -from caosdb.connection.mockup import (MockUpServerConnection, MockUpResponse) -from box_loan import (_caller, create_person, query_person, get_person, - EMAIL, FIRST_NAME, LAST_NAME, PERSON, - assert_date_in_future, DataError, EmailPatternError, - assert_email_pattern) - - -def setup(): - configure_connection(url="unittests", username="testuser", - password_method="plain", - password="testpassword", timeout=200, - implementation=MockUpServerConnection) - - -def get_data_example(): - return abspath(join(dirname(__file__), "request_loan_form.json")) - - -def test_caller(): - args = [get_data_example()] - - def test_func(data): - assert data["box"] == 2345, "should contain the data from json" - return 1337 - - assert _caller(test_func, args) == 1337 - - -def test_create_person(): - p = create_person("anna", "lytik", "a@b.com") - assert p.get_parents()[0].name == PERSON.name - assert p.get_property(FIRST_NAME.name).value == "anna" - assert p.get_property(LAST_NAME.name).value == "lytik" - assert p.get_property(EMAIL.name).value == "a@b.com" - - -def test_query_person(): - connection = get_connection() - entities = ('<Response><Query results="1"/>' - '<RecordType id="1234"/></Response>') - - def query_resource(**kwargs): - query = kwargs["path"].split("query=")[1] - assert query.startswith("FIND%20RECORD%20" + PERSON.name) - return MockUpResponse(200, {}, entities) - connection._delegate_connection.resources.append(query_resource) - - p = query_person("petri", "schale", "c@d.com") - assert p.id == 1234 - - -def test_get_person_with_update(): - connection = get_connection() - person_xml = ( - '<Response>{query}' - '<Record id="1234">' - '<Property name="email">{email}</Property>' - '</Record>' - '</Response>') - - def query_resource(**kwargs): - if kwargs["method"] == "GET": - return MockUpResponse(200, {}, - person_xml.format( - query='<Query results="1"/>', - email="old@email")) - else: - return MockUpResponse(200, {}, - person_xml.format( - query='', - email="new@email")) - connection._delegate_connection.resources.append(query_resource) - - p = get_person("firstname", None, "old@email") - assert p.get_property(EMAIL.name).value == "new@email" - - -def test_create_person_with_wrong_email_pattern(): - with raises(EmailPatternError): - assert_email_pattern("@asdf") - with raises(EmailPatternError): - assert_email_pattern("asdf") - with raises(EmailPatternError): - assert_email_pattern("asdfa.de") - with raises(EmailPatternError): - assert_email_pattern("sdfg@sdfg") - - # the following shoudl be ok - assert_email_pattern("a@b.da") - assert_email_pattern("\"#\"@ö.de") - - -def test_assert_date_in_future(): - assert assert_date_in_future("2050-01-01") is None - with raises(DataError): - assert_date_in_future("1971-01-01") diff --git a/caosdb-server/scripting/bin/test/loan_management/test_request_loan.py b/caosdb-server/scripting/bin/test/loan_management/test_request_loan.py deleted file mode 100644 index d13537bd954ed4aa9d21be36785ee7d27ab235a7..0000000000000000000000000000000000000000 --- a/caosdb-server/scripting/bin/test/loan_management/test_request_loan.py +++ /dev/null @@ -1,102 +0,0 @@ -from os.path import abspath, dirname, join -from pytest import raises -from caosdb import get_connection, configure_connection, Record -from caosdb.connection.mockup import (MockUpServerConnection, MockUpResponse) -from caosadvancedtools.serverside.helper import get_data -from box_loan import (PERSON, FIRST_NAME, EMAIL, BOX, BORROWER, - EXPECTED_RETURN, EXHAUST_CONTENTS, COMMENT, - DESTINATION, F_FIRST_NAME, F_EMAIL, - F_EXPECTED_RETURN_DATE, DataError, BOX_NUMBER) -from request_loan import (create_loan, _issue_loan_request) - - -def setup(): - configure_connection(url="unittests", username="testuser", - password_method="plain", - password="testpassword", timeout=200, - implementation=MockUpServerConnection) - - -def get_data_example(): - return abspath(join(dirname(__file__), "request_loan_form.json")) - - -def test_issue_loan_request_with_wrong_return_date(): - data = get_data(get_data_example()) - data[F_EXPECTED_RETURN_DATE] = "1983-02-03" - with raises(DataError): - _issue_loan_request(data) - - -def test_create_loan(): - borrower = Record(name="Person1") - l = create_loan(1234, - borrower, - "2020-03-23", - False, - "blablabla", - "my office") - assert l.get_property(BOX.name).value == 1234 - assert l.get_property(BORROWER.name).value == borrower - assert l.get_property(EXPECTED_RETURN.name).value == "2020-03-23" - assert l.get_property(EXHAUST_CONTENTS.name).value == False - assert l.get_property(COMMENT.name).value == "blablabla" - assert l.get_property(DESTINATION.name).value == "my office" - - -def test_issue_loan_request(): - data = get_data(get_data_example()) - person_xml = ( - '<Response>{{query}}' - '<Record id="1234">' - '<Property name="{FN}">{FIRST_NAME}</Property>' - '<Property name="{EMAIL}">{{email}}</Property>' - '</Record>' - '</Response>').format(FN=FIRST_NAME.name, EMAIL=EMAIL.name, - FIRST_NAME=data[F_FIRST_NAME]) - box_xml = ( - '<Response>{{query}}' - '<Record id="2345">' - '<Property name="{BN}">{BOXNUM}</Property>' - '</Record>' - '</Response>').format(BN=BOX_NUMBER.name, BOXNUM="0815") - - def query_resource(**kwargs): - if kwargs["method"] == "GET" and BOX.name in kwargs["path"]: - query = kwargs["path"].split("query=")[1] - assert query.startswith("FIND%20RECORD%20" + BOX.name) - return MockUpResponse(200, {}, - box_xml.format( - query='<Query results="1"/>')) - if kwargs["method"] == "GET" and PERSON.name in kwargs["path"]: - query = kwargs["path"].split("query=")[1] - assert query.startswith("FIND%20RECORD%20" + PERSON.name) - return MockUpResponse(200, {}, - person_xml.format( - query='<Query results="1"/>', - email="old@email")) - elif kwargs["method"] == "PUT": - assert data[F_EMAIL] in kwargs["body"].decode("utf-8") - return MockUpResponse(200, {}, - person_xml.format( - query='', - email=data[F_EMAIL])) - else: - assert "" in kwargs["body"].decode("utf-8") - body = kwargs["body"].decode( - "utf-8").replace('"-1"', '"4567"').replace("Insert", "Response") - return MockUpResponse(200, {}, body) - - connection = get_connection() - connection._delegate_connection.resources.append(query_resource) - - borrower, loan, box = _issue_loan_request(data) - - assert str(loan.id) == "4567" - assert str(loan.get_property(BOX).value) == "2345" - assert str(loan.get_property(BORROWER).value) == str(borrower.id) - assert str(borrower.id) == "1234" - assert borrower.get_property(EMAIL).value == data[F_EMAIL] - assert borrower.get_property(FIRST_NAME).value == data[F_FIRST_NAME] - assert str(box.id) == "2345" - assert box.get_property(BOX_NUMBER).value == "0815" diff --git a/caosdb-server/scripting/bin/test/loan_management/test_request_return.py b/caosdb-server/scripting/bin/test/loan_management/test_request_return.py deleted file mode 100644 index fa7f52c7379ec9e8066740573ecdb0289413af82..0000000000000000000000000000000000000000 --- a/caosdb-server/scripting/bin/test/loan_management/test_request_return.py +++ /dev/null @@ -1,134 +0,0 @@ -from os.path import abspath, dirname, join -from pytest import raises -from caosdb import get_connection, configure_connection -from caosdb.connection.mockup import (MockUpServerConnection, MockUpResponse) -from caosadvancedtools.serverside.helper import get_data -from box_loan import (FIRST_NAME, EMAIL, LAST_NAME, F_FIRST_NAME, - F_LAST_NAME, F_EMAIL, EXPECTED_RETURN, LENT, - RETURN_REQUESTED, PERSON, BORROWER, BOX, - LOAN_REQUESTED, StateError, F_EXPECTED_RETURN_DATE, - DataError, LOAN) -from request_return import get_loan, _issue_return_request - - -def setup(): - configure_connection(url="unittests", username="testuser", - password_method="plain", - password="testpassword", timeout=200, - implementation=MockUpServerConnection) - - -def get_data_example(): - return abspath(join(dirname(__file__), "request_loan_form.json")) - - -def append_resource(resource): - connection = get_connection() - connection._delegate_connection.resources.append(resource) - - -def test_get_loan(): - entities = '<Response><Query results="1"/><Record id="1234"/></Response>' - - def resource(**kwargs): - assert "RECORD%20Loan%20WITH%20ID%20%3D%201234" in kwargs["path"] - return MockUpResponse(200, {}, entities) - append_resource(resource) - - loan = get_loan(1234) - assert loan.id == 1234 - - -def test_return_request_with_wrong_return_date(): - data = get_data(get_data_example()) - data[F_EXPECTED_RETURN_DATE] = "1983-02-03" - with raises(DataError): - _issue_return_request(data) - - data[F_EXPECTED_RETURN_DATE] = "asdf" - with raises(DataError): - _issue_return_request(data) - - data[F_EXPECTED_RETURN_DATE] = "" - with raises(DataError): - _issue_return_request(data) - - del data[F_EXPECTED_RETURN_DATE] - with raises(DataError): - _issue_return_request(data) - - -def test_return_request_with_wrong_loan_states(): - data = get_data(get_data_example()) - loan_xml = ( - '<Response><Query results="1"/><Record id="12345">' - '<Parent name="{LO}"/>' - '<Property name="{ER}">1985-05-21</Property>' - '<Property name="{BOX}">2345</Property>' - '<Property name="{BOR}">1234</Property>' - '<Property name="{L_REQ}">2020-03-02</Property>' - '</Record></Response>').format(ER=EXPECTED_RETURN.name, - BOX=BOX.name, BOR=BORROWER.name, - L_REQ=LOAN_REQUESTED.name, - LO=LOAN.name) - - def resource(**kwargs): - return MockUpResponse(200, {}, loan_xml) - append_resource(resource) - - with raises(StateError): - _issue_return_request(data) - - -def test_issue_return_request(): - data = get_data(get_data_example()) - person_xml = ( - '<Response><Query results="1"/>' - '<Record id="1234">' - '<Property name="{FN}">{FIRST_NAME}</Property>' - '<Property name="{LN}">{LAST_NAME}</Property>' - '<Property name="{EM}">{EMAIL}</Property>' - '</Record>' - '</Response>').format(FN=FIRST_NAME.name, EM=EMAIL.name, - LN=LAST_NAME.name, FIRST_NAME=data[F_FIRST_NAME], - LAST_NAME=data[F_LAST_NAME], EMAIL=data[F_EMAIL]) - loan_xml = ( - '<Response><Query results="1"/><Record id="12345">' - '<Parent name="{LO}"/>' - '<Property name="{ER}">1985-05-21</Property>' - '<Property name="{BOX}">2345</Property>' - '<Property name="{BOR}">1234</Property>' - '<Property name="{LENT}">2020-03-02</Property>' - '{{RR}}' - '</Record></Response>').format(ER=EXPECTED_RETURN.name, BOX=BOX.name, - BOR=BORROWER.name, LENT=LENT.name, - LO=LOAN.name) - - def resource(**kwargs): - if kwargs["method"] == "GET": - if "12345" in kwargs["path"]: - # retrieve loan - return MockUpResponse(200, {}, - loan_xml.format(RR="")) - - query = kwargs["path"].split("query=")[1] - assert query.startswith("FIND%20RECORD%20" + PERSON.name) - return MockUpResponse(200, {}, person_xml) - elif kwargs["method"] == "PUT": - assert RETURN_REQUESTED.name in kwargs["body"].decode("utf-8") - p = '<Property name="{RR}">asdf</Property>'.format( - RR=RETURN_REQUESTED.name) - return MockUpResponse(200, {}, loan_xml.format(RR=p)) - - append_resource(resource) - - returner, loan = _issue_return_request(data) - - assert loan.id == 12345 - assert loan.get_property(BOX.name).value == "2345" - assert loan.get_property(BORROWER.name).value == str(returner.id) - assert loan.get_property(RETURN_REQUESTED.name).value == "asdf" - - assert returner.id == 1234 - assert returner.get_property(EMAIL.name).value == data[F_EMAIL] - assert returner.get_property(EMAIL.name).value == data[F_EMAIL] diff --git a/caosdb-server/caosdb-webui/src/ext/html/forms/loan-forms/borrow_checkout.html b/loan-custom/caosdb-server/caosdb-webui/src/ext/html/forms/loan-forms/borrow_checkout.html similarity index 100% rename from caosdb-server/caosdb-webui/src/ext/html/forms/loan-forms/borrow_checkout.html rename to loan-custom/caosdb-server/caosdb-webui/src/ext/html/forms/loan-forms/borrow_checkout.html diff --git a/caosdb-server/caosdb-webui/src/ext/html/forms/loan-forms/return_box.html b/loan-custom/caosdb-server/caosdb-webui/src/ext/html/forms/loan-forms/return_box.html similarity index 100% rename from caosdb-server/caosdb-webui/src/ext/html/forms/loan-forms/return_box.html rename to loan-custom/caosdb-server/caosdb-webui/src/ext/html/forms/loan-forms/return_box.html diff --git a/caosdb-server/caosdb-webui/src/ext/js/box_loan.js b/loan-custom/caosdb-server/caosdb-webui/src/ext/js/box_loan.js similarity index 96% rename from caosdb-server/caosdb-webui/src/ext/js/box_loan.js rename to loan-custom/caosdb-server/caosdb-webui/src/ext/js/box_loan.js index 4f567a92a993e7343aabcdf98a157417b2c780b8..de85db38d1f34c17303cc111e2ac42a4cf45dac7 100644 --- a/caosdb-server/caosdb-webui/src/ext/js/box_loan.js +++ b/loan-custom/caosdb-server/caosdb-webui/src/ext/js/box_loan.js @@ -1,3 +1,24 @@ +/* + * This file is a part of the LinkAhead Project. + * + * Copyright (C) 2020-2024 Henrik tom Wörden (h.tomwoerden@indiscale.com) + * Copyright (C) 2020-2024 IndiScale GmbH (info@indiscale.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ + /** * Return the formatted date of today. */ diff --git a/loan-custom/caosdb-server/scripting/bin/loan_management/accept_loan_request.py b/loan-custom/caosdb-server/scripting/bin/loan_management/accept_loan_request.py new file mode 100755 index 0000000000000000000000000000000000000000..55678576e624bc31bef164a23695ccad47195ad1 --- /dev/null +++ b/loan-custom/caosdb-server/scripting/bin/loan_management/accept_loan_request.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# encoding: utf-8 +# +# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> +# Copyright (C) 2024 Henrik tom Wörden (h.tomwoerden@indiscale.com) +# Copyright (C) 2024 IndiScale GmbH (info@indiscale.com) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# +""" +Accept a loan request. +""" +from loan.box_loan import main +from loan.accept_loan_request import accept_loan_request + +if __name__ == "__main__": + main(accept_loan_request) diff --git a/loan-custom/caosdb-server/scripting/bin/loan_management/accept_return_request.py b/loan-custom/caosdb-server/scripting/bin/loan_management/accept_return_request.py new file mode 100755 index 0000000000000000000000000000000000000000..e3f2826baa63733d72ec2547a9df93b2ef6d04d0 --- /dev/null +++ b/loan-custom/caosdb-server/scripting/bin/loan_management/accept_return_request.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# encoding: utf-8 +# +# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> +# Copyright (C) 2024 Henrik tom Wörden (h.tomwoerden@indiscale.com) +# Copyright (C) 2024 IndiScale GmbH (info@indiscale.com) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# +""" +Accept a return request. +""" +from loan.box_loan import main +from loan.accept_return_request import accept_return_request + +if __name__ == "__main__": + main(accept_return_request) diff --git a/loan-custom/caosdb-server/scripting/bin/loan_management/confirm_loan.py b/loan-custom/caosdb-server/scripting/bin/loan_management/confirm_loan.py new file mode 100755 index 0000000000000000000000000000000000000000..ae4c05176027115f21f59b413cffe479f5475f0b --- /dev/null +++ b/loan-custom/caosdb-server/scripting/bin/loan_management/confirm_loan.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# encoding: utf-8 +# +# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> +# Copyright (C) 2024 Henrik tom Wörden (h.tomwoerden@indiscale.com) +# Copyright (C) 2024 IndiScale GmbH (info@indiscale.com) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# +""" +Confirm a loan. +""" +from loan.box_loan import main +from loan.confirm_loan import confirm_loan + +if __name__ == "__main__": + main(confirm_loan) diff --git a/loan-custom/caosdb-server/scripting/bin/loan_management/manual_return.py b/loan-custom/caosdb-server/scripting/bin/loan_management/manual_return.py new file mode 100755 index 0000000000000000000000000000000000000000..f1ed64b7a5cd66e83b4d1871ac6c25f9f643d00c --- /dev/null +++ b/loan-custom/caosdb-server/scripting/bin/loan_management/manual_return.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# encoding: utf-8 +# +# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> +# Copyright (C) 2024 Henrik tom Wörden (h.tomwoerden@indiscale.com) +# Copyright (C) 2024 IndiScale GmbH (info@indiscale.com) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# +""" +Manually return a box. +""" +from loan.box_loan import main +from loan.manual_return import manual_return + +if __name__ == "__main__": + main(manual_return) diff --git a/loan-custom/caosdb-server/scripting/bin/loan_management/reject_return_request.py b/loan-custom/caosdb-server/scripting/bin/loan_management/reject_return_request.py new file mode 100755 index 0000000000000000000000000000000000000000..fc7ac40c822b6bd3b7c2c307fdf4a574658506ca --- /dev/null +++ b/loan-custom/caosdb-server/scripting/bin/loan_management/reject_return_request.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# encoding: utf-8 +# +# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# +""" +Reject a return request. +""" +from loan.box_loan import main +from loan.reject_return_request import reject_return_request + +if __name__ == "__main__": + main(reject_return_request) diff --git a/loan-custom/caosdb-server/scripting/bin/loan_management/request_loan.py b/loan-custom/caosdb-server/scripting/bin/loan_management/request_loan.py new file mode 100755 index 0000000000000000000000000000000000000000..1457a15599d7796a21f6f5b83fcbb19d6834f343 --- /dev/null +++ b/loan-custom/caosdb-server/scripting/bin/loan_management/request_loan.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# encoding: utf-8 +# +# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# +""" +Creates a loan request using information provided by a web formular. +""" +from loan.box_loan import main +from loan.request_loan import issue_loan_request + +if __name__ == "__main__": + main(issue_loan_request) diff --git a/loan-custom/caosdb-server/scripting/bin/loan_management/request_return.py b/loan-custom/caosdb-server/scripting/bin/loan_management/request_return.py new file mode 100755 index 0000000000000000000000000000000000000000..55bccb08508d85fe180eb7b4350d03fe13c81e8f --- /dev/null +++ b/loan-custom/caosdb-server/scripting/bin/loan_management/request_return.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# encoding: utf-8 +# +# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# +""" +Creates a return request using information provided by a web formular. + +A return request is a loan record with a returnRequested property. +""" +from loan.box_loan import main +from loan.request_return import issue_return_request + +if __name__ == "__main__": + main(issue_return_request) diff --git a/loanpy/CHANGELOG.md b/loanpy/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..dd4609543d8988a0a2bf0f35846a1cc80beb55f1 --- /dev/null +++ b/loanpy/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] ## + +### Added ### + +### Changed ### + +### Deprecated ### + +### Removed ### + +### Fixed ### + +### Security ### + +### Documentation ### diff --git a/loanpy/LICENSE b/loanpy/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..ce8d9f20bb5ddc2a66a21364acadb90e1c5abcea --- /dev/null +++ b/loanpy/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + Check SFS + Copyright (C) 2020 Alexander Schlemmer + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<http://www.gnu.org/licenses/>. diff --git a/loanpy/Makefile b/loanpy/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..b8bc521c7f9046dbcbea56c08a26903ba924acab --- /dev/null +++ b/loanpy/Makefile @@ -0,0 +1,48 @@ +# ** header v3.0 +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2020 IndiScale GmbH <info@indiscale.com> +# Copyright (C) 2020 Daniel Hornung <d.hornung@indiscale.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# ** end header + +# This Makefile is a wrapper for several other scripts. + +.PHONY: help + +help: + @echo 'Type `make doc` for documentation, or `make install` for (local) installation.' + +doc: + $(MAKE) -C src/doc html + +install: + @echo "Not implemented yet, use pip for installation." + +check: style lint +.PHONY: check + +style: + pycodestyle --count src unittests +.PHONY: style + +lint: + pylint --unsafe-load-any-extension=y -d all -e E,F src/linkahead_python_package_template +.PHONY: lint + +unittest: + tox -r +.PHONY: unittest diff --git a/loanpy/README.md b/loanpy/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4e457007fa2dc3a3b3d241183ef7d2e7e5b69e79 --- /dev/null +++ b/loanpy/README.md @@ -0,0 +1,50 @@ +# LinkAhead Python Package Template + +This Repo serves as a template for LinkAhead related Python packages, e.g., +custom crawler converters. + +## Usage + +To create a new Python package, fork this repo and change the names in the `src` +directory, `pyproject.toml`, `Makefile`, `src/doc/Makefile`, `src/doc/conf.py` +and `tox.ini`. Also edit the `authors` field therein. If applicable, create a +`citation.cff` for the new project. + +Also update this `README.md` to contain actual package information including +license, copyright, conttributors, etc. See [Pylib's +README.md](https://gitlab.com/linkahead/linkahead-pylib/-/blob/main/README.md?ref_type=heads) +as an example. + +Then, write your Python code in `/src/<package_name>`, and add unit tests in +`unittests`. Documentation goes to `/src/doc`. + +If you want to publish the result on PyPi, there is a release.sh helper +script. Make sure that your `pyproject.toml` contains the correct and complete +metadata and check the `RELEASE_GUIDELINES.md` + +### Unit Tests + +Run `tox` or alternatively `pytest unittests/`. + +### Code style and liniting + +Run `make style lint` after installing the dependencies listed below. + +### Documentation + +Run `make doc` after installing the dependencies listed below. + +## Dependencies + +Package and optional dependencies are declared in the `pyproject.toml`; +additional dependencies for testing are listed in the `tox.ini`. + +For linting and code-style we additionally require + +- `pylint` + +For building the documentation we require + +- `sphinx` +- `recommonmark` +- `sphinx-rtd-theme` diff --git a/loanpy/RELEASE_GUIDELINES.md b/loanpy/RELEASE_GUIDELINES.md new file mode 100644 index 0000000000000000000000000000000000000000..46ef6a8d31643281e46ee7139b2fb81e1e221215 --- /dev/null +++ b/loanpy/RELEASE_GUIDELINES.md @@ -0,0 +1,47 @@ +# Release Guidelines for the CaosDB Python Client Library + +This document specifies release guidelines in addition to the general release +guidelines of the CaosDB Project +([RELEASE_GUIDELINES.md](https://gitlab.com/caosdb/caosdb/blob/dev/RELEASE_GUIDELINES.md)) + +## General Prerequisites + +* All tests are passing. +* FEATURES.md is up-to-date and a public API is being declared in that document. +* CHANGELOG.md is up-to-date. +* dependencies in `setup.cfg` are up-to-date. + +## Steps + +1. Create a release branch from the dev branch. This prevents further changes + to the code base and a never ending release process. Naming: `release-<VERSION>` + +2. Update CHANGELOG.md + +3. Check all general prerequisites. + +4. Update the version: + - `version` variables in `src/doc/conf.py` + - Version in [pyproject.toml](./pyproject.toml). + - `CITATION.cff` (update version and date) + +5. Merge the release branch into the main branch. + +6. Tag the latest commit of the main branch with `v<VERSION>`. + +7. Delete the release branch. + +8. Remove possibly existing `./dist` directory with old release. + +9. Publish the release by executing `./release.sh` with uploads the caosdb + module to the Python Package Index [pypi.org](https://pypi.org). + +10. Merge the main branch back into the dev branch. + +11. After the merge of main to dev, start a new development version by + increasing at least the micro version in [pyproject.toml](./pyproject.toml) + and preparing CHANGELOG.md. + +12. Create releases on gitlab.com and gitlab.indiscale.com that contain (at + least) the most recent section of the CHANGELOG as the description and link + to the PyPi package. diff --git a/loanpy/integrationtests/basic_test.py b/loanpy/integrationtests/basic_test.py new file mode 100644 index 0000000000000000000000000000000000000000..5fda6aa0eb00c47e8124fe79476f6463776a510a --- /dev/null +++ b/loanpy/integrationtests/basic_test.py @@ -0,0 +1,192 @@ +# This file is a part of the LinkAhead Project. +# +# Copyright (C) 2024 Henrik tom Wörden (h.tomwoerden@indiscale.com) +# Copyright (C) 2024 IndiScale GmbH (info@indiscale.com) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +""" +basic integration test of the loan workflow +""" + +from pytest import fixture +import json +import linkahead as db +import re +from tempfile import NamedTemporaryFile +from linkahead.utils.server_side_scripting import run_server_side_script +from loan.box_loan import (BORROWER, BOX, COMMENT, DESTINATION, EXHAUST_CONTENTS, LENT, + F_CURRENT_LOCATION, + EXPECTED_RETURN, F_BOX, F_COMMENT, F_DESTINATION,PERSON, LOAN_ACCEPTED, + F_EMAIL, F_EXHAUST_CONTENTS, F_EXPECTED_RETURN_DATE, EMAIL, F_LOAN, + F_FIRST_NAME, F_LAST_NAME, FIRST_NAME, LAST_NAME, LOAN, LOCATION, RETURNED, + RETURN_ACCEPTED, + LOAN_REQUESTED, assert_date_in_future, CONTENT, RETURNLOCATION, + assert_key_in_data, get_box, insert_or_update_person, main, RETURN_REQUESTED, + send_loan_request_mail) + + +TESTLOANCOMMENT = 'This is a test' +TESTRETURNCOMMENT = 'This is a return test' +TESTBOXNUMBER = '0123 TEST' +TESTDESTINATION = 'TEST DEST' +TESTCURRENTDESTINATION = 'TEST CUR DEST' +TESTBORROWEREMAIL = 'test@example.com' +TESTRETURNDATE="2025-03-04" +TESTFN = "Alice" +TESTLN = "Wunderland" + + +def save_dict_to_jsonfile(data): + tmp = NamedTemporaryFile(delete=False, suffix=".json") + tmp.close() + with open(tmp.name, "w") as fi: + json.dump(data, fi) + return tmp.name + +def delete_stuff(): + to_be_deleted = db.Container() + to_be_deleted.extend(db.execute_query(f"FIND loan with {COMMENT.name}='{TESTLOANCOMMENT}'")) + to_be_deleted.extend(db.execute_query(f"FIND '{TESTDESTINATION}'")) + to_be_deleted.extend(db.execute_query(f"FIND '{TESTCURRENTDESTINATION}'")) + to_be_deleted.extend(db.execute_query(f"FIND '{TESTBOXNUMBER}'")) + to_be_deleted.extend(db.execute_query(f"FIND person with {EMAIL.name}='{TESTBORROWEREMAIL}'")) + if len(to_be_deleted)>0: + to_be_deleted.delete() + +@fixture(autouse=True) +def prepare(): + delete_stuff() + db.Record(name=TESTBOXNUMBER).add_parent(BOX.name).insert() + db.Record(name=TESTDESTINATION).add_parent(LOCATION.name).insert() + db.Record(name=TESTCURRENTDESTINATION).add_parent(LOCATION.name).insert() + (db.Record().add_parent(PERSON.name).add_property(EMAIL.name, TESTBORROWEREMAIL) + .add_property(FIRST_NAME.name, TESTFN) + .add_property(LAST_NAME.name, TESTLN).insert()) + yield + delete_stuff() + + +def test_request_loan(): + + ##### request loan ##### + data = {} + data[F_EXHAUST_CONTENTS] = False + data[F_EXPECTED_RETURN_DATE]= "2042-02-02" + data[F_BOX]=db.utils.get_entity.get_entity_by_name(TESTBOXNUMBER).id + data[F_COMMENT]=TESTLOANCOMMENT + data[F_DESTINATION]=db.utils.get_entity.get_entity_by_name(TESTDESTINATION).id + data[F_EMAIL]=TESTBORROWEREMAIL + data[F_LAST_NAME]=TESTLN + data[F_FIRST_NAME]=TESTFN + + response = run_server_side_script("loan_management/request_loan.py", + #"pos0", + #option1="val1", + files={"-p0": save_dict_to_jsonfile(data)}, + **{"auth-token":db.get_connection()._authenticator.auth_token}) + assert response.stderr is None + assert response.code == 0 + loan = db.execute_query(f"FIND loan with {COMMENT.name}='{TESTLOANCOMMENT}'", unique=True) + assert loan.get_property(f"{BOX.name}").value == data[F_BOX] + assert loan.get_property(f"{LOAN_REQUESTED.name}").value.startswith("20") + assert loan.get_property(f"{BORROWER.name}").value == db.execute_query( + f"FIND {PERSON.name} with {EMAIL.name}='{TESTBORROWEREMAIL}'", unique=True).id + assert loan.get_property(f"{EXPECTED_RETURN.name}").value == data[F_EXPECTED_RETURN_DATE] + assert loan.get_property(f"{EXHAUST_CONTENTS.name}").value == data[F_EXHAUST_CONTENTS] + assert loan.get_property(f"{DESTINATION.name}").value == data[F_DESTINATION] + assert loan.get_property(f"{LOAN_ACCEPTED.name}") is None + assert loan.get_property(f"{LENT.name}") is None + + ##### accept loan ##### + data = {} + data[F_LOAN] = loan.id + response = run_server_side_script("loan_management/accept_loan_request.py", + #"pos0", + #option1="val1", + files={"-p0": save_dict_to_jsonfile(data)}, + **{"auth-token":db.get_connection()._authenticator.auth_token}) + assert response.stderr is None + assert response.code == 0 + loan = db.execute_query(f"FIND loan with {COMMENT.name}='{TESTLOANCOMMENT}'", unique=True) + assert loan.get_property(f"{LOAN_ACCEPTED.name}").value.startswith("20") + assert loan.get_property(f"{LENT.name}") is None + assert loan.get_property(f"{RETURN_REQUESTED.name}") is None + + ##### confirm loan ##### + data = {} + data[F_LOAN] = loan.id + response = run_server_side_script("loan_management/confirm_loan.py", + #"pos0", + #option1="val1", + files={"-p0": save_dict_to_jsonfile(data)}, + **{"auth-token":db.get_connection()._authenticator.auth_token}) + assert response.stderr is None + assert response.code == 0 + loan = db.execute_query(f"FIND loan with {COMMENT.name}='{TESTLOANCOMMENT}'", unique=True) + assert loan.get_property(f"{LENT.name}").value.startswith("20") + + ##### request return ##### + data = {} + data[F_LOAN] = loan.id + # TODO: this is writen in the content field. Change name? + data[F_COMMENT] = TESTRETURNCOMMENT + # TODO: this is written in RETURNLOCATION. Change name? + data[F_CURRENT_LOCATION]=db.utils.get_entity.get_entity_by_name(TESTCURRENTDESTINATION).id + data[F_EXPECTED_RETURN_DATE] = TESTRETURNDATE + data[F_EMAIL]=TESTBORROWEREMAIL + data[F_LAST_NAME]=TESTLN + data[F_FIRST_NAME]=TESTFN + response = run_server_side_script("loan_management/request_return.py", + #"pos0", + #option1="val1", + files={"-p0": save_dict_to_jsonfile(data)}, + **{"auth-token":db.get_connection()._authenticator.auth_token}) + assert response.stderr is None + assert response.code == 0 + loan = db.execute_query(f"FIND loan with {COMMENT.name}='{TESTLOANCOMMENT}'", unique=True) + assert loan.get_property(f"{LOAN_ACCEPTED.name}").value.startswith("20") + assert loan.get_property(f"{CONTENT.name}").value == TESTRETURNCOMMENT + assert loan.get_property(f"{EXPECTED_RETURN.name}").value == TESTRETURNDATE + assert loan.get_property(f"{RETURNLOCATION.name}").value == db.utils.get_entity.get_entity_by_name(TESTCURRENTDESTINATION).id + assert loan.get_property(f"{RETURN_REQUESTED.name}").value.startswith("20") + assert loan.get_property(f"{RETURN_ACCEPTED.name}") is None + # TODO test change of borrower + + # accept return + data = {} + data[F_LOAN] = loan.id + response = run_server_side_script("loan_management/accept_return_request.py", + #"pos0", + #option1="val1", + files={"-p0": save_dict_to_jsonfile(data)}, + **{"auth-token":db.get_connection()._authenticator.auth_token}) + assert response.stderr is None + assert response.code == 0 + loan = db.execute_query(f"FIND loan with {COMMENT.name}='{TESTLOANCOMMENT}'", unique=True) + assert loan.get_property(f"{RETURN_ACCEPTED.name}").value.startswith("20") + assert loan.get_property(f"{RETURNED.name}") is None + + # manual return + data = {} + data[F_LOAN] = loan.id + response = run_server_side_script("loan_management/manual_return.py", + #"pos0", + #option1="val1", + files={"-p0": save_dict_to_jsonfile(data)}, + **{"auth-token":db.get_connection()._authenticator.auth_token}) + assert response.stderr is None + assert response.code == 0 + loan = db.execute_query(f"FIND loan with {COMMENT.name}='{TESTLOANCOMMENT}'", unique=True) + assert loan.get_property(f"{RETURNED.name}").value.startswith("20") diff --git a/loanpy/pyproject.toml b/loanpy/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..c8603d8f8fe0c68c7002c156b442bb302ba5ca6f --- /dev/null +++ b/loanpy/pyproject.toml @@ -0,0 +1,36 @@ +[build-system] +requires = ["setuptools >= 61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "linkahead-sss-loanpy" +description = "Server side scripting part of the loan management in LinkAhead" +version = "0.0.1" +readme = "README.md" +license = {file = "LICENSE"} +authors = [ + {name = "Henrik tom Wörden", email="h.tomwoerden@indiscale.com"}, + {name = "Florian Spreckelsen", email= "f.spreckelsen@indiscale.com"} +] +maintainers = [ + {name = "Henrik tom Wörden", email="h.tomwoerden@indiscale.com"} +] +keywords = ["Data management", "Research data management"] +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", + "Operating System :: OS Independent", +] +requires-python = ">= 3.8" +dependencies = [ + "caosadvancedtools", + "py3-validate-email", + "linkahead" +] + +[project.urls] +Homepage = "https://getlinkahead.com" +Documentation = "https://docs.indiscale.com" +Repository = "https://gitlab.indiscale.com/caosdb/src/linkahead-loan" +Issues = "https://gitlab.indiscale.com/caosdb/src/linkahead-loan/-/issues" +Changelog = "https://gitlab.indiscale.com/caosdb/src/linkahead-loan/-/blob/main/CHANGELOG.md?ref_type=heads" diff --git a/loanpy/release.sh b/loanpy/release.sh new file mode 100755 index 0000000000000000000000000000000000000000..f6335ae20d0c29e760b508aac831a35460a59ef3 --- /dev/null +++ b/loanpy/release.sh @@ -0,0 +1,4 @@ +#!/bin/bash +rm -rf dist/ build/ .eggs/ +python setup.py sdist bdist_wheel +python -m twine upload dist/* diff --git a/loanpy/src/doc/Makefile b/loanpy/src/doc/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..6f4670d0e922026e200840034b8df2d9cb5ab9c2 --- /dev/null +++ b/loanpy/src/doc/Makefile @@ -0,0 +1,47 @@ +# This file is a part of the LinkAhead Project. +# +# Copyright (C) 2020 IndiScale GmbH <info@indiscale.com> +# Copyright (C) 2020 Daniel Hornung <d.hornung@indiscale.com> +# Copyright (C) 2021 Alexander Schlemmer <alexander.schlemmer@ds.mpg.de> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# + +# This Makefile is a wrapper for sphinx scripts. +# +# It is based upon the autocreated makefile for Sphinx documentation. + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= -a +SPHINXBUILD ?= sphinx-build +SPHINXAPIDOC ?= sphinx-apidoc +PY_BASEDIR = ../linkahead_python_package_template +SOURCEDIR = . +BUILDDIR = ../../build/doc + + +.PHONY: doc-help Makefile + +# Put it first so that "make" without argument is like "make help". +doc-help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile apidoc + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +apidoc: + @$(SPHINXAPIDOC) -o _apidoc --separate $(PY_BASEDIR) diff --git a/loanpy/src/doc/conf.py b/loanpy/src/doc/conf.py new file mode 100644 index 0000000000000000000000000000000000000000..cfd8a066f58516d6c7df8d580ab582cff68e94f5 --- /dev/null +++ b/loanpy/src/doc/conf.py @@ -0,0 +1,218 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# Based on the configuration for caosdb-pylib. +# +# # Copyright (C) 2021 Alexander Schlemmer <alexander.schlemmer@ds.mpg.de> +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, add these +# directories to sys.path here. This is particularly necessary if this package is installed at a +# different version, for example via `pip install`. +# +# If the directory is relative to the documentation root, use os.path.abspath to make it absolute, +# like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('..')) + +import sphinx_rtd_theme # noqa: E402 + + +# -- Project information ----------------------------------------------------- + +project = 'caosdb-caoscrawler' +copyright = '2024, IndiScale' +author = 'Your Name' + +# The short X.Y version +version = '0.0.1' +# The full version, including alpha/beta/rc tags +# release = '0.5.2-rc2' +release = '0.0.1-dev' + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.autosectionlabel', + 'sphinx.ext.intersphinx', + 'sphinx.ext.napoleon', # For Google style docstrings + "recommonmark", # For markdown files. + "sphinx_rtd_theme", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +source_suffix = ['.rst', '.md'] + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = "en" + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = None + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# + +html_theme = "sphinx_rtd_theme" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] # ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'caosdb-caoscrawlerdoc' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'caosdb-caoscrawler.tex', 'caosdb-caoscrawler Documentation', + 'MPIDS', 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'caosdb-caoscrawler', 'caosdb-caoscrawler documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'caosdb-caoscrawler', 'caosdb-caoscrawler documentation', + author, 'caosdb-caoscrawler', 'One line description of project.', + 'Miscellaneous'), +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + + +# -- Extension configuration ------------------------------------------------- + +# True to prefix each section label with the name of the document it is in, followed by a colon. For +# example, index:Introduction for a section called Introduction that appears in document +# index.rst. Useful for avoiding ambiguity when the same section heading appears in different +# documents. +# +# Note: This stops "normal" links from working, so it should be kept at False. +# autosectionlabel_prefix_document = True + +# -- Options for intersphinx ------------------------------------------------- + +# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#confval-intersphinx_mapping +intersphinx_mapping = { + "python": ("https://docs.python.org/", None), + "caosdb-mysqlbackend": ("https://docs.indiscale.com/caosdb-mysqlbackend/", + None), + "caosdb-server": ("https://docs.indiscale.com/caosdb-server/", None), + "caosdb-pylib": ("https://docs.indiscale.com/caosdb-pylib/", None), + "caosdb-advanced-user-tools": ("https://docs.indiscale.com/caosdb-advanced-user-tools/", None), +} + + +# TODO Which options do we want? +autodoc_default_options = { + 'members': None, + 'undoc-members': None, +} diff --git a/loanpy/src/doc/index.rst b/loanpy/src/doc/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..6756f30810e3f4a3aaedc9b6b9a2852bdb4161c6 --- /dev/null +++ b/loanpy/src/doc/index.rst @@ -0,0 +1,4 @@ +LinkkAhead Python Package Template +================================== + +**TODO**: Write your documentation here. diff --git a/loanpy/src/loan/__init__.py b/loanpy/src/loan/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..566fc50b34bb879f43543ee8ecc5415bbe86a8d4 --- /dev/null +++ b/loanpy/src/loan/__init__.py @@ -0,0 +1,18 @@ +# +# This file is a part of the LinkAhead Project. +# +# Copyright (C) 2024 IndiScale GmbH <info@indiscale.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# diff --git a/caosdb-server/scripting/bin/loan_management/accept_loan_request.py b/loanpy/src/loan/accept_loan_request.py similarity index 96% rename from caosdb-server/scripting/bin/loan_management/accept_loan_request.py rename to loanpy/src/loan/accept_loan_request.py index c4bc70401eb526130bac037756c350246d2b26a8..7e57f94c979e6d439531f2a8d0c44d01500969a4 100755 --- a/caosdb-server/scripting/bin/loan_management/accept_loan_request.py +++ b/loanpy/src/loan/accept_loan_request.py @@ -23,7 +23,7 @@ Accept a loan request. from __future__ import absolute_import import caosdb as db from caosadvancedtools.serverside.helper import print_success, get_timestamp -from box_loan import (main, get_loan, F_LOAN, assert_loan_state, +from .box_loan import (main, get_loan, F_LOAN, assert_loan_state, LOAN_ACCEPTED, S_LOAN_ACCEPTED, S_LOAN_REQUESTED, get_borrower_names, set_property) diff --git a/caosdb-server/scripting/bin/loan_management/accept_return_request.py b/loanpy/src/loan/accept_return_request.py similarity index 97% rename from caosdb-server/scripting/bin/loan_management/accept_return_request.py rename to loanpy/src/loan/accept_return_request.py index b28a7d4c0aa2a61c0794566d01cb38f8a6a827cd..05619d9d5d6b524f15e981b4742cbeb72750c1e9 100755 --- a/caosdb-server/scripting/bin/loan_management/accept_return_request.py +++ b/loanpy/src/loan/accept_return_request.py @@ -24,7 +24,7 @@ Accept a return request. from __future__ import absolute_import import caosdb as db from caosadvancedtools.serverside.helper import print_success, get_timestamp -from box_loan import (BOX_BORROWED, CONTENT, main, get_loan, set_property, +from .box_loan import (BOX_BORROWED, CONTENT, main, get_loan, set_property, F_LOAN, assert_loan_state, RETURN_ACCEPTED, RETURNLOCATION, S_RETURN_ACCEPTED, S_RETURN_REQUESTED, get_borrower_names, set_location) diff --git a/caosdb-server/scripting/bin/loan_management/box_loan.py b/loanpy/src/loan/box_loan.py similarity index 89% rename from caosdb-server/scripting/bin/loan_management/box_loan.py rename to loanpy/src/loan/box_loan.py index 393c715c36c34f647be10f69acf877dee0ea9bc9..b6dbad3e2760915d29c44eb0dac9d089c18163a6 100644 --- a/caosdb-server/scripting/bin/loan_management/box_loan.py +++ b/loanpy/src/loan/box_loan.py @@ -34,8 +34,9 @@ from caosadvancedtools.serverside.helper import (DataModelError, get_data, print_info, print_warning, send_mail) from caosadvancedtools.serverside.logging import configure_server_side_logging -from caosdb.exceptions import EmptyUniqueQueryError +from linkahead.exceptions import EmptyUniqueQueryError from validate_email import validate_email +from .conf import * LOGGER_NAME = "box_loan" LOGGER = logging.getLogger(LOGGER_NAME) @@ -73,11 +74,6 @@ _EMTPY_DATA = { F_CURRENT_LOCATION: None } -BOX = db.RecordType(name="Box") -BOX_RETURNED = "Box (returned)" -BOX_BORROWED = "Box (borrowed)" -PERSON = db.RecordType(name="Person") -LOAN = db.RecordType(name="Loan") RECORD_TYPES = [ BOX, @@ -85,25 +81,6 @@ RECORD_TYPES = [ LOAN, ] -# Properties -FIRST_NAME = db.Property(name="firstName", datatype=db.TEXT) -LAST_NAME = db.Property(name="lastName", datatype=db.TEXT) -EMAIL = db.Property(name="email", datatype=db.TEXT) -LOCATION = db.RecordType(name="Location") -DESTINATION = db.Property(name="LoanLocation", datatype="Location") -RETURNLOCATION = db.Property(name="ReturnLocation", datatype="Location") -COMMENT = db.Property(name="comment", datatype=db.TEXT) -EXHAUST_CONTENTS = db.Property(name="exhaustContents", datatype=db.BOOLEAN) -BORROWER = db.Property(name="Borrower", datatype=PERSON.name) -CONTENT = db.Property(name="Content", datatype=db.TEXT) -LOAN_REQUESTED = db.Property(name="loanRequested", datatype=db.DATETIME) -EXPECTED_RETURN = db.Property(name="expectedReturn", datatype=db.DATETIME) -LOAN_ACCEPTED = db.Property(name="loanAccepted", datatype=db.DATETIME) -LENT = db.Property(name="lent", datatype=db.DATETIME) -RETURN_REQUESTED = db.Property(name="returnRequested", datatype=db.DATETIME) -RETURN_ACCEPTED = db.Property(name="returnAccepted", datatype=db.DATETIME) -RETURNED = db.Property(name="returned", datatype=db.DATETIME) -BOX_NUMBER = db.Property(name="Number", datatype=db.TEXT) PROPERTIES = [ FIRST_NAME, @@ -229,10 +206,10 @@ def get_box(box): return get_record_by_id(BOX.name, box) -def get_loan(loan): +def get_loan(loan_id): """ Retrieve a loan record by id. """ - return get_record_by_id(LOAN.name, loan) + return get_record_by_id(LOAN.name, loan_id) def get_loan_state(loan): @@ -448,23 +425,16 @@ def query_person(firstname, lastname, email): 1. the email address 2. or the first name and the last name """ - query = ('FIND RECORD {rt_person} ' - 'WITH {pemail} = "{email}" ' - 'OR ({pfn} = "{firstname}" ' - 'AND {pln} = "{lastname}") ' - ).format( - rt_person=PERSON.name, - pemail=EMAIL.name, - email=email, - pfn=FIRST_NAME.name, - firstname=firstname, - pln=LAST_NAME.name, - lastname=lastname - ) + query=(f'FIND RECORD {PERSON.name} ' + f'WITH {EMAIL.name} = "{email}" ' + f'OR ({FIRST_NAME.name} = "{firstname}" AND {LAST_NAME.name} = "{lastname}") ' + ) return db.execute_query(query, unique=True) + + def _update_person(person, firstname, lastname, email): """Update the persons names and email address. @@ -479,7 +449,7 @@ def _update_person(person, firstname, lastname, email): person.update() -def get_person(firstname, lastname, email): +def insert_or_update_person(firstname, lastname, email): """ Retrieve a person Record or create it. """ try: person = query_person(firstname, lastname, email) @@ -524,6 +494,7 @@ def set_property(entity, prop, value, case_sensitive=True): def check_data_model(): + # TODO init_data_model is a bad name; no initialization is done return init_data_model(RECORD_TYPES + PROPERTIES) diff --git a/loanpy/src/loan/conf.py b/loanpy/src/loan/conf.py new file mode 100644 index 0000000000000000000000000000000000000000..a2eeb2ec78ed6ea64ede3a9a7d5d27dd466e1288 --- /dev/null +++ b/loanpy/src/loan/conf.py @@ -0,0 +1,29 @@ +import linkahead as db +# RecordTypes +BOX = db.RecordType(name="Box") +PERSON = db.RecordType(name="Person") +LOAN = db.RecordType(name="Loan") +# Properties +FIRST_NAME = db.Property(name="firstName", datatype=db.TEXT) +LAST_NAME = db.Property(name="lastName", datatype=db.TEXT) +EMAIL = db.Property(name="email", datatype=db.TEXT) +LOCATION = db.RecordType(name="Location") +DESTINATION = db.Property(name="LoanLocation", datatype="Location") +RETURNLOCATION = db.Property(name="ReturnLocation", datatype="Location") +COMMENT = db.Property(name="comment", datatype=db.TEXT) +EXHAUST_CONTENTS = db.Property(name="exhaustContents", datatype=db.BOOLEAN) +BORROWER = db.Property(name="Borrower", datatype=PERSON.name) +CONTENT = db.Property(name="Content", datatype=db.TEXT) +LOAN_REQUESTED = db.Property(name="loanRequested", datatype=db.DATETIME) +EXPECTED_RETURN = db.Property(name="expectedReturn", datatype=db.DATETIME) +LOAN_ACCEPTED = db.Property(name="loanAccepted", datatype=db.DATETIME) +LENT = db.Property(name="lent", datatype=db.DATETIME) +RETURN_REQUESTED = db.Property(name="returnRequested", datatype=db.DATETIME) +RETURN_ACCEPTED = db.Property(name="returnAccepted", datatype=db.DATETIME) +RETURNED = db.Property(name="returned", datatype=db.DATETIME) +BOX_NUMBER = db.Property(name="Number", datatype=db.TEXT) + +# Other Strings +# TODO: Adapt datamodel and remove name override +BOX_RETURNED = "Box (returned)" +BOX_BORROWED = "Box (borrowed)" diff --git a/caosdb-server/scripting/bin/loan_management/confirm_loan.py b/loanpy/src/loan/confirm_loan.py similarity index 90% rename from caosdb-server/scripting/bin/loan_management/confirm_loan.py rename to loanpy/src/loan/confirm_loan.py index 4b463b7cad7435d2484e8c9ce5d63585b7dfc339..f503f8ee56fcd7b41c26bcbc14d6d4e206497fea 100755 --- a/caosdb-server/scripting/bin/loan_management/confirm_loan.py +++ b/loanpy/src/loan/confirm_loan.py @@ -25,9 +25,9 @@ from __future__ import absolute_import import caosdb as db from caosadvancedtools.serverside.helper import get_timestamp, print_success -from box_loan import (BOX, BOX_BORROWED, DESTINATION, F_LOAN, LENT, S_LENT, - S_LOAN_ACCEPTED, assert_loan_state, get_borrower_names, - get_loan, main, set_location, set_property) +from .box_loan import (BOX, BOX_BORROWED, DESTINATION, F_LOAN, LENT, S_LENT, + S_LOAN_ACCEPTED, assert_loan_state, get_borrower_names, + get_loan, main, set_location, set_property) def _set_lent_box(loan): @@ -35,6 +35,7 @@ def _set_lent_box(loan): This stores the version of the box when it was delivered to the borrower. """ + # TODO: Adapt datamodel and remove name override box_prop = loan.get_property(BOX) box_prop.name = BOX_BORROWED box_prop.value = str(box_prop.value) + "@HEAD" diff --git a/caosdb-server/scripting/bin/loan_management/manual_return.py b/loanpy/src/loan/manual_return.py similarity index 89% rename from caosdb-server/scripting/bin/loan_management/manual_return.py rename to loanpy/src/loan/manual_return.py index 9aa7e83c3f3210ad719d41a0b1aa00896e7e5aca..06fec359759e810c8e705ce7ab5fdb4130295238 100755 --- a/caosdb-server/scripting/bin/loan_management/manual_return.py +++ b/loanpy/src/loan/manual_return.py @@ -25,10 +25,10 @@ from __future__ import absolute_import import caosdb as db from caosadvancedtools.serverside.helper import get_timestamp, print_success -from box_loan import (BOX, BOX_BORROWED, BOX_RETURNED, CONTENT, F_LOAN, - RETURNED, RETURNLOCATION, S_RETURN_ACCEPTED, S_RETURNED, - assert_loan_state, get_borrower_names, get_loan, main, - set_location, set_property) +from .box_loan import (BOX, BOX_BORROWED, BOX_RETURNED, CONTENT, F_LOAN, + RETURNED, RETURNLOCATION, S_RETURN_ACCEPTED, S_RETURNED, + assert_loan_state, get_borrower_names, get_loan, main, + set_location, set_property) def _set_returned_box(loan): @@ -37,6 +37,7 @@ def _set_returned_box(loan): This stores the version of the box that was returned by a borrower. """ box_id = loan.get_property(BOX_BORROWED).value.split("@")[0] + # TODO: Adapt datamodel and remove name override loan.add_property(property=BOX, name=BOX_RETURNED, value=box_id + "@HEAD") @@ -61,6 +62,7 @@ def _manual_return(data): assert_loan_state(loan, S_RETURN_ACCEPTED) # This changes the state from "return_accepted" to "returned". + # TODO why twice???? set_property(loan, RETURNED, get_timestamp()) set_property(loan, RETURNED, get_timestamp()) diff --git a/caosdb-server/scripting/bin/loan_management/reject_return_request.py b/loanpy/src/loan/reject_return_request.py similarity index 96% rename from caosdb-server/scripting/bin/loan_management/reject_return_request.py rename to loanpy/src/loan/reject_return_request.py index 0d8227edc96ec1482d30d44448dfa1788fafac7a..58d2349055bf0870de257b6a1a250e18da24a65a 100755 --- a/caosdb-server/scripting/bin/loan_management/reject_return_request.py +++ b/loanpy/src/loan/reject_return_request.py @@ -23,7 +23,7 @@ Reject a return request. from __future__ import absolute_import import caosdb as db from caosadvancedtools.serverside.helper import print_success -from box_loan import (main, get_loan, F_LOAN, assert_loan_state, +from .box_loan import (main, get_loan, F_LOAN, assert_loan_state, S_RETURN_REQUESTED, RETURN_REQUESTED, S_LENT, get_borrower_names) diff --git a/caosdb-server/scripting/bin/loan_management/request_loan.py b/loanpy/src/loan/request_loan.py similarity index 90% rename from caosdb-server/scripting/bin/loan_management/request_loan.py rename to loanpy/src/loan/request_loan.py index 93714405aa3332def925695eeadc2d4ceb21ebe0..723f569c784f4af9b54ea3dce131ba3f09b58971 100755 --- a/caosdb-server/scripting/bin/loan_management/request_loan.py +++ b/loanpy/src/loan/request_loan.py @@ -22,15 +22,15 @@ Creates a loan request using information provided by a web formular. """ from __future__ import absolute_import -import caosdb as db +import linkahead as db from caosadvancedtools.serverside.helper import get_timestamp, print_success -from box_loan import (BORROWER, BOX, COMMENT, DESTINATION, EXHAUST_CONTENTS, +from .box_loan import (BORROWER, BOX, COMMENT, DESTINATION, EXHAUST_CONTENTS, EXPECTED_RETURN, F_BOX, F_COMMENT, F_DESTINATION, F_EMAIL, F_EXHAUST_CONTENTS, F_EXPECTED_RETURN_DATE, F_FIRST_NAME, F_LAST_NAME, FIRST_NAME, LAST_NAME, LOAN, LOAN_REQUESTED, assert_date_in_future, - assert_key_in_data, get_box, get_person, main, + assert_key_in_data, get_box, insert_or_update_person, main, send_loan_request_mail) @@ -80,8 +80,7 @@ def _check_data(data): def _issue_loan_request(data): """ Insert a loan record a insert/update a person record. """ data = _check_data(data) - box = get_box(data[F_BOX]) - borrower = get_person(firstname=data[F_FIRST_NAME], + borrower = insert_or_update_person(firstname=data[F_FIRST_NAME], lastname=data[F_LAST_NAME], email=data[F_EMAIL]) loan = create_loan(box=data[F_BOX], @@ -90,13 +89,9 @@ def _issue_loan_request(data): exhaust_contents=data[F_EXHAUST_CONTENTS], comment=data[F_COMMENT], destination=data[F_DESTINATION]) - c = db.Container().extend([ - borrower, - loan - ]) - c.insert() + loan.insert() - return borrower, loan, box + return borrower, loan def issue_loan_request(data): @@ -114,7 +109,7 @@ def issue_loan_request(data): The borrower is a Person Record, with firstName, lastName and email, identified by either email, oder firstName+lastName. """ - borrower, loan, _ = _issue_loan_request(data) + borrower, loan = _issue_loan_request(data) fn = borrower.get_property(FIRST_NAME.name).value ln = borrower.get_property(LAST_NAME.name).value diff --git a/caosdb-server/scripting/bin/loan_management/request_return.py b/loanpy/src/loan/request_return.py similarity index 91% rename from caosdb-server/scripting/bin/loan_management/request_return.py rename to loanpy/src/loan/request_return.py index d64e72e49c7cbcf51d6277d2d8d3ca34b1c81838..011f85aa206395a7c9a844a56e3184ca5157b081 100755 --- a/caosdb-server/scripting/bin/loan_management/request_return.py +++ b/loanpy/src/loan/request_return.py @@ -28,12 +28,12 @@ from __future__ import absolute_import from caosadvancedtools.serverside.helper import get_timestamp, print_success -from box_loan import (BORROWER, COMMENT, CONTENT, EXPECTED_RETURN, F_COMMENT, +from .box_loan import (BORROWER, COMMENT, CONTENT, EXPECTED_RETURN, F_COMMENT, F_CURRENT_LOCATION, F_EMAIL, F_EXPECTED_RETURN_DATE, F_FIRST_NAME, F_LAST_NAME, F_LOAN, FIRST_NAME, LAST_NAME, RETURN_REQUESTED, RETURNLOCATION, S_LENT, assert_date_in_future, assert_key_in_data, - assert_loan_state, get_loan, get_person, main, + assert_loan_state, get_loan, insert_or_update_person, main, send_return_request_mail, set_property) @@ -55,7 +55,7 @@ def _issue_return_request(data): loan = get_loan(data[F_LOAN]) assert_loan_state(loan, S_LENT) - returner = get_person(data[F_FIRST_NAME], data[F_LAST_NAME], data[F_EMAIL]) + returner = insert_or_update_person(data[F_FIRST_NAME], data[F_LAST_NAME], data[F_EMAIL]) loan.add_property(RETURN_REQUESTED, get_timestamp()) set_property(loan, BORROWER, returner.id) diff --git a/loanpy/tox.ini b/loanpy/tox.ini new file mode 100644 index 0000000000000000000000000000000000000000..d72772249f9487943e771fe0012bdbb9b62eef8d --- /dev/null +++ b/loanpy/tox.ini @@ -0,0 +1,21 @@ +[tox] +envlist = py38, py39, py310, py311, py312, py313 +skip_missing_interpreters = true + +[testenv] +deps = . + pytest + pytest-cov + +commands = + py.test --cov=linkahead_python_package_template -vv {posargs} + +[flake8] +max-line-length = 100 + +[pycodestyle] +max-line-length = 100 + +[pytest] +testpaths = unittests +xfail_strict = True diff --git a/caosdb-server/scripting/bin/test/loan_management/request_loan_form.json b/loanpy/unittests/request_loan_form.json similarity index 100% rename from caosdb-server/scripting/bin/test/loan_management/request_loan_form.json rename to loanpy/unittests/request_loan_form.json diff --git a/loanpy/unittests/test_box_loan.py b/loanpy/unittests/test_box_loan.py new file mode 100644 index 0000000000000000000000000000000000000000..77906a04891c4c7da901d8863f9d9f814c7ac829 --- /dev/null +++ b/loanpy/unittests/test_box_loan.py @@ -0,0 +1,62 @@ +# This file is a part of the LinkAhead Project. +# +# Copyright (C) 2024 Henrik tom Wörden (h.tomwoerden@indiscale.com) +# Copyright (C) 2020-2024 IndiScale GmbH (info@indiscale.com) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +from os.path import abspath, dirname, join +from pytest import raises +from linkahead import get_connection, configure_connection +from linkahead.connection.mockup import (MockUpServerConnection, MockUpResponse) +from loan.box_loan import (_caller, create_person, + EMAIL, FIRST_NAME, LAST_NAME, PERSON, + assert_date_in_future, DataError, EmailPatternError, + assert_email_pattern) + +from utils import get_form_data_example + + + +def test_return_value_of_caller(): + tval = 1337 + assert _caller(lambda d: tval, [get_form_data_example()] +) == tval + +def test_create_person(): + p = create_person("anna", "lytik", "a@b.com") + assert p.get_parents()[0].name == PERSON.name + assert p.get_property(FIRST_NAME.name).value == "anna" + assert p.get_property(LAST_NAME.name).value == "lytik" + assert p.get_property(EMAIL.name).value == "a@b.com" + +def test_email_validation(): + with raises(EmailPatternError): + assert_email_pattern("@asdf") + with raises(EmailPatternError): + assert_email_pattern("asdf") + with raises(EmailPatternError): + assert_email_pattern("asdfa.de") + with raises(EmailPatternError): + assert_email_pattern("sdfg@sdfg") + + # the following shoudl be ok + assert_email_pattern("a@b.da") + assert_email_pattern("\"#\"@ö.de") + +def test_assert_date_in_future(): + # I can't wait for 2050 :-) + assert assert_date_in_future("2050-01-01") is None + with raises(DataError): + assert_date_in_future("1971-01-01") diff --git a/caosdb-server/scripting/bin/test/loan_management/test_manual_return.py b/loanpy/unittests/test_manual_return.py similarity index 77% rename from caosdb-server/scripting/bin/test/loan_management/test_manual_return.py rename to loanpy/unittests/test_manual_return.py index 790575c73acc1c8d4fa5bb66f37c49d84b6845a3..f1753dde016d3659a9a06e5746a1fcfd060f212b 100644 --- a/caosdb-server/scripting/bin/test/loan_management/test_manual_return.py +++ b/loanpy/unittests/test_manual_return.py @@ -1,6 +1,6 @@ -from caosdb import Record -from box_loan import BOX, BOX_RETURNED, BOX_BORROWED -from manual_return import _set_returned_box +from linkahead import Record +from loan.box_loan import BOX, BOX_RETURNED, BOX_BORROWED +from loan.manual_return import _set_returned_box def test_set_returned_box(): diff --git a/loanpy/unittests/test_request_loan.py b/loanpy/unittests/test_request_loan.py new file mode 100644 index 0000000000000000000000000000000000000000..63c937579048001ac944988be11058659f08baef --- /dev/null +++ b/loanpy/unittests/test_request_loan.py @@ -0,0 +1,34 @@ +from os.path import abspath, dirname, join +from pytest import raises +from linkahead import get_connection, configure_connection, Record +from linkahead.connection.mockup import (MockUpServerConnection, MockUpResponse) +from caosadvancedtools.serverside.helper import get_data +from loan.box_loan import (PERSON, FIRST_NAME, EMAIL, BOX, BORROWER, + EXPECTED_RETURN, EXHAUST_CONTENTS, COMMENT, + DESTINATION, F_FIRST_NAME, F_EMAIL, + F_EXPECTED_RETURN_DATE, DataError, BOX_NUMBER) +from loan.request_loan import (create_loan, _issue_loan_request) +from utils import get_form_data_example + + +def test_issue_loan_request_with_wrong_return_date(): + data = get_data(get_form_data_example()) + data[F_EXPECTED_RETURN_DATE] = "1983-02-03" + with raises(DataError): + _issue_loan_request(data) + + +def test_create_loan(): + borrower = Record(name="Person1") + l = create_loan(1234, + borrower, + "2020-03-23", + False, + "blablabla", + "my office") + assert l.get_property(BOX.name).value == 1234 + assert l.get_property(BORROWER.name).value == borrower + assert l.get_property(EXPECTED_RETURN.name).value == "2020-03-23" + assert l.get_property(EXHAUST_CONTENTS.name).value == False + assert l.get_property(COMMENT.name).value == "blablabla" + assert l.get_property(DESTINATION.name).value == "my office" diff --git a/loanpy/unittests/test_request_return.py b/loanpy/unittests/test_request_return.py new file mode 100644 index 0000000000000000000000000000000000000000..8f6bd3b00d8cf02707eb0e688ff92e871f7f42f6 --- /dev/null +++ b/loanpy/unittests/test_request_return.py @@ -0,0 +1,31 @@ +from os.path import abspath, dirname, join +from pytest import raises +from linkahead import get_connection, configure_connection +from linkahead.connection.mockup import (MockUpServerConnection, MockUpResponse) +from caosadvancedtools.serverside.helper import get_data +from loan.box_loan import (FIRST_NAME, EMAIL, LAST_NAME, F_FIRST_NAME, + F_LAST_NAME, F_EMAIL, EXPECTED_RETURN, LENT, + RETURN_REQUESTED, PERSON, BORROWER, BOX, + LOAN_REQUESTED, StateError, F_EXPECTED_RETURN_DATE, + DataError, LOAN) +from loan.request_return import get_loan, _issue_return_request +from utils import get_form_data_example + + +def test_return_request_with_wrong_return_date(): + data = get_data(get_form_data_example()) + data[F_EXPECTED_RETURN_DATE] = "1983-02-03" + with raises(DataError): + _issue_return_request(data) + + data[F_EXPECTED_RETURN_DATE] = "asdf" + with raises(DataError): + _issue_return_request(data) + + data[F_EXPECTED_RETURN_DATE] = "" + with raises(DataError): + _issue_return_request(data) + + del data[F_EXPECTED_RETURN_DATE] + with raises(DataError): + _issue_return_request(data) diff --git a/loanpy/unittests/utils.py b/loanpy/unittests/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..c56975795276d029c644b06b1d0a2bc3aba7add8 --- /dev/null +++ b/loanpy/unittests/utils.py @@ -0,0 +1,26 @@ +# This file is a part of the LinkAhead Project. +# +# Copyright (C) 2024 Henrik tom Wörden (h.tomwoerden@indiscale.com) +# Copyright (C) 2020-2024 IndiScale GmbH (info@indiscale.com) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +""" +some utility functions +""" + +from os.path import abspath, dirname, join + +def get_form_data_example(): + return abspath(join(dirname(__file__), "request_loan_form.json")) diff --git a/notes b/notes new file mode 100644 index 0000000000000000000000000000000000000000..d59de74242e0e3c7a4f372064a06f3577ae2fc20 --- /dev/null +++ b/notes @@ -0,0 +1,18 @@ +The loan references objects that are lent. The configuration denotes what RTs +can be lent. + + +Currently, the configuration has to be done twice: for python and web + + +# Fragen +Soll die Email Adress wirklich on the fly mit geändert werden? Alternative +(select person record and check that email is uptodate) + + +Introduce a single loan.py file with argparse (`loan.py accept a b c`, etc) + +Person without first or lastname seem to be a problem. + + +Warum wird der Borrower beim Return verändert? Nur anbieten email anzupassen?