Commit 4c0f2742bdca25329733ebab91a494432308a8a5
Exists in
dev
Merge branch 'dev' of https://gitlab.irap.omp.eu/pyros-irap/pyros into dev
Showing
23 changed files
with
410 additions
and
302 deletions
Show diff stats
data/README.md deleted
docker/Dockerfile
... | ... | @@ -171,6 +171,8 @@ RUN useradd -m -s /bin/bash pyros_user --uid $uid |
171 | 171 | |
172 | 172 | # Create the work dir and set permissions as pyros_user |
173 | 173 | RUN mkdir -p /home/pyros_user/app/ && chown -R pyros_user:pyros_user /home/pyros_user/app |
174 | + | |
175 | +# cd /home/pyros_user/app | |
174 | 176 | WORKDIR /home/pyros_user/app |
175 | 177 | |
176 | 178 | # Switch from root to pyros_user |
... | ... | @@ -180,21 +182,24 @@ USER pyros_user |
180 | 182 | # TODO: pourquoi numpy ??? A virer ? |
181 | 183 | #RUN pip install --user numpy |
182 | 184 | |
183 | -# Copy local host machine files to image | |
184 | -COPY --chown=pyros_user:pyros_user . . | |
185 | -# Copy some aliases | |
186 | -RUN cp .bash_aliases .. | |
187 | -#RUN mv .bash_aliases .. | |
185 | +RUN mkdir -p ./vendor/guitastro/install/ | |
186 | +RUN mkdir -p ./install/ | |
188 | 187 | |
188 | +# Copy local host machine files to image | |
189 | +# Dockerfile contains this volume => ../..:/home/pyros_user/app | |
190 | +# Thus, this will copy PYROS_SOFT/* ==TO==> /home/pyros_user/app/ (the WORKDIR) | |
191 | +COPY ./vendor/guitastro/install/requirements.in ./vendor/guitastro/install/requirements.in | |
192 | +COPY ./install/requirements.txt ./install/requirements.txt | |
193 | +COPY ./install/requirements_dev.txt ./install/requirements_dev.txt | |
189 | 194 | |
190 | 195 | # Adding local/bin to path to avoid pip warning |
191 | 196 | ENV PATH "$PATH:/home/pyros_user/.local/bin" |
192 | 197 | |
193 | 198 | |
194 | 199 | # Installing click on the image to prevent error on the first execution of the installation script |
195 | -RUN python3 -m pip install --user click | |
200 | +RUN python3 -m pip install --user click | |
196 | 201 | |
197 | -RUN python3 -m pip install --user setuptools==58 | |
202 | +RUN python3 -m pip install --user setuptools==58 | |
198 | 203 | |
199 | 204 | # (EP 23/3/2022) Installing pip-tools for the management of all the requirements*.txt files (python dependencies packages) |
200 | 205 | # NB : |
... | ... | @@ -203,14 +208,14 @@ RUN python3 -m pip install --user setuptools==58 |
203 | 208 | # - Unfortunatly, it is difficult to use with several requirements*.txt files as it is the case for this software : pyros + sphinx + guitastro... |
204 | 209 | # - So we cannot yet use it completely and still have to use the traditional "pip install -r" anyway ... |
205 | 210 | # - But we can at least use it to generate all the requirements*.txt files in a far better format |
206 | -RUN python3 -m pip install --user pip-tools | |
211 | +RUN python3 -m pip install --user pip-tools | |
207 | 212 | |
208 | 213 | # Installing packages required for Guitastro |
209 | 214 | RUN pip-compile ./vendor/guitastro/install/requirements.in |
210 | -RUN python3 -m pip install --user -r ./vendor/guitastro/install/requirements.txt | |
215 | +RUN python3 -m pip install --user -r ./vendor/guitastro/install/requirements.txt | |
211 | 216 | # Maybe unnecessary because same requirements as for pyros requirements_dev (?) |
212 | 217 | #RUN pip install --user -r ./vendor/guitastro/install/requirements_dev.txt |
213 | 218 | |
214 | 219 | # Installing packages required for PyROS |
215 | -RUN python3 -m pip install --user -r ./install/requirements.txt | |
216 | -RUN python3 -m pip install --user -r ./install/requirements_dev.txt | |
220 | +RUN python3 -m pip install --user -r ./install/requirements.txt | |
221 | +RUN python3 -m pip install --user -r ./install/requirements_dev.txt | ... | ... |
docker/PYROS_DOCKER_BUILD
1 | 1 | #!/usr/bin/env bash |
2 | 2 | |
3 | +# Git clone guitastro | |
4 | +# | |
5 | +# This could be done directly in the Dockerfile | |
6 | +# but it is better to do it here because if it failed during the build | |
7 | +# we would have to do all the build again... | |
3 | 8 | [ ! -d "../vendor/guitastro/" ] && git clone https://gitlab.irap.omp.eu/guitastrolib/guitastro.git ../vendor/guitastro/ |
9 | +[ ! -d "../vendor/guitastro/" ] && echo "Error : git clone of GuitAstro failed => cannot go on with the build" && exit 1 | |
4 | 10 | |
5 | -export CURRENT_UID=$(id -u) | |
11 | +export CURRENT_UID=$(id -u) | |
12 | +export COMPUTER_HOSTNAME=$HOSTNAME | |
6 | 13 | |
7 | 14 | # exit if root |
8 | 15 | if [ $CURRENT_UID -eq 0 ] ; then | ... | ... |
docker/PYROS_DOCKER_INSTALL_DB
docker/PYROS_DOCKER_RUN
docker/PYROS_DOCKER_RUN_WEBSERVER_ONLY
docker/PYROS_DOCKER_SHELL
docker/PYROS_DOCKER_START
... | ... | @@ -12,7 +12,7 @@ docker compose down --remove-orphans |
12 | 12 | echo |
13 | 13 | echo "Then START:" |
14 | 14 | # Take docker-compose.yml folder name ("docker") as containers group name in Docker Desktop by default |
15 | -docker compose up -d | |
15 | +docker compose up --no-build -d | |
16 | 16 | # Change this group name with -p option |
17 | 17 | #docker compose -p pyros-app up -d |
18 | 18 | ... | ... |
docker/PYROS_DOCKER_START.bat
docker/PYROS_DOCKER_TEST
docker/PYROS_DOCKER_UPDATE
1 | 1 | #!/usr/bin/env bash |
2 | 2 | |
3 | +# PRE-CONDITION : pyros container must be running | |
4 | + | |
3 | 5 | # If no container is running Start it |
4 | 6 | if ! [ $(docker ps | grep 'pyros' | wc -l) -eq 4 ] |
5 | 7 | then |
... | ... | @@ -16,10 +18,12 @@ echo "Updating Guitastro source code" |
16 | 18 | echo "Updating PyROS source code" |
17 | 19 | git pull |
18 | 20 | |
19 | -echo "Updating observatory source code" | |
20 | -for dir in ../../PYROS_OBSERVATORY/* | |
21 | -do | |
22 | - git pull | |
21 | +# 3) Update all observatories with git repo | |
22 | +for dir in ../../PYROS_OBSERVATORY/* ; do | |
23 | + if [ -d .git ] ; then | |
24 | + echo "Updating observatory $dir source code" | |
25 | + git pull | |
26 | + fi | |
23 | 27 | done |
24 | 28 | |
25 | 29 | # 3) pyros.py update => update BD + doc + Guitastro requirements | ... | ... |
docker/PYROS_DOCKER_UPDATE_AND_RESTART
docker/docker-compose.yml
1 | 1 | version: "3.9" |
2 | 2 | |
3 | +# Application name (group name for the 4 images composing the PyROS app) | |
3 | 4 | name: pyros-app |
4 | 5 | |
5 | 6 | services: |
... | ... | @@ -111,6 +112,7 @@ services: |
111 | 112 | tty: true |
112 | 113 | # stdin_open is the -i option in docker exec |
113 | 114 | stdin_open: true |
115 | + # Local PYROS_SOFT/ folder is linked to container /home/pyros_user/app folder | |
114 | 116 | volumes: |
115 | 117 | - ../..:/home/pyros_user/app |
116 | 118 | # tells which port of local machine can communicate with the docker image (host:container), host is your local machine | ... | ... |
pyros.py
... | ... | @@ -115,10 +115,7 @@ try: |
115 | 115 | except KeyError: |
116 | 116 | WITH_DOCKER = False |
117 | 117 | |
118 | -if type(WITH_DOCKER) is str and re.match("^y$|^Y$|^yes$|^Yes$", WITH_DOCKER.rstrip()) != None: | |
119 | - WITH_DOCKER = True | |
120 | -else: | |
121 | - WITH_DOCKER = False | |
118 | +WITH_DOCKER = type(WITH_DOCKER) is str and re.match("^y$|^Y$|^yes$|^Yes$", WITH_DOCKER.rstrip()) != None | |
122 | 119 | |
123 | 120 | my_abs_path = os.path.dirname(os.path.realpath(__file__)) |
124 | 121 | # VENV_ROOT = "private" |
... | ... | @@ -534,7 +531,7 @@ def dbshell(): |
534 | 531 | |
535 | 532 | @pyros_launcher.command(help="Update (only if necessary) the python packages AND the source code AND the DB structure") |
536 | 533 | def update(): |
537 | - install_or_update(UPDATE=True) | |
534 | + _install_or_update(UPDATE=True) | |
538 | 535 | ''' |
539 | 536 | print("Running update command") |
540 | 537 | # 1) Update source code (git pull) |
... | ... | @@ -568,13 +565,13 @@ def update(): |
568 | 565 | def install(packages_only, database_only): |
569 | 566 | with_packages = not database_only |
570 | 567 | with_database = not packages_only |
571 | - install_or_update(UPDATE=False, with_packages=with_packages, | |
572 | - with_database=with_database) | |
568 | + _install_or_update(UPDATE=False, with_packages=with_packages, with_database=with_database) | |
573 | 569 | # install_or_update(UPDATE=False, with_packages=packages_only, with_database=database_only) |
574 | 570 | # install_or_update(UPDATE=False, packages_only=packages_only, database_only=database_only) |
575 | 571 | |
572 | + | |
576 | 573 | # def install_or_update(UPDATE: bool = False, with_packages: bool = False, with_database: bool = False): |
577 | -def install_or_update(UPDATE: bool = False, with_packages: bool = True, with_database: bool = True): | |
574 | +def _install_or_update(UPDATE: bool = False, with_packages: bool = True, with_database: bool = True): | |
578 | 575 | SQL_USER = os.environ.get("MYSQL_PYROS_LOGIN").strip() |
579 | 576 | SQL_PSWD = os.environ.get("MYSQL_PYROS_PWD").strip() |
580 | 577 | os.environ["PATH_TO_OBSCONF_FOLDER"] = os.path.join(os.path.abspath( |
... | ... | @@ -583,12 +580,10 @@ def install_or_update(UPDATE: bool = False, with_packages: bool = True, with_dat |
583 | 580 | PYROS_DJANGO_BASE_DIR), os.environ["PATH_TO_OBSCONF_FOLDER"] + "observatory.yml") |
584 | 581 | ACTION = "UPDATING" if UPDATE else "INSTALLING" |
585 | 582 | |
586 | - # if WITH_DOCKER: with_database = True | |
583 | + # if WITH_DOCKER: only install the DB (with_database = True) | |
587 | 584 | if WITH_DOCKER: |
588 | 585 | with_packages = False |
589 | - | |
590 | 586 | # if not with_packages and not with_database: with_packages = with_database = True |
591 | - | |
592 | 587 | print("- with_packages:", with_packages) |
593 | 588 | print("- with_database:", with_database) |
594 | 589 | |
... | ... | @@ -597,37 +592,36 @@ def install_or_update(UPDATE: bool = False, with_packages: bool = True, with_dat |
597 | 592 | # if (os.path.basename(os.getcwd()) != "private"): |
598 | 593 | |
599 | 594 | num = 0 |
600 | - # 1) Update source code (git pull) | |
595 | + | |
601 | 596 | if UPDATE: |
597 | + # 1) Update source code (git pull) | |
602 | 598 | num += 1 |
603 | - printFullTerm( | |
604 | - Colors.BLUE, f"{num}) UPDATING SOURCE CODE: Running git pull") | |
599 | + printFullTerm(Colors.BLUE, f"{num}) UPDATING SOURCE CODE: Running git pull") | |
605 | 600 | if not WITH_DOCKER: |
606 | 601 | _gitpull() or die() |
607 | 602 | |
608 | - # 2) Update python packages (pip upgrade AND pip install requirements) | |
609 | 603 | if with_packages: |
604 | + # 2) Install venv and packages in it | |
605 | + # Update python packages (pip upgrade AND pip install requirements) | |
610 | 606 | num += 1 |
611 | 607 | printFullTerm(Colors.BLUE, f"{num}) {ACTION} PYTHON PACKAGES") |
612 | 608 | # (UPDATE) Re-install VENV if disappeared |
613 | 609 | install_venv(EVEN_IF_ALREADY_EXISTS=not UPDATE) |
614 | 610 | install_packages() |
611 | + | |
615 | 612 | if UPDATE: |
616 | 613 | print("Running UPDATE command") |
617 | 614 | else: |
618 | - # Install GitPython package for git support inside python | |
615 | + # 3) Install GitPython package for git support inside python | |
619 | 616 | print("Running INSTALL command") |
620 | 617 | try: |
621 | 618 | from git import Repo |
622 | 619 | except: |
623 | 620 | pip = "pip" if IS_WINDOWS else "pip3" |
624 | - if WITH_DOCKER: | |
625 | - process = subprocess.Popen( | |
626 | - pip + " install --user --upgrade GitPython", shell=True) | |
627 | - else: | |
628 | - process = subprocess.Popen( | |
629 | - pip + " install --upgrade GitPython", shell=True) | |
621 | + usermode = '--user' if WITH_DOCKER else '' | |
622 | + process = subprocess.Popen(pip + f" install {usermode} --upgrade GitPython", shell=True) | |
630 | 623 | process.wait() |
624 | + # if run ok | |
631 | 625 | if process.returncode == 0: |
632 | 626 | # self.addExecuted(self.current_command, command) |
633 | 627 | from git import Repo |
... | ... | @@ -645,57 +639,58 @@ def install_or_update(UPDATE: bool = False, with_packages: bool = True, with_dat |
645 | 639 | change_dir("PREVIOUS") |
646 | 640 | ''' |
647 | 641 | |
648 | - # Guitastro | |
649 | - GUITASTRO_PATH = os.path.join(os.getcwd(), "./vendor/guitastro") | |
650 | - if with_packages and not WITH_DOCKER : | |
651 | - #GUITASTRO_PATH = os.path.join(os.getcwd(), "../vendor/guitastro") | |
652 | - change_dir("..") | |
642 | + # 4) Install GUITASTRO + its requirements | |
643 | + # (ONLY FOR NON DOCKER INSTALL) | |
653 | 644 | print(os.getcwd()) |
654 | - # 1) clone repo if not yet done | |
655 | - if not os.path.exists(GUITASTRO_PATH) and not WITH_DOCKER : | |
656 | - print("Guitastro : Cloning repository") | |
657 | - cloned_repo = Repo.clone_from( | |
658 | - "https://gitlab.irap.omp.eu/guitastrolib/guitastro.git", GUITASTRO_PATH) | |
659 | - print("Cloned successfully: ", cloned_repo.__class__ is Repo) | |
660 | - if UPDATE and os.path.exists(GUITASTRO_PATH): | |
661 | - gitpull_guitastro() | |
662 | - change_dir("PREVIOUS") | |
663 | - # 2) install/update requirements & generate API doc | |
664 | - if os.path.exists(GUITASTRO_PATH) and not WITH_DOCKER: | |
665 | - # TODO: update guitastro (git pull from vendor/guitastro/) | |
666 | - print("\nGuitastro : Installing/Updating python package dependencies\n") | |
667 | - # Upgrade pip if new version available | |
668 | - os.system(VENV_PYTHON + ' -m pip install --upgrade pip') | |
669 | - # TODO: faire les apt-get intall aussi (indi, ...) | |
670 | - venv_pip_install2(GUITASTRO_PATH + '/install/requirements.txt', '-r') | |
671 | - #venv_pip_install2(GUITASTRO_PATH + '/install/requirements_linux.txt', '-r') | |
672 | - venv_pip_install2(GUITASTRO_PATH + '/install/requirements_dev.txt', '-r') | |
673 | - ''' | |
674 | - print("Guitastro : Generating (updating) API documentation (using Sphinx)") | |
675 | - # Make html doc from RST | |
676 | - # cd doc/sourcedoc/ | |
677 | - change_dir(GUITASTRO_PATH + '/doc_rst/') | |
678 | - # ./MAKE_DOC.sh | |
679 | - res = execProcess('/bin/bash MAKE_DOC.sh') | |
680 | - # Come back to where we were before | |
681 | - # cd - | |
682 | - change_dir("PREVIOUS") | |
683 | - ''' | |
684 | - | |
685 | - | |
686 | - # 3) Update PlantUML diagrams | |
645 | + if not WITH_DOCKER: | |
646 | + GUITASTRO_PATH = os.path.join(os.getcwd(), "./vendor/guitastro") | |
647 | + if with_packages: | |
648 | + change_dir("..") | |
649 | + print(os.getcwd()) | |
650 | + if not os.path.exists(GUITASTRO_PATH): | |
651 | + # a) clone repo if not yet done | |
652 | + print("Guitastro : Cloning repository") | |
653 | + cloned_repo = Repo.clone_from( | |
654 | + "https://gitlab.irap.omp.eu/guitastrolib/guitastro.git", GUITASTRO_PATH) | |
655 | + print("Cloned successfully: ", cloned_repo.__class__ is Repo) | |
656 | + if os.path.exists(GUITASTRO_PATH): | |
657 | + if UPDATE: | |
658 | + gitpull_guitastro() | |
659 | + change_dir("PREVIOUS") | |
660 | + # b) install/update requirements & generate API doc | |
661 | + # TODO: update guitastro (git pull from vendor/guitastro/) | |
662 | + print("\nGuitastro : Installing/Updating python package dependencies\n") | |
663 | + # Upgrade pip if new version available | |
664 | + os.system(VENV_PYTHON + ' -m pip install --upgrade pip') | |
665 | + # TODO: faire les apt-get intall aussi (indi, ...) | |
666 | + _venv_pip_install2(GUITASTRO_PATH + '/install/requirements.txt', '-r') | |
667 | + #venv_pip_install2(GUITASTRO_PATH + '/install/requirements_linux.txt', '-r') | |
668 | + _venv_pip_install2(GUITASTRO_PATH + '/install/requirements_dev.txt', '-r') | |
669 | + ''' | |
670 | + print("Guitastro : Generating (updating) API documentation (using Sphinx)") | |
671 | + # Make html doc from RST | |
672 | + # cd doc/sourcedoc/ | |
673 | + change_dir(GUITASTRO_PATH + '/doc_rst/') | |
674 | + # ./MAKE_DOC.sh | |
675 | + res = execProcess('/bin/bash MAKE_DOC.sh') | |
676 | + # Come back to where we were before | |
677 | + # cd - | |
678 | + change_dir("PREVIOUS") | |
679 | + ''' | |
680 | + | |
681 | + # 5) Update PlantUML diagrams (in UPDATE or INSTALL mode) | |
687 | 682 | num += 1 |
688 | 683 | printFullTerm(Colors.BLUE, f"{num}) UPDATING UML DIAGRAMS") |
689 | 684 | _update_plantuml_diags() or die() |
690 | 685 | print(os.getcwd()) |
691 | 686 | |
692 | - # 4) Update Sphinx API doc | |
687 | + # 6) Update Sphinx API doc (Non Windows only) (in UPDATE or INSTALL mode) | |
693 | 688 | num += 1 |
694 | 689 | printFullTerm(Colors.BLUE, f"{num}) UPDATING API DOC (with Sphinx)") |
695 | 690 | if not IS_WINDOWS: |
696 | 691 | _update_api_doc() or die() |
697 | 692 | |
698 | - # 5) Install/Update database structure (make migrations + migrate) | |
693 | + # 7) Install/Update database structure (make migrations + migrate) | |
699 | 694 | if with_database: |
700 | 695 | num += 1 |
701 | 696 | printFullTerm(Colors.BLUE, f"{num}) {ACTION} DATABASE") |
... | ... | @@ -703,9 +698,8 @@ def install_or_update(UPDATE: bool = False, with_packages: bool = True, with_dat |
703 | 698 | _updatedb() or die() |
704 | 699 | else: |
705 | 700 | res = install_database(VENV) |
706 | - if(res == 0): | |
707 | - print( | |
708 | - f"You can connect to PyROS as '{SQL_USER}' user with the password '{SQL_PSWD}' ") | |
701 | + if (res == 0): | |
702 | + print(f"You can connect to PyROS as '{SQL_USER}' user with the password '{SQL_PSWD}' ") | |
709 | 703 | return True |
710 | 704 | |
711 | 705 | |
... | ... | @@ -727,8 +721,7 @@ def test(module, function): |
727 | 721 | if module is None: |
728 | 722 | # apps = ['obs_config','scp_mgmt','common', 'scheduling', 'seq_submit', 'user_mgmt', 'alert_mgmt.tests.TestStrategyChange'] |
729 | 723 | # Removing alert_mgmt, scheduler from tests |
730 | - apps = ['obs_config', "scp_mgmt", | |
731 | - 'dashboard', 'user_mgmt', 'seq_submit','api'] | |
724 | + apps = ['obs_config', "scp_mgmt", 'dashboard', 'user_mgmt', 'seq_submit', 'api'] | |
732 | 725 | else: |
733 | 726 | os.environ["PATH_TO_OBSCONF_FILE"] = os.path.join(os.path.abspath( |
734 | 727 | PYROS_DJANGO_BASE_DIR), "obs_config/fixtures/observatory_configuration_ok_simple.yml") |
... | ... | @@ -760,7 +753,7 @@ def test(module, function): |
760 | 753 | # execProcessFromVenv('manage.py test ' + app) or die() |
761 | 754 | # KEEP test_pyros database after tests |
762 | 755 | # execProcessFromVenv('manage.py test --keep ' + app) or die() |
763 | - execProcessFromVenv('manage.py test --keep --noinput ' + app,foreground=True) or die() | |
756 | + execProcessFromVenv('manage.py test --keep --noinput ' + app, foreground=True) or die() | |
764 | 757 | change_dir("PREVIOUS") |
765 | 758 | # execProcess("python install.py install") |
766 | 759 | return True |
... | ... | @@ -789,8 +782,7 @@ def gitpull_guitastro(): |
789 | 782 | change_dir("./vendor/guitastro/") |
790 | 783 | GIT = "git.exe" if IS_WINDOWS else "git" |
791 | 784 | if not test_mode(): |
792 | - return execProcess(f"{GIT} pull",foreground=True) | |
793 | - | |
785 | + return execProcess(f"{GIT} pull", foreground=True) | |
794 | 786 | return True |
795 | 787 | |
796 | 788 | |
... | ... | @@ -816,21 +808,24 @@ def initdb(): |
816 | 808 | return True |
817 | 809 | |
818 | 810 | |
819 | - | |
820 | -@pyros_launcher.command(help="Install dependencies") | |
811 | +@pyros_launcher.command(help="Install dependencies from Observatory config DEPENDENCY section") | |
821 | 812 | # @global_test_options |
822 | -@click.option('--observatory', '-o', help='the observatory name to be used') | |
823 | -@click.option('--unit', '-u', help='the unit name to be used') | |
813 | +@click.option('--observatory', '-o', help='the Observatory name to be used') | |
814 | +@click.option('--unit', '-u', help='the Unit name to be used') | |
824 | 815 | @click.option("--foreground","-fg", is_flag=True, help="Print stdout and error in terminal") |
825 | 816 | def install_dependencies(observatory: str, unit: str, foreground: bool): |
826 | - if observatory == None or len(observatory) == 0: | |
817 | + if (observatory is None) or (len(observatory) == 0): | |
827 | 818 | observatory = "default" |
828 | 819 | default_obsconfig_folder = os.path.join( |
829 | - os.path.abspath(PYROS_DJANGO_BASE_DIR), "../../../config/pyros_observatory/") | |
820 | + os.path.abspath(PYROS_DJANGO_BASE_DIR), | |
821 | + "../../../config/pyros_observatory/" | |
822 | + ) | |
830 | 823 | path_to_obs_config_folder = default_obsconfig_folder+"pyros_observatory_"+observatory+"/" |
831 | 824 | else: |
832 | 825 | observatories_configuration_folder = os.path.join( |
833 | - os.path.abspath(PYROS_DJANGO_BASE_DIR), "../../../../PYROS_OBSERVATORY/") | |
826 | + os.path.abspath(PYROS_DJANGO_BASE_DIR), | |
827 | + "../../../../PYROS_OBSERVATORY/" | |
828 | + ) | |
834 | 829 | if len(glob.glob(observatories_configuration_folder+"pyros_observatory_"+observatory+"/")) != 1: |
835 | 830 | # Observatory configuration folder not found |
836 | 831 | print( |
... | ... | @@ -845,15 +840,14 @@ def install_dependencies(observatory: str, unit: str, foreground: bool): |
845 | 840 | # Search for observatory config file |
846 | 841 | obs_config_file_name = glob.glob(os.path.join(path_to_obs_config_folder, "observatory.yml"))[0] |
847 | 842 | |
848 | - obs_config_file_path = os.path.join( | |
849 | - path_to_obs_config_folder, obs_config_file_name) | |
843 | + obs_config_file_path = os.path.join(path_to_obs_config_folder, obs_config_file_name) | |
850 | 844 | os.environ["PATH_TO_OBSCONF_FILE"] = obs_config_file_path |
851 | 845 | os.environ["PATH_TO_OBSCONF_FOLDER"] = path_to_obs_config_folder |
852 | 846 | os.environ["unit_name"] = unit if unit else '' |
853 | 847 | |
854 | 848 | # add path to pyros_django folder as the config class is supposed to work within this folder |
855 | 849 | cmd_test_obs_config = f"-c \"from src.core.pyros_django.obs_config.obsconfig_class import OBSConfig\nOBSConfig('{obs_config_file_path}')\"" |
856 | - if not execProcessFromVenv(cmd_test_obs_config,foreground=True): | |
850 | + if not execProcessFromVenv(cmd_test_obs_config, foreground=True): | |
857 | 851 | # Observatory configuration has an issue |
858 | 852 | exit(1) |
859 | 853 | else: |
... | ... | @@ -862,12 +856,8 @@ def install_dependencies(observatory: str, unit: str, foreground: bool): |
862 | 856 | from git import Repo, GitError |
863 | 857 | except: |
864 | 858 | pip = "pip" if IS_WINDOWS else "pip3" |
865 | - if WITH_DOCKER: | |
866 | - process = subprocess.Popen( | |
867 | - pip + " install --user --upgrade GitPython", shell=True) | |
868 | - else: | |
869 | - process = subprocess.Popen( | |
870 | - pip + " install --upgrade GitPython", shell=True) | |
859 | + usermode = '--user' if WITH_DOCKER else '' | |
860 | + process = subprocess.Popen(pip + f" install {usermode} --upgrade GitPython", shell=True) | |
871 | 861 | process.wait() |
872 | 862 | if process.returncode == 0: |
873 | 863 | # self.addExecuted(self.current_command, command) |
... | ... | @@ -1129,8 +1119,7 @@ def new_start(configfile: str, observatory: str, unit: str, computer_hostname: s |
1129 | 1119 | # pip = "pip" if platform.system() == "Windows" else "pip3" |
1130 | 1120 | # TODO: "python -m pip" au lieu de "pip" |
1131 | 1121 | pip = "pip" if IS_WINDOWS else "pip3" |
1132 | - process = subprocess.Popen( | |
1133 | - pip + " install --user --upgrade pykwalify", shell=True) | |
1122 | + process = subprocess.Popen(pip + " install --user --upgrade pykwalify", shell=True) | |
1134 | 1123 | process.wait() |
1135 | 1124 | if process.returncode == 0: |
1136 | 1125 | # self.addExecuted(self.current_command, command) |
... | ... | @@ -1311,12 +1300,9 @@ def _update_api_doc(): |
1311 | 1300 | print(os.getcwd()) |
1312 | 1301 | DOC_RST_PATH = "doc/doc_rst/" |
1313 | 1302 | # 0) Upgrade pip if new version available |
1314 | - if WITH_DOCKER: | |
1315 | - os.system(VENV_PYTHON + ' -m pip install --user --upgrade pip') | |
1316 | - else: | |
1317 | - os.system(VENV_PYTHON + ' -m pip install --upgrade pip') | |
1303 | + _venv_pip_install2('pip', '--upgrade') | |
1318 | 1304 | # 1) install/update Sphinx requirements (only if not yet installed) |
1319 | - venv_pip_install2(DOC_RST_PATH+'requirements.txt', '-r') | |
1305 | + _venv_pip_install2(DOC_RST_PATH+'requirements.txt', '-r') | |
1320 | 1306 | # 2) make html doc from RST doc |
1321 | 1307 | # cd doc/sourcedoc/ |
1322 | 1308 | change_dir(DOC_RST_PATH) |
... | ... | @@ -1488,20 +1474,24 @@ def notused_install_required(): |
1488 | 1474 | exit(1) |
1489 | 1475 | |
1490 | 1476 | |
1491 | -def venv_pip_install(package_name: str, options: str = ''): | |
1492 | - if WITH_DOCKER: | |
1493 | - os.system(VENV_PIP + ' install --user ' + options + ' ' + package_name) | |
1494 | - else: | |
1495 | - os.system(VENV_PIP + ' install ' + options + ' ' + package_name) | |
1477 | +def _venv_pip_install_from_venv(PIP_MODULE: bool, package_name: str, options: str = ''): | |
1478 | + ''' | |
1479 | + pip install package_name with options | |
1480 | + If PIP_MODULE install with 'python -m pip' else install with 'pip' | |
1481 | + If WITH_DOCKER => install in user mode (with --user option) | |
1482 | + ''' | |
1483 | + #_pip_install(options + ' ' + package_name) | |
1484 | + pip_cmd = VENV_PYTHON+' -m pip' if PIP_MODULE else VENV_PIP | |
1485 | + usermode = '--user' if WITH_DOCKER else '' | |
1486 | + os.system(f'{pip_cmd} install {usermode} {options} {package_name}') | |
1496 | 1487 | |
1497 | 1488 | |
1498 | -def venv_pip_install2(package_name: str, options: str = ''): | |
1499 | - if WITH_DOCKER: | |
1500 | - os.system(VENV_PYTHON + ' -m pip install --user ' | |
1501 | - + options + ' ' + package_name) | |
1502 | - else: | |
1503 | - os.system(VENV_PYTHON + ' -m pip install ' | |
1504 | - + options + ' ' + package_name) | |
1489 | +def _venv_pip_install(package_name: str, options: str = ''): | |
1490 | + _venv_pip_install_from_venv(False, package_name, options) | |
1491 | + | |
1492 | + | |
1493 | +def _venv_pip_install2(package_name: str, options: str = ''): | |
1494 | + _venv_pip_install_from_venv(True, package_name, options) | |
1505 | 1495 | |
1506 | 1496 | |
1507 | 1497 | def install_venv(EVEN_IF_ALREADY_EXISTS: bool = False): |
... | ... | @@ -1573,10 +1563,7 @@ def install_packages(): |
1573 | 1563 | print(Colors.LOG_BLUE + "-----------------------------Upgrade pip, wheel, and setuptools" + |
1574 | 1564 | "-----------------------------"+END_OF_LINE + Colors.END) |
1575 | 1565 | # Upgrade pip |
1576 | - if WITH_DOCKER: | |
1577 | - os.system(VENV_PYTHON + ' -m pip install --user --upgrade pip') | |
1578 | - else: | |
1579 | - os.system(VENV_PYTHON + ' -m pip install --upgrade pip') | |
1566 | + _venv_pip_install2('pip', '--upgrade') | |
1580 | 1567 | ''' |
1581 | 1568 | if (platform.system() == "Windows"): |
1582 | 1569 | os.system(venv + '\Scripts\python -m pip install --upgrade pip') |
... | ... | @@ -1585,27 +1572,24 @@ def install_packages(): |
1585 | 1572 | ''' |
1586 | 1573 | |
1587 | 1574 | # Pip upgrade wheel and setuptools |
1588 | - venv_pip_install('wheel', '--upgrade') | |
1575 | + _venv_pip_install('wheel', '--upgrade') | |
1589 | 1576 | # os.system(VENV_PIP+' install --upgrade wheel') |
1590 | 1577 | # Not working with python 3.8 (on 17/02/2022) |
1591 | 1578 | # venv_pip_install('setuptools', '--upgrade') |
1592 | - if WITH_DOCKER: | |
1593 | - os.system(VENV_PIP+' install --user setuptools==58') | |
1594 | - else: | |
1595 | - os.system(VENV_PIP+' install setuptools==58') | |
1579 | + _venv_pip_install('setuptools==58') | |
1596 | 1580 | |
1597 | 1581 | # Pip install required packages from REQUIREMENTS file |
1598 | 1582 | print() |
1599 | 1583 | |
1600 | 1584 | # General normal packages |
1601 | 1585 | print(Colors.LOG_BLUE + "-----------------------------Installing python packages via pip-----------------------------" + Colors.END) |
1602 | - venv_pip_install('../install/'+REQUIREMENTS, '-r') | |
1586 | + _venv_pip_install('../install/'+REQUIREMENTS, '-r') | |
1603 | 1587 | # os.system(VENV_PIP+' install -r ../install' + os.sep + REQUIREMENTS) |
1604 | 1588 | |
1605 | 1589 | # DEV only packages |
1606 | 1590 | if DEV: |
1607 | 1591 | print(Colors.LOG_BLUE + "-----------------------------Installing DEV python packages via pip-----------------------------" + Colors.END) |
1608 | - venv_pip_install('../install/'+REQUIREMENTS_DEV, '-r') | |
1592 | + _venv_pip_install('../install/'+REQUIREMENTS_DEV, '-r') | |
1609 | 1593 | print("FIN INSTALL PACKAGES") |
1610 | 1594 | return None |
1611 | 1595 | |
... | ... | @@ -1662,12 +1646,9 @@ def install_observatory(observatory): |
1662 | 1646 | from git import Repo, GitError |
1663 | 1647 | except: |
1664 | 1648 | pip = "pip" if IS_WINDOWS else "pip3" |
1665 | - if WITH_DOCKER: | |
1666 | - process = subprocess.Popen( | |
1667 | - pip + " install --user --upgrade GitPython", shell=True) | |
1668 | - else: | |
1669 | - process = subprocess.Popen( | |
1670 | - pip + " install --upgrade GitPython", shell=True) | |
1649 | + | |
1650 | + usermode = '--user' if WITH_DOCKER else '' | |
1651 | + process = subprocess.Popen(pip + f" install {usermode} --upgrade GitPython", shell=True) | |
1671 | 1652 | process.wait() |
1672 | 1653 | if process.returncode == 0: |
1673 | 1654 | # self.addExecuted(self.current_command, command) | ... | ... |
src/core/pyros_django/majordome/agent/AgentBasic.py renamed to src/core/pyros_django/majordome/agent/A_Basic.py
1 | 1 | #!/usr/bin/env python3 |
2 | 2 | |
3 | 3 | |
4 | -import time | |
5 | -import sys, argparse | |
4 | +#import time | |
5 | +import sys | |
6 | +import argparse | |
7 | +import os | |
6 | 8 | |
7 | 9 | ##import utils.Logger as L |
8 | 10 | |
9 | 11 | ##from .Agent import Agent |
10 | 12 | ##sys.path.append("..") |
11 | 13 | ###from agent.Agent import Agent, build_agent |
12 | -sys.path.append("../../../..") | |
14 | + | |
15 | +###sys.path.append("../../../..") | |
16 | + | |
17 | +# pwd = PYROS/ | |
18 | +pwd = os.environ['PROJECT_ROOT_PATH'] | |
19 | +if pwd not in sys.path: | |
20 | + sys.path.append(pwd) | |
21 | + | |
22 | +# Add paths PYROS/src and PYROS/src/core/pyros_django | |
23 | +short_paths = ['src', 'src/core/pyros_django'] | |
24 | +for short_path in short_paths: | |
25 | + path = os.path.join(pwd, short_path) | |
26 | + # ou plutot ? | |
27 | + #path = os.path.abspath(os.path.join(pwd, short_path)) | |
28 | + if path not in sys.path: | |
29 | + sys.path.insert(0, path) | |
30 | + | |
13 | 31 | #from src.core.pyros_django.common.models import AgentCmd |
14 | -from src.core.pyros_django.majordome.agent.Agent import Agent, build_agent | |
32 | +#from src.core.pyros_django.majordome.agent.Agent import Agent, build_agent | |
33 | +from majordome.agent.Agent import Agent, build_agent | |
34 | + | |
15 | 35 | #from src.core.pyros_django.common.models import AgentCmd |
16 | 36 | |
17 | 37 | from typing import List, Tuple, Union, Any |
... | ... | @@ -20,16 +40,16 @@ from typing import List, Tuple, Union, Any |
20 | 40 | |
21 | 41 | |
22 | 42 | |
23 | -class AgentBasic(Agent): | |
43 | +class A_Basic(Agent): | |
24 | 44 | |
25 | 45 | # FOR TEST ONLY |
26 | - # Run this agent in simulator mode | |
46 | + # - Run this agent in simulator mode | |
27 | 47 | TEST_MODE = False |
28 | - # Run the assertion tests at the end | |
48 | + # - Run the assertion tests at the end | |
29 | 49 | TEST_WITH_FINAL_TEST = False |
30 | 50 | #TEST_MAX_DURATION_SEC = None |
31 | 51 | TEST_MAX_DURATION_SEC = 400 |
32 | - # Who should I send commands to ? | |
52 | + # - Who should I send commands to ? | |
33 | 53 | TEST_COMMANDS_DEST = "myself" |
34 | 54 | #TEST_COMMANDS_DEST = "AgentB" |
35 | 55 | # Scenario to be executed |
... | ... | @@ -459,7 +479,7 @@ if __name__ == "__main__": |
459 | 479 | parser.add_argument("--computer",dest="computer",help='Launch agent with simulated computer hostname',action="store") |
460 | 480 | parser.add_argument("-t", action="store_true") |
461 | 481 | args = vars(parser.parse_args()) |
462 | - agent = build_agent(AgentBasic,param_constr=args) | |
482 | + agent = build_agent(A_Basic, param_constr=args) | |
463 | 483 | |
464 | 484 | ''' |
465 | 485 | TEST_MODE, configfile = extract_parameters() | ... | ... |
src/core/pyros_django/majordome/agent/Agent.py
... | ... | @@ -154,12 +154,13 @@ import config.old_config as config_old |
154 | 154 | #from config import * |
155 | 155 | |
156 | 156 | from majordome.models import AgentSurvey, AgentCmd, AgentLogs |
157 | -from user_mgmt.models import Period | |
157 | +from user_mgmt.models import Period, Quota | |
158 | 158 | |
159 | 159 | from vendor.guitastro.src.guitastro import Ephemeris |
160 | 160 | import pickle |
161 | 161 | |
162 | 162 | |
163 | +# Aliases for Cmd exceptions | |
163 | 164 | CmdException = AgentCmd.CmdException |
164 | 165 | CmdExceptionUnknown = AgentCmd.CmdExceptionUnknown |
165 | 166 | CmdExceptionUnimplemented = AgentCmd.CmdExceptionUnimplemented |
... | ... | @@ -218,7 +219,7 @@ class Colors: |
218 | 219 | BOLD = "\033[1m" |
219 | 220 | UNDERLINE = "\033[4m" |
220 | 221 | |
221 | -def printColor(color: Colors, message, file=sys.stdout, eol=os.linesep, forced=False): | |
222 | +def print_colored(color: Colors, message, file=sys.stdout, eol=os.linesep, forced=False): | |
222 | 223 | #system = platform.system() |
223 | 224 | """ |
224 | 225 | if (self.disp == False and forced == False): |
... | ... | @@ -237,10 +238,10 @@ def printFullTerm(color: Colors, string: str): |
237 | 238 | row = 1000 |
238 | 239 | disp = True |
239 | 240 | value = int(columns / 2 - len(string) / 2) |
240 | - printColor(color, "-" * value, eol="") | |
241 | - printColor(color, string, eol="") | |
241 | + print_colored(color, "-" * value, eol="") | |
242 | + print_colored(color, string, eol="") | |
242 | 243 | value += len(string) |
243 | - printColor(color, "-" * (columns - value)) | |
244 | + print_colored(color, "-" * (columns - value)) | |
244 | 245 | return 0 |
245 | 246 | |
246 | 247 | |
... | ... | @@ -301,23 +302,22 @@ class Agent: |
301 | 302 | # --- - INSTANCE attributes are accessible via agent.__dict__ |
302 | 303 | # --- |
303 | 304 | |
304 | - | |
305 | 305 | class EXEC_MODE(Enum): |
306 | 306 | SEQUENTIAL = 0 |
307 | 307 | THREAD = 1 |
308 | 308 | PROCESS = 2 |
309 | 309 | |
310 | 310 | # Default modes |
311 | - DEBUG_MODE:bool = False | |
311 | + DEBUG_MODE: bool = False | |
312 | 312 | #TEST_MODE = False |
313 | 313 | |
314 | 314 | # By default, a command is valid during 5s (and then perempted) |
315 | - DEFAULT_CMD_VALIDITY_DURATION:int = 5 | |
315 | + DEFAULT_CMD_VALIDITY_DURATION: int = 5 | |
316 | 316 | |
317 | 317 | # Wait a fixed number of seconds before each loop ? |
318 | 318 | #WITH_RANDOM_WAIT = False |
319 | 319 | # 1 sec by default |
320 | - __DELAY_NB_SEC:int = 1 | |
320 | + __DELAY_NB_SEC: int = 1 | |
321 | 321 | # - YES if TEST mode (in init()) |
322 | 322 | |
323 | 323 | # Default LOG level is INFO |
... | ... | @@ -332,7 +332,7 @@ class Agent: |
332 | 332 | - exec_mode (EXEC_MODE) : EXEC_MODE.SEQUENTIAL, EXEC_MODE.THREAD, or EXEC_MODE.PROCESS |
333 | 333 | - tooltip : description text (displayed on clic) |
334 | 334 | ''' |
335 | - _AGENT_SPECIFIC_COMMANDS: Dict [ str, Tuple[int, EXEC_MODE, str] ] = { | |
335 | + _AGENT_SPECIFIC_COMMANDS: Dict[str, Tuple[int, EXEC_MODE, str]] = { | |
336 | 336 | # Format : “cmd_name” : (timeout, exec_mode, tooltip) |
337 | 337 | |
338 | 338 | #"do_specific1" : (10, EXEC_MODE.SEQUENTIAL, ''), |
... | ... | @@ -352,14 +352,15 @@ class Agent: |
352 | 352 | # Maximum duration of this agent (only for SIMULATION mode) |
353 | 353 | # If set to 0, it will never exit except if asked (or CTRL-C) |
354 | 354 | # If set to 20, it will exit after 20s |
355 | - TEST_MAX_DURATION_SEC :int=0 | |
355 | + TEST_MAX_DURATION_SEC: int = 0 | |
356 | 356 | #TEST_MAX_DURATION_SEC = 30 |
357 | 357 | # Run this agent in simulator mode |
358 | 358 | #TEST_MODE = True |
359 | - WITH_SIMULATOR:bool = False | |
359 | + WITH_SIMULATOR: bool = False | |
360 | 360 | # Run the assertion tests at the end |
361 | - TEST_WITH_FINAL_TEST:bool = False | |
361 | + TEST_WITH_FINAL_TEST: bool = False | |
362 | 362 | |
363 | + # Aliases | |
363 | 364 | CMD_STATUS = AgentCmd.CMD_STATUS_CODES |
364 | 365 | AGT_STATUS = AgentSurvey.STATUS_CHOICES |
365 | 366 | |
... | ... | @@ -393,8 +394,8 @@ class Agent: |
393 | 394 | ##_TEST_COMMANDS_LIST: List[ Tuple[ bool, str, int, Optional[str], AgentCmd.CMD_STATUS_CODES ] ] = [ |
394 | 395 | |
395 | 396 | # Alias type for _TEST_COMMANDS_LIST (for more readability) |
396 | - TestCommand = Tuple[ bool, str, Optional[int], Optional[str], Optional[int]] | |
397 | - _TEST_COMMANDS_LIST: List[ TestCommand ] = [ | |
397 | + TestCommand = Tuple[bool, str, Optional[int], Optional[str], Optional[int]] | |
398 | + _TEST_COMMANDS_LIST: List[TestCommand] = [ | |
398 | 399 | # Format : (DO_IT, "self cmd_name cmd_args", validity, "expected_result", expected_status), |
399 | 400 | |
400 | 401 | #("self do_stop now", 200, '15.5', None), |
... | ... | @@ -591,8 +592,8 @@ class Agent: |
591 | 592 | ##_cmdts: AgentCmd = None |
592 | 593 | ##_next_cmdts = None |
593 | 594 | |
594 | - __agent_survey:AgentSurvey = None | |
595 | - __pending_commands:QuerySet = None # [] | |
595 | + __agent_survey: AgentSurvey = None | |
596 | + __pending_commands: QuerySet = None # [] | |
596 | 597 | |
597 | 598 | # List of agents I will send commands to |
598 | 599 | _my_client_agents_aliases = [] |
... | ... | @@ -610,28 +611,28 @@ class Agent: |
610 | 611 | |
611 | 612 | # new obsconfig init for agent: |
612 | 613 | ##def __init__(self, RUN_IN_THREAD=True): |
613 | - def __init__(self,simulated_computer=None): | |
614 | + def __init__(self, simulated_computer=None): | |
614 | 615 | |
615 | 616 | # Instance attributes declaration (with default values, or None) |
616 | - self.__UP_SINCE : Final = datetime.now(tz=timezone.utc) | |
617 | + self.__UP_SINCE: Final = datetime.now(tz=timezone.utc) | |
617 | 618 | #self.UP_SINCE = datetime.utcnow() |
618 | - self.__ROUTINE_ITER_START_IS_RUNNING:bool = False | |
619 | - self.__ROUTINE_ITER_END_IS_RUNNING:bool = False | |
620 | - self.__test_cmd_received_num:int = 0 # only for tests | |
619 | + self.__ROUTINE_ITER_START_IS_RUNNING: bool = False | |
620 | + self.__ROUTINE_ITER_END_IS_RUNNING: bool = False | |
621 | + self.__test_cmd_received_num: int = 0 # only for tests | |
621 | 622 | # Current Command running |
622 | - self.__CC :Optional[AgentCmd] #= None | |
623 | - self.__CC_thread :Union[StoppableThreadEvenWhenSleeping, multiprocessing.Process] #= None | |
623 | + self.__CC: Optional[AgentCmd] # = None | |
624 | + self.__CC_thread: Union[StoppableThreadEvenWhenSleeping, multiprocessing.Process] #= None | |
624 | 625 | # Previous Command running |
625 | 626 | ##self.__CC_prev :Optional[AgentCmd] = None |
626 | 627 | # Current Command exception (if occurs) |
627 | - self.__CCE :Optional[Exception] #= None | |
628 | + self.__CCE: Optional[Exception] #= None | |
628 | 629 | self.name = "Generic Agent" |
629 | - self.__status :str #= None | |
630 | - self.__mode :str #= None | |
631 | - self.unit :str #= None | |
630 | + self.__status: str # = None | |
631 | + self.__mode: str # = None | |
632 | + self.unit: str # = None | |
632 | 633 | #self.TEST_COMMANDS :List #= None |
633 | - self.TEST_COMMANDS :Iterable[Agent.TestCommand] #= None | |
634 | - self.__iter_num :int = 0 | |
634 | + self.TEST_COMMANDS: Iterable[Agent.TestCommand] # = None | |
635 | + self.__iter_num: int = 0 | |
635 | 636 | #print(AgentSurvey.MODE_CHOICES.IDLE) |
636 | 637 | #sys.exit() |
637 | 638 | |
... | ... | @@ -639,18 +640,21 @@ class Agent: |
639 | 640 | #self.__mode = self.MODE_ATTENTIVE |
640 | 641 | self.set_mode_attentive() |
641 | 642 | #self._set_mode(MODES.) |
643 | + | |
644 | + # Set Obs config | |
642 | 645 | obs_config_file_path = os.environ["PATH_TO_OBSCONF_FILE"] |
643 | 646 | path_to_obs_config_folder = os.environ["PATH_TO_OBSCONF_FOLDER"] |
644 | 647 | unit = os.environ["unit_name"] |
645 | - oc = OBSConfig(obs_config_file_path,unit) | |
648 | + oc = OBSConfig(obs_config_file_path, unit) | |
646 | 649 | pyros_yaml_path = os.environ["pyros_config_file"] |
647 | 650 | pyros_config = ConfigPyros(pyros_yaml_path) |
648 | 651 | self.set_config(oc, obs_config_file_path, path_to_obs_config_folder, unit, pyros_config, pyros_yaml_path) |
652 | + | |
653 | + # Agent name | |
649 | 654 | agent_name_from_config = self.get_config().get_agent_name_from_config(self.__class__.__name__,simulated_computer) |
650 | - if agent_name_from_config: | |
651 | - self.name = agent_name_from_config | |
652 | - else: | |
653 | - self.name = self.__class__.__name__ | |
655 | + self.name = agent_name_from_config if agent_name_from_config else self.__class__.__name__ | |
656 | + | |
657 | + # LOG | |
654 | 658 | log.addHandler(handler_filebyagent(logging.INFO, self.name)) |
655 | 659 | #log.addHandler(handler_filebyagent(logging.INFO, self.name)) |
656 | 660 | log.debug("start Agent init") |
... | ... | @@ -670,10 +674,11 @@ class Agent: |
670 | 674 | |
671 | 675 | self.TEST_COMMANDS = iter(self._TEST_COMMANDS_LIST) |
672 | 676 | ##self.RUN_IN_THREAD = RUN_IN_THREAD |
677 | + | |
673 | 678 | self.__set_status(self.AGT_STATUS.LAUNCHED) |
674 | 679 | ####self._set_idle() |
675 | 680 | |
676 | - # Create 1st survey if none | |
681 | + # Get survey or create 1st one if none | |
677 | 682 | #tmp = AgentSurvey.objects.filter(name=self.name) |
678 | 683 | #if len(tmp) == 0: |
679 | 684 | #nb_agents = AgentSurvey.objects.filter(name=self.name).count() |
... | ... | @@ -857,18 +862,16 @@ class Agent: |
857 | 862 | @property |
858 | 863 | def ROUTINE_ITER_START_IS_RUNNING(self): |
859 | 864 | return self.__ROUTINE_ITER_START_IS_RUNNING |
865 | + | |
860 | 866 | @property |
861 | 867 | def ROUTINE_ITER_END_IS_RUNNING(self): |
862 | 868 | return self.__ROUTINE_ITER_END_IS_RUNNING |
863 | 869 | |
864 | - | |
865 | - | |
866 | - | |
867 | 870 | def set_config(self, oc: OBSConfig, obs_config_file_path: str, path_to_obs_config_folder: str, unit: str, pyros_config: str, pyros_yaml_path: str): |
868 | 871 | self._oc = { |
869 | - 'config' : oc, | |
870 | - 'pyros_config' : pyros_config, | |
871 | - 'env' : [ | |
872 | + 'config': oc, | |
873 | + 'pyros_config': pyros_config, | |
874 | + 'env': [ | |
872 | 875 | obs_config_file_path, |
873 | 876 | path_to_obs_config_folder, |
874 | 877 | unit, |
... | ... | @@ -962,10 +965,6 @@ class Agent: |
962 | 965 | print("\n") |
963 | 966 | |
964 | 967 | |
965 | - | |
966 | - | |
967 | - | |
968 | - | |
969 | 968 | def get_config_filename(self, config_filename: str): |
970 | 969 | if not config_filename: |
971 | 970 | #config_filename = self.DEFAULT_CONFIG_FILE_NAME |
... | ... | @@ -1013,7 +1012,7 @@ class Agent: |
1013 | 1012 | |
1014 | 1013 | |
1015 | 1014 | |
1016 | - def _get_real_agent_name(self, agent_alias_name:str)->str: | |
1015 | + def _get_real_agent_name(self, agent_alias_name: str) -> str: | |
1017 | 1016 | #self.printd("key is", agent_alias_name) |
1018 | 1017 | ''' |
1019 | 1018 | if not self._my_client_agents: return agent_alias_name |
... | ... | @@ -1023,7 +1022,7 @@ class Agent: |
1023 | 1022 | |
1024 | 1023 | |
1025 | 1024 | |
1026 | - def run(self, nb_iter:int=None, FOR_REAL:bool=True): | |
1025 | + def run(self, nb_iter: int = None, FOR_REAL: bool = True): | |
1027 | 1026 | """ |
1028 | 1027 | FOR_REAL: set to False if you don't want Agent to send commands to devices but just print messages without really doing anything |
1029 | 1028 | """ |
... | ... | @@ -3593,7 +3592,17 @@ class Agent: |
3593 | 3592 | val[0] = d_total |
3594 | 3593 | val[-1] = d_total - val[-2] |
3595 | 3594 | # --- update db TODO |
3596 | - | |
3595 | + quota = Quota() | |
3596 | + quota_attributes = {} | |
3597 | + quota_attributes["id_period"] = operiod.id | |
3598 | + quota_attributes["night_id"] = night | |
3599 | + quota_attributes["d_previousq"] = d_prev | |
3600 | + quota_attributes["d_currentq"] = d_cur | |
3601 | + quota_attributes["d_totalq"] = d_total | |
3602 | + quota_attributes["d_nextq"] = d_total - d_cur | |
3603 | + quota.set_attributes_and_save(quota_attributes) | |
3604 | + operiod.quota = quota | |
3605 | + operiod.save() | |
3597 | 3606 | #log.info(f"Write {filename=}") |
3598 | 3607 | #pickle.dump(night_info, open(filename, "wb")) |
3599 | 3608 | return night_info | ... | ... |
src/core/pyros_django/misc/fixtures/initial_fixture_dev_TZ.json
... | ... | @@ -287,8 +287,7 @@ |
287 | 287 | "fields": { |
288 | 288 | "name": "Admin", |
289 | 289 | "desc": "", |
290 | - "priority": 8, | |
291 | - "quota": 9999.0 | |
290 | + "priority": 8 | |
292 | 291 | } |
293 | 292 | }, |
294 | 293 | { |
... | ... | @@ -297,8 +296,7 @@ |
297 | 296 | "fields": { |
298 | 297 | "name": "Observer", |
299 | 298 | "desc": "", |
300 | - "priority": 2, | |
301 | - "quota": 9999.0 | |
299 | + "priority": 2 | |
302 | 300 | } |
303 | 301 | }, |
304 | 302 | { |
... | ... | @@ -307,8 +305,7 @@ |
307 | 305 | "fields": { |
308 | 306 | "name": "TAC", |
309 | 307 | "desc": "", |
310 | - "priority": 1, | |
311 | - "quota": 9999.0 | |
308 | + "priority": 1 | |
312 | 309 | } |
313 | 310 | }, |
314 | 311 | { |
... | ... | @@ -317,8 +314,7 @@ |
317 | 314 | "fields": { |
318 | 315 | "name": "Management board member", |
319 | 316 | "desc": "", |
320 | - "priority": 3, | |
321 | - "quota": 9999.0 | |
317 | + "priority": 3 | |
322 | 318 | } |
323 | 319 | }, |
324 | 320 | { |
... | ... | @@ -327,8 +323,7 @@ |
327 | 323 | "fields": { |
328 | 324 | "name": "Operator", |
329 | 325 | "desc": "", |
330 | - "priority": 4, | |
331 | - "quota": 9999.0 | |
326 | + "priority": 4 | |
332 | 327 | } |
333 | 328 | }, |
334 | 329 | { |
... | ... | @@ -337,8 +332,7 @@ |
337 | 332 | "fields": { |
338 | 333 | "name": "Unit-PI", |
339 | 334 | "desc": "", |
340 | - "priority": 7, | |
341 | - "quota": 9999.0 | |
335 | + "priority": 7 | |
342 | 336 | } |
343 | 337 | }, |
344 | 338 | { |
... | ... | @@ -347,8 +341,7 @@ |
347 | 341 | "fields": { |
348 | 342 | "name": "Unit-board", |
349 | 343 | "desc": "", |
350 | - "priority": 6, | |
351 | - "quota": 9999.0 | |
344 | + "priority": 6 | |
352 | 345 | } |
353 | 346 | }, |
354 | 347 | { |
... | ... | @@ -357,8 +350,7 @@ |
357 | 350 | "fields": { |
358 | 351 | "name": "Visitor", |
359 | 352 | "desc": "Account without any privilege", |
360 | - "priority": 0, | |
361 | - "quota": 0.0 | |
353 | + "priority": 0 | |
362 | 354 | } |
363 | 355 | }, |
364 | 356 | { |
... | ... | @@ -366,7 +358,7 @@ |
366 | 358 | "pk": 2, |
367 | 359 | "fields": { |
368 | 360 | "name": "CNRS", |
369 | - "quota": 20 | |
361 | + "f_quota": 0.2 | |
370 | 362 | } |
371 | 363 | }, |
372 | 364 | { |
... | ... | @@ -374,7 +366,7 @@ |
374 | 366 | "pk": 1, |
375 | 367 | "fields": { |
376 | 368 | "name": "CNES", |
377 | - "quota": 80 | |
369 | + "f_quota": 0.8 | |
378 | 370 | } |
379 | 371 | }, |
380 | 372 | { | ... | ... |
src/core/pyros_django/scheduling/A_Scheduler.py
... | ... | @@ -29,6 +29,7 @@ import argparse |
29 | 29 | import os |
30 | 30 | import pickle |
31 | 31 | import socket |
32 | + | |
32 | 33 | pwd = os.environ['PROJECT_ROOT_PATH'] |
33 | 34 | if pwd not in sys.path: |
34 | 35 | sys.path.append(pwd) |
... | ... | @@ -39,7 +40,8 @@ for short_path in short_paths: |
39 | 40 | if path not in sys.path: |
40 | 41 | sys.path.insert(0, path) |
41 | 42 | |
42 | -from src.core.pyros_django.majordome.agent.Agent import Agent, build_agent, log, parse_args | |
43 | +#from src.core.pyros_django.majordome.agent.Agent import Agent, build_agent, log, parse_args | |
44 | +from majordome.agent.Agent import Agent, build_agent, log, parse_args | |
43 | 45 | from seq_submit.models import Sequence |
44 | 46 | from user_mgmt.models import Period, ScientificProgram, SP_Period |
45 | 47 | from scheduling.models import PredictiveSchedule, EffectiveSchedule |
... | ... | @@ -220,6 +222,12 @@ class A_Scheduler(Agent): |
220 | 222 | # 'vote_referee2' |
221 | 223 | """ |
222 | 224 | |
225 | + | |
226 | + def update_db_quota_sequence(sequence, quota_attributes, id_period, night_id, d_total=sequence_info['duration']): | |
227 | + sequence_quota = sequence.quota | |
228 | + sp_quota = sequence.scientific_program | |
229 | + institute_quota = | |
230 | + | |
223 | 231 | def _compute_schedule_1(self): |
224 | 232 | """Simple scheduler based on selection-insertion one state algorithm. |
225 | 233 | ... | ... |
src/core/pyros_django/scp_mgmt/A_SCP_Manager.py
... | ... | @@ -17,7 +17,7 @@ for short_path in short_paths: |
17 | 17 | |
18 | 18 | # Project imports |
19 | 19 | from majordome.agent.Agent import Agent, build_agent |
20 | -from user_mgmt.models import PyrosUser, SP_Period, Period, SP_Period, SP_Period_Guest, SP_PeriodWorkflow | |
20 | +from user_mgmt.models import PyrosUser, Institute, SP_Period, Period, SP_Period, SP_Period_Guest, SP_PeriodWorkflow | |
21 | 21 | import vendor.guitastro.src.guitastro as guitastro |
22 | 22 | |
23 | 23 | # Django imports |
... | ... | @@ -229,6 +229,9 @@ class A_SCP_Manager(Agent): |
229 | 229 | next_sp_to_be_notified = next_sp.filter(status=SP_Period.STATUSES_ACCEPTED,is_valid = True) |
230 | 230 | self.send_mail_to_observers_for_notification(next_sp_to_be_notified) |
231 | 231 | SP_PeriodWorkflow.objects.create(period=self.period,action=SP_PeriodWorkflow.NOTIFICATION) |
232 | + self.update_sun_moon_ephems() | |
233 | + self.set_quota_for_institutes(self.period.id) | |
234 | + self.set_quota_for_sp(self.period.id) | |
232 | 235 | |
233 | 236 | def routine_process_body(self): |
234 | 237 | print("routine automatic period workflow") |
... | ... | @@ -243,66 +246,27 @@ class A_SCP_Manager(Agent): |
243 | 246 | for n in range(int((end_date - start_date).days)): |
244 | 247 | yield start_date + timedelta(n) |
245 | 248 | |
246 | - def do_generate_ephem_moon_and_sun_for_period(self, period_id:int): | |
247 | - # Obsolete TODO | |
248 | - period = Period.objects.get(id=period_id) | |
249 | - period_start_date = period.start_date | |
250 | - period_end_date = period.end_date | |
251 | - | |
252 | - root_path = os.environ.get("PROJECT_ROOT_PATH") | |
253 | - os.chdir(root_path) | |
254 | - | |
255 | - home = guitastro.Home(self._oc["config"].getHome()) | |
256 | - self._fn.longitude(home.longitude) | |
257 | - | |
258 | - ephem_data_night_folder = self._fn.rootdir | |
259 | - if os.path.exists(ephem_data_night_folder): | |
260 | - period_id = str(period.id) | |
261 | - # form correct period string | |
262 | - if len(str(period.id)) < 3: | |
263 | - while len(period_id) < 3: | |
264 | - period_id = "0" + period_id | |
265 | - period_id = "P" + period_id | |
266 | - ephem_data_night_folder = os.path.join(ephem_data_night_folder, period_id) | |
267 | - if not os.path.exists(ephem_data_night_folder): | |
268 | - os.makedirs(ephem_data_night_folder, exist_ok=True) | |
269 | - for single_date in self.daterange(period_start_date, period_end_date): | |
270 | - current_date = single_date.strftime("%Y%m%d") | |
271 | - | |
272 | - eph = guitastro.Ephemeris() | |
273 | - target_sun = "sun" | |
274 | - ephem_sun = eph.target2night(target_sun, current_date, None, None) | |
275 | - | |
276 | - self._fn.fcontext_create("pyros_eph", "Ephemeris PyROS") | |
277 | - self._fn.fcontext = "pyros_eph" | |
278 | - self._fn.pathnaming("PyROS.eph.1") | |
279 | - self._fn.rootdir = "/tmp/eph" | |
280 | - self._fn.extension = ".f" | |
281 | - param = {} | |
282 | - param['period'] = period_id | |
283 | - param['date'] = current_date | |
284 | - param['unit'] = self.pconfig.unit_name | |
285 | - param['version'] = 1 | |
286 | - param['target'] = "sun" | |
287 | - fname = self._fn.naming_set(param) | |
288 | - file_name_sun = self._fn.join(fname) | |
289 | - | |
290 | - # moon parameters | |
291 | - param['target'] = "moon" | |
292 | - fname = self._fn.naming_set(param) | |
293 | - file_name_moon = self._fn.join(fname) | |
294 | - os.chdir(ephem_data_night_folder) | |
295 | - pickle.dump(ephem_sun, open(f"{file_name_sun}.f","wb")) | |
296 | - target_moon = "moon" | |
297 | - ephem_moon = eph.target2night(target_moon, current_date, None, None) | |
298 | - pickle.dump(ephem_moon, open(f"{file_name_moon}.f","wb")) | |
299 | - | |
300 | - | |
301 | - | |
302 | - | |
303 | - # lire tous les fichiers sun de la period et appliquer le sky_elev pour déterminer le quota total de période | |
304 | - # prendre le champ alt & | |
305 | - # -> prendre tous les éléments en dessous de duskelev (cf l412 d'A_Sheduler, exemple 14 des éphémérides pour trier ces éléménts) et faire un sum | |
249 | + def set_quota_for_institutes(self, id_period): | |
250 | + for institute in Institute.objects.all(): | |
251 | + quota_f = institute.quota_f | |
252 | + # the lowest id of quota table for this period should be the first night of the period | |
253 | + period_quota = Period.objects.get(id=id_period).quota | |
254 | + institute_quota = period_quota.convert_to_quota(quota_f) | |
255 | + new_quota = Quota() | |
256 | + new_quota.set_attributes_and_save(institute_quota) | |
257 | + institute.quota = new_quota | |
258 | + institute.save() | |
259 | + | |
260 | + def set_quota_for_SP(self, id_period): | |
261 | + period = Period.objects.get(id=id_period) | |
262 | + for sp_period in SP_Period.objects.filter(period=period) | |
263 | + sp = sp_period.scientific_program | |
264 | + institute = sp.institute | |
265 | + institute_quota = institute.quota | |
266 | + new_quota = Quota() | |
267 | + quota_attributes = institute_quota.convert_to_quota(sp.quota_f) | |
268 | + new_quota.set_attributes_and_save(quota_attributes) | |
269 | + | |
306 | 270 | |
307 | 271 | if __name__ == "__main__": |
308 | 272 | |
... | ... | @@ -313,7 +277,7 @@ if __name__ == "__main__": |
313 | 277 | print("ARGV OF AGENT SP :",sys.argv) |
314 | 278 | if len(sys.argv) > 1 and sys.argv[1] == "test": |
315 | 279 | print("i'm in test") |
316 | - agentSP = AgentSP(use_db_test=True) | |
280 | + agentSP = A_SCP_Manager(use_db_test=True) | |
317 | 281 | agentSP.run() |
318 | 282 | #agent = build_agent(agentSP, RUN_IN_THREAD=True) |
319 | 283 | else: | ... | ... |
src/core/pyros_django/scp_mgmt/forms.py
... | ... | @@ -39,13 +39,13 @@ class SP_PeriodForm(forms.ModelForm): |
39 | 39 | fields = ( |
40 | 40 | #"period", |
41 | 41 | "public_visibility", |
42 | - "quota_minimal", | |
43 | - "quota_nominal", | |
44 | - "quota_allocated", | |
45 | - "over_quota_duration", | |
46 | - "over_quota_duration_allocated", | |
47 | - "token", | |
48 | - "token_allocated", | |
42 | + # "quota_minimal", | |
43 | + # "quota_nominal", | |
44 | + # "quota_allocated", | |
45 | + # "over_quota_duration", | |
46 | + # "over_quota_duration_allocated", | |
47 | + # "token", | |
48 | + # "token_allocated", | |
49 | 49 | "vote_referee1", |
50 | 50 | "reason_referee1", |
51 | 51 | "vote_referee2", | ... | ... |
src/core/pyros_django/scp_mgmt/models.py
1 | -#from django.db import models | |
1 | +from django.db import models | |
2 | + | |
3 | + | |
4 | +class Quota(models.Model): | |
5 | + id_period = models.BigIntegerField(blank=True, null=True) | |
6 | + night_id = models.CharField(max_length=8, null=True, blank=True, db_index=True) | |
7 | + d_totalq = models.BigIntegerField(default=0, blank=True, null=True) | |
8 | + d_totalx = models.BigIntegerField(default=0, blank=True, null=True) | |
9 | + | |
10 | + d_previousq = models.BigIntegerField(default=0, blank=True, null=True) | |
11 | + d_previousx = models.BigIntegerField(default=0, blank=True, null=True) | |
12 | + | |
13 | + d_currentq = models.BigIntegerField(default=0, blank=True, null=True) | |
14 | + d_currentx = models.BigIntegerField(default=0, blank=True, null=True) | |
15 | + | |
16 | + d_passedq = models.BigIntegerField(default=0, blank=True, null=True) | |
17 | + d_passedx = models.BigIntegerField(default=0, blank=True, null=True) | |
18 | + | |
19 | + d_scheduleq = models.BigIntegerField(default=0, blank=True, null=True) | |
20 | + d_schedulex = models.BigIntegerField(default=0, blank=True, null=True) | |
21 | + | |
22 | + d_nextq = models.BigIntegerField(default=0, blank=True, null=True) | |
23 | + d_nextx = models.BigIntegerField(default=0, blank=True, null=True) | |
24 | + | |
25 | + @property | |
26 | + def d_total(self): | |
27 | + return self.d_totalq + self.d_totalx | |
28 | + | |
29 | + @property | |
30 | + def d_previous(self): | |
31 | + return self.d_previousq + self.d_previousx | |
32 | + | |
33 | + @property | |
34 | + def d_current(self): | |
35 | + return self.d_currentq + self.d_currentx | |
36 | + | |
37 | + @property | |
38 | + def d_passed(self): | |
39 | + return self.d_passedq + self.d_passedx | |
40 | + | |
41 | + @property | |
42 | + def d_schedule(self): | |
43 | + return self.d_scheduleq + self.d_schedulex | |
44 | + | |
45 | + @property | |
46 | + def d_next(self): | |
47 | + return self.d_nextq + self.d_nextx | |
48 | + | |
49 | + def set_attributes_and_save(self, quota_attributes:dict): | |
50 | + | |
51 | + if quota_attributes.get("id_period") != None: | |
52 | + self.id_period = quota_attributes["id_period"] | |
53 | + if quota_attributes.get("night_id") != None: | |
54 | + self.night_id = quota_attributes["night_id"] | |
55 | + if quota_attributes.get("d_totalq") != None: | |
56 | + self.d_totalq = quota_attributes["d_totalq"] | |
57 | + if quota_attributes.get("d_totalx") != None: | |
58 | + self.d_totalx = quota_attributes["d_totalx"] | |
59 | + if quota_attributes.get("d_previousq") != None: | |
60 | + self.d_previousq = quota_attributes["d_previousq"] | |
61 | + if quota_attributes.get("d_previousx") != None: | |
62 | + self.d_previousx = quota_attributes["d_previousx"] | |
63 | + if quota_attributes.get("d_currentq") != None: | |
64 | + self.d_currentq = quota_attributes["d_currentq"] | |
65 | + if quota_attributes.get("d_currentx") != None: | |
66 | + self.d_currentx = quota_attributes["d_currentx"] | |
67 | + if quota_attributes.get("d_scheduleq") != None: | |
68 | + self.d_scheduleq = quota_attributes["d_scheduleq"] | |
69 | + if quota_attributes.get("d_schedulex") != None: | |
70 | + self.d_schedule = quota_attributes["d_schedulex"] | |
71 | + if quota_attributes.get("d_nextq") != None: | |
72 | + self.d_nextq = quota_attributes["d_nextq"] | |
73 | + if quota_attributes.get("d_nextx") != None: | |
74 | + self.d_nextx = quota_attributes["d_nextx"] | |
75 | + self.save() | |
76 | + | |
77 | + def convert_to_quota(self, quota_f): | |
78 | + quota_institute = {} | |
79 | + | |
80 | + quota_institute["d_totalq"] = self.d_totalq * quota_f | |
81 | + quota_institute["d_totalx"] = self.d_totalx * quota_f | |
82 | + quota_institute["d_previousq"] = self.d_previousq * quota_f | |
83 | + quota_institute["d_previousx"] = self.d_previousx * quota_f | |
84 | + quota_institute["d_currentq"] = self.d_currentq * quota_f | |
85 | + quota_institute["d_currentx"] = self.d_currentx * quota_f | |
86 | + quota_institute["d_passedq"] = self.d_passedq * quota_f | |
87 | + quota_institute["d_passedx"] = self.d_passedx * quota_f | |
88 | + quota_institute["d_scheduleq"] = self.d_scheduleq * quota_f | |
89 | + quota_institute["d_schedulex"] = self.d_schedulex * quota_f | |
90 | + quota_institute["d_nextq"] = self.d_nextq * quota_f | |
91 | + quota_institute["d_nextx"] = self.d_nextx * quota_f | |
92 | + | |
93 | + return quota_institute | ... | ... |
src/core/pyros_django/seq_submit/models.py
... | ... | @@ -34,6 +34,7 @@ from django.db.models import Q |
34 | 34 | |
35 | 35 | # Project imports |
36 | 36 | from user_mgmt.models import PyrosUser, ScientificProgram, Period |
37 | +from scp_mgmt.models import Quota | |
37 | 38 | # DeviceCommand is used by class Command |
38 | 39 | sys.path.append("../../..") |
39 | 40 | from vendor.guitastro.src.guitastro import FileNames, Ima |
... | ... | @@ -349,8 +350,9 @@ class Sequence(models.Model): |
349 | 350 | obsolete = models.BooleanField(default=False) |
350 | 351 | processing = models.BooleanField(default=False) |
351 | 352 | flag = models.CharField(max_length=45, blank=True, null=True) |
352 | - period = models.ForeignKey(Period, on_delete=models.DO_NOTHING, related_name="sequences", blank=True, null=True) | |
353 | + period = models.ForeignKey(Period, on_delete=models.DO_NOTHING, related_name="sequence_period", blank=True, null=True) | |
353 | 354 | #period = models.ForeignKey("Period", on_delete=models.DO_NOTHING, related_name="sequences", blank=True, null=True) |
355 | + quota = models.ForeignKey(Quota, on_delete=models.DO_NOTHING,related_name="sequence_quotas", blank=True, null=True) | |
354 | 356 | |
355 | 357 | start_date = models.DateTimeField( |
356 | 358 | blank=True, null=True, default=timezone.now, editable=True) | ... | ... |
src/core/pyros_django/user_mgmt/models.py
... | ... | @@ -37,6 +37,11 @@ from django.db.models.query import QuerySet |
37 | 37 | from django.utils import timezone |
38 | 38 | |
39 | 39 | |
40 | +# Pyros import | |
41 | + | |
42 | +from scp_mgmt.models import Quota | |
43 | + | |
44 | + | |
40 | 45 | |
41 | 46 | |
42 | 47 | # Project imports |
... | ... | @@ -52,7 +57,6 @@ class UserLevel(models.Model): |
52 | 57 | name = models.CharField(max_length=45, blank=True, null=True) |
53 | 58 | desc = models.TextField(blank=True, null=True) |
54 | 59 | priority = models.IntegerField(blank=True, null=True) |
55 | - quota = models.FloatField(blank=True, null=True) | |
56 | 60 | |
57 | 61 | class Meta: |
58 | 62 | managed = True |
... | ... | @@ -79,9 +83,11 @@ class Country(models.Model): |
79 | 83 | class Institute(models.Model): |
80 | 84 | name = models.CharField(max_length=100, blank=False, |
81 | 85 | null=False, unique=True) |
82 | - # quota % | |
83 | - quota = models.IntegerField( | |
84 | - validators=[MinValueValidator(0), MaxValueValidator(100)]) | |
86 | + # fraction quota | |
87 | + quota_f = models.FloatField( | |
88 | + validators=[MinValueValidator(0), MaxValueValidator(1)], blank=True, null=True) | |
89 | + | |
90 | + quota = models.ForeignKey(Quota, on_delete=models.DO_NOTHING,related_name="institute_quotas", blank=True, null=True) | |
85 | 91 | #representative_user = models.ForeignKey("PyrosUser", on_delete=models.DO_NOTHING,related_name="institutes",default=1) |
86 | 92 | |
87 | 93 | def __str__(self) -> str: |
... | ... | @@ -90,6 +96,8 @@ class Institute(models.Model): |
90 | 96 | class Meta: |
91 | 97 | db_table = "institute" |
92 | 98 | |
99 | + | |
100 | + | |
93 | 101 | class ScienceTheme(models.Model): |
94 | 102 | name = models.CharField(max_length=120, blank=False, null=False, default="", unique=True) |
95 | 103 | def __str__(self) -> str: |
... | ... | @@ -367,6 +375,8 @@ class Period(models.Model): |
367 | 375 | data_accessibility_duration = models.PositiveIntegerField( |
368 | 376 | blank=True, null=True, default=365*10, editable=True) |
369 | 377 | |
378 | + quota = models.ForeignKey(Quota, on_delete=models.DO_NOTHING,related_name="period_quotas", blank=True, null=True) | |
379 | + | |
370 | 380 | @property |
371 | 381 | def end_date(self): |
372 | 382 | return self.start_date + relativedelta(days=self.exploitation_duration) |
... | ... | @@ -461,10 +471,13 @@ class ScientificProgram(models.Model): |
461 | 471 | description_short = models.TextField(default="", max_length=320) |
462 | 472 | description_long = models.TextField(default="") |
463 | 473 | institute = models.ForeignKey('Institute', on_delete=models.DO_NOTHING, related_name="scientific_programs") |
464 | - sp_pi = models.ForeignKey('PyrosUser', on_delete=models.DO_NOTHING, related_name="Scientific_Program_Users") | |
474 | + sp_pi = models.ForeignKey('PyrosUser', on_delete=models.DO_NOTHING, related_name="scientific_Program_Users") | |
465 | 475 | science_theme = models.ForeignKey(ScienceTheme, on_delete=models.DO_NOTHING, related_name="scientific_program_theme", default=1) |
466 | 476 | is_auto_validated = models.BooleanField(default=False) |
467 | 477 | objects = ScientificProgramManager() |
478 | + quota = models.ForeignKey(Quota, on_delete=models.DO_NOTHING,related_name="scientific_program_quotas", blank=True, null=True) | |
479 | + quota_f = models.FloatField( | |
480 | + validators=[MinValueValidator(0), MaxValueValidator(1)], blank=True, null=True) | |
468 | 481 | |
469 | 482 | class Meta: |
470 | 483 | managed = True |
... | ... | @@ -525,14 +538,6 @@ class SP_Period(models.Model): |
525 | 538 | is_valid = models.TextField( |
526 | 539 | choices=IS_VALID, default=IS_VALID_REJECTED, blank=True) |
527 | 540 | status = models.TextField(choices=STATUSES, default=STATUSES_DRAFT) |
528 | - quota_minimal = models.PositiveIntegerField(default=0) | |
529 | - quota_nominal = models.PositiveIntegerField(default=0) | |
530 | - quota_allocated = models.PositiveIntegerField(default=0, blank=True) | |
531 | - quota_remaining = models.PositiveIntegerField(default=0, blank=True) | |
532 | - over_quota_duration = models.PositiveIntegerField(default=0) | |
533 | - over_quota_duration_allocated = models.PositiveIntegerField( | |
534 | - default=0, blank=True) | |
535 | - over_quota_duration_remaining = models.PositiveIntegerField(default=0) | |
536 | 541 | token = models.PositiveIntegerField(default=0) |
537 | 542 | token_allocated = models.PositiveIntegerField(default=0, blank=True) |
538 | 543 | token_remaining = models.PositiveIntegerField(default=0, blank=True) | ... | ... |