From a6e636042a40776beb38f49f28a98a149db32235 Mon Sep 17 00:00:00 2001 From: Alexis Koralewski Date: Tue, 2 Nov 2021 17:33:31 +0100 Subject: [PATCH] adding agentSP and tests for F02, updating other tests according new attributes of SCP --- pyros.py | 9 ++++++--- src/core/pyros_django/common/models.py | 49 +++++++++++++++++++++++++++++++++++++++---------- src/core/pyros_django/misc/fixtures/tests/scientific_program_TZ.json | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ src/core/pyros_django/obsconfig/tests.py | 5 +++-- src/core/pyros_django/pyros/settings.py | 4 ++-- src/core/pyros_django/scheduler/Scheduler.py | 10 +++++----- src/core/pyros_django/scheduler/UserManager.py | 2 +- src/core/pyros_django/scientific_program/AgentSP.py | 222 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/core/pyros_django/scientific_program/functions.py | 1 - src/core/pyros_django/scientific_program/templates/scientific_program/create_period.html | 55 ++++++++++++++++++++----------------------------------- src/core/pyros_django/scientific_program/templates/scientific_program/period_detail.html | 1 + src/core/pyros_django/scientific_program/templates/scientific_program/scientific_program_detail.html | 2 +- src/core/pyros_django/scientific_program/templates/scientific_program/scientific_program_detail_edit.html | 121 ------------------------------------------------------------------------------------------------------------------------- src/core/pyros_django/scientific_program/templates/scientific_program/scientific_program_period_detail_edit.html | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/core/pyros_django/scientific_program/templates/scientific_program/test_tac_auto.html | 9 +++++++++ src/core/pyros_django/scientific_program/tests.py | 889 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- src/core/pyros_django/scientific_program/urls.py | 3 ++- src/core/pyros_django/scientific_program/views.py | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------- src/core/pyros_django/user_manager/tests.py | 3 ++- 19 files changed, 1294 insertions(+), 417 deletions(-) create mode 100644 src/core/pyros_django/scientific_program/AgentSP.py delete mode 100644 src/core/pyros_django/scientific_program/templates/scientific_program/scientific_program_detail_edit.html create mode 100644 src/core/pyros_django/scientific_program/templates/scientific_program/scientific_program_period_detail_edit.html create mode 100644 src/core/pyros_django/scientific_program/templates/scientific_program/test_tac_auto.html diff --git a/pyros.py b/pyros.py index b8a8e9d..e4bca1f 100755 --- a/pyros.py +++ b/pyros.py @@ -37,7 +37,8 @@ AGENTS = { "agentX" : "AgentX", "agentA" : "AgentA", "agentB" : "AgentB", - "agentM" : "AgentM", + "agentM" : "AgentM", + "agentSP" : "AgentSP", #"agentDevice" : "AgentDevice", #"agentDeviceTelescopeGemini" : "AgentDeviceTelescopeGemini", "agentDeviceGemini" : "AgentDeviceGemini", @@ -524,8 +525,8 @@ def test(app): #start_dir = os.getcwd() if app == None: #apps = ['obsconfig','scientific_program','common', 'scheduler', 'routine_manager', 'user_manager', 'alert_manager.tests.TestStrategyChange'] - # Removing temporiraly scientific_program and alert_manager from tests - apps = ['obsconfig','common', 'scheduler', 'routine_manager', 'user_manager'] + # Removing alert_manager from tests + apps = ['obsconfig',"scientific_program",'common', 'scheduler', 'routine_manager', 'user_manager'] else: os.environ["PATH_TO_OBSCONF_FILE"] = os.path.join(os.path.abspath(PYROS_DJANGO_BASE_DIR),"obsconfig/fixtures/observatory_configuration_ok_simple.yml") change_dir(PYROS_DJANGO_BASE_DIR) @@ -712,6 +713,8 @@ def start(agent:str, configfile:str,observatory:str): ##agentX.run(FOR_REAL=True) if agent_name == "agentM": os.chdir(PYROS_DJANGO_BASE_DIR+"/monitoring/") + elif agent_name == "agentSP": + os.chdir(PYROS_DJANGO_BASE_DIR+"/scientific_program/") else: os.chdir(PYROS_DJANGO_BASE_DIR+"/agent/") #cmd = "-m AgentX" diff --git a/src/core/pyros_django/common/models.py b/src/core/pyros_django/common/models.py index f1a484a..917e116 100644 --- a/src/core/pyros_django/common/models.py +++ b/src/core/pyros_django/common/models.py @@ -10,7 +10,7 @@ from typing import List from django.core.validators import MaxValueValidator, MinValueValidator # DJANGO imports -from django.contrib.auth.models import AbstractUser +from django.contrib.auth.models import AbstractUser, UserManager from django.db import models from django.db.models import Q, Max from django.core.validators import MaxValueValidator, MinValueValidator @@ -1234,7 +1234,6 @@ class ScienceTheme(models.Model): def __str__(self) -> str: return str(self.name) - class Institute(models.Model): name = models.CharField(max_length=100,blank=False,null=False,unique=True) quota = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(100)]) @@ -1243,6 +1242,13 @@ class Institute(models.Model): def __str__(self) -> str: return str(self.name) +class PyrosUserManager(UserManager): + def tac_users(self): + return PyrosUser.objects.filter(user_level__name="TAC") + + def unit_users(self): + return PyrosUser.objects.filter(Q(user_level__name="Unit-PI")|Q(user_level__name="Unit-board")) + class PyrosUser(AbstractUser): username = models.CharField(max_length=255, blank=False, null=False, unique=True) is_active = models.BooleanField(default='False') @@ -1274,6 +1280,8 @@ class PyrosUser(AbstractUser): # can_del_void_req = models.BooleanField(default=False) validator = models.ForeignKey("PyrosUser", on_delete=models.DO_NOTHING, null=True, related_name="pyros_users") referee_themes = models.ManyToManyField("ScienceTheme",related_name="referee_themes",blank=True) + + objects = PyrosUserManager() class Meta: managed = True db_table = 'pyros_user' @@ -1397,7 +1405,6 @@ class PeriodManager(models.Manager): submission_periods_id = [] periods = Period.objects.filter(start_date__gte=today) for period in periods: - print(period) if period.submission_start_date <= today and period.submission_end_date > today: submission_periods_id.append(period.id) periods = periods.filter(id__in=submission_periods_id) @@ -1418,7 +1425,7 @@ class PeriodManager(models.Manager): validation_periods_id = [] periods = Period.objects.filter(start_date__gte=today) for period in periods: - if period.unit_pi_validation_start_date >= today and period.notification_start_date < today: + if period.unit_pi_validation_start_date <= today and period.notification_start_date > today: validation_periods_id.append(period.id) periods = periods.filter(id__in=validation_periods_id) return periods @@ -1428,7 +1435,7 @@ class PeriodManager(models.Manager): notification_periods_id = [] periods = Period.objects.filter(start_date__gte=today) for period in periods: - if period.notification_start_date >= today and period.start_date < today: + if period.notification_start_date <= today and period.start_date > today: notification_periods_id.append(period.id) periods = periods.filter(id__in=notification_periods_id) return periods @@ -1453,6 +1460,12 @@ class PeriodManager(models.Manager): today = timezone.now().date() previous_periods = Period.objects.filter(start_date__lt=today).order_by("-start_date").exclude(id=self.exploitation_period().id) return previous_periods + + def next_period(self)->any: + current_period = self.exploitation_period() + next_period = Period.objects.filter(start_date__gt=current_period.start_date).order_by("start_date").first() + return next_period + class Period(models.Model): # if change of default value, those values need to be changed also in create_period and edit_period.html (Javascript ) @@ -1467,7 +1480,7 @@ class Period(models.Model): start_date = models.DateField(blank=True, null=True, default=timezone.now,editable=True) exploitation_duration = models.PositiveIntegerField(blank=True, null=True, default=182,editable=True) - submission_duration = models.PositiveIntegerField(blank=True, null=True, default=182,editable=True) + submission_duration = models.PositiveIntegerField(blank=True, null=True, default=136,editable=True) evaluation_duration = models.PositiveIntegerField(blank=True,null=True,default=31,editable=True) validation_duration = models.PositiveIntegerField(blank=True, null=True, default=5,editable=True) notification_duration = models.PositiveIntegerField(blank=True, null=True, default=10,editable=True) @@ -1480,19 +1493,19 @@ class Period(models.Model): @property def submission_start_date(self): - return self.start_date + relativedelta(days=-self.exploitation_duration) + return self.start_date + relativedelta(days=-(self.submission_duration+self.evaluation_duration+self.validation_duration+self.notification_duration)) @property def submission_end_date(self): - return self.start_date + relativedelta(days=-self.evaluation_duration) + return self.submission_start_date + relativedelta(days=self.submission_duration) @property def unit_pi_validation_start_date(self): - return self.start_date + relativedelta(days=-self.validation_duration) + return self.submission_end_date + relativedelta(days=self.evaluation_duration) @property def notification_start_date(self): - return self.start_date + relativedelta(days=-self.notification_duration) + return self.unit_pi_validation_start_date + relativedelta(days=self.validation_duration) @property def property_of_data_end_date(self): @@ -1616,6 +1629,22 @@ class SP_Period_User(models.Model): class SP_Period_Guest(models.Model): SP_Period = models.ForeignKey(SP_Period, on_delete=models.DO_NOTHING,related_name="SP_Period_Guests") email = models.EmailField(max_length=254) + +class SP_PeriodWorkflow(models.Model): + SUBMISSION = "SUB" + EVALUATION = "EVAL" + VALIDATION = "VALI" + NOTIFICATION = "NOTI" + ACTIONS_CHOICES = ( + (SUBMISSION,"Submission"), + (EVALUATION,"Evaluation"), + (VALIDATION,"Validation"), + (NOTIFICATION,"Notification") + ) + action = models.CharField(max_length=120,choices=ACTIONS_CHOICES) + period = models.ForeignKey("Period",on_delete=models.DO_NOTHING,related_name="SP_Period_Workflows") + created = models.DateTimeField(blank=True, null=True, auto_now_add=True) + class Sequence(models.Model): """ Definition of Status enum values """ diff --git a/src/core/pyros_django/misc/fixtures/tests/scientific_program_TZ.json b/src/core/pyros_django/misc/fixtures/tests/scientific_program_TZ.json index 3b59fd4..b87a2a4 100644 --- a/src/core/pyros_django/misc/fixtures/tests/scientific_program_TZ.json +++ b/src/core/pyros_django/misc/fixtures/tests/scientific_program_TZ.json @@ -126,7 +126,7 @@ "username": "observer", "first_name": "", "last_name": "", - "email": "", + "email": "observer@test.com", "is_staff": true, "is_active": true, "date_joined": "2016-05-02T14:02:36.495Z", @@ -153,7 +153,7 @@ "username": "tac", "first_name": "", "last_name": "", - "email": "", + "email": "tac@test.com", "is_staff": true, "is_active": true, "date_joined": "2016-05-02T14:02:36.495Z", @@ -167,7 +167,8 @@ "tel": "", "address": "", "laboratory": "", - "institute": 1 + "institute": 1, + "referee_themes":[1] } }, { @@ -180,7 +181,7 @@ "username": "unit_pi", "first_name": "", "last_name": "", - "email": "", + "email": "unit_pi@test.com", "is_staff": true, "is_active": true, "date_joined": "2016-05-02T14:02:36.495Z", @@ -207,7 +208,7 @@ "username": "operator", "first_name": "", "last_name": "", - "email": "", + "email": "operator@test.com", "is_staff": true, "is_active": true, "date_joined": "2016-05-02T14:02:36.495Z", @@ -234,7 +235,89 @@ "username": "observer2", "first_name": "", "last_name": "", - "email": "", + "email": "observer2@test.com", + "is_staff": true, + "is_active": true, + "date_joined": "2016-05-02T14:02:36.495Z", + "groups": [], + "user_permissions": [], + "country": 1, + "user_level": [2], + "desc": "", + "created": "2016-06-03T09:29:15.450Z", + "updated": "2016-06-03T09:29:15.450Z", + "tel": "", + "address": "", + "laboratory": "", + "institute": 1 + } +}, +{ + "model": "common.pyrosuser", + "pk": 7, + "fields": { + "password": "pbkdf2_sha256$24000$NC9UO4LPGvDN$+pkRcZUbmV3HEtbrhBdDCSPnAsDVValrbwLt7cXqrJE=", + "last_login": "2016-06-03T09:27:53.245Z", + "is_superuser": false, + "username": "tac2", + "first_name": "", + "last_name": "", + "email": "tac2@test.com", + "is_staff": true, + "is_active": true, + "date_joined": "2016-05-02T14:02:36.495Z", + "groups": [], + "user_permissions": [], + "country": 1, + "user_level": [3], + "desc": "", + "created": "2016-06-03T09:29:15.450Z", + "updated": "2016-06-03T09:29:15.450Z", + "tel": "", + "address": "", + "laboratory": "", + "institute": 1, + "referee_themes":[1] + } +}, +{ + "model": "common.pyrosuser", + "pk": 8, + "fields": { + "password": "pbkdf2_sha256$24000$NC9UO4LPGvDN$+pkRcZUbmV3HEtbrhBdDCSPnAsDVValrbwLt7cXqrJE=", + "last_login": "2016-06-03T09:27:53.245Z", + "is_superuser": false, + "username": "observer3", + "first_name": "", + "last_name": "", + "email": "observer3@test.com", + "is_staff": true, + "is_active": true, + "date_joined": "2016-05-02T14:02:36.495Z", + "groups": [], + "user_permissions": [], + "country": 1, + "user_level": [2], + "desc": "", + "created": "2016-06-03T09:29:15.450Z", + "updated": "2016-06-03T09:29:15.450Z", + "tel": "", + "address": "", + "laboratory": "", + "institute": 1 + } +}, +{ + "model": "common.pyrosuser", + "pk": 9, + "fields": { + "password": "pbkdf2_sha256$24000$NC9UO4LPGvDN$+pkRcZUbmV3HEtbrhBdDCSPnAsDVValrbwLt7cXqrJE=", + "last_login": "2016-06-03T09:27:53.245Z", + "is_superuser": false, + "username": "observer4", + "first_name": "", + "last_name": "", + "email": "observer4@test.com", "is_staff": true, "is_active": true, "date_joined": "2016-05-02T14:02:36.495Z", @@ -250,5 +333,26 @@ "laboratory": "", "institute": 1 } +}, +{ + "model":"common.ScienceTheme", + "pk":1, + "fields":{ + "name":"Solar System" + } +}, +{ + "model":"common.ScienceTheme", + "pk":2, + "fields":{ + "name":"Galatic" + } +}, +{ + "model":"common.ScienceTheme", + "pk":3, + "fields":{ + "name":"Extra Galatic" + } } ] \ No newline at end of file diff --git a/src/core/pyros_django/obsconfig/tests.py b/src/core/pyros_django/obsconfig/tests.py index a1b8a18..7c77c81 100644 --- a/src/core/pyros_django/obsconfig/tests.py +++ b/src/core/pyros_django/obsconfig/tests.py @@ -39,7 +39,7 @@ class ObservatoryConfigurationTests(TestCase): u1 = PyrosUser.objects.get(username="haribo") u1.set_password("password123") u1.save() - self.client.login(username="haribo",password="password123") + self.client.post(reverse("login_validation"),{"email":"haribo","password":"password123"}) response = self.client.get(reverse('obs_astronomer_config')) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'base.html') @@ -57,7 +57,8 @@ class ObservatoryConfigurationTests(TestCase): u1 = PyrosUser.objects.get(username="haribo") u1.set_password("password123") u1.save() - self.client.login(username="haribo",password="password123") + #self.client.login(username="haribo",password="password123") + self.client.post(reverse("login_validation"),{"email":"haribo","password":"password123"}) response = self.client.get(reverse('obs_astronomer_config')) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'base.html') diff --git a/src/core/pyros_django/pyros/settings.py b/src/core/pyros_django/pyros/settings.py index 0815dae..0599311 100644 --- a/src/core/pyros_django/pyros/settings.py +++ b/src/core/pyros_django/pyros/settings.py @@ -399,7 +399,7 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' python_version = subprocess.run( "python --version | cut -d ' ' -f 2 | cut -d '.' -f 1,2",shell=True,stdout=subprocess.PIPE,universal_newlines=True) python_version = python_version.stdout -today = "2021-10-14" +today = "2021-11-02" django_version_major,django_version_minor = django.VERSION[:2][0],django.VERSION[:2][1] -pyros_version = "0.2.10.0" +pyros_version = "0.2.11.0" VERSION_NUMBER = f"{pyros_version}_{django_version_major}.{django_version_minor}_{python_version}_{today}" \ No newline at end of file diff --git a/src/core/pyros_django/scheduler/Scheduler.py b/src/core/pyros_django/scheduler/Scheduler.py index 55a784e..c309adc 100644 --- a/src/core/pyros_django/scheduler/Scheduler.py +++ b/src/core/pyros_django/scheduler/Scheduler.py @@ -1,7 +1,7 @@ from operator import attrgetter from scheduler.templatetags.jdconverter import jdtodate -from .UserManager import UserManager +from .UserManager import UserQuotaManager from .Interval import * from django.db.models import Q @@ -221,11 +221,11 @@ class Scheduler(IntervalManagement): #print("sequences are", self.sequences) for sequence, shs in list(self.sequences): #print("placeSequences() sequence is", sequence) - quota = UserManager.determineQuota(sequence) + quota = UserQuotaManager.determineQuota(sequence) #print("quota is", quota) - if not UserManager.isSufficient(quota, sequence): + if not UserQuotaManager.isSufficient(quota, sequence): shs.status = Sequence.REJECTED - shs.desc = UserManager.REJECTED + shs.desc = UserQuotaManager.REJECTED continue matching_intervals = self.getMatchingIntervals(sequence) if len(matching_intervals) > 0: @@ -350,7 +350,7 @@ class Scheduler(IntervalManagement): return 0 def decreaseQuota(self, sequence: Sequence, quota: float) -> int: - user = UserManager(sequence.request.pyros_user) + user = UserQuotaManager(sequence.request.pyros_user) if SIMULATION: return 0 return user.decreaseQuota(Decimal(quota)) diff --git a/src/core/pyros_django/scheduler/UserManager.py b/src/core/pyros_django/scheduler/UserManager.py index 6918706..5d94a77 100644 --- a/src/core/pyros_django/scheduler/UserManager.py +++ b/src/core/pyros_django/scheduler/UserManager.py @@ -2,7 +2,7 @@ from common.models import * from utils.Logger import * from decimal import * -class UserManager(Logger): +class UserQuotaManager(Logger): REJECTED = "Insufficient quota" def __init__(self, user: PyrosUser): diff --git a/src/core/pyros_django/scientific_program/AgentSP.py b/src/core/pyros_django/scientific_program/AgentSP.py new file mode 100644 index 0000000..7c5ca65 --- /dev/null +++ b/src/core/pyros_django/scientific_program/AgentSP.py @@ -0,0 +1,222 @@ +import sys + +sys.path.append("..") +sys.path.append("../../../..") +from src.core.pyros_django.agent.Agent import Agent, build_agent +from common.models import Period, SP_Period, PyrosUser, SP_Period_Guest, SP_PeriodWorkflow, ScientificProgram,SP_Period_User, ScienceTheme +from django.shortcuts import reverse +from django.conf import settings +from django.core.mail import send_mail +from dateutil.relativedelta import relativedelta +from django.db.models import Q +from django.utils import timezone +from django.test.utils import setup_test_environment +import numpy as np + +class AgentSP(Agent): + + period = None + + def __init__(self, config_filename=None, RUN_IN_THREAD=True,use_db_test=False): + ##if name is None: name = self.__class__.__name__ + if use_db_test: + print("USE DB TEST") + setup_test_environment() + super().__init__(None, RUN_IN_THREAD) + print(Period.objects.all()) + next_period = Period.objects.next_period() + period = next_period + + # @override + def init(self): + super().init() + + def associate_tac_sp_auto(self,themes,tac_users,scientific_programs): + print("Associating tac to sp") + matrix_tac_themes = np.zeros([len(tac_users),len(themes)]) + maxtrix_themes_sp = np.zeros([len(themes),len(scientific_programs)]) + matrix_tac_sp = np.zeros([len(tac_users),len(scientific_programs)]) + for i,tac_user in enumerate(tac_users): + for j,theme in enumerate(themes): + if theme.name in tac_user.get_referee_themes_as_str(): + matrix_tac_themes[i,j] = 1 + for i,theme in enumerate(themes): + for j,sp in enumerate(scientific_programs): + if theme.id == sp.science_theme.id: + maxtrix_themes_sp[i,j] = 1 + matrix_tac_sp = np.dot(matrix_tac_themes,maxtrix_themes_sp) + nb_tac_per_sp = np.sum(matrix_tac_sp,axis=0) + next_period = Period.objects.next_period() + for i,sp in enumerate(scientific_programs): + if nb_tac_per_sp[i-1] == 2: + # We auto assign the tac users to scientific programs + print(sp) + sp_period = SP_Period.objects.get(scientific_program=sp,period=next_period) + available_tac_users = PyrosUser.objects.filter(referee_themes=sp.science_theme) + print("available tacs :") + print(available_tac_users) + sp_period.referee1 = available_tac_users[0] + sp_period.referee2 = available_tac_users[1] + sp_period.save() + #return matrix_tac_sp + + def change_sp_status(self,scientific_programs,new_status): + print(f"---- CHANGE STATUS FOR {scientific_programs} TO {new_status}------- ") + for sp in scientific_programs: + if sp.status != new_status: + sp.status = new_status + sp.save() + + def send_mail_to_tac_for_evaluation(self,tac_users,next_period): + domain = settings.DEFAULT_DOMAIN + url = f"{domain}{reverse('list_submitted_scientific_program')}" + mail_subject = '[PyROS CC] The evaluation period is now opened' + mail_message = (f"Hi,\n\nYou can now evaluate scientific programs for the next period ({next_period}).\n" + f"Click on the following link {url} to evaluate your assignated scientific programs." + "\n\nCordially,\n\nPyROS Control Center") + email_list = tac_users.values_list("email") + for email in email_list: + send_mail( + mail_subject, + mail_message, + from_email=None, + recipient_list=[email], + fail_silently=False, + ) + + def send_mail_to_observers_for_notification(self,sp_periods): + for sp_period in sp_periods: + sp_pi = sp_period.scientific_program.sp_pi + scientific_program = sp_period.scientific_program + domain = settings.DEFAULT_DOMAIN + url = f"{domain}{reverse('sp_register',args=(scientific_program.pk,sp_period.period.pk))}" + mail_subject = '[PyROS CC] New registration to a scientific program' + mail_message = (f"Hi,\n\nYou were invited to join a scientific program that as been submitted using PyROS.\n" + f"The name of the scientific program is {scientific_program.name} and his PI is {sp_pi.first_name} {sp_pi.last_name}.\n" + f"To accept this invitation, click on the following link : {url}\n" + f"Once you have joined the scientific program, you can start to submit sequences" + "You might be asked to login first and will be redirected to the scientific program page.\n" + "If the redirection doesn't work, click again on the link after you've logged in.\n" + "If you don't own an PyROS account, go on the website in order to create an account with the same mail adress that you are using to read this mail." + "\n\nCordially,\n\nPyROS Control Center") + invited_observers_of_sp = SP_Period_Guest.objects.filter(SP_Period=sp_period).values("user") + recipient_list = invited_observers_of_sp + for invited_observer in recipient_list: + send_mail( + mail_subject, + mail_message, + from_email=None, + recipient_list=[invited_observer], + fail_silently=False, + ) + + def send_mail_to_unit_users_for_tac_assignation(self): + domain = settings.DEFAULT_DOMAIN + url = f"{domain}{reverse('list_drafted_scientific_program')}" + mail_subject = '[PyROS CC] TAC assignation to scientific programs for the next period' + mail_message = (f"Hi,\n\nYou can assign TAC users to scientific programs by choosing them in the {url} page.\n" + "PyROS has suggested TAC to some of the scientific programs but you can change those assignations.\n" + f"The TAC assignation will be effective and couldn't be modified at the {self.period.submission_end_date}.\n" + "\n\nCordially,\n\nPyROS Control Center") + unit_users = PyrosUser.objects.unit_users().values_list("email",flat=True) + send_mail( + mail_subject, + mail_message, + from_email=None, + recipient_list=unit_users, + fail_silently=False, + ) + print("--------- SEND MAIL TO UNIT USERS ----------") + + + def automatic_period_workflow(self): + today = timezone.now().date() + next_period = Period.objects.next_period() + # check if next_period has changed + if self.period != next_period: + self.period = next_period + # get scientific program for next_period + next_sp = SP_Period.objects.filter(period=next_period) + auto_validated_sp = ScientificProgram.objects.filter(is_auto_validated=True) + auto_validated_sp_periods = SP_Period.objects.filter(scientific_program__in=auto_validated_sp,period=next_period) + # remove auto validated sp from next_sp + next_sp = next_sp.exclude(scientific_program__in=auto_validated_sp) + # get all tac users + tac_users = PyrosUser.objects.filter(user_level__name="TAC") + # submission workflow + if not SP_PeriodWorkflow.objects.filter(action=SP_PeriodWorkflow.SUBMISSION, period=self.period).exists(): + print("routine automatic period workflow SUBMISSION") + # if the next_period is actually in the "submission" subperiod + if next_period in Period.objects.submission_periods(): + # we have to assign TAC to SP + themes = ScienceTheme.objects.all() + # get id of scientific programs from SP_Period + sp_id = next_sp.exclude(scientific_program__is_auto_validated=True).filter(Q(referee1=None)|Q(referee2=None)).values("scientific_program") + # get scientific programs + sp = ScientificProgram.objects.filter(id__in=sp_id).order_by("name") + # if we are ten days before the end of the submission period, we have to assign TAC to scientific programs + # and send a mail to the Unit users to they assign the TAC users to SP + if next_period.submission_end_date + relativedelta(days=-10) == today : + self.associate_tac_sp_auto(themes,tac_users,sp) + # send mail to unit pi to tell him to associate TAC to SP + self.send_mail_to_unit_users_for_tac_assignation() + SP_PeriodWorkflow.objects.create(period=self.period,action=SP_PeriodWorkflow.SUBMISSION) + + if not SP_PeriodWorkflow.objects.filter(action=SP_PeriodWorkflow.EVALUATION, period=self.period).exists(): + print("routine automatic period workflow EVALUATION") + if next_period in Period.objects.evaluation_periods() and next_period.submission_end_date == today : + next_sp = SP_Period.objects.filter(period=next_period).exclude(scientific_program__in=auto_validated_sp).filter(status=SP_Period.STATUSES_DRAFT) + self.change_sp_status(next_sp,SP_Period.STATUSES_SUBMITTED) + self.send_mail_to_tac_for_evaluation(tac_users,next_period) + + # for auto validated sp, we have to change their status + self.change_sp_status(auto_validated_sp_periods,SP_Period.STATUSES_ACCEPTED) + for sp in auto_validated_sp_periods: + sp.is_valid = SP_Period.IS_VALID_ACCEPTED + sp.save() + + SP_PeriodWorkflow.objects.create(period=self.period,action=SP_PeriodWorkflow.EVALUATION) + if not SP_PeriodWorkflow.objects.filter(action=SP_PeriodWorkflow.VALIDATION, period=self.period).exists(): + print("routine automatic period workflow VALIDATION") + if next_period.unit_pi_validation_start_date == today : + next_sp = SP_Period.objects.filter(period=next_period).exclude(scientific_program__in=auto_validated_sp).filter(status=SP_Period.STATUSES_SUBMITTED) + self.change_sp_status(next_sp,SP_Period.STATUSES_EVALUATED) + next_sp = SP_Period.objects.filter(period=next_period).exclude(scientific_program__in=auto_validated_sp).filter(status=SP_Period.STATUSES_EVALUATED) + SP_PeriodWorkflow.objects.create(period=self.period,action=SP_PeriodWorkflow.VALIDATION) + if not SP_PeriodWorkflow.objects.filter(action=SP_PeriodWorkflow.NOTIFICATION, period=self.period).exists(): + print("routine automatic period workflow NOTIFICATION") + if next_period in Period.objects.notification_periods(): + next_sp_accepted = SP_Period.objects.filter(period=next_period).filter(is_valid=SP_Period.IS_VALID_ACCEPTED) + self.change_sp_status(next_sp_accepted,SP_Period.STATUSES_ACCEPTED) + next_sp_rejected = SP_Period.objects.filter(period=next_period).filter(is_valid=SP_Period.IS_VALID_REJECTED) + self.change_sp_status(next_sp_rejected,SP_Period.STATUSES_REJECTED) + next_sp_to_be_notified = next_sp.filter(status=SP_Period.STATUSES_ACCEPTED,is_valid = True) + self.send_mail_to_observers_for_notification(next_sp_to_be_notified) + SP_PeriodWorkflow.objects.create(period=self.period,action=SP_PeriodWorkflow.NOTIFICATION) + + def routine_process_body(self): + print("routine automatic period workflow") + print(SP_PeriodWorkflow.objects.all()) + for sp_period_workflow in SP_PeriodWorkflow.objects.all(): + print(sp_period_workflow.period) + print(sp_period_workflow.action) + self.automatic_period_workflow() + + +if __name__ == "__main__": + + # with thread + RUN_IN_THREAD=True + # with process + #RUN_IN_THREAD=False + if len(sys.argv) > 1 and sys.argv[1] == "test": + agent = AgentSP(None,RUN_IN_THREAD=RUN_IN_THREAD,use_db_test=True) + else: + agent = build_agent(AgentSP, RUN_IN_THREAD=RUN_IN_THREAD) + ''' + TEST_MODE, configfile = extract_parameters() + agent = AgentM("AgentM", configfile, RUN_IN_THREAD) + agent.setSimulatorMode(TEST_MODE) + ''' + print(agent) + agent.run() diff --git a/src/core/pyros_django/scientific_program/functions.py b/src/core/pyros_django/scientific_program/functions.py index e3eb4b1..8629ef7 100644 --- a/src/core/pyros_django/scientific_program/functions.py +++ b/src/core/pyros_django/scientific_program/functions.py @@ -1,4 +1,3 @@ -from common.models import Period from dateutil.relativedelta import relativedelta def get_svg_timeline(previous_period,current_period,future_period): diff --git a/src/core/pyros_django/scientific_program/templates/scientific_program/create_period.html b/src/core/pyros_django/scientific_program/templates/scientific_program/create_period.html index 1aff5ac..e49ea9f 100644 --- a/src/core/pyros_django/scientific_program/templates/scientific_program/create_period.html +++ b/src/core/pyros_django/scientific_program/templates/scientific_program/create_period.html @@ -145,19 +145,24 @@ function loadDate(){ // set date for each duration duration_value = parseInt($("#id_submission_duration").val()) - date = period_start_date.addDays(-duration_value); + proposal_duration = parseInt($("#id_submission_duration").val()) + parseInt($("#id_evaluation_duration").val()) + + parseInt($("#id_validation_duration").val()) + parseInt($("#id_notification_duration").val()); + date = period_start_date.addDays(-proposal_duration); + submission_start_date = date; $("#submission_duration_date").html(date.toLocaleDateString("en-GB")); duration_value = parseInt($("#id_evaluation_duration").val()) - date = period_start_date.addDays(-duration_value); + date = submission_start_date.addDays(duration_value); + evaluation_start_date = date; $("#evaluation_duration_date").html(date.toLocaleDateString("en-GB")); duration_value = parseInt($("#id_validation_duration").val()) - date = period_start_date.addDays(-duration_value); + date = evaluation_start_date.addDays(duration_value); + validation_start_date = date; $("#validation_duration_date").html(date.toLocaleDateString("en-GB")); duration_value = parseInt($("#id_notification_duration").val()) - date = period_start_date.addDays(-duration_value); + date = validation_start_date.addDays(duration_value); $("#notification_duration_date").html(date.toLocaleDateString("en-GB")); duration_value = parseInt($("#id_exploitation_duration").val()) @@ -180,52 +185,32 @@ $( document ).ready(function() { // IMPORTANT NOTE : In Javascript Month count start to zero (Zero is january, 1 is february etc) $("#id_exploitation_duration").on("input",function(){ - duration_value = parseInt($("#id_exploitation_duration").val()) - split = $("#start_date_picker").val().split("/"); - period_start_date = new Date(split[2],split[1]-1,split[0]); - date = period_start_date.addDays(duration_value); - $("#exploitation_duration_date").html(date.toLocaleDateString("en-GB")); + loadDate(); }); $("#id_submission_duration").on("input",function(){ - duration_value = parseInt($("#id_submission_duration").val()) - split = $("#start_date_picker").val().split("/"); - period_start_date = new Date(split[2],split[1]-1,split[0]); - date = period_start_date.addDays(duration_value); - $("#submission_duration_date").html(date.toLocaleDateString("en-GB")); + loadDate(); }); $("#id_evaluation_duration").on("input",function(){ - duration_value = parseInt($("#id_evaluation_duration").val()) - split = $("#start_date_picker").val().split("/"); - period_start_date = new Date(split[2],split[1]-1,split[0]); - date = period_start_date.addDays(duration_value); - $("#evaluation_duration_date").html(date.toLocaleDateString("en-GB")); + loadDate(); }); -$("#id_notation_duration").on("input",function(){ - duration_value = parseInt($("#id_notation_duration").val()) - split = $("#start_date_picker").val().split("/"); - period_start_date = new Date(split[2],split[1]-1,split[0]); - date = period_start_date.addDays(duration_value); - $("#notation_duration_date").html(date.toLocaleDateString("en-GB")); +$("#id_validation_duration").on("input",function(){ + loadDate(); +}); + +$("#id_notification_duration").on("input",function(){ + loadDate(); }); $("#id_property_of_data_duration").on("input",function(){ - duration_value = parseInt($("#id_property_of_data_duration").val()) - split = $("#start_date_picker").val().split("/"); - period_start_date = new Date(split[2],split[1]-1,split[0]); - date = period_start_date.addDays(duration_value); - $("#property_of_data_duration_date").html(date.toLocaleDateString("en-GB")); + loadDate(); }); $("#id_data_accessibility_duration").on("input",function(){ - duration_value = parseInt($("#id_data_accessibility_duration").val()) - split = $("#start_date_picker").val().split("/"); - period_start_date = new Date(split[2],split[1]-1,split[0]); - date = period_start_date.addDays(duration_value); - $("#data_accessibility_duration_date").html(date.toLocaleDateString("en-GB")); + loadDate(); }); $(function() { diff --git a/src/core/pyros_django/scientific_program/templates/scientific_program/period_detail.html b/src/core/pyros_django/scientific_program/templates/scientific_program/period_detail.html index 50195f8..3c6f184 100644 --- a/src/core/pyros_django/scientific_program/templates/scientific_program/period_detail.html +++ b/src/core/pyros_django/scientific_program/templates/scientific_program/period_detail.html @@ -18,6 +18,7 @@

Submission start date : {{ period.submission_start_date }}

Submission end date : {{ period.submission_end_date }}

Unit-PI validation start date : {{ period.unit_pi_validation_start_date}}

+

Notification start date : {{ period.notification_start_date}}

Property of date end date : {{ period.property_of_data_end_date }}

Data accessibility end date : {{ period.data_accessibility_end_date }}

diff --git a/src/core/pyros_django/scientific_program/templates/scientific_program/scientific_program_detail.html b/src/core/pyros_django/scientific_program/templates/scientific_program/scientific_program_detail.html index f928c5e..a51e0f2 100644 --- a/src/core/pyros_django/scientific_program/templates/scientific_program/scientific_program_detail.html +++ b/src/core/pyros_django/scientific_program/templates/scientific_program/scientific_program_detail.html @@ -46,7 +46,7 @@