Commit a6e636042a40776beb38f49f28a98a149db32235

Authored by Alexis Koralewski
1 parent b6a13fc4
Exists in dev

adding agentSP and tests for F02, updating other tests according new attributes of SCP

pyros.py
... ... @@ -37,7 +37,8 @@ AGENTS = {
37 37 "agentX" : "AgentX",
38 38 "agentA" : "AgentA",
39 39 "agentB" : "AgentB",
40   - "agentM" : "AgentM",
  40 + "agentM" : "AgentM",
  41 + "agentSP" : "AgentSP",
41 42 #"agentDevice" : "AgentDevice",
42 43 #"agentDeviceTelescopeGemini" : "AgentDeviceTelescopeGemini",
43 44 "agentDeviceGemini" : "AgentDeviceGemini",
... ... @@ -524,8 +525,8 @@ def test(app):
524 525 #start_dir = os.getcwd()
525 526 if app == None:
526 527 #apps = ['obsconfig','scientific_program','common', 'scheduler', 'routine_manager', 'user_manager', 'alert_manager.tests.TestStrategyChange']
527   - # Removing temporiraly scientific_program and alert_manager from tests
528   - apps = ['obsconfig','common', 'scheduler', 'routine_manager', 'user_manager']
  528 + # Removing alert_manager from tests
  529 + apps = ['obsconfig',"scientific_program",'common', 'scheduler', 'routine_manager', 'user_manager']
529 530 else:
530 531 os.environ["PATH_TO_OBSCONF_FILE"] = os.path.join(os.path.abspath(PYROS_DJANGO_BASE_DIR),"obsconfig/fixtures/observatory_configuration_ok_simple.yml")
531 532 change_dir(PYROS_DJANGO_BASE_DIR)
... ... @@ -712,6 +713,8 @@ def start(agent:str, configfile:str,observatory:str):
712 713 ##agentX.run(FOR_REAL=True)
713 714 if agent_name == "agentM":
714 715 os.chdir(PYROS_DJANGO_BASE_DIR+"/monitoring/")
  716 + elif agent_name == "agentSP":
  717 + os.chdir(PYROS_DJANGO_BASE_DIR+"/scientific_program/")
715 718 else:
716 719 os.chdir(PYROS_DJANGO_BASE_DIR+"/agent/")
717 720 #cmd = "-m AgentX"
... ...
src/core/pyros_django/common/models.py
... ... @@ -10,7 +10,7 @@ from typing import List
10 10 from django.core.validators import MaxValueValidator, MinValueValidator
11 11  
12 12 # DJANGO imports
13   -from django.contrib.auth.models import AbstractUser
  13 +from django.contrib.auth.models import AbstractUser, UserManager
14 14 from django.db import models
15 15 from django.db.models import Q, Max
16 16 from django.core.validators import MaxValueValidator, MinValueValidator
... ... @@ -1234,7 +1234,6 @@ class ScienceTheme(models.Model):
1234 1234  
1235 1235 def __str__(self) -> str:
1236 1236 return str(self.name)
1237   -
1238 1237 class Institute(models.Model):
1239 1238 name = models.CharField(max_length=100,blank=False,null=False,unique=True)
1240 1239 quota = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(100)])
... ... @@ -1243,6 +1242,13 @@ class Institute(models.Model):
1243 1242 def __str__(self) -> str:
1244 1243 return str(self.name)
1245 1244  
  1245 +class PyrosUserManager(UserManager):
  1246 + def tac_users(self):
  1247 + return PyrosUser.objects.filter(user_level__name="TAC")
  1248 +
  1249 + def unit_users(self):
  1250 + return PyrosUser.objects.filter(Q(user_level__name="Unit-PI")|Q(user_level__name="Unit-board"))
  1251 +
1246 1252 class PyrosUser(AbstractUser):
1247 1253 username = models.CharField(max_length=255, blank=False, null=False, unique=True)
1248 1254 is_active = models.BooleanField(default='False')
... ... @@ -1274,6 +1280,8 @@ class PyrosUser(AbstractUser):
1274 1280 # can_del_void_req = models.BooleanField(default=False)
1275 1281 validator = models.ForeignKey("PyrosUser", on_delete=models.DO_NOTHING, null=True, related_name="pyros_users")
1276 1282 referee_themes = models.ManyToManyField("ScienceTheme",related_name="referee_themes",blank=True)
  1283 +
  1284 + objects = PyrosUserManager()
1277 1285 class Meta:
1278 1286 managed = True
1279 1287 db_table = 'pyros_user'
... ... @@ -1397,7 +1405,6 @@ class PeriodManager(models.Manager):
1397 1405 submission_periods_id = []
1398 1406 periods = Period.objects.filter(start_date__gte=today)
1399 1407 for period in periods:
1400   - print(period)
1401 1408 if period.submission_start_date <= today and period.submission_end_date > today:
1402 1409 submission_periods_id.append(period.id)
1403 1410 periods = periods.filter(id__in=submission_periods_id)
... ... @@ -1418,7 +1425,7 @@ class PeriodManager(models.Manager):
1418 1425 validation_periods_id = []
1419 1426 periods = Period.objects.filter(start_date__gte=today)
1420 1427 for period in periods:
1421   - if period.unit_pi_validation_start_date >= today and period.notification_start_date < today:
  1428 + if period.unit_pi_validation_start_date <= today and period.notification_start_date > today:
1422 1429 validation_periods_id.append(period.id)
1423 1430 periods = periods.filter(id__in=validation_periods_id)
1424 1431 return periods
... ... @@ -1428,7 +1435,7 @@ class PeriodManager(models.Manager):
1428 1435 notification_periods_id = []
1429 1436 periods = Period.objects.filter(start_date__gte=today)
1430 1437 for period in periods:
1431   - if period.notification_start_date >= today and period.start_date < today:
  1438 + if period.notification_start_date <= today and period.start_date > today:
1432 1439 notification_periods_id.append(period.id)
1433 1440 periods = periods.filter(id__in=notification_periods_id)
1434 1441 return periods
... ... @@ -1453,6 +1460,12 @@ class PeriodManager(models.Manager):
1453 1460 today = timezone.now().date()
1454 1461 previous_periods = Period.objects.filter(start_date__lt=today).order_by("-start_date").exclude(id=self.exploitation_period().id)
1455 1462 return previous_periods
  1463 +
  1464 + def next_period(self)->any:
  1465 + current_period = self.exploitation_period()
  1466 + next_period = Period.objects.filter(start_date__gt=current_period.start_date).order_by("start_date").first()
  1467 + return next_period
  1468 +
1456 1469  
1457 1470 class Period(models.Model):
1458 1471 # 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):
1467 1480 start_date = models.DateField(blank=True, null=True, default=timezone.now,editable=True)
1468 1481  
1469 1482 exploitation_duration = models.PositiveIntegerField(blank=True, null=True, default=182,editable=True)
1470   - submission_duration = models.PositiveIntegerField(blank=True, null=True, default=182,editable=True)
  1483 + submission_duration = models.PositiveIntegerField(blank=True, null=True, default=136,editable=True)
1471 1484 evaluation_duration = models.PositiveIntegerField(blank=True,null=True,default=31,editable=True)
1472 1485 validation_duration = models.PositiveIntegerField(blank=True, null=True, default=5,editable=True)
1473 1486 notification_duration = models.PositiveIntegerField(blank=True, null=True, default=10,editable=True)
... ... @@ -1480,19 +1493,19 @@ class Period(models.Model):
1480 1493  
1481 1494 @property
1482 1495 def submission_start_date(self):
1483   - return self.start_date + relativedelta(days=-self.exploitation_duration)
  1496 + return self.start_date + relativedelta(days=-(self.submission_duration+self.evaluation_duration+self.validation_duration+self.notification_duration))
1484 1497  
1485 1498 @property
1486 1499 def submission_end_date(self):
1487   - return self.start_date + relativedelta(days=-self.evaluation_duration)
  1500 + return self.submission_start_date + relativedelta(days=self.submission_duration)
1488 1501  
1489 1502 @property
1490 1503 def unit_pi_validation_start_date(self):
1491   - return self.start_date + relativedelta(days=-self.validation_duration)
  1504 + return self.submission_end_date + relativedelta(days=self.evaluation_duration)
1492 1505  
1493 1506 @property
1494 1507 def notification_start_date(self):
1495   - return self.start_date + relativedelta(days=-self.notification_duration)
  1508 + return self.unit_pi_validation_start_date + relativedelta(days=self.validation_duration)
1496 1509  
1497 1510 @property
1498 1511 def property_of_data_end_date(self):
... ... @@ -1616,6 +1629,22 @@ class SP_Period_User(models.Model):
1616 1629 class SP_Period_Guest(models.Model):
1617 1630 SP_Period = models.ForeignKey(SP_Period, on_delete=models.DO_NOTHING,related_name="SP_Period_Guests")
1618 1631 email = models.EmailField(max_length=254)
  1632 +
  1633 +class SP_PeriodWorkflow(models.Model):
  1634 + SUBMISSION = "SUB"
  1635 + EVALUATION = "EVAL"
  1636 + VALIDATION = "VALI"
  1637 + NOTIFICATION = "NOTI"
  1638 + ACTIONS_CHOICES = (
  1639 + (SUBMISSION,"Submission"),
  1640 + (EVALUATION,"Evaluation"),
  1641 + (VALIDATION,"Validation"),
  1642 + (NOTIFICATION,"Notification")
  1643 + )
  1644 + action = models.CharField(max_length=120,choices=ACTIONS_CHOICES)
  1645 + period = models.ForeignKey("Period",on_delete=models.DO_NOTHING,related_name="SP_Period_Workflows")
  1646 + created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
  1647 +
1619 1648 class Sequence(models.Model):
1620 1649  
1621 1650 """ Definition of Status enum values """
... ...
src/core/pyros_django/misc/fixtures/tests/scientific_program_TZ.json
... ... @@ -126,7 +126,7 @@
126 126 "username": "observer",
127 127 "first_name": "",
128 128 "last_name": "",
129   - "email": "",
  129 + "email": "observer@test.com",
130 130 "is_staff": true,
131 131 "is_active": true,
132 132 "date_joined": "2016-05-02T14:02:36.495Z",
... ... @@ -153,7 +153,7 @@
153 153 "username": "tac",
154 154 "first_name": "",
155 155 "last_name": "",
156   - "email": "",
  156 + "email": "tac@test.com",
157 157 "is_staff": true,
158 158 "is_active": true,
159 159 "date_joined": "2016-05-02T14:02:36.495Z",
... ... @@ -167,7 +167,8 @@
167 167 "tel": "",
168 168 "address": "",
169 169 "laboratory": "",
170   - "institute": 1
  170 + "institute": 1,
  171 + "referee_themes":[1]
171 172 }
172 173 },
173 174 {
... ... @@ -180,7 +181,7 @@
180 181 "username": "unit_pi",
181 182 "first_name": "",
182 183 "last_name": "",
183   - "email": "",
  184 + "email": "unit_pi@test.com",
184 185 "is_staff": true,
185 186 "is_active": true,
186 187 "date_joined": "2016-05-02T14:02:36.495Z",
... ... @@ -207,7 +208,7 @@
207 208 "username": "operator",
208 209 "first_name": "",
209 210 "last_name": "",
210   - "email": "",
  211 + "email": "operator@test.com",
211 212 "is_staff": true,
212 213 "is_active": true,
213 214 "date_joined": "2016-05-02T14:02:36.495Z",
... ... @@ -234,7 +235,89 @@
234 235 "username": "observer2",
235 236 "first_name": "",
236 237 "last_name": "",
237   - "email": "",
  238 + "email": "observer2@test.com",
  239 + "is_staff": true,
  240 + "is_active": true,
  241 + "date_joined": "2016-05-02T14:02:36.495Z",
  242 + "groups": [],
  243 + "user_permissions": [],
  244 + "country": 1,
  245 + "user_level": [2],
  246 + "desc": "",
  247 + "created": "2016-06-03T09:29:15.450Z",
  248 + "updated": "2016-06-03T09:29:15.450Z",
  249 + "tel": "",
  250 + "address": "",
  251 + "laboratory": "",
  252 + "institute": 1
  253 + }
  254 +},
  255 +{
  256 + "model": "common.pyrosuser",
  257 + "pk": 7,
  258 + "fields": {
  259 + "password": "pbkdf2_sha256$24000$NC9UO4LPGvDN$+pkRcZUbmV3HEtbrhBdDCSPnAsDVValrbwLt7cXqrJE=",
  260 + "last_login": "2016-06-03T09:27:53.245Z",
  261 + "is_superuser": false,
  262 + "username": "tac2",
  263 + "first_name": "",
  264 + "last_name": "",
  265 + "email": "tac2@test.com",
  266 + "is_staff": true,
  267 + "is_active": true,
  268 + "date_joined": "2016-05-02T14:02:36.495Z",
  269 + "groups": [],
  270 + "user_permissions": [],
  271 + "country": 1,
  272 + "user_level": [3],
  273 + "desc": "",
  274 + "created": "2016-06-03T09:29:15.450Z",
  275 + "updated": "2016-06-03T09:29:15.450Z",
  276 + "tel": "",
  277 + "address": "",
  278 + "laboratory": "",
  279 + "institute": 1,
  280 + "referee_themes":[1]
  281 + }
  282 +},
  283 +{
  284 + "model": "common.pyrosuser",
  285 + "pk": 8,
  286 + "fields": {
  287 + "password": "pbkdf2_sha256$24000$NC9UO4LPGvDN$+pkRcZUbmV3HEtbrhBdDCSPnAsDVValrbwLt7cXqrJE=",
  288 + "last_login": "2016-06-03T09:27:53.245Z",
  289 + "is_superuser": false,
  290 + "username": "observer3",
  291 + "first_name": "",
  292 + "last_name": "",
  293 + "email": "observer3@test.com",
  294 + "is_staff": true,
  295 + "is_active": true,
  296 + "date_joined": "2016-05-02T14:02:36.495Z",
  297 + "groups": [],
  298 + "user_permissions": [],
  299 + "country": 1,
  300 + "user_level": [2],
  301 + "desc": "",
  302 + "created": "2016-06-03T09:29:15.450Z",
  303 + "updated": "2016-06-03T09:29:15.450Z",
  304 + "tel": "",
  305 + "address": "",
  306 + "laboratory": "",
  307 + "institute": 1
  308 + }
  309 +},
  310 +{
  311 + "model": "common.pyrosuser",
  312 + "pk": 9,
  313 + "fields": {
  314 + "password": "pbkdf2_sha256$24000$NC9UO4LPGvDN$+pkRcZUbmV3HEtbrhBdDCSPnAsDVValrbwLt7cXqrJE=",
  315 + "last_login": "2016-06-03T09:27:53.245Z",
  316 + "is_superuser": false,
  317 + "username": "observer4",
  318 + "first_name": "",
  319 + "last_name": "",
  320 + "email": "observer4@test.com",
238 321 "is_staff": true,
239 322 "is_active": true,
240 323 "date_joined": "2016-05-02T14:02:36.495Z",
... ... @@ -250,5 +333,26 @@
250 333 "laboratory": "",
251 334 "institute": 1
252 335 }
  336 +},
  337 +{
  338 + "model":"common.ScienceTheme",
  339 + "pk":1,
  340 + "fields":{
  341 + "name":"Solar System"
  342 + }
  343 +},
  344 +{
  345 + "model":"common.ScienceTheme",
  346 + "pk":2,
  347 + "fields":{
  348 + "name":"Galatic"
  349 + }
  350 +},
  351 +{
  352 + "model":"common.ScienceTheme",
  353 + "pk":3,
  354 + "fields":{
  355 + "name":"Extra Galatic"
  356 + }
253 357 }
254 358 ]
255 359 \ No newline at end of file
... ...
src/core/pyros_django/obsconfig/tests.py
... ... @@ -39,7 +39,7 @@ class ObservatoryConfigurationTests(TestCase):
39 39 u1 = PyrosUser.objects.get(username="haribo")
40 40 u1.set_password("password123")
41 41 u1.save()
42   - self.client.login(username="haribo",password="password123")
  42 + self.client.post(reverse("login_validation"),{"email":"haribo","password":"password123"})
43 43 response = self.client.get(reverse('obs_astronomer_config'))
44 44 self.assertEqual(response.status_code, 200)
45 45 self.assertTemplateUsed(response, 'base.html')
... ... @@ -57,7 +57,8 @@ class ObservatoryConfigurationTests(TestCase):
57 57 u1 = PyrosUser.objects.get(username="haribo")
58 58 u1.set_password("password123")
59 59 u1.save()
60   - self.client.login(username="haribo",password="password123")
  60 + #self.client.login(username="haribo",password="password123")
  61 + self.client.post(reverse("login_validation"),{"email":"haribo","password":"password123"})
61 62 response = self.client.get(reverse('obs_astronomer_config'))
62 63 self.assertEqual(response.status_code, 200)
63 64 self.assertTemplateUsed(response, 'base.html')
... ...
src/core/pyros_django/pyros/settings.py
... ... @@ -399,7 +399,7 @@ EMAIL_BACKEND = &#39;django.core.mail.backends.console.EmailBackend&#39;
399 399  
400 400 python_version = subprocess.run( "python --version | cut -d ' ' -f 2 | cut -d '.' -f 1,2",shell=True,stdout=subprocess.PIPE,universal_newlines=True)
401 401 python_version = python_version.stdout
402   -today = "2021-10-14"
  402 +today = "2021-11-02"
403 403 django_version_major,django_version_minor = django.VERSION[:2][0],django.VERSION[:2][1]
404   -pyros_version = "0.2.10.0"
  404 +pyros_version = "0.2.11.0"
405 405 VERSION_NUMBER = f"{pyros_version}_{django_version_major}.{django_version_minor}_{python_version}_{today}"
406 406 \ No newline at end of file
... ...
src/core/pyros_django/scheduler/Scheduler.py
1 1 from operator import attrgetter
2 2  
3 3 from scheduler.templatetags.jdconverter import jdtodate
4   -from .UserManager import UserManager
  4 +from .UserManager import UserQuotaManager
5 5 from .Interval import *
6 6 from django.db.models import Q
7 7  
... ... @@ -221,11 +221,11 @@ class Scheduler(IntervalManagement):
221 221 #print("sequences are", self.sequences)
222 222 for sequence, shs in list(self.sequences):
223 223 #print("placeSequences() sequence is", sequence)
224   - quota = UserManager.determineQuota(sequence)
  224 + quota = UserQuotaManager.determineQuota(sequence)
225 225 #print("quota is", quota)
226   - if not UserManager.isSufficient(quota, sequence):
  226 + if not UserQuotaManager.isSufficient(quota, sequence):
227 227 shs.status = Sequence.REJECTED
228   - shs.desc = UserManager.REJECTED
  228 + shs.desc = UserQuotaManager.REJECTED
229 229 continue
230 230 matching_intervals = self.getMatchingIntervals(sequence)
231 231 if len(matching_intervals) > 0:
... ... @@ -350,7 +350,7 @@ class Scheduler(IntervalManagement):
350 350 return 0
351 351  
352 352 def decreaseQuota(self, sequence: Sequence, quota: float) -> int:
353   - user = UserManager(sequence.request.pyros_user)
  353 + user = UserQuotaManager(sequence.request.pyros_user)
354 354 if SIMULATION:
355 355 return 0
356 356 return user.decreaseQuota(Decimal(quota))
... ...
src/core/pyros_django/scheduler/UserManager.py
... ... @@ -2,7 +2,7 @@ from common.models import *
2 2 from utils.Logger import *
3 3 from decimal import *
4 4  
5   -class UserManager(Logger):
  5 +class UserQuotaManager(Logger):
6 6 REJECTED = "Insufficient quota"
7 7  
8 8 def __init__(self, user: PyrosUser):
... ...
src/core/pyros_django/scientific_program/AgentSP.py 0 โ†’ 100644
... ... @@ -0,0 +1,222 @@
  1 +import sys
  2 +
  3 +sys.path.append("..")
  4 +sys.path.append("../../../..")
  5 +from src.core.pyros_django.agent.Agent import Agent, build_agent
  6 +from common.models import Period, SP_Period, PyrosUser, SP_Period_Guest, SP_PeriodWorkflow, ScientificProgram,SP_Period_User, ScienceTheme
  7 +from django.shortcuts import reverse
  8 +from django.conf import settings
  9 +from django.core.mail import send_mail
  10 +from dateutil.relativedelta import relativedelta
  11 +from django.db.models import Q
  12 +from django.utils import timezone
  13 +from django.test.utils import setup_test_environment
  14 +import numpy as np
  15 +
  16 +class AgentSP(Agent):
  17 +
  18 + period = None
  19 +
  20 + def __init__(self, config_filename=None, RUN_IN_THREAD=True,use_db_test=False):
  21 + ##if name is None: name = self.__class__.__name__
  22 + if use_db_test:
  23 + print("USE DB TEST")
  24 + setup_test_environment()
  25 + super().__init__(None, RUN_IN_THREAD)
  26 + print(Period.objects.all())
  27 + next_period = Period.objects.next_period()
  28 + period = next_period
  29 +
  30 + # @override
  31 + def init(self):
  32 + super().init()
  33 +
  34 + def associate_tac_sp_auto(self,themes,tac_users,scientific_programs):
  35 + print("Associating tac to sp")
  36 + matrix_tac_themes = np.zeros([len(tac_users),len(themes)])
  37 + maxtrix_themes_sp = np.zeros([len(themes),len(scientific_programs)])
  38 + matrix_tac_sp = np.zeros([len(tac_users),len(scientific_programs)])
  39 + for i,tac_user in enumerate(tac_users):
  40 + for j,theme in enumerate(themes):
  41 + if theme.name in tac_user.get_referee_themes_as_str():
  42 + matrix_tac_themes[i,j] = 1
  43 + for i,theme in enumerate(themes):
  44 + for j,sp in enumerate(scientific_programs):
  45 + if theme.id == sp.science_theme.id:
  46 + maxtrix_themes_sp[i,j] = 1
  47 + matrix_tac_sp = np.dot(matrix_tac_themes,maxtrix_themes_sp)
  48 + nb_tac_per_sp = np.sum(matrix_tac_sp,axis=0)
  49 + next_period = Period.objects.next_period()
  50 + for i,sp in enumerate(scientific_programs):
  51 + if nb_tac_per_sp[i-1] == 2:
  52 + # We auto assign the tac users to scientific programs
  53 + print(sp)
  54 + sp_period = SP_Period.objects.get(scientific_program=sp,period=next_period)
  55 + available_tac_users = PyrosUser.objects.filter(referee_themes=sp.science_theme)
  56 + print("available tacs :")
  57 + print(available_tac_users)
  58 + sp_period.referee1 = available_tac_users[0]
  59 + sp_period.referee2 = available_tac_users[1]
  60 + sp_period.save()
  61 + #return matrix_tac_sp
  62 +
  63 + def change_sp_status(self,scientific_programs,new_status):
  64 + print(f"---- CHANGE STATUS FOR {scientific_programs} TO {new_status}------- ")
  65 + for sp in scientific_programs:
  66 + if sp.status != new_status:
  67 + sp.status = new_status
  68 + sp.save()
  69 +
  70 + def send_mail_to_tac_for_evaluation(self,tac_users,next_period):
  71 + domain = settings.DEFAULT_DOMAIN
  72 + url = f"{domain}{reverse('list_submitted_scientific_program')}"
  73 + mail_subject = '[PyROS CC] The evaluation period is now opened'
  74 + mail_message = (f"Hi,\n\nYou can now evaluate scientific programs for the next period ({next_period}).\n"
  75 + f"Click on the following link {url} to evaluate your assignated scientific programs."
  76 + "\n\nCordially,\n\nPyROS Control Center")
  77 + email_list = tac_users.values_list("email")
  78 + for email in email_list:
  79 + send_mail(
  80 + mail_subject,
  81 + mail_message,
  82 + from_email=None,
  83 + recipient_list=[email],
  84 + fail_silently=False,
  85 + )
  86 +
  87 + def send_mail_to_observers_for_notification(self,sp_periods):
  88 + for sp_period in sp_periods:
  89 + sp_pi = sp_period.scientific_program.sp_pi
  90 + scientific_program = sp_period.scientific_program
  91 + domain = settings.DEFAULT_DOMAIN
  92 + url = f"{domain}{reverse('sp_register',args=(scientific_program.pk,sp_period.period.pk))}"
  93 + mail_subject = '[PyROS CC] New registration to a scientific program'
  94 + mail_message = (f"Hi,\n\nYou were invited to join a scientific program that as been submitted using PyROS.\n"
  95 + f"The name of the scientific program is {scientific_program.name} and his PI is {sp_pi.first_name} {sp_pi.last_name}.\n"
  96 + f"To accept this invitation, click on the following link : {url}\n"
  97 + f"Once you have joined the scientific program, you can start to submit sequences"
  98 + "You might be asked to login first and will be redirected to the scientific program page.\n"
  99 + "If the redirection doesn't work, click again on the link after you've logged in.\n"
  100 + "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."
  101 + "\n\nCordially,\n\nPyROS Control Center")
  102 + invited_observers_of_sp = SP_Period_Guest.objects.filter(SP_Period=sp_period).values("user")
  103 + recipient_list = invited_observers_of_sp
  104 + for invited_observer in recipient_list:
  105 + send_mail(
  106 + mail_subject,
  107 + mail_message,
  108 + from_email=None,
  109 + recipient_list=[invited_observer],
  110 + fail_silently=False,
  111 + )
  112 +
  113 + def send_mail_to_unit_users_for_tac_assignation(self):
  114 + domain = settings.DEFAULT_DOMAIN
  115 + url = f"{domain}{reverse('list_drafted_scientific_program')}"
  116 + mail_subject = '[PyROS CC] TAC assignation to scientific programs for the next period'
  117 + mail_message = (f"Hi,\n\nYou can assign TAC users to scientific programs by choosing them in the {url} page.\n"
  118 + "PyROS has suggested TAC to some of the scientific programs but you can change those assignations.\n"
  119 + f"The TAC assignation will be effective and couldn't be modified at the {self.period.submission_end_date}.\n"
  120 + "\n\nCordially,\n\nPyROS Control Center")
  121 + unit_users = PyrosUser.objects.unit_users().values_list("email",flat=True)
  122 + send_mail(
  123 + mail_subject,
  124 + mail_message,
  125 + from_email=None,
  126 + recipient_list=unit_users,
  127 + fail_silently=False,
  128 + )
  129 + print("--------- SEND MAIL TO UNIT USERS ----------")
  130 +
  131 +
  132 + def automatic_period_workflow(self):
  133 + today = timezone.now().date()
  134 + next_period = Period.objects.next_period()
  135 + # check if next_period has changed
  136 + if self.period != next_period:
  137 + self.period = next_period
  138 + # get scientific program for next_period
  139 + next_sp = SP_Period.objects.filter(period=next_period)
  140 + auto_validated_sp = ScientificProgram.objects.filter(is_auto_validated=True)
  141 + auto_validated_sp_periods = SP_Period.objects.filter(scientific_program__in=auto_validated_sp,period=next_period)
  142 + # remove auto validated sp from next_sp
  143 + next_sp = next_sp.exclude(scientific_program__in=auto_validated_sp)
  144 + # get all tac users
  145 + tac_users = PyrosUser.objects.filter(user_level__name="TAC")
  146 + # submission workflow
  147 + if not SP_PeriodWorkflow.objects.filter(action=SP_PeriodWorkflow.SUBMISSION, period=self.period).exists():
  148 + print("routine automatic period workflow SUBMISSION")
  149 + # if the next_period is actually in the "submission" subperiod
  150 + if next_period in Period.objects.submission_periods():
  151 + # we have to assign TAC to SP
  152 + themes = ScienceTheme.objects.all()
  153 + # get id of scientific programs from SP_Period
  154 + sp_id = next_sp.exclude(scientific_program__is_auto_validated=True).filter(Q(referee1=None)|Q(referee2=None)).values("scientific_program")
  155 + # get scientific programs
  156 + sp = ScientificProgram.objects.filter(id__in=sp_id).order_by("name")
  157 + # if we are ten days before the end of the submission period, we have to assign TAC to scientific programs
  158 + # and send a mail to the Unit users to they assign the TAC users to SP
  159 + if next_period.submission_end_date + relativedelta(days=-10) == today :
  160 + self.associate_tac_sp_auto(themes,tac_users,sp)
  161 + # send mail to unit pi to tell him to associate TAC to SP
  162 + self.send_mail_to_unit_users_for_tac_assignation()
  163 + SP_PeriodWorkflow.objects.create(period=self.period,action=SP_PeriodWorkflow.SUBMISSION)
  164 +
  165 + if not SP_PeriodWorkflow.objects.filter(action=SP_PeriodWorkflow.EVALUATION, period=self.period).exists():
  166 + print("routine automatic period workflow EVALUATION")
  167 + if next_period in Period.objects.evaluation_periods() and next_period.submission_end_date == today :
  168 + next_sp = SP_Period.objects.filter(period=next_period).exclude(scientific_program__in=auto_validated_sp).filter(status=SP_Period.STATUSES_DRAFT)
  169 + self.change_sp_status(next_sp,SP_Period.STATUSES_SUBMITTED)
  170 + self.send_mail_to_tac_for_evaluation(tac_users,next_period)
  171 +
  172 + # for auto validated sp, we have to change their status
  173 + self.change_sp_status(auto_validated_sp_periods,SP_Period.STATUSES_ACCEPTED)
  174 + for sp in auto_validated_sp_periods:
  175 + sp.is_valid = SP_Period.IS_VALID_ACCEPTED
  176 + sp.save()
  177 +
  178 + SP_PeriodWorkflow.objects.create(period=self.period,action=SP_PeriodWorkflow.EVALUATION)
  179 + if not SP_PeriodWorkflow.objects.filter(action=SP_PeriodWorkflow.VALIDATION, period=self.period).exists():
  180 + print("routine automatic period workflow VALIDATION")
  181 + if next_period.unit_pi_validation_start_date == today :
  182 + next_sp = SP_Period.objects.filter(period=next_period).exclude(scientific_program__in=auto_validated_sp).filter(status=SP_Period.STATUSES_SUBMITTED)
  183 + self.change_sp_status(next_sp,SP_Period.STATUSES_EVALUATED)
  184 + next_sp = SP_Period.objects.filter(period=next_period).exclude(scientific_program__in=auto_validated_sp).filter(status=SP_Period.STATUSES_EVALUATED)
  185 + SP_PeriodWorkflow.objects.create(period=self.period,action=SP_PeriodWorkflow.VALIDATION)
  186 + if not SP_PeriodWorkflow.objects.filter(action=SP_PeriodWorkflow.NOTIFICATION, period=self.period).exists():
  187 + print("routine automatic period workflow NOTIFICATION")
  188 + if next_period in Period.objects.notification_periods():
  189 + next_sp_accepted = SP_Period.objects.filter(period=next_period).filter(is_valid=SP_Period.IS_VALID_ACCEPTED)
  190 + self.change_sp_status(next_sp_accepted,SP_Period.STATUSES_ACCEPTED)
  191 + next_sp_rejected = SP_Period.objects.filter(period=next_period).filter(is_valid=SP_Period.IS_VALID_REJECTED)
  192 + self.change_sp_status(next_sp_rejected,SP_Period.STATUSES_REJECTED)
  193 + next_sp_to_be_notified = next_sp.filter(status=SP_Period.STATUSES_ACCEPTED,is_valid = True)
  194 + self.send_mail_to_observers_for_notification(next_sp_to_be_notified)
  195 + SP_PeriodWorkflow.objects.create(period=self.period,action=SP_PeriodWorkflow.NOTIFICATION)
  196 +
  197 + def routine_process_body(self):
  198 + print("routine automatic period workflow")
  199 + print(SP_PeriodWorkflow.objects.all())
  200 + for sp_period_workflow in SP_PeriodWorkflow.objects.all():
  201 + print(sp_period_workflow.period)
  202 + print(sp_period_workflow.action)
  203 + self.automatic_period_workflow()
  204 +
  205 +
  206 +if __name__ == "__main__":
  207 +
  208 + # with thread
  209 + RUN_IN_THREAD=True
  210 + # with process
  211 + #RUN_IN_THREAD=False
  212 + if len(sys.argv) > 1 and sys.argv[1] == "test":
  213 + agent = AgentSP(None,RUN_IN_THREAD=RUN_IN_THREAD,use_db_test=True)
  214 + else:
  215 + agent = build_agent(AgentSP, RUN_IN_THREAD=RUN_IN_THREAD)
  216 + '''
  217 + TEST_MODE, configfile = extract_parameters()
  218 + agent = AgentM("AgentM", configfile, RUN_IN_THREAD)
  219 + agent.setSimulatorMode(TEST_MODE)
  220 + '''
  221 + print(agent)
  222 + agent.run()
... ...
src/core/pyros_django/scientific_program/functions.py
1   -from common.models import Period
2 1 from dateutil.relativedelta import relativedelta
3 2  
4 3 def get_svg_timeline(previous_period,current_period,future_period):
... ...
src/core/pyros_django/scientific_program/templates/scientific_program/create_period.html
... ... @@ -145,19 +145,24 @@ function loadDate(){
145 145 // set date for each duration
146 146  
147 147 duration_value = parseInt($("#id_submission_duration").val())
148   - date = period_start_date.addDays(-duration_value);
  148 + proposal_duration = parseInt($("#id_submission_duration").val()) + parseInt($("#id_evaluation_duration").val())
  149 + + parseInt($("#id_validation_duration").val()) + parseInt($("#id_notification_duration").val());
  150 + date = period_start_date.addDays(-proposal_duration);
  151 + submission_start_date = date;
149 152 $("#submission_duration_date").html(date.toLocaleDateString("en-GB"));
150 153  
151 154 duration_value = parseInt($("#id_evaluation_duration").val())
152   - date = period_start_date.addDays(-duration_value);
  155 + date = submission_start_date.addDays(duration_value);
  156 + evaluation_start_date = date;
153 157 $("#evaluation_duration_date").html(date.toLocaleDateString("en-GB"));
154 158  
155 159 duration_value = parseInt($("#id_validation_duration").val())
156   - date = period_start_date.addDays(-duration_value);
  160 + date = evaluation_start_date.addDays(duration_value);
  161 + validation_start_date = date;
157 162 $("#validation_duration_date").html(date.toLocaleDateString("en-GB"));
158 163  
159 164 duration_value = parseInt($("#id_notification_duration").val())
160   - date = period_start_date.addDays(-duration_value);
  165 + date = validation_start_date.addDays(duration_value);
161 166 $("#notification_duration_date").html(date.toLocaleDateString("en-GB"));
162 167  
163 168 duration_value = parseInt($("#id_exploitation_duration").val())
... ... @@ -180,52 +185,32 @@ $( document ).ready(function() {
180 185  
181 186 // IMPORTANT NOTE : In Javascript Month count start to zero (Zero is january, 1 is february etc)
182 187 $("#id_exploitation_duration").on("input",function(){
183   - duration_value = parseInt($("#id_exploitation_duration").val())
184   - split = $("#start_date_picker").val().split("/");
185   - period_start_date = new Date(split[2],split[1]-1,split[0]);
186   - date = period_start_date.addDays(duration_value);
187   - $("#exploitation_duration_date").html(date.toLocaleDateString("en-GB"));
  188 + loadDate();
188 189 });
189 190  
190 191 $("#id_submission_duration").on("input",function(){
191   - duration_value = parseInt($("#id_submission_duration").val())
192   - split = $("#start_date_picker").val().split("/");
193   - period_start_date = new Date(split[2],split[1]-1,split[0]);
194   - date = period_start_date.addDays(duration_value);
195   - $("#submission_duration_date").html(date.toLocaleDateString("en-GB"));
  192 + loadDate();
196 193 });
197 194  
198 195 $("#id_evaluation_duration").on("input",function(){
199   - duration_value = parseInt($("#id_evaluation_duration").val())
200   - split = $("#start_date_picker").val().split("/");
201   - period_start_date = new Date(split[2],split[1]-1,split[0]);
202   - date = period_start_date.addDays(duration_value);
203   - $("#evaluation_duration_date").html(date.toLocaleDateString("en-GB"));
  196 + loadDate();
204 197 });
205 198  
206   -$("#id_notation_duration").on("input",function(){
207   - duration_value = parseInt($("#id_notation_duration").val())
208   - split = $("#start_date_picker").val().split("/");
209   - period_start_date = new Date(split[2],split[1]-1,split[0]);
210   - date = period_start_date.addDays(duration_value);
211   - $("#notation_duration_date").html(date.toLocaleDateString("en-GB"));
  199 +$("#id_validation_duration").on("input",function(){
  200 + loadDate();
  201 +});
  202 +
  203 +$("#id_notification_duration").on("input",function(){
  204 + loadDate();
212 205 });
213 206  
214 207  
215 208 $("#id_property_of_data_duration").on("input",function(){
216   - duration_value = parseInt($("#id_property_of_data_duration").val())
217   - split = $("#start_date_picker").val().split("/");
218   - period_start_date = new Date(split[2],split[1]-1,split[0]);
219   - date = period_start_date.addDays(duration_value);
220   - $("#property_of_data_duration_date").html(date.toLocaleDateString("en-GB"));
  209 + loadDate();
221 210 });
222 211  
223 212 $("#id_data_accessibility_duration").on("input",function(){
224   - duration_value = parseInt($("#id_data_accessibility_duration").val())
225   - split = $("#start_date_picker").val().split("/");
226   - period_start_date = new Date(split[2],split[1]-1,split[0]);
227   - date = period_start_date.addDays(duration_value);
228   - $("#data_accessibility_duration_date").html(date.toLocaleDateString("en-GB"));
  213 + loadDate();
229 214 });
230 215  
231 216 $(function() {
... ...
src/core/pyros_django/scientific_program/templates/scientific_program/period_detail.html
... ... @@ -18,6 +18,7 @@
18 18 <p><strong>Submission start date : </strong>{{ period.submission_start_date }}</p>
19 19 <p><strong>Submission end date : </strong>{{ period.submission_end_date }}</p>
20 20 <p><strong>Unit-PI validation start date : </strong>{{ period.unit_pi_validation_start_date}}</p>
  21 + <p><strong>Notification start date : </strong>{{ period.notification_start_date}}</p>
21 22 <p><strong>Property of date end date : </strong>{{ period.property_of_data_end_date }}</p>
22 23 <p><strong>Data accessibility end date : </strong>{{ period.data_accessibility_end_date }}</p>
23 24  
... ...
src/core/pyros_django/scientific_program/templates/scientific_program/scientific_program_detail.html
... ... @@ -46,7 +46,7 @@
46 46 <form class="modal-content" action="{% url "delete_scientific_program" scientific_program.id %}" method="post">
47 47 {% csrf_token %}
48 48 <div class="container">
49   - <h1> Delete Account </h1>
  49 + <h1> Delete scientific program </h1>
50 50 <p>Are you sure you want to delete this scientific program ?</p>
51 51  
52 52 <div class="clearfix">
... ...
src/core/pyros_django/scientific_program/templates/scientific_program/scientific_program_detail_edit.html renamed to src/core/pyros_django/scientific_program/templates/scientific_program/scientific_program_period_detail_edit.html
1   -{% extends 'base.html' %}
2   -{% load tags %}
3   -{% block content %}
4   -<form id="editSPForm" action="" method="post">
5   - {% csrf_token %}
6   -
7   - {% for field in SPForm %}
8   - <div class="fieldWrapper">
9   - {{ field.label_tag }} {{ field.value }}
10   - {{ field.as_hidden }}
11   - </div>
12   - {% endfor %}
13   - {% if sp_period.status == "Draft" %}
14   - <p style="color:red;"><strong>This proposal will be automatically submitted for evaluation on {{ sp_period.period.submission_end_date }}</strong> </p>
15   - {% endif %}
16   - {% if USER_LEVEL|ifinlist:"Unit-PI,Admin,Unit-board"%}
17   - {% if sp_period.referee1 != None and sp_period.referee1 == request.user %}
18   - <p><strong>vote referee 1: </strong>{{ sp_period.vote_referee1 }} {{ sp_period.reason_referee1 }} by {{ sp_period.referee1.first_name }} {{ sp_period.referee1.last_name}} </p>
19   - {% endif %}
20   - {% if sp_period.referee2 != None %}
21   - <p><strong>vote referee 2: </strong>{{ sp_period.vote_referee1 }} {{ sp_period.reason_referee1 }} by {{ sp_period.referee1.first_name }} {{ sp_period.referee1.last_name}} </p>
22   - {% endif %}
23   - <p><strong>Minimal quota : </strong>{{ sp_period.quota_minimal }}</p>
24   - <p><strong>Nominal quota : </strong>{{ sp_period.quota_nominal }}</p>
25   - <p><strong>Over quota duration : </strong>{{ sp_period.over_quota_duration }}</p>
26   - <p><strong>Token : </strong>{{ sp_period.token }}</p>
27   - {% endif %}
28   - {% for field in SP_PeriodForm %}
29   - {% if "referee1" in field.label_tag %}
30   - {% if SP_PeriodForm.reason_referee1.value == "" %}
31   -
32   - {% if sp_period.status == "Submitted" and USER_LEVEL|ifinlist:"TAC,Admin"%}
33   - <div class="fieldWrapper">
34   - {{ field.label_tag }} {{ field }}
35   - {{ field.errors }}
36   - {% if field.help_text %}
37   - <p class="help">{{ field.help_text|safe }}</p>
38   - {% endif %}
39   - </div>
40   - {% endif %}
41   -
42   - {% else %}
43   - {% if sp_period.referee1 == request.user %}
44   - {% if sp_period.status == "Submitted" and USER_LEVEL|ifinlist:"TAC,Admin"%}
45   - <div class="fieldWrapper">
46   - {{ field.label_tag }} {{ field }}
47   - {{ field.errors }}
48   - {% if field.help_text %}
49   - <p class="help">{{ field.help_text|safe }}</p>
50   - {% endif %}
51   - </div>
52   - {% endif %}
53   - {% else %}
54   - {{ field.as_hidden}}
55   - {% endif %}
56   - {% endif %}
57   - {% elif "referee2" in field.label_tag %}
58   - {% if SP_PeriodForm.reason_referee1.value != "" and SP_PeriodForm.reason_referee2.value == "" and request.user != sp_period.referee1 %}
59   - {% if sp_period.status == "Submitted" and USER_LEVEL|ifinlist:"TAC,Admin"%}
60   - <div class="fieldWrapper">
61   - {{ field.label_tag }} {{ field }}
62   - {{ field.errors }}
63   - {% if field.help_text %}
64   - <p class="help">{{ field.help_text|safe }}</p>
65   - {% endif %}
66   - </div>
67   - {% endif %}
68   - {% else %}
69   - {{ field.as_hidden}}
70   - {% endif %}
71   - {% elif "is_valid" in field.label_tag or "allocated" in field.label_tag or "priority" in field.label_tag %}
72   - {% if sp_period.status != "Draft" and USER_LEVEL|ifinlist:"Unit-PI,Admin" %}
73   - <div class="fieldWrapper">
74   - {{ field.label_tag }} {{ field }}
75   - {{ field.errors }}
76   - {% if field.help_text %}
77   - <p class="help">{{ field.help_text|safe }}</p>
78   - {% endif %}
79   - </div>
80   - {% endif %}
81   -
82   - {% elif sp_period.status == "Draft" and USER_LEVEL|ifinlist:"Observer" %}
83   - <div class="fieldWrapper">
84   - {{ field.label_tag }} {{ field }}
85   - {{ field.errors }}
86   - {% if field.help_text %}
87   - <p class="help">{{ field.help_text|safe }}</p>
88   - {% endif %}
89   - </div>
90   - {% else %}
91   - {{ field.as_hidden}}
92   - {% endif %}
93   - {% endfor %}
94   - {% if sp_period.status == "Draft" and USER_LEVEL|ifinlist:"Observer" %}
95   - <label> Invite user(s) with their email adress: (list them separated by a ","):</label>
96   - <div class="fieldWrapper">
97   - <textarea id="users" name="users">{{guests|default_if_none:""}} </textarea>
98   - {{ error_message }}
99   - </div>
100   - {% endif %}
101   - {% if USER_LEVEL|ifinlist:"Admin,Observer" %}
102   - <p> <b> Users : </b>
103   - {% for user in users_of_sp_period %}
104   - {% comment %}
105   - input name is the same for all role so we can retrieve a list of selected values
106   - {% endcomment %}
107   - <div>
108   - {% if sp_period.status == "Draft" and USER_LEVEL|ifinlist:"Observer" %}
109   - <input type="checkbox" checked id="{{user.username}}" name="user" value="{{user.id}}">
110   - {% endif %}
111   - <label for="{{user.username}}"> {{ user.first_name }} {{ user.last_name }} ({{user.username}})</label>
112   - </div>
113   - {% endfor %}
114   - {% endif %}
115   - <div>
116   - <input id="submit" class="btn btn-info" type="submit" value="Submit" />
117   - <a href="{% url "detail_scientific_program_period" id_sp=id_sp id_period=id_period %}" class="btn btn-info" role="button">Cancel</a>
118   - </div>
119   -</form>
120   -
  1 +{% extends 'base.html' %}
  2 +{% load tags %}
  3 +{% block content %}
  4 +<form id="editSPForm" action="" method="post">
  5 + {% csrf_token %}
  6 +
  7 + {% for field in SPForm %}
  8 + <div class="fieldWrapper">
  9 + {{ field.label_tag }} {{ field.value }}
  10 + {{ field.as_hidden }}
  11 + </div>
  12 + {% endfor %}
  13 + {% if sp_period.status == "Draft" %}
  14 + <p style="color:red;"><strong>This proposal will be automatically submitted for evaluation on {{ sp_period.period.submission_end_date }}</strong> </p>
  15 + {% endif %}
  16 + {% if CAN_VIEW_TAC_VOTES %}
  17 + {% if sp_period.referee1 != None and sp_period.referee1 == request.user %}
  18 + <p><strong>vote referee 1: </strong>{{ sp_period.vote_referee1 }} {{ sp_period.reason_referee1 }} by {{ sp_period.referee1.first_name }} {{ sp_period.referee1.last_name}} </p>
  19 + {% endif %}
  20 + {% if sp_period.referee2 != None %}
  21 + <p><strong>vote referee 2: </strong>{{ sp_period.vote_referee1 }} {{ sp_period.reason_referee1 }} by {{ sp_period.referee1.first_name }} {{ sp_period.referee1.last_name}} </p>
  22 + {% endif %}
  23 + <p><strong>Minimal quota : </strong>{{ sp_period.quota_minimal }}</p>
  24 + <p><strong>Nominal quota : </strong>{{ sp_period.quota_nominal }}</p>
  25 + <p><strong>Over quota duration : </strong>{{ sp_period.over_quota_duration }}</p>
  26 + <p><strong>Token : </strong>{{ sp_period.token }}</p>
  27 + {% endif %}
  28 + {% for field in SP_PeriodForm %}
  29 + {% if "referee1" in field.label_tag %}
  30 + {% if SP_PeriodForm.reason_referee1.value == "" %}
  31 +
  32 + {% if sp_period.status == "Submitted" and USER_LEVEL|ifinlist:"TAC,Admin"%}
  33 + <div class="fieldWrapper">
  34 + {{ field.label_tag }} {{ field }}
  35 + {{ field.errors }}
  36 + {% if field.help_text %}
  37 + <p class="help">{{ field.help_text|safe }}</p>
  38 + {% endif %}
  39 + </div>
  40 + {% endif %}
  41 +
  42 + {% else %}
  43 + {% if sp_period.referee1 == request.user %}
  44 + {% if sp_period.status == "Submitted" and USER_LEVEL|ifinlist:"TAC,Admin"%}
  45 + <div class="fieldWrapper">
  46 + {{ field.label_tag }} {{ field }}
  47 + {{ field.errors }}
  48 + {% if field.help_text %}
  49 + <p class="help">{{ field.help_text|safe }}</p>
  50 + {% endif %}
  51 + </div>
  52 + {% endif %}
  53 + {% else %}
  54 + {{ field.as_hidden}}
  55 + {% endif %}
  56 + {% endif %}
  57 + {% elif "referee2" in field.label_tag %}
  58 + {% if SP_PeriodForm.reason_referee1.value != "" and SP_PeriodForm.reason_referee2.value == "" and request.user != sp_period.referee1 %}
  59 + {% if sp_period.status == "Submitted" and USER_LEVEL|ifinlist:"TAC,Admin"%}
  60 + <div class="fieldWrapper">
  61 + {{ field.label_tag }} {{ field }}
  62 + {{ field.errors }}
  63 + {% if field.help_text %}
  64 + <p class="help">{{ field.help_text|safe }}</p>
  65 + {% endif %}
  66 + </div>
  67 + {% endif %}
  68 + {% else %}
  69 + {{ field.as_hidden}}
  70 + {% endif %}
  71 + {% elif "is_valid" in field.label_tag or "allocated" in field.label_tag or "priority" in field.label_tag %}
  72 + {% if sp_period.status != "Draft" and USER_LEVEL|ifinlist:"Unit-PI,Admin" %}
  73 + <div class="fieldWrapper">
  74 + {{ field.label_tag }} {{ field }}
  75 + {{ field.errors }}
  76 + {% if field.help_text %}
  77 + <p class="help">{{ field.help_text|safe }}</p>
  78 + {% endif %}
  79 + </div>
  80 + {% endif %}
  81 +
  82 + {% elif sp_period.status == "Draft" and USER_LEVEL|ifinlist:"Observer" %}
  83 + <div class="fieldWrapper">
  84 + {{ field.label_tag }} {{ field }}
  85 + {{ field.errors }}
  86 + {% if field.help_text %}
  87 + <p class="help">{{ field.help_text|safe }}</p>
  88 + {% endif %}
  89 + </div>
  90 + {% else %}
  91 + {{ field.as_hidden}}
  92 + {% endif %}
  93 + {% endfor %}
  94 + {% if sp_period.status == "Draft" and USER_LEVEL|ifinlist:"Observer" %}
  95 + <label> Invite user(s) with their email adress: (list them separated by a ","):</label>
  96 + <div class="fieldWrapper">
  97 + <textarea id="users" name="users">{{guests|default_if_none:""}} </textarea>
  98 + {{ error_message }}
  99 + </div>
  100 + {% endif %}
  101 + {% if USER_LEVEL|ifinlist:"Admin,Observer" %}
  102 + <p> <b> Users : </b>
  103 + {% for user in users_of_sp_period %}
  104 + {% comment %}
  105 + input name is the same for all role so we can retrieve a list of selected values
  106 + {% endcomment %}
  107 + <div>
  108 + {% if sp_period.status == "Draft" and USER_LEVEL|ifinlist:"Observer" %}
  109 + <input type="checkbox" checked id="{{user.username}}" name="user" value="{{user.id}}">
  110 + {% endif %}
  111 + <label for="{{user.username}}"> {{ user.first_name }} {{ user.last_name }} ({{user.username}})</label>
  112 + </div>
  113 + {% endfor %}
  114 + {% endif %}
  115 + <div>
  116 + <input id="submit" class="btn btn-info" type="submit" value="Submit" />
  117 + <a href="{% url "detail_scientific_program_period" id_sp=id_sp id_period=id_period %}" class="btn btn-info" role="button">Cancel</a>
  118 + </div>
  119 +</form>
  120 +
121 121 {% endblock %}
122 122 \ No newline at end of file
... ...
src/core/pyros_django/scientific_program/templates/scientific_program/test_tac_auto.html 0 โ†’ 100644
... ... @@ -0,0 +1,9 @@
  1 +{% extends 'base.html' %}
  2 +{% block content %}
  3 +
  4 +
  5 +<div>
  6 +
  7 + <img src="data:image/png;base64,{{ data }}" alt="">
  8 +</div>
  9 +{% endblock content %}
... ...
src/core/pyros_django/scientific_program/tests.py
  1 +from logging import log
  2 +from sys import stdout
1 3 from django.http import response
2 4 from django.test import TestCase
  5 +from django.utils import timezone, tree
3 6 from common.models import PyrosUser,ScientificProgram,Period,SP_Period,SP_Period_User,SP_Period_Guest,UserLevel,Institute,Country
4 7 from django.urls import reverse
5 8 from django.core import mail
6 9 from django.conf import settings
7   -import unittest,os
  10 +from datetime import date
  11 +from datetime import datetime
  12 +from unittest.mock import patch
  13 +import subprocess,time,os
  14 +import multiprocessing
  15 +from .AgentSP import *
8 16 # Create your tests here.
9 17 class ScientificProgramTests(TestCase):
10 18 fixtures = ['tests/scientific_program_TZ.json']
11 19 def setUp(self) -> None:
12   - """
13   - institute = Institute.objects.get(id=1)
14   - country = Country.objects.create()
15   -
16   - self.usr1 = PyrosUser.objects.create(username="toto", country=country, institute=institute)
17   - UserLevel.objects.get(name = "Observer").pyros_users.add(self.usr1)
18   - self.usr1.save()
19   - self.usr2 = PyrosUser.objects.create(username="titi", country=country, institute=institute)
20   - UserLevel.objects.get(name = "Unit-PI").pyros_users.add(self.usr2)
21   - self.usr2.save()
22   - self.usr3 = PyrosUser.objects.create(username="tata", country=country, institute=institute)
23   - UserLevel.objects.get(name = "Operator").pyros_users.add(self.usr3)
24   - self.usr3.save()
25   - self.usr4 = PyrosUser.objects.create(username="tutu", country=country, institute=institute)
26   - UserLevel.objects.get(name = "TAC").pyros_users.add(self.usr4)
27   - self.usr4.save()
28   - """
29 20 password = "password123"
30 21 Period.objects.create()
31   - Period.objects.create(start_date=Period.objects.all().first().end_date)
  22 + self.period2 = Period.objects.create(start_date=Period.objects.all().first().end_date)
  23 + self.period3 = Period.objects.create(start_date=self.period2.end_date,submission_duration=550)
32 24 self.usr1 = PyrosUser.objects.get(username="observer")
33 25 self.usr2 = PyrosUser.objects.get(username="unit_pi")
34 26 self.usr3 = PyrosUser.objects.get(username="tac")
35 27 self.usr4 = PyrosUser.objects.get(username="operator")
36 28 self.usr5 = PyrosUser.objects.get(username="observer2")
  29 + self.usr6 = PyrosUser.objects.get(username="tac2")
  30 + self.usr7 = PyrosUser.objects.get(username="observer3")
  31 + self.usr8 = PyrosUser.objects.get(username="observer4")
37 32  
38 33  
39 34 self.usr1.set_password(password)
... ... @@ -46,216 +41,722 @@ class ScientificProgramTests(TestCase):
46 41 self.usr4.save()
47 42 self.usr5.set_password(password)
48 43 self.usr5.save()
49   - def test_SCP_observer_can_create_sp(self):
50   - self.client.login(username=self.usr1,password="password123")
51   - path = reverse('create_scientific_program')
52   - response = self.client.post(path, {"name": "test", "description_short": "sp short desc", "description_long": "sp long desc",
53   - "public_visibility": "All", "quota_minimal": 10, "quota_nominal": 15,
54   - "over_quota_duration": 5, "token": 3,
55   - "users":"test@example.com,test2@example.com,test3@example.com"
56   - })
57   - self.assertEqual(response.status_code,302)
58   - self.assertEqual(len(mail.outbox),3)
59   - self.assertIn("test@example.com",mail.outbox[0].recipients())
60   - self.assertIn("test2@example.com",mail.outbox[1].recipients())
61   - self.assertIn("test3@example.com",mail.outbox[2].recipients())
62   - domain = settings.DEFAULT_DOMAIN
63   - url = f"{domain}{reverse('sp_register',args=(ScientificProgram.objects.all().order_by('-id').first().pk,Period.objects.all().order_by('-id').first().pk))}"
64   - self.assertIn(url,mail.outbox[2].body)
65   - self.assertEqual(ScientificProgram.objects.all().count(),1)
66   - self.assertEqual(list(SP_Period_Guest.objects.all().values_list("email",flat=True)),["test@example.com","test2@example.com","test3@example.com"])
67   - self.assertEqual(SP_Period.objects.get(scientific_program__name="test").status,SP_Period.STATUSES_DRAFT)
68   - self.assertEqual(ScientificProgram.objects.get(name="test").sp_pi,self.usr1)
  44 + self.usr6.set_password(password)
  45 + self.usr6.save()
  46 + self.usr7.set_password(password)
  47 + self.usr7.save()
  48 + self.usr8.set_password(password)
  49 + self.usr8.save()
  50 +
  51 + def logout(self):
69 52 self.client.get(reverse("user_logout"))
70   - # u2 shouldn't be able to create a new scientific program
71   - self.client.login(username=self.usr2,password="password123")
  53 + print("Log out")
  54 + def login_as_user(self,number):
  55 + if number == 1:
  56 + self.client.post(reverse("login_validation"),{"email":self.usr1,"password":"password123"})
  57 + elif number == 2:
  58 + self.client.post(reverse("login_validation"),{"email":self.usr2,"password":"password123"})
  59 + elif number == 3:
  60 + self.client.post(reverse("login_validation"),{"email":self.usr3,"password":"password123"})
  61 + elif number == 4:
  62 + self.client.post(reverse("login_validation"),{"email":self.usr4,"password":"password123"})
  63 + elif number == 5:
  64 + self.client.post(reverse("login_validation"),{"email":self.usr5,"password":"password123"})
  65 + elif number == 6:
  66 + self.client.post(reverse("login_validation"),{"email":self.usr6,"password":"password123"})
  67 + elif number == 7:
  68 + self.client.post(reverse("login_validation"),{"email":self.usr7,"password":"password123"})
  69 + elif number == 8:
  70 + self.client.post(reverse("login_validation"),{"email":self.usr8,"password":"password123"})
  71 + print(f'Log in as {self.client.session["user"]}')
  72 +
  73 + def create_dummy_SCP(self,name,user_number):
  74 + """
  75 + Generate a SCP
  76 + """
  77 + self.login_as_user(user_number)
72 78 path = reverse('create_scientific_program')
73   - response = self.client.post(path, {"name": "test2", "description_short": "sp short desc", "description_long": "sp long desc",
74   - "public_visibility": "All", "quota_minimal": 10, "quota_nominal": 15,
75   - "over_quota_duration": 5, "token": 3,
76   - "users":"test@example.com,test2@example.com,test3@example.com"
77   - })
  79 + post_data = {
  80 + "name":name,
  81 + "description_short":"sp short desc",
  82 + "description_long":"sp long desc",
  83 + "science_theme":1,
  84 + }
  85 + response = self.client.post(path,post_data)
  86 + self.logout()
  87 + return ScientificProgram.objects.get(name=name)
  88 +
  89 + def create_dummy_SCP_Period(self,scientific_program,user_number):
  90 + self.login_as_user(user_number)
  91 +
  92 + path = reverse('create_scientific_program_period',kwargs={"id_SP":scientific_program.id})
  93 + post_data = {
  94 + "period":Period.objects.next_period().id,
  95 + "public_visibility":SP_Period.VISIBILITY_YES,
  96 + "quota_minimal":2,
  97 + "quota_nominal":5,
  98 + "over_quota_duration":2,
  99 + "token":2
  100 + }
  101 +
  102 + response = self.client.post(path,post_data)
  103 + sp_period = SP_Period.objects.get(scientific_program=scientific_program,period=Period.objects.next_period())
  104 +
  105 + self.logout()
  106 + return sp_period
  107 +
  108 + def create_dummies_SCP_and_SCP_Period(self,name_of_SCP,user_number):
  109 + self.create_dummy_SCP(name_of_SCP,user_number)
  110 + scientific_program = ScientificProgram.objects.get(name=name_of_SCP)
  111 + sp_period = self.create_dummy_SCP_Period(scientific_program,user_number)
  112 + return sp_period
  113 +
  114 + def test_SCP_Observer_can_submit_new_SP_from_scratch(self):
  115 + self.login_as_user(1)
  116 + path = reverse('create_scientific_program')
  117 + post_data = {
  118 + "name":"test",
  119 + "description_short":"sp short desc",
  120 + "description_long":"sp long desc",
  121 + "science_theme":1,
  122 + }
  123 + response = self.client.post(path,post_data)
78 124 self.assertEqual(response.status_code,302)
79   - self.assertEqual(len(mail.outbox),3)
80 125 self.assertEqual(ScientificProgram.objects.all().count(),1)
  126 + current_sp = ScientificProgram.objects.all().first()
  127 + self.assertEqual(current_sp.sp_pi,self.usr1)
81 128  
82   - def test_SCP_view_sp_list(self):
83   - self.client.login(username=self.usr1,password="password123")
  129 + self.logout()
  130 +
  131 + self.login_as_user(2)
84 132 path = reverse('create_scientific_program')
85   - self.client.post(path, {"name": "test", "description_short": "sp short desc", "description_long": "sp long desc",
86   - "public_visibility": "All", "quota_minimal": 10, "quota_nominal": 15,
87   - "over_quota_duration": 5, "token": 3,
88   - "users":"test@example.com,test2@example.com,test3@example.com"
89   - })
90   -
91   - self.client.post(path, {"name": "sp2", "description_short": "sp short desc", "description_long": "sp long desc",
92   - "public_visibility": "All", "quota_minimal": 10, "quota_nominal": 15,
93   - "over_quota_duration": 5, "token": 3,
94   - "users":"test@example.com,test2@example.com,test3@example.com"
95   - })
96   - self.client.get(reverse("user_logout"))
97   - # login with another user to create a SP that user 1 shouldn't be able to see
  133 + post_data = {
  134 + "name":"test 2",
  135 + "description_short":"test2 short desc",
  136 + "description_long":"test2 long desc",
  137 + "science_theme":2,
  138 + }
  139 + response = self.client.post(path,post_data)
  140 + self.assertEqual(response.status_code,302)
  141 + # user can't access to this page
  142 + self.assertEqual(response.context,None)
  143 + self.logout()
  144 +
  145 + def test_SCP_Observer_can_submit_new_SP_from_template(self):
  146 + self.create_dummy_SCP("test",1)
  147 + current_sp = ScientificProgram.objects.all().first()
  148 +
  149 + self.login_as_user(1)
  150 + path = reverse('create_scientific_program_with_template',kwargs={"id_SP":current_sp.id})
  151 + post_data = {
  152 + "name":"test 2",
  153 + "description_short":"test2 short desc",
  154 + "description_long":"test2 long desc",
  155 + "science_theme":2,
  156 + }
  157 + response = self.client.post(path,post_data)
  158 + self.assertEqual(response.status_code,302)
  159 + self.assertEqual(ScientificProgram.objects.all().count(),2)
  160 + current_sp = ScientificProgram.objects.all().first()
  161 + self.assertEqual(current_sp.sp_pi,self.usr1)
  162 +
  163 + self.logout()
  164 +
  165 + self.login_as_user(2)
98 166  
99   - self.client.login(username=self.usr5,password="password123")
100   - self.client.post(path, {"name": "third_project", "description_short": "sp short desc", "description_long": "sp long desc",
101   - "public_visibility": "All", "quota_minimal": 10, "quota_nominal": 15,
102   - "over_quota_duration": 5, "token": 3,
103   - "users":"test@example.com,test2@example.com,test3@example.com"
104   - })
105   - self.client.get(reverse("user_logout"))
106   - # log in again as user 1 to see the scientific program list
107   - self.client.login(username=self.usr1,password="password123")
108   - path = reverse("scientific_program_list")
109   - response = self.client.get(path)
110   - self.assertContains(response,"test")
111   - self.assertContains(response,"sp2")
112   - self.assertNotContains(response,"third_project")
113   - self.client.get(reverse("user_logout"))
  167 + path = reverse('create_scientific_program_with_template',kwargs={"id_SP":current_sp.id})
  168 + post_data = {
  169 + "name":"test 3",
  170 + "description_short":"test3 short desc",
  171 + "description_long":"test3 long desc",
  172 + "science_theme":2,
  173 + }
  174 + response = self.client.post(path,post_data)
  175 + self.assertEqual(response.status_code,302)
  176 + # user can't access to this page
  177 + self.assertEqual(response.context,None)
  178 + self.assertEqual(ScientificProgram.objects.all().count(),2)
  179 + self.logout()
114 180  
115   - # log in as unit pi (user 2)
116   - self.client.login(username=self.usr2,password="password123")
117   - path = reverse("scientific_program_list")
118   - response = self.client.get(path)
119   - self.assertContains(response,"test")
120   - self.assertContains(response,"sp2")
121   - self.assertContains(response,"third_project")
  181 + def test_SCP_Observer_can_submit_SP_Period_from_scratch(self):
  182 + self.create_dummy_SCP("test",1)
  183 + self.logout()
  184 + self.login_as_user(1)
  185 + current_sp = ScientificProgram.objects.all().first()
  186 + path = reverse('create_scientific_program_period',kwargs={"id_SP":current_sp.id})
  187 + post_data = {
  188 + "period":Period.objects.next_period().id,
  189 + "public_visibility":SP_Period.VISIBILITY_YES,
  190 + "quota_minimal":2,
  191 + "quota_nominal":5,
  192 + "over_quota_duration":2,
  193 + "token":2
  194 + }
  195 +
  196 + response = self.client.post(path,post_data)
  197 +
  198 + self.assertEqual(response.status_code,302)
  199 + self.assertEqual(SP_Period.objects.filter(scientific_program=current_sp).count(),1)
  200 + current_sp_period = SP_Period.objects.filter(scientific_program=current_sp).first()
  201 + self.assertEqual(current_sp_period.quota_minimal,2)
  202 + self.assertEqual(current_sp_period.status,SP_Period.STATUSES_DRAFT)
122 203  
123   - self.client.get(reverse("user_logout"))
  204 + self.logout()
124 205  
125   - # log in as operator (shoudln't be able to see anything)
126   - self.client.login(username=self.usr4,password="password123")
127   - path = reverse("scientific_program_list")
128   - response = self.client.get(path)
129   - # user is redirected to his previous page, however it was the first page he visited to he is redirected to a None page
  206 + self.login_as_user(2)
  207 + path = reverse('create_scientific_program_period',args=[1])
  208 + post_data = {
  209 + "public_visibility":SP_Period.VISIBILITY_YES,
  210 + "quota_minimal":2,
  211 + "quota_nominal":5,
  212 + "quota_allocated":None,
  213 + "over_quota_duration":2,
  214 + "over_quota_duration_allocated":None,
  215 + "token":2,
  216 + "token_allocated":None,
  217 + "vote_referee1":None,
  218 + "reason_referee1":None,
  219 + "vote_referee2":None,
  220 + "reason_referee2":None,
  221 + "priority":None,
  222 + "is_valid":False
  223 + }
  224 + response = self.client.post(path,post_data)
130 225 self.assertEqual(response.status_code,302)
  226 + # user can't access to this page
131 227 self.assertEqual(response.context,None)
  228 + self.logout()
132 229  
133   - def test_SCP_view_sp_details(self):
134   - self.client.login(username=self.usr1,password="password123")
135   - path = reverse('create_scientific_program')
136   - self.client.post(path, {"name": "test", "description_short": "sp short desc", "description_long": "sp long desc",
137   - "public_visibility": "All", "quota_minimal": 10, "quota_nominal": 15,
138   - "over_quota_duration": 5, "token": 3,
139   - "users":"test@example.com,test2@example.com,test3@example.com"
140   - })
  230 + def test_SCP_Observer_can_submit_existing_SP_for_new_Period(self):
  231 + self.create_dummy_SCP("test",1)
  232 + current_scientific_program = ScientificProgram.objects.all().first()
  233 + self.create_dummy_SCP_Period(current_scientific_program,1)
  234 + sp_period = SP_Period.objects.all().first()
141 235  
142   - self.client.post(path, {"name": "sp2", "description_short": "sp short desc", "description_long": "sp long desc",
143   - "public_visibility": "All", "quota_minimal": 10, "quota_nominal": 15,
144   - "over_quota_duration": 5, "token": 3,
145   - "users":"test@example.com,test2@example.com,test3@example.com"
146   - })
147   - self.client.get(reverse("user_logout"))
148   - # login with another user to create a SP that user 1 shouldn't be able to see
  236 + self.login_as_user(1)
  237 + path = reverse('repropose_scientific_program',kwargs={"id_SP":current_scientific_program.id,"id_SP_Period":sp_period.id})
  238 + post_data = {
  239 + "period":self.period3.id,
  240 + "public_visibility":SP_Period.VISIBILITY_YES,
  241 + "quota_minimal":2,
  242 + "quota_nominal":5,
  243 + "over_quota_duration":2,
  244 + "token":2
  245 + }
  246 + response = self.client.post(path,post_data)
  247 + self.assertEqual(SP_Period.objects.filter(scientific_program=current_scientific_program).count(),2)
  248 + current_sp_period = SP_Period.objects.filter(scientific_program=current_scientific_program)[1]
  249 + SP_Period_User(SP_Period=current_sp_period,user=self.usr5).save()
  250 + self.assertEqual(current_sp_period.quota_minimal,2)
  251 + self.assertEqual(current_sp_period.status,SP_Period.STATUSES_DRAFT)
  252 + # We should have one user which is from the previous period
  253 + self.assertEqual(SP_Period_User.objects.filter(SP_Period=current_sp_period).count(),1)
  254 +
  255 + def test_SCP_Unit_PI_can_add_Period(self):
  256 + self.login_as_user(2)
  257 + path = reverse("create_period")
  258 + today = timezone.now().date().strftime("%d/%m/%Y")
  259 + post_data = {
  260 + "start_date_picker":today,
  261 + "exploitation_duration":182,
  262 + "submission_duration":182,
  263 + "evaluation_duration":31,
  264 + "validation_duration":5,
  265 + "notification_duration":10,
  266 + "property_of_data_duration":365,
  267 + "data_accessibility_duration":365*10
  268 + }
149 269  
150   - self.client.login(username=self.usr5,password="password123")
151   - self.client.post(path, {"name": "third_project", "description_short": "sp short desc", "description_long": "sp long desc",
152   - "public_visibility": "All", "quota_minimal": 10, "quota_nominal": 15,
153   - "over_quota_duration": 5, "token": 3,
154   - "users":"test@example.com,test2@example.com,test3@example.com"
155   - })
156   - self.client.get(reverse("user_logout"))
157   - # log in as user 1
158   - self.client.login(username=self.usr1,password="password123")
159   - path = reverse("detail_scientific_program",args=[ScientificProgram.objects.get(name="test").id])
160   - response = self.client.get(path)
  270 +
  271 + response = self.client.post(path,post_data)
  272 + # This creation should fail, we should have 3 periods
  273 + self.assertEqual(Period.objects.all().count(),3)
161 274 self.assertEqual(response.status_code,200)
162   - self.assertContains(response,"sp long desc")
163   - self.assertContains(response,"test")
  275 + self.assertTrue(response.context["error"])
  276 +
  277 + start_date = Period.objects.latest_period().end_date.strftime("%d/%m/%Y")
  278 + post_data = {
  279 + "start_date_picker":start_date,
  280 + "exploitation_duration":182,
  281 + "submission_duration":182,
  282 + "evaluation_duration":31,
  283 + "validation_duration":5,
  284 + "notification_duration":10,
  285 + "property_of_data_duration":365,
  286 + "data_accessibility_duration":365*10
  287 + }
  288 + response = self.client.post(path,post_data)
  289 + # This creation should success, we should have 4 periods and should be redirected to period_list
  290 + self.assertEqual(Period.objects.all().count(),4)
  291 + self.assertEqual(response.status_code,302)
  292 + self.logout()
  293 +
  294 + def test_SCP_view_main_menu(self):
  295 + self.create_dummy_SCP("test",1)
  296 + sp_period = self.create_dummy_SCP_Period(ScientificProgram.objects.all().first(),1)
  297 + self.login_as_user(1)
  298 + path = reverse("index_scientific_program")
164 299  
165   - path = reverse("detail_scientific_program",args=[ScientificProgram.objects.get(name="sp2").id])
166 300 response = self.client.get(path)
167   - self.assertEqual(response.status_code,200)
168   - self.assertContains(response,"sp long desc")
169   - self.assertContains(response,"sp2")
  301 + # u1 should be able to add, view scientific programs and view Periods
  302 + self.assertTrue(response.context["CAN_SUBMIT_SP"])
  303 + self.assertTrue(response.context["CAN_VIEW_SP"])
  304 + self.assertTrue(response.context["CAN_VIEW_EXPLOITATION_PERIOD"])
  305 + self.assertFalse(response.context["CAN_VIEW_ALL_SP"])
  306 + self.assertFalse(response.context["CAN_EVALUATE_SP"])
  307 + self.assertFalse(response.context["CAN_VALIDATE_SP"])
  308 + self.assertFalse(response.context["CAN_CREATE_EXPLOITATION_PERIOD"])
  309 + self.assertFalse(response.context["CAN_ASSOCIATE_TAC_TO_SP"])
170 310  
171   - self.client.get(reverse("user_logout"))
  311 + self.logout()
  312 + with patch('django.utils.timezone.now', return_value=datetime.strptime(str(Period.objects.next_period().submission_start_date),"%Y-%m-%d")):
  313 + # u2 should be able to associate TAC to SP
  314 + # Submission period
  315 + self.login_as_user(2)
  316 + response = self.client.get(path)
  317 + self.assertTrue(response.context["CAN_ASSOCIATE_TAC_TO_SP"])
  318 + self.logout()
  319 +
  320 + with patch('django.utils.timezone.now', return_value=datetime.strptime(str(Period.objects.next_period().unit_pi_validation_start_date),"%Y-%m-%d")):
  321 + self.login_as_user(2)
  322 + # Validation period
  323 + sp_period.status = SP_Period.STATUSES_EVALUATED
  324 + sp_period.save()
  325 + response = self.client.get(path)
  326 + self.assertEqual(self.client.session["role"],"Unit-PI")
  327 + # u2 should be able to do everything outside submitting, evaluate SP and see the obsever's view for sp
  328 + self.assertTrue(response.context["CAN_VIEW_EXPLOITATION_PERIOD"])
  329 + self.assertTrue(response.context["CAN_VIEW_ALL_SP"])
  330 + self.assertTrue(response.context["CAN_CREATE_EXPLOITATION_PERIOD"])
  331 + self.assertTrue(response.context["CAN_VALIDATE_SP"])
  332 + self.assertFalse(response.context["CAN_ASSOCIATE_TAC_TO_SP"])
  333 + self.assertFalse(response.context["CAN_VIEW_SP"])
  334 + self.assertFalse(response.context["CAN_SUBMIT_SP"])
  335 + self.assertFalse(response.context["CAN_EVALUATE_SP"])
  336 + self.logout()
  337 +
  338 + with patch('django.utils.timezone.now', return_value=datetime.strptime(str(Period.objects.next_period().submission_end_date),"%Y-%m-%d")):
  339 + self.login_as_user(3)
  340 + sp_period.status = SP_Period.STATUSES_SUBMITTED
  341 + sp_period.save()
  342 +
  343 + response = self.client.get(path)
  344 + # u3 should be able to only evaluate SP
  345 + self.assertTrue(response.context["CAN_EVALUATE_SP"])
  346 + self.assertFalse(response.context["CAN_VIEW_EXPLOITATION_PERIOD"])
  347 + self.assertFalse(response.context["CAN_VIEW_ALL_SP"])
  348 + self.assertFalse(response.context["CAN_CREATE_EXPLOITATION_PERIOD"])
  349 + self.assertFalse(response.context["CAN_VALIDATE_SP"])
  350 + self.assertFalse(response.context["CAN_VIEW_SP"])
  351 + self.assertFalse(response.context["CAN_SUBMIT_SP"])
  352 + self.assertFalse(response.context["CAN_ASSOCIATE_TAC_TO_SP"])
172 353  
173   - # log in as user 2
174   - self.client.login(username=self.usr2,password="password123")
175   - path = reverse("detail_scientific_program",args=[ScientificProgram.objects.get(name="test").id])
  354 + self.logout()
  355 +
  356 + def test_SCP_view_sp_list_and_detail(self):
  357 + sp1 = self.create_dummies_SCP_and_SCP_Period("test",1)
  358 + sp2 = self.create_dummies_SCP_and_SCP_Period("test2",1)
  359 + sp3 = self.create_dummies_SCP_and_SCP_Period("test3",1)
  360 + sp4 = self.create_dummies_SCP_and_SCP_Period("test4",1)
  361 + sp5 = self.create_dummies_SCP_and_SCP_Period("test5",1)
  362 + sp6 = self.create_dummies_SCP_and_SCP_Period("test6",8)
  363 + sp7 = self.create_dummies_SCP_and_SCP_Period("test7",7)
  364 + sp2.status = SP_Period.STATUSES_SUBMITTED
  365 + sp2.referee2 = self.usr3
  366 + sp2.referee1 = self.usr6
  367 + sp2.save()
  368 + sp3.status = SP_Period.STATUSES_EVALUATED
  369 + sp3.referee1 = self.usr6
  370 + sp3.referee2 = self.usr3
  371 + sp3.save()
  372 + sp4.status = SP_Period.STATUSES_ACCEPTED
  373 + sp4.save()
  374 + SP_Period_User.objects.create(SP_Period=sp4,user=self.usr5)
  375 + sp5.status = SP_Period.STATUSES_REJECTED
  376 + sp6.status = SP_Period.STATUSES_ACCEPTED
  377 + sp6.save()
  378 + SP_Period_User.objects.create(SP_Period=sp6,user=self.usr1)
  379 + sp7.status = SP_Period.STATUSES_ACCEPTED
  380 + sp7.save()
  381 + SP_Period_User.objects.create(SP_Period=sp7,user=self.usr5)
  382 +
  383 + self.login_as_user(1)
  384 + path = reverse("own_scientific_program_list")
176 385 response = self.client.get(path)
177   - self.assertEqual(response.status_code,200)
178   - self.assertContains(response,"sp long desc")
179   - self.assertContains(response,"test")
  386 + # Can't compare 2 queryset, it returns an error despite they are equal.. so we transform those queryset into list and compare them.
  387 + self.assertEqual(list(ScientificProgram.objects.filter(sp_pi=self.usr1).order_by("-id")),list(response.context["sp_of_user"]))
180 388  
181   - path = reverse("detail_scientific_program",args=[ScientificProgram.objects.get(name="sp2").id])
  389 + path = reverse("detail_scientific_program_period",kwargs={"id_sp":sp1.scientific_program.id,"id_period":sp1.period.id})
182 390 response = self.client.get(path)
183   - self.assertEqual(response.status_code,200)
184   - self.assertContains(response,"sp long desc")
185   - self.assertContains(response,"sp2")
  391 + self.assertFalse(response.context["CAN_VIEW_TAC_VOTES"])
186 392  
187   - path = reverse("detail_scientific_program",args=[ScientificProgram.objects.get(name="third_project").id])
  393 + self.logout()
  394 +
  395 + # login as TAC 1 and put today date to evaluation period
  396 + with patch('django.utils.timezone.now', return_value=datetime.strptime(str(Period.objects.next_period().submission_end_date),"%Y-%m-%d")):
  397 + self.login_as_user(3)
  398 + path = reverse("list_submitted_scientific_program")
  399 + response = self.client.get(path)
  400 + self.assertEqual(list(SP_Period.objects.filter(referee2=self.usr3,period=Period.objects.next_period(),status=SP_Period.STATUSES_SUBMITTED)),response.context["list_of_evaluated_sp"])
  401 + path = reverse("detail_scientific_program_period",kwargs={"id_sp":sp2.scientific_program.id,"id_period":sp2.period.id})
  402 +
  403 + response = self.client.get(path)
  404 + self.assertEqual(response.status_code,302)
  405 + # user can't access to this page
  406 + self.assertEqual(response.context,None)
  407 + self.logout()
  408 +
  409 + # log in as user observer
  410 + self.login_as_user(5)
  411 + path = reverse("scientific_program_list")
188 412 response = self.client.get(path)
189   - self.assertEqual(response.status_code,200)
190   - self.assertContains(response,"sp long desc")
191   - self.assertContains(response,"third_project")
  413 + sp_of_user = list(("test4","test7"))
  414 + self.assertListEqual(sp_of_user,list(response.context["sp_of_user"].values_list("name",flat=True)))
  415 + path = reverse("detail_scientific_program_period",kwargs={"id_sp":sp4.scientific_program.id,"id_period":sp4.period.id})
  416 + response = self.client.get(path)
  417 + self.assertFalse(response.context["CAN_VIEW_TAC_VOTES"])
  418 + self.assertFalse(response.context["CAN_SUBMIT_PROPOSAL"])
  419 + self.logout()
  420 +
  421 + # log in as unit pi
  422 + self.login_as_user(2)
  423 + path = reverse("scientific_program_list")
  424 + response = self.client.get(path)
  425 + self.assertListEqual(list(ScientificProgram.objects.all().order_by("-id")),list(response.context["scientific_programs"]))
  426 + self.logout()
192 427  
193   - self.client.get(reverse("user_logout"))
  428 + def test_SCP_list_Periods(self):
  429 + self.login_as_user(1)
  430 + path = reverse("period_list")
  431 + response = self.client.get(path)
  432 + self.assertContains(response,"(Current period)")
  433 +
  434 + self.assertEqual(Period.objects.all().count(),len(response.context["future_periods"]))
  435 + self.logout()
  436 +
  437 + def test_SCP_detail_Period(self):
  438 + sp1 = self.create_dummies_SCP_and_SCP_Period("test",1)
  439 + sp2 = self.create_dummies_SCP_and_SCP_Period("test2",1)
  440 + sp3 = self.create_dummies_SCP_and_SCP_Period("test3",1)
  441 + sp4 = self.create_dummies_SCP_and_SCP_Period("test4",1)
  442 + sp5 = self.create_dummies_SCP_and_SCP_Period("test5",1)
  443 +
  444 + self.login_as_user(1)
  445 + period = sp1.period
  446 + path = reverse("detail_period",kwargs={"id":period.id})
  447 + response = self.client.get(path)
  448 + # we should see the same number of SP_Period (5) that are associated to user 1
  449 + self.assertEqual(SP_Period.objects.all().count(),len(response.context["sp_periods"]))
  450 + self.logout()
  451 +
  452 + def test_SCP_edit(self):
  453 + sp1 = self.create_dummies_SCP_and_SCP_Period("test",1)
  454 + sp2 = self.create_dummies_SCP_and_SCP_Period("test2",1)
  455 + sp3 = self.create_dummies_SCP_and_SCP_Period("test3",1)
  456 + sp4 = self.create_dummies_SCP_and_SCP_Period("test4",1)
  457 + sp5 = self.create_dummies_SCP_and_SCP_Period("test5",5)
  458 + sp2.status = SP_Period.STATUSES_SUBMITTED
  459 + sp2.save()
  460 + sp3.status = SP_Period.STATUSES_ACCEPTED
  461 + sp3.save()
  462 + sp4.status = SP_Period.STATUSES_ACCEPTED
  463 + sp4.save()
  464 + sp5.status = SP_Period.STATUSES_ACCEPTED
  465 + sp5.save()
  466 + post_data = {
  467 +
  468 + "name": "test",
  469 + "description_short":"modified test",
  470 + "description_long":"modified test",
  471 + "science_theme" :"2",
  472 + "public_visibility":"Yes",
  473 + "quota_minimal":"0",
  474 + "quota_nominal":"0",
  475 + "over_quota_duration":"0",
  476 + "token":"0",
  477 + "vote_referee1":"",
  478 + "reason_referee1":"",
  479 + "users":"",
  480 + }
  481 + self.login_as_user(1)
  482 + path = reverse("edit_scientific_program_period",kwargs={"id_sp":sp1.scientific_program.id,"id_period":sp1.period.id})
  483 + response = self.client.post(path,post_data)
  484 + self.assertEqual(response.status_code,302)
  485 + updated_sp = SP_Period.objects.get(id=sp1.id)
  486 + self.assertEqual(updated_sp.scientific_program.description_short,"modified test")
  487 +
  488 + path = reverse("edit_scientific_program_period",kwargs={"id_sp":sp2.scientific_program.id,"id_period":sp2.period.id})
  489 + post_data = {
  490 +
  491 + "name": "test",
  492 + "description_short":"modified test2",
  493 + "description_long":"modified test2",
  494 + "science_theme" :"2",
  495 + "public_visibility":"Yes",
  496 + "quota_minimal":"10",
  497 + "quota_nominal":"0",
  498 + "over_quota_duration":"0",
  499 + "token":"0",
  500 + "vote_referee1":"",
  501 + "reason_referee1":"",
  502 + "users":"",
  503 + }
  504 + response = self.client.post(path,post_data)
  505 + self.assertEqual(response.status_code,302)
  506 + updated_sp = SP_Period.objects.get(id=sp2.id)
  507 + # user can't modify this SP_Period because the status isn't DRAFT
  508 + self.assertNotEqual(updated_sp.scientific_program.description_short,"modified test2")
  509 + self.assertNotEqual(updated_sp.quota_minimal,10)
  510 +
  511 + path = reverse("edit_scientific_program_period",kwargs={"id_sp":sp5.scientific_program.id,"id_period":sp5.period.id})
  512 + response = self.client.post(path,post_data)
  513 + # u1 can't edit SP5 because he is not the SP_PI
  514 + self.assertEqual(response.status_code,302)
  515 + self.assertNotEqual(updated_sp.scientific_program.description_short,"modified test2")
  516 + self.assertNotEqual(updated_sp.quota_minimal,10)
  517 + self.assertEqual(response.url,reverse("detail_scientific_program_period",kwargs={"id_sp":sp5.scientific_program.id,"id_period":sp5.period.id}))
  518 + self.logout()
  519 +
  520 + def test_SCP_add_users(self):
  521 + sp1 = self.create_dummies_SCP_and_SCP_Period("test",1)
  522 + post_data = {
194 523  
195   - # log in as user 4 (operator)
  524 + "name": "test",
  525 + "description_short":"modified test",
  526 + "description_long":"modified test",
  527 + "science_theme" :"2",
  528 + "public_visibility":"Yes",
  529 + "quota_minimal":"0",
  530 + "quota_nominal":"0",
  531 + "over_quota_duration":"0",
  532 + "token":"0",
  533 + "vote_referee1":"",
  534 + "reason_referee1":"",
  535 + "users":"observer2@test.com",
  536 + }
  537 + self.login_as_user(1)
  538 + path = reverse("edit_scientific_program_period",kwargs={"id_sp":sp1.scientific_program.id,"id_period":sp1.period.id})
  539 + response = self.client.post(path,post_data)
  540 + self.assertEqual(SP_Period_Guest.objects.filter(SP_Period=sp1).first().email,"observer2@test.com")
  541 + self.assertEqual(len(mail.outbox),1)
  542 + self.assertIn("observer2@test.com",mail.outbox[0].recipients())
  543 + self.logout()
196 544  
197   - self.client.login(username=self.usr4,password="password123")
198   - path = reverse("detail_scientific_program",args=[ScientificProgram.objects.get(name="test").id])
  545 + url = reverse("sp_register",kwargs={"id_SP":sp1.scientific_program.id,"id_period":sp1.period.id})
  546 + self.login_as_user(5)
  547 + response = self.client.get(url)
  548 + self.assertEqual(response.status_code,302)
  549 + self.assertEqual(response.url,reverse("detail_scientific_program_period",kwargs={"id_sp":sp1.scientific_program.id,"id_period":sp1.period.id}))
  550 + self.assertEqual(SP_Period_Guest.objects.all().count(),0)
  551 + self.assertEqual(SP_Period_User.objects.filter(SP_Period=sp1).count(),1)
  552 + self.assertEqual(SP_Period_User.objects.filter(SP_Period=sp1).first().user,self.usr5)
  553 + self.logout()
  554 +
  555 + def test_SCP_delete(self):
  556 + sp1 = self.create_dummies_SCP_and_SCP_Period("test",1)
  557 + sp2 = self.create_dummy_SCP("test2",1)
  558 + self.login_as_user(1)
  559 + path = reverse("delete_scientific_program",kwargs={"id_sp":sp1.scientific_program.id})
199 560 response = self.client.get(path)
200   - # user is redirected to his previous page, however it was the first page he visited to he is redirected to a None page
  561 + # Can't delete this SP because it's linked to a period
201 562 self.assertEqual(response.status_code,302)
202   - self.assertEqual(response.context,None)
  563 + self.assertEqual(response.url,reverse("detail_scientific_program", args=[sp1.scientific_program.id]))
  564 + self.assertEqual(ScientificProgram.objects.all().count(),2)
203 565  
204   - path = reverse("detail_scientific_program",args=[ScientificProgram.objects.get(name="sp2").id])
  566 + path = reverse("delete_scientific_program",kwargs={"id_sp":sp2.id})
205 567 response = self.client.get(path)
206   - # user is redirected to his previous page, however it was the first page he visited to he is redirected to a None page
  568 + # Can delete this SP
207 569 self.assertEqual(response.status_code,302)
208   - self.assertEqual(response.context,None)
  570 + self.assertEqual(response.url,reverse("index_scientific_program"))
  571 + self.assertEqual(ScientificProgram.objects.all().count(),1)
  572 +
  573 + self.logout()
209 574  
210   - path = reverse("detail_scientific_program",args=[ScientificProgram.objects.get(name="third_project").id])
  575 + def test_SCP_Period_delete(self):
  576 + sp1 = self.create_dummies_SCP_and_SCP_Period("test",1)
  577 + sp2 = self.create_dummies_SCP_and_SCP_Period("test2",1)
  578 + sp2.status = SP_Period.STATUSES_ACCEPTED
  579 + sp2.save()
  580 + self.login_as_user(1)
  581 + path = reverse("delete_scientific_program_period",kwargs={"id_sp":sp1.scientific_program.id,"id_period":sp1.period.id})
211 582 response = self.client.get(path)
212   - # user is redirected to his previous page, however it was the first page he visited to he is redirected to a None page
  583 + # Can delete this SP because it's in draft status
213 584 self.assertEqual(response.status_code,302)
214   - self.assertEqual(response.context,None)
215   -
216   - def test_SCP_sp_update(self):
217   - self.client.login(username=self.usr1,password="password123")
218   - path = reverse('create_scientific_program')
219   - self.client.post(path, {"name": "sp1", "description_short": "sp short desc", "description_long": "sp long desc",
220   - "public_visibility": "All", "quota_minimal": 10, "quota_nominal": 15,
221   - "over_quota_duration": 5, "token": 3,
222   - "users":"test@example.com,test2@example.com,test3@example.com"
223   - })
  585 + self.assertEqual(response.url,reverse("detail_scientific_program", args=[sp1.scientific_program.id]))
  586 + self.assertEqual(SP_Period.objects.filter(scientific_program=sp1.scientific_program).count(),0)
  587 +
  588 + path = reverse("delete_scientific_program_period",kwargs={"id_sp":sp2.scientific_program.id,"id_period":sp2.period.id})
  589 + response = self.client.get(path)
  590 + # Can't delete this SP
  591 + self.assertEqual(response.status_code,302)
  592 + self.assertEqual(response.url,reverse("detail_scientific_program", args=[sp2.scientific_program.id]))
  593 + self.assertEqual(SP_Period.objects.filter(scientific_program=sp2.scientific_program).count(),1)
  594 + self.logout()
  595 +
  596 + def run_agent_SP(self):
  597 + agent = build_agent(AgentSP, RUN_IN_THREAD=True)
  598 + print(agent)
  599 + agent.run()
  600 + def test_SCP_lifecycle(self):
224 601  
225   - self.client.post(path, {"name": "sp2", "description_short": "sp short desc", "description_long": "sp long desc",
226   - "public_visibility": "All", "quota_minimal": 10, "quota_nominal": 15,
227   - "over_quota_duration": 5, "token": 3,
228   - "users":"test@example.com,test2@example.com,test3@example.com"
229   - })
230   - self.client.post(path, {"name": "sp3", "description_short": "sp short desc", "description_long": "sp long desc",
231   - "public_visibility": "All", "quota_minimal": 10, "quota_nominal": 15,
232   - "over_quota_duration": 5, "token": 3,
233   - "users":"test@example.com,test2@example.com,test3@example.com"
234   - })
235   - self.client.post(path, {"name": "sp4", "description_short": "sp short desc", "description_long": "sp long desc",
236   - "public_visibility": "All", "quota_minimal": 10, "quota_nominal": 15,
237   - "over_quota_duration": 5, "token": 3,
238   - "users":"test@example.com,test2@example.com,test3@example.com"
239   - })
240   - self.client.get(reverse("user_logout"))
241   - # login with another user to create a SP that user 1 shouldn't be able to see
242 602  
243   - self.client.login(username=self.usr5,password="password123")
244   - self.client.post(path, {"name": "sp5", "description_short": "sp short desc", "description_long": "sp long desc",
245   - "public_visibility": "All", "quota_minimal": 10, "quota_nominal": 15,
246   - "over_quota_duration": 5, "token": 3,
247   - "users":"test@example.com,test2@example.com,test3@example.com"
248   - })
249   - self.client.get(reverse("user_logout"))
250   - # log in as user 1
251   - self.client.login(username=self.usr1,password="password123")
252   - period = Period.objects.all().order_by('-id').first()
253   - sp1 = ScientificProgram.objects.get(name="sp1")
254   - path = reverse("edit_scientific_program_period",args=[sp1.id,period.id])
255   - self.assertEqual(SP_Period.objects.get(scientific_program=sp1,period=period).status,SP_Period.STATUSES_DRAFT)
256   - self.client.post(path, {"name": "sp4", "description_short": "sp modified short desc", "description_long": "sp long desc",
257   - "public_visibility": "All", "quota_minimal": 15, "quota_nominal": 20,
258   - "over_quota_duration": 5, "token": 3,
259   - "users":"test@example.com,test2@example.com"
260   - })
261   -
262 603 \ No newline at end of file
  604 + print("--------- LIFE CYCLE START----------")
  605 + sp1 = self.create_dummies_SCP_and_SCP_Period("test",1)
  606 + sp2 = self.create_dummies_SCP_and_SCP_Period("test2",1)
  607 + sp3 = self.create_dummies_SCP_and_SCP_Period("test3",1)
  608 + sp3.scientific_program.is_auto_validated = True
  609 + sp3.scientific_program.save()
  610 + os.chdir("./scientific_program/")
  611 + with patch('django.utils.timezone.now', return_value=datetime.strptime(str(Period.objects.next_period().submission_end_date + relativedelta(days=-10)),"%Y-%m-%d")):
  612 + # TAC Assignation
  613 + p = multiprocessing.Process(target= self.run_agent_SP,args=())
  614 + p.daemon = True
  615 + p.start()
  616 +
  617 + time.sleep(10)
  618 + p.terminate()
  619 + for sp_period in SP_Period.objects.all().exclude(id=sp3.id):
  620 + # sp period should have referees
  621 + self.assertNotEqual(sp_period.referee1,None)
  622 + self.assertNotEqual(sp_period.referee2,None)
  623 +
  624 +
  625 + with patch('django.utils.timezone.now', return_value=datetime.strptime(str(Period.objects.next_period().submission_end_date),"%Y-%m-%d")):
  626 + # Evaluation start
  627 + p = multiprocessing.Process(target= self.run_agent_SP,args=())
  628 + p.daemon = True
  629 + p.start()
  630 +
  631 + time.sleep(10)
  632 + p.terminate()
  633 + for sp_period in SP_Period.objects.all().exclude(id=sp3.id):
  634 + # sp period should have referees
  635 + self.assertEqual(sp_period.status,SP_Period.STATUSES_SUBMITTED)
  636 +
  637 + # sp3 should be valid
  638 + self.assertEqual(SP_Period.objects.get(id=sp3.id).status,SP_Period.STATUSES_ACCEPTED)
  639 + self.assertEqual(SP_Period.objects.get(id=sp3.id).is_valid,SP_Period.IS_VALID_ACCEPTED)
  640 +
  641 + # log in as tac to vote
  642 + self.login_as_user(3)
  643 + path = reverse("edit_scientific_program_period",kwargs={"id_sp":sp1.scientific_program.id,"id_period":sp1.period.id})
  644 + post_data = {
  645 +
  646 + "name": sp1.scientific_program.name,
  647 + "description_short":"modified test",
  648 + "description_long":"modified test",
  649 + "science_theme" :sp1.scientific_program.science_theme,
  650 + "public_visibility":"Yes",
  651 + "quota_minimal":sp1.quota_minimal,
  652 + "quota_nominal":sp1.quota_nominal,
  653 + "over_quota_duration":sp1.over_quota_duration,
  654 + "token":sp1.token,
  655 + "vote_referee1":SP_Period.VOTES_YES,
  656 + "reason_referee1":"i agree",
  657 + "users":"",
  658 + }
  659 + response = self.client.post(path,post_data)
  660 + self.assertEqual(response.status_code,302)
  661 + self.assertEqual(SP_Period.objects.get(id=sp1.id).vote_referee1,SP_Period.VOTES_YES)
  662 + self.assertEqual(SP_Period.objects.get(id=sp1.id).reason_referee1,"i agree")
  663 + self.logout()
  664 +
  665 + # log in as tac 2 to vote
  666 + self.login_as_user(6)
  667 + path = reverse("edit_scientific_program_period",kwargs={"id_sp":sp1.scientific_program.id,"id_period":sp1.period.id})
  668 + post_data = {
  669 +
  670 + "name": sp1.scientific_program.name,
  671 + "description_short":"modified test",
  672 + "description_long":"modified test",
  673 + "science_theme" :sp1.scientific_program.science_theme,
  674 + "public_visibility":"Yes",
  675 + "quota_minimal":sp1.quota_minimal,
  676 + "quota_nominal":sp1.quota_nominal,
  677 + "over_quota_duration":sp1.over_quota_duration,
  678 + "token":sp1.token,
  679 + "vote_referee2":SP_Period.VOTES_YES,
  680 + "reason_referee2":"i agree",
  681 + "users":"",
  682 + }
  683 + response = self.client.post(path,post_data)
  684 + self.assertEqual(response.status_code,302)
  685 + self.assertEqual(SP_Period.objects.get(id=sp1.id).vote_referee2,SP_Period.VOTES_YES)
  686 + self.assertEqual(SP_Period.objects.get(id=sp1.id).reason_referee2,"i agree")
  687 + self.logout()
  688 +
  689 + with patch('django.utils.timezone.now', return_value=datetime.strptime(str(Period.objects.next_period().unit_pi_validation_start_date),"%Y-%m-%d")):
  690 + # validation start
  691 + p = multiprocessing.Process(target= self.run_agent_SP,args=())
  692 + p.daemon = True
  693 + p.start()
  694 +
  695 + time.sleep(10)
  696 + p.terminate()
  697 + for sp_period in SP_Period.objects.all().exclude(id=sp3.id):
  698 + # sp period should be evaluated
  699 + self.assertEqual(sp_period.status,SP_Period.STATUSES_EVALUATED)
  700 + # log in as unit pi
  701 +
  702 + self.login_as_user(2)
  703 + # valid sp1
  704 + path = reverse("edit_scientific_program_period",kwargs={"id_sp":sp1.scientific_program.id,"id_period":sp1.period.id})
  705 + post_data = {
  706 + "name": sp1.scientific_program.name,
  707 + "description_short":"modified test",
  708 + "description_long":"modified test",
  709 + "science_theme" :sp1.scientific_program.science_theme,
  710 + "public_visibility":"Yes",
  711 + "quota_minimal":sp1.quota_minimal,
  712 + "quota_nominal":sp1.quota_nominal,
  713 + "over_quota_duration":sp1.over_quota_duration,
  714 + "token":sp1.token,
  715 + "is_valid":SP_Period.IS_VALID_ACCEPTED,
  716 + "quota_allocated" : 10,
  717 + "token_allocated": 5,
  718 + "over_quota_duration_allocated": 5
  719 + }
  720 + response = self.client.post(path,post_data)
  721 + self.assertEqual(response.status_code,302)
  722 + self.assertEqual(SP_Period.objects.get(id=sp1.id).quota_allocated,10)
  723 + self.assertEqual(SP_Period.objects.get(id=sp1.id).is_valid,SP_Period.IS_VALID_ACCEPTED)
  724 + self.assertEqual(SP_Period.objects.get(id=sp1.id).token_allocated,5)
  725 + self.assertEqual(SP_Period.objects.get(id=sp1.id).over_quota_duration_allocated,5)
  726 +
  727 + # reject sp 2
  728 + path = reverse("edit_scientific_program_period",kwargs={"id_sp":sp2.scientific_program.id,"id_period":sp2.period.id})
  729 + post_data = {
  730 + "name": sp2.scientific_program.name,
  731 + "description_short":"modified test",
  732 + "description_long":"modified test",
  733 + "science_theme" :sp2.scientific_program.science_theme,
  734 + "public_visibility":"Yes",
  735 + "quota_minimal":sp2.quota_minimal,
  736 + "quota_nominal":sp2.quota_nominal,
  737 + "over_quota_duration":sp2.over_quota_duration,
  738 + "token":sp2.token,
  739 + "is_valid":SP_Period.IS_VALID_REJECTED,
  740 + "quota_allocated" : 0,
  741 + "token_allocated": 0,
  742 + "over_quota_duration_allocated": 0
  743 + }
  744 + response = self.client.post(path,post_data)
  745 + self.assertEqual(response.status_code,302)
  746 + self.assertEqual(SP_Period.objects.get(id=sp2.id).is_valid,SP_Period.IS_VALID_REJECTED)
  747 + self.assertEqual(SP_Period.objects.get(id=sp2.id).quota_allocated,0)
  748 + self.assertEqual(SP_Period.objects.get(id=sp2.id).token_allocated,0)
  749 + self.assertEqual(SP_Period.objects.get(id=sp2.id).over_quota_duration_allocated,0)
  750 + self.logout()
  751 +
  752 +
  753 + with patch('django.utils.timezone.now', return_value=datetime.strptime(str(Period.objects.next_period().notification_start_date),"%Y-%m-%d")):
  754 + # validation start
  755 + p = multiprocessing.Process(target= self.run_agent_SP,args=())
  756 + p.daemon = True
  757 + p.start()
  758 +
  759 + time.sleep(10)
  760 + p.terminate()
  761 + self.assertEqual(SP_Period.objects.get(id=sp1.id).status,SP_Period.STATUSES_ACCEPTED)
  762 + self.assertEqual(SP_Period.objects.get(id=sp3.id).status,SP_Period.STATUSES_ACCEPTED)
  763 + self.assertEqual(SP_Period.objects.get(id=sp2.id).status,SP_Period.STATUSES_REJECTED)
... ...
src/core/pyros_django/scientific_program/urls.py
... ... @@ -10,6 +10,7 @@ urlpatterns = [
10 10 path('add_SP_with_template/<int:id_SP>', views.create_scientific_program_with_template,name='create_scientific_program_with_template'),
11 11 path('add_SP_Period/<int:id_SP>/', views.create_scientific_program_period,name='create_scientific_program_period'),
12 12 path('repropose_scientific_program/<int:id_SP>/<int:id_SP_Period>', views.create_scientific_program_period,name='repropose_scientific_program'),
  13 + path('detail_scientific_program/<int:id>/', views.detail_scientific_program,name='detail_scientific_program'),
13 14 path('detail_scientific_program_period/<int:id_sp>/<int:id_period>', views.detail_scientific_program_period,name='detail_scientific_program_period'),
14 15 path('edit_scientific_program_period/<int:id_sp>/<int:id_period>', views.edit_scientific_program_period,name='edit_scientific_program_period'),
15 16 path('delete_scientific_program_period/<int:id_sp>/<int:id_period>', views.delete_scientific_program_period,name='delete_scientific_program_period'),
... ... @@ -18,7 +19,6 @@ urlpatterns = [
18 19 path('associate_tac_to_scientific_program/<int:id_sp>/<int:id_period>/',views.associate_tac_to_scientific_program,name="associate_tac_to_scientific_program"),
19 20 path('scientific_program_list', views.scientific_program_list,name='scientific_program_list'),
20 21 path('own_scientific_program_list', views.own_scientific_program_list,name='own_scientific_program_list'),
21   - path('detail_scientific_program/<int:id>/', views.detail_scientific_program,name='detail_scientific_program'),
22 22 path("list_evaluated_scientific_program",views.list_evaluated_scientific_program,name="list_evaluated_scientific_program"),
23 23 path("list_submitted_scientific_program",views.list_submitted_scientific_program,name="list_submitted_scientific_program"),
24 24 path("list_scientific_program_as_template",views.list_scientific_program_as_template,name="list_scientific_program_as_template"),
... ... @@ -43,4 +43,5 @@ urlpatterns = [
43 43 path("detail_science_theme/<int:id>/",views.detail_science_theme,name="detail_science_theme"),
44 44 path("edit_science_theme/<int:id>/",views.edit_science_theme,name="edit_science_theme"),
45 45 path("delete_science_theme/<int:id>/",views.delete_science_theme,name="delete_science_theme"),
  46 + path("test_tac_auto",views.test_tac_auto,name="test_tac_auto")
46 47 ]
... ...
src/core/pyros_django/scientific_program/views.py
... ... @@ -16,7 +16,8 @@ from django.utils import timezone
16 16 from django.db.models import Q,F
17 17 from .functions import get_global_svg_timeline, get_svg_timeline, get_proposal_svg_timeline
18 18 from django.views.decorators.csrf import csrf_exempt
19   -import re
  19 +import matplotlib.pyplot as plt
  20 +import re,io,urllib,base64
20 21  
21 22 # Scientific program CRUD
22 23  
... ... @@ -41,8 +42,7 @@ def index_scientific_program(request):
41 42 validation_periods = Period.objects.filter(
42 43 start_date__gte=today, submission_end_date__gt=today,unit_pi_validation_start_date__gte=today)
43 44 """
44   - does_next_period_exist = Period.objects.filter(
45   - start_date__gt=today).order_by("start_date").first() != None
  45 + does_next_period_exist = Period.objects.next_period() != None
46 46 current_period = Period.objects.exploitation_period()
47 47 future_period = Period.objects.filter(start_date__gte=current_period.end_date).first()
48 48 previous_period = Period.objects.previous_periods().first()
... ... @@ -50,6 +50,7 @@ def index_scientific_program(request):
50 50 validation_periods = Period.objects.validation_periods()
51 51 sp_to_be_evaluated = SP_Period.objects.filter(period__in=evaluation_periods,status__in=(SP_Period.STATUSES_SUBMITTED,SP_Period.STATUSES_EVALUATED)).count() > 0
52 52 sp_to_be_validated = SP_Period.objects.filter(period__in=validation_periods,status__in=(SP_Period.STATUSES_EVALUATED,SP_Period.STATUSES_ACCEPTED,SP_Period.STATUSES_REJECTED)).count() > 0
  53 + does_next_period_need_tac_association = Period.objects.next_period() in Period.objects.submission_periods()
53 54 # date_diff_today_end_of_property will give a value to make
54 55 # the date of today shift through the time line
55 56 if previous_period:
... ... @@ -65,7 +66,7 @@ def index_scientific_program(request):
65 66 CAN_VIEW_ALL_SP = request.session.get("role") in ("Admin","Unit-PI","Unit-board")
66 67 CAN_VIEW_EXPLOITATION_PERIOD = request.session.get("role") in ("Admin","Observer","Unit-PI","Unit-board")
67 68 CAN_CREATE_EXPLOITATION_PERIOD = request.session.get("role") in ("Admin","Unit-PI","Unit-board")
68   - CAN_ASSOCIATE_TAC_TO_SP = request.session.get("role") in ("Admin","Unit-PI","Unit-board")
  69 + CAN_ASSOCIATE_TAC_TO_SP = request.session.get("role") in ("Admin","Unit-PI","Unit-board") and does_next_period_need_tac_association
69 70 svg = get_svg_timeline(previous_period,current_period,future_period)
70 71 global_svg = get_global_svg_timeline(previous_period,current_period,future_period)
71 72 #global_svg = get_global_svg_timeline(current_period,current_period,future_period)
... ... @@ -79,7 +80,7 @@ def index_scientific_program(request):
79 80 "svg":svg,
80 81 "global_svg":global_svg,
81 82 "CAN_VALIDATE_SP": CAN_VALIDATE_SP,
82   - "CAN_EVALUATE": CAN_EVALUATE_SP,
  83 + "CAN_EVALUATE_SP": CAN_EVALUATE_SP,
83 84 "CAN_SUBMIT_SP":CAN_SUBMIT_SP,
84 85 "CAN_VIEW_SP": CAN_VIEW_SP,
85 86 "CAN_VIEW_EXPLOITATION_PERIOD": CAN_VIEW_EXPLOITATION_PERIOD,
... ... @@ -238,7 +239,8 @@ def list_drafted_scientific_program(request):
238 239 select_message = "Select an SP to associate TAC users to it for the incomming evaluation time"
239 240 none_message = f"There is no existing SP to be associated with TAC users for a new exploitation period"
240 241 non_autovalidated_sp = ScientificProgram.objects.exclude(is_auto_validated=True)
241   - sp_periods_without_tac = SP_Period.objects.filter(scientific_program__in=non_autovalidated_sp,period=Period.objects.submission_periods().first()).filter(Q(referee1=None)|Q(referee2=None)).order_by("scientific_program__science_theme")
  242 + #sp_periods_without_tac = SP_Period.objects.filter(scientific_program__in=non_autovalidated_sp,period=Period.objects.submission_periods().first()).filter(Q(referee1=None)|Q(referee2=None)).order_by("scientific_program__science_theme")
  243 + sp_periods_without_tac = SP_Period.objects.filter(scientific_program__in=non_autovalidated_sp,period=Period.objects.submission_periods().first()).order_by("scientific_program__science_theme")
242 244 return render(request, "scientific_program/list_of_scientific_program_select.html", {
243 245 "select_message": select_message,
244 246 "none_message": none_message,
... ... @@ -331,8 +333,10 @@ def list_submitted_scientific_program(request):
331 333 next_period = Period.objects.evaluation_periods().first()
332 334 none_message = f"There is no SP to be evaluated for next exploitation period {next_period}"
333 335 # Note : ~Q mean not query
334   - list_of_sp = SP_Period.objects.filter(Q(referee1=None) & ~Q(referee2=request.user) | ~Q(
335   - referee1=request.user) & Q(referee2=None), status=SP_Period.STATUSES_SUBMITTED, period=next_period)
  336 + #list_of_sp = SP_Period.objects.filter(Q(referee1=None) & ~Q(referee2=request.user) | ~Q(
  337 + # referee1=request.user) & Q(referee2=None), status=SP_Period.STATUSES_SUBMITTED, period=next_period)
  338 +
  339 + list_of_sp = SP_Period.objects.filter(status=SP_Period.STATUSES_SUBMITTED, period=next_period)
336 340 list_of_evaluated_sp = []
337 341 for sp in list_of_sp:
338 342 if sp.referee1 == request.user or sp.referee2 == request.user:
... ... @@ -390,6 +394,7 @@ def accept_sp_invitation(request, id_SP, id_period):
390 394 SP_Period=sp_period, email=current_user.email).delete()
391 395 return HttpResponseRedirect(reverse("detail_scientific_program_period", kwargs={"id_sp": id_SP, "id_period": id_period}))
392 396 return HttpResponseRedirect(reverse("index"))
  397 +
393 398 @login_required
394 399 @level_required("Admin", "Unit-PI", "Unit-board")
395 400 def associate_tac_to_scientific_program(request,id_sp,id_period):
... ... @@ -443,7 +448,11 @@ def edit_scientific_program_period(request, id_sp, id_period):
443 448 list_of_emails = None
444 449 recipient_list = []
445 450 if request.POST:
  451 + if SPForm.is_valid() and sp_period.status == SP_Period.STATUSES_DRAFT:
  452 + SPForm.save()
446 453 if SP_Period_form.is_valid():
  454 + if sp_period.status == SP_Period.STATUSES_DRAFT:
  455 + SP_period = SP_Period_form.save()
447 456 # can send invitation until the end of exploitation
448 457 if request.POST.get("users") and today < period.end_date:
449 458 # Invite user to join the SP
... ... @@ -482,8 +491,9 @@ def edit_scientific_program_period(request, id_sp, id_period):
482 491 recipient_list=[recipient],
483 492 fail_silently=False,
484 493 )
  494 +
485 495 else:
486   - return render(request, 'scientific_program/scientific_program_detail_edit.html', {
  496 + return render(request, 'scientific_program/scientific_program_period_detail_edit.html', {
487 497 'id_sp': id_sp,
488 498 "id_period": id_period,
489 499 "SPForm": SPForm,
... ... @@ -494,21 +504,8 @@ def edit_scientific_program_period(request, id_sp, id_period):
494 504 "sp_period": sp_period,
495 505 "error_message":error_message
496 506 })
497   - SP_period = SP_Period_form.save()
498 507 # save changes on user list
499   - if request.POST.getlist("user"):
500   - for sp_period_user in users_informations:
501   - if sp_period_user.id not in map(int, request.POST.getlist("user")):
502   - sp_period_user.delete()
503   -
504   - for user in request.POST.getlist("user"):
505   - user_instance = PyrosUser.objects.get(id=int(user))
506   - if user_instance not in users_informations:
507   - SP_Period_User.objects.create(
508   - SP_Period=sp_period, user=user_instance)
509   - else:
510   - SP_Period_User.objects.filter(
511   - SP_Period=sp_period).delete()
  508 +
512 509 return redirect('detail_scientific_program_period', id_sp=scientific_program.id, id_period=id_period)
513 510 if SP_Period_form.is_valid() and request.session.get("role") == "TAC" and sp_period.status != SP_Period.STATUSES_DRAFT:
514 511 # vote TAC
... ... @@ -535,15 +532,16 @@ def edit_scientific_program_period(request, id_sp, id_period):
535 532 #sp_period.status = SP_Period.STATUSES_EVALUATED
536 533 sp_period.save()
537 534 return redirect('detail_scientific_program_period', id_sp=scientific_program.id, id_period=id_period)
538   - elif SP_Period_form.is_valid() and request.session.get("role") in ["Unit-PI", "Unit-board"] and sp_period.status in [SP_Period.STATUSES_EVALUATED, SP_Period.STATUSES_ACCEPTED, SP_Period.STATUSES_REJECTED]:
  535 + elif SP_Period_form.is_valid() and request.session.get("role") in ["Admin","Unit-PI", "Unit-board"] and sp_period.status in [SP_Period.STATUSES_EVALUATED, SP_Period.STATUSES_ACCEPTED, SP_Period.STATUSES_REJECTED]:
539 536 #if request.POST.get("is_valid") != None:
540 537 #sp_period.status = request.POST.get("is_valid")
541 538 SP_Period_form.save()
542   - sp_period.save()
543 539 return redirect('detail_scientific_program_period', id_sp=scientific_program.id, id_period=id_period)
544 540 else:
545 541 print(SP_Period_form.errors)
546   - return render(request, 'scientific_program/scientific_program_detail_edit.html', {
  542 + return redirect('detail_scientific_program_period', id_sp=scientific_program.id, id_period=id_period)
  543 + CAN_VIEW_TAC_VOTES = request.session.get("role") in ("Admin","Unit-PI","Unit-board")
  544 + return render(request, 'scientific_program/scientific_program_period_detail_edit.html', {
547 545 'id_sp': id_sp,
548 546 "id_period": id_period,
549 547 "SPForm": SPForm,
... ... @@ -552,7 +550,8 @@ def edit_scientific_program_period(request, id_sp, id_period):
552 550 "users_of_sp_period": users_informations,
553 551 "SP_PeriodForm": SP_Period_form,
554 552 "sp_period": sp_period,
555   - "error_message":error_message
  553 + "error_message":error_message,
  554 + "CAN_VIEW_TAC_VOTES":CAN_VIEW_TAC_VOTES
556 555 })
557 556  
558 557  
... ... @@ -588,7 +587,7 @@ def detail_scientific_program(request, id):
588 587 })
589 588  
590 589  
591   -@level_required("Admin", "Unit-PI", "Observer", "TAC")
  590 +@level_required("Admin", "Unit-PI", "Observer")
592 591 @login_required
593 592 def detail_scientific_program_period(request, id_sp, id_period):
594 593 scientific_program = get_object_or_404(ScientificProgram, pk=id_sp)
... ... @@ -644,12 +643,11 @@ def delete_scientific_program(request, id_sp):
644 643 scientific_program = get_object_or_404(ScientificProgram, pk=id_sp)
645 644 sp_periods = SP_Period.objects.filter(scientific_program=scientific_program)
646 645 if sp_periods:
647   - for sp_period in sp_periods:
648   - SP_Period_User.objects.filter(SP_Period=sp_period).delete()
649   - SP_Period_Guest.objects.filter(SP_Period=sp_period).delete()
650   - sp_periods.delete()
651   - scientific_program.delete()
652   - return HttpResponseRedirect(reverse('index_scientific_program'))
  646 + # can't delete
  647 + return HttpResponseRedirect(reverse("detail_scientific_program", args=[scientific_program.id ]))
  648 + else:
  649 + scientific_program.delete()
  650 + return HttpResponseRedirect(reverse('index_scientific_program'))
653 651  
654 652 @level_required("Admin", "Unit-PI", "Observer")
655 653 @login_required()
... ... @@ -658,6 +656,7 @@ def delete_scientific_program_period(request, id_sp, id_period):
658 656 sp_period = SP_Period.objects.get(
659 657 scientific_program=scientific_program, period=Period.objects.get(id=id_period))
660 658 if sp_period.status == SP_Period.STATUSES_DRAFT:
  659 + # Delete only if in draft status
661 660 sp_period_users = SP_Period_User.objects.filter(SP_Period=sp_period)
662 661 sp_period_guests = SP_Period_Guest.objects.filter(SP_Period=sp_period)
663 662 sp_period_guests.delete()
... ... @@ -1153,3 +1152,25 @@ def delete_science_theme(request,id):
1153 1152 return HttpResponseRedirect(reverse('science_theme_list'))
1154 1153 return HttpResponseRedirect(reverse('detail_science_theme',args=[science_theme.id]))
1155 1154  
  1155 +def test_tac_auto(request):
  1156 + next_period = Period.objects.next_period()
  1157 + themes = ScienceTheme.objects.all()
  1158 + tac_users = PyrosUser.objects.filter(user_level__name="TAC")
  1159 + sp_id = SP_Period.objects.filter(period=next_period).filter(Q(referee1=None)|Q(referee2=None)).values("scientific_program")
  1160 + sp = ScientificProgram.objects.filter(id__in=sp_id).order_by("name")
  1161 +
  1162 + matrix = associate_tac_sp_auto(themes,tac_users,sp)
  1163 + fig,ax = plt.subplots(1,1)
  1164 + plt.xlabel("scientific programs")
  1165 + plt.ylabel("Number of available TAC users")
  1166 + plt.pcolor(matrix,edgecolors='k', linewidths=2)
  1167 + plt.colorbar(ticks=[0,1])
  1168 + ax.set_xticklabels(sp.values_list("name",flat=True))
  1169 + plt.yticks([i for i in range(len(tac_users))],tac_users.values_list("last_name",flat=True),rotation="45")
  1170 + #ax.set_yticklabels()
  1171 + buf = io.BytesIO()
  1172 + fig.savefig(buf,format="png")
  1173 + buf.seek(0)
  1174 + string = base64.b64encode(buf.read())
  1175 + uri = urllib.parse.quote(string)
  1176 + return render(request,"scientific_program/test_tac_auto.html",{"data":uri})
1156 1177 \ No newline at end of file
... ...
src/core/pyros_django/user_manager/tests.py
... ... @@ -292,7 +292,8 @@ class UserManagerTests(TestCase):
292 292  
293 293 u2 = PyrosUser.objects.get(username="u2@test.fr")
294 294  
295   - response = self.client.post(reverse("login_validation"),{"email":u2.email,"password":"password123"})
  295 + response = self.client.post(reverse("login_validation"),{"email":u2.email,"password":"password123"})
  296 +
296 297 self.assertEqual(response.status_code,302)
297 298 response = self.client.get(reverse("users"))
298 299 self.assertEqual(response.status_code,200)
... ...