Commit 3b81a22bb2e7f52c16b3b5f004378046963882db

Authored by Alexis Koralewski
1 parent 94e0e6e2
Exists in dev

Rework on Request/ Sequences, remove tests of routine manager

pyros.py
... ... @@ -494,7 +494,8 @@ def install_or_update(UPDATE:bool=False, packages_only:bool=False, database_only
494 494 if UPDATE:
495 495 num+=1
496 496 printFullTerm(Colors.BLUE, f"{num}) UPDATING SOURCE CODE: Running git pull")
497   - _gitpull() or die()
  497 + if not WITH_DOCKER:
  498 + _gitpull() or die()
498 499  
499 500 # 2) Update python packages (pip upgrade AND pip install requirements)
500 501 if packages_only:
... ... @@ -534,8 +535,8 @@ def test(app):
534 535 #start_dir = os.getcwd()
535 536 if app == None:
536 537 #apps = ['obsconfig','scientific_program','common', 'scheduler', 'routine_manager', 'user_manager', 'alert_manager.tests.TestStrategyChange']
537   - # Removing alert_manager from tests
538   - apps = ['obsconfig',"scientific_program",'common', 'scheduler', 'routine_manager', 'user_manager']
  538 + # Removing alert_manager and routine_manager from tests
  539 + apps = ['obsconfig',"scientific_program",'common', 'scheduler', 'user_manager']
539 540 else:
540 541 os.environ["PATH_TO_OBSCONF_FILE"] = os.path.join(os.path.abspath(PYROS_DJANGO_BASE_DIR),"obsconfig/fixtures/observatory_configuration_ok_simple.yml")
541 542 change_dir(PYROS_DJANGO_BASE_DIR)
... ...
src/core/pyros_django/common/admin.py
... ... @@ -101,8 +101,8 @@ class AlbumInline(admin.TabularInline):
101 101  
102 102 class PlanInline(admin.TabularInline):
103 103 model = Plan
104   - readonly_fields = ("name",)
105   - fields = ("name",)
  104 + #readonly_fields = ("name",)
  105 + #fields = ("name",)
106 106 show_change_link = True
107 107  
108 108  
... ... @@ -144,9 +144,10 @@ class AlertInline(admin.TabularInline):
144 144 # Admin model classes
145 145  
146 146 class RequestAdmin(PyrosModelAdmin):
147   - inlines = [
148   - SequenceInline,
149   - ]
  147 + pass
  148 + # inlines = [
  149 + # SequenceInline,
  150 + # ]
150 151  
151 152  
152 153 class SequenceAdmin(PyrosModelAdmin):
... ... @@ -199,9 +200,10 @@ class UserLevelAdmin(PyrosModelAdmin):
199 200  
200 201  
201 202 class FilterAdmin(PyrosModelAdmin):
202   - inlines = [
203   - PlanInline,
204   - ]
  203 + # inlines = [
  204 + # PlanInline,
  205 + # ]
  206 + pass
205 207  
206 208  
207 209 class FilterWheelAdmin(PyrosModelAdmin):
... ... @@ -217,9 +219,10 @@ class NrtAnalysisAdmin(PyrosModelAdmin):
217 219  
218 220  
219 221 class DetectorAdmin(PyrosModelAdmin):
220   - inlines = [
221   - AlbumInline,
222   - ]
  222 + pass
  223 + # inlines = [
  224 + # AlbumInline,
  225 + # ]
223 226  
224 227  
225 228 class TelescopeAdmin(PyrosModelAdmin):
... ...
src/core/pyros_django/common/models.py
... ... @@ -356,8 +356,10 @@ class AgentSurvey(models.Model):
356 356 class Album(models.Model):
357 357 sequence = models.ForeignKey(
358 358 'Sequence', on_delete=models.CASCADE, related_name="albums")
359   - detector = models.ForeignKey(
360   - 'Detector', models.DO_NOTHING, related_name="albums", blank=True, null=True)
  359 + # detector = models.ForeignKey(
  360 + # 'Detector', models.DO_NOTHING, related_name="albums", blank=True, null=True)
  361 + name_of_channel_group = models.CharField(blank=True,null=True,max_length=150)
  362 + name_of_channel = models.CharField(blank=True,null=True,max_length=150)
361 363 name = models.CharField(max_length=45, blank=True, null=True)
362 364 desc = models.TextField(blank=True, null=True)
363 365 created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
... ... @@ -1069,6 +1071,7 @@ class NrtAnalysis(models.Model):
1069 1071 return (str(self.name))
1070 1072  
1071 1073  
  1074 +"""
1072 1075 class Plan(models.Model):
1073 1076 album = models.ForeignKey(Album, on_delete=models.CASCADE, related_name="plans")
1074 1077 filter = models.ForeignKey(Filter, models.DO_NOTHING, related_name="plans", blank=True, null=True)
... ... @@ -1089,9 +1092,16 @@ class Plan(models.Model):
1089 1092  
1090 1093 def __str__(self):
1091 1094 return (str(self.name))
  1095 +"""
1092 1096  
1093   -
1094   -
  1097 +class Plan(models.Model):
  1098 + album = models.ForeignKey(Album, on_delete=models.CASCADE, related_name="plans")
  1099 + created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
  1100 + updated = models.DateTimeField(blank=True, null=True, auto_now=True)
  1101 + duration = models.FloatField(default=0, blank=True, null=True)
  1102 + nb_images = models.PositiveIntegerField(blank=True, null=True,validators=[MinValueValidator(1)])
  1103 + config_attributes = models.JSONField(blank=True,null = True)
  1104 +
1095 1105  
1096 1106 class PlcDeviceStatus(models.Model):
1097 1107 device = models.ForeignKey('PlcDevice', on_delete=models.CASCADE, related_name='current_status')
... ... @@ -1646,6 +1656,7 @@ class SP_PeriodWorkflow(models.Model):
1646 1656 created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
1647 1657  
1648 1658 class Sequence(models.Model):
  1659 + # TODO : mettre un status : draft ou submitted
1649 1660  
1650 1661 """ Definition of Status enum values """
1651 1662  
... ... @@ -1674,8 +1685,12 @@ class Sequence(models.Model):
1674 1685 (INVALID, "Invalid"),
1675 1686 )
1676 1687  
1677   - request = models.ForeignKey(
1678   - Request, on_delete=models.CASCADE, related_name="sequences")
  1688 + #request = models.ForeignKey(
  1689 + # Request, on_delete=models.CASCADE, related_name="sequences")
  1690 + pyros_user = models.ForeignKey(
  1691 + 'PyrosUser', on_delete=models.DO_NOTHING, related_name="sequences")
  1692 + scientific_program = models.ForeignKey(
  1693 + 'ScientificProgram', on_delete=models.DO_NOTHING, related_name="sequences", blank=True, null=True)
1679 1694 name = models.CharField(max_length=45, blank=True, null=True)
1680 1695 desc = models.TextField(blank=True, null=True)
1681 1696 created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
... ... @@ -1696,6 +1711,9 @@ class Sequence(models.Model):
1696 1711 obsolete = models.BooleanField(default=False)
1697 1712 processing = models.BooleanField(default=False)
1698 1713 flag = models.CharField(max_length=45, blank=True, null=True)
  1714 +
  1715 + start_date = models.DateTimeField(blank=True, null=True, default=timezone.now,editable=True)
  1716 + end_date = models.DateTimeField(blank=True, null=True, default=timezone.now,editable=True)
1699 1717 # jd1 et jd2 = julian day start / end
1700 1718 jd1 = models.DecimalField(default=0.0, max_digits=15, decimal_places=8)
1701 1719 jd2 = models.DecimalField(default=0.0, max_digits=15, decimal_places=8)
... ... @@ -1705,6 +1723,8 @@ class Sequence(models.Model):
1705 1723 default=-1.0, max_digits=15, decimal_places=8)
1706 1724 # décomposer duration en duration pointing + duration album
1707 1725 overhead = models.DecimalField(default=0, max_digits=15, decimal_places=8)
  1726 + submitted = models.BooleanField(default=False)
  1727 + config_attributes = models.JSONField(blank=True,null = True)
1708 1728  
1709 1729 ra = models.FloatField(blank=True, null=True)
1710 1730 dec = models.FloatField(blank=True, null=True)
... ...
src/core/pyros_django/common/tests.py
... ... @@ -7,6 +7,7 @@ from django.utils import timezone
7 7 from utils.Logger import *
8 8 log = setupLogger("common", "common")
9 9  
  10 +"""
10 11 class RequestBuilderTests(TestCase):
11 12  
12 13 fixtures = ['tests/common_test_TZ.json']
... ... @@ -62,7 +63,7 @@ class RequestBuilderTests(TestCase):
62 63 self.assertEqual(len(albums), 3, "There should be 3 albums")
63 64 for album in albums:
64 65 self.assertEqual(album.plans.count(), 1, "There should be 1 plan in each album")
65   -
  66 +"""
66 67  
67 68 class CountryTests(TestCase):
68 69  
... ... @@ -88,7 +89,7 @@ class DeviceTests(TestCase):
88 89 countries = Country.objects.all()
89 90 self.assertEqual(countries.count(), 0, "perdu !")
90 91  
91   -
  92 +"""
92 93 class RequestTests(TestCase):
93 94  
94 95 def setUp(self):
... ... @@ -181,3 +182,4 @@ class RequestTests(TestCase):
181 182 self.assertEqual(img1.name, "img1.1") # img1 et img11 sont différents
182 183 img1 = req1.sequences.get().albums.get().plans.get().images.get(
183 184 name="img1.2")
  185 +"""
184 186 \ No newline at end of file
... ...
src/core/pyros_django/dashboard/templatetags/tags.py
... ... @@ -125,4 +125,12 @@ def get_item(dictionnary,key):
125 125  
126 126 @register.filter
127 127 def get_type(value):
128   - return type(value).__name__
129 128 \ No newline at end of file
  129 + return type(value).__name__
  130 +
  131 +@register.filter
  132 +def to_int(value):
  133 + return int(value)
  134 +
  135 +@register.filter
  136 +def index(indexable, i):
  137 + return indexable[i]
130 138 \ No newline at end of file
... ...
src/core/pyros_django/routine_manager/fields.py 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +from django import forms
  2 +
  3 +class LinkedSelectField(forms.RadioSelect):
  4 + def clean(self,value):
  5 + super.clean(value)
  6 +
  7 + def __init__(self, *args, **kwargs):
  8 + super(LinkedSelectField, self).__init__(*args, **kwargs)
  9 +
  10 +
  11 +
  12 +
  13 +
... ...
src/core/pyros_django/routine_manager/forms.py
1 1 from django.conf import settings
2 2 from django import forms
  3 +from django.db.models.fields import PositiveIntegerField
3 4 from common.models import *
4 5 from routine_manager.validators import check_album_validity
5 6  
6   -import logging
  7 +import logging,ast
  8 +
  9 +from src.core.pyros_django.routine_manager.widgets import LinkedSelectWidget
7 10 log = logging.getLogger("routine_manager-views")
8 11  
9 12 class RequestForm(forms.ModelForm):
... ... @@ -37,14 +40,17 @@ class SequenceForm(forms.ModelForm):
37 40 ('MD', 'BETWEEN_JD1_JD2'),
38 41 )
39 42  
40   - start_expo_pref = forms.ChoiceField(label="Start exposure prefered time", choices=START_EXPO_PREF)
  43 + start_expo_pref = forms.ChoiceField(label="Start sequence prefered time", choices=START_EXPO_PREF)
41 44 duration = forms.IntegerField(label="duration")
42 45  
43 46 class Meta:
44 47 model = Sequence
45   - fields = ("name", "target_coords", "jd1", "jd2")
  48 + fields = ("scientific_program", "name", "start_date", "end_date")
  49 + #target_coords = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'RADEC 0H10M -15D'}))
  50 + start_date = forms.DateTimeField(input_formats=['%d/%m/%Y %H:%M'],label="Start date and time")
  51 + end_date = forms.DateTimeField(input_formats=['%d/%m/%Y %H:%M'],label="End date and time")
46 52  
47   - def __init__(self, *args, readonly=False, **kwargs):
  53 + def __init__(self, *args, readonly=False, data_from_config, sp_list=None, **kwargs):
48 54 super(SequenceForm, self).__init__(*args, **kwargs)
49 55 if (settings.DEBUG):
50 56 log.info("From __init__ of SequenceForm")
... ... @@ -53,26 +59,98 @@ class SequenceForm(forms.ModelForm):
53 59 field.widget.attrs['readonly'] = True
54 60 field.widget.attrs['class'] = 'form-control'
55 61 field.required = True
56   -
  62 +
57 63 self.fields['duration'].widget.attrs['readonly'] = True
58 64 self.fields['duration'].required = False
59   -
  65 + if sp_list == None:
  66 + sp_list = ScientificProgram.objects.all()
  67 + self.fields["scientific_program"].queryset = sp_list
60 68 seq = kwargs["instance"]
61 69 if seq.duration:
62 70 self.fields["duration"].initial = seq.duration * 86400
  71 + # fields from config
  72 + for component in data_from_config:
  73 + for main_key in component.keys():
  74 + edited_seq_value = None
  75 + if seq.config_attributes != None and seq.config_attributes.get(main_key,None) != None :
  76 + edited_seq_value = seq.config_attributes[main_key]
  77 + capability = component[main_key]
  78 + unit_of_field = capability.get('unit','None')
  79 + label_str = f"{capability['label']}"
  80 + if unit_of_field != "" and unit_of_field != "None":
  81 + label_str += f" ({unit_of_field})"
  82 + if capability.get("is_container",None) == True:
  83 + if type(capability["value"] == dict):
  84 + initial_index = 0
  85 + first_key = list(capability["value"].keys())[0]
  86 + number_of_element = len(capability["value"][first_key])
  87 + container_choices = []
  88 + for i in range(number_of_element):
  89 + choice_value = ""
  90 + for sub_key in capability["value"].keys():
  91 + if edited_seq_value:
  92 + edited_value = edited_seq_value[sub_key]
  93 + initial_index = capability["value"][sub_key].index(edited_value)
  94 + list_value_of_current_index = capability["value"][sub_key][i]
  95 + choice_value+=f"{sub_key}:{list_value_of_current_index};"
  96 + choice_value = choice_value[:-1]
  97 + container_choices.append((choice_value,""))
  98 +
  99 + self.fields[main_key] = forms.ChoiceField(choices=container_choices,label=capability["label"],widget=LinkedSelectWidget(attrs={"label":capability["label"],"sublists":capability["value"],"initial_index":initial_index,"name":main_key}))
  100 + else:
  101 + # Never happen ? container that isn't a dict
  102 + container_choices = []
  103 + for value in capability["value"]:
  104 + container_choices.append((value,value))
  105 + self.fields[main_key] = forms.ChoiceField(choices=container_choices,label=label_str,widget=forms.CheckboxSelectMultiple,initial=container_choices[0])
  106 + elif capability.get("is_enum",None) == True:
  107 + initial_index = 0
  108 + if edited_seq_value:
  109 + initial_index = capability["value"].index(edited_seq_value)
  110 + enum_choices = []
  111 + for value in capability["value"]:
  112 + enum_choices.append((value,value))
  113 + self.fields[main_key] = forms.ChoiceField(choices=enum_choices,label=label_str,widget=forms.RadioSelect,initial=enum_choices[initial_index])
  114 + else:
  115 + initial_value = capability["value"]
  116 + if edited_seq_value:
  117 + initial_value = edited_seq_value
  118 + if type(capability["value"]) == int:
  119 + max_value = capability.get("limsup",None)
  120 + min_value = capability.get("liminf",None)
  121 + self.fields[main_key] = forms.IntegerField(initial=initial_value,max_value=max_value,min_value=min_value,label=label_str)
  122 + elif type(capability["value"]) == float:
  123 + max_value = capability.get("limsup",None)
  124 + min_value = capability.get("liminf",None)
  125 + self.fields[main_key] = forms.FloatField(initial=initial_value,max_value=max_value,min_value=min_value,label=label_str)
  126 + else:
  127 + # type is str or anything else
  128 + self.fields[main_key] = forms.CharField(initial=initial_value,label=label_str)
  129 + for field in self.fields.values():
  130 + if readonly == True:
  131 + field.widget.attrs['readonly'] = True
63 132  
64 133 def save(self):
65 134 seq = super(SequenceForm, self).save()
66 135 if (settings.DEBUG):
67 136 log.info("From save function of SequenceForm")
68   - if self.cleaned_data["start_expo_pref"] == "IM":
  137 + start_expo_pref = self.cleaned_data.pop("start_expo_pref")
  138 + if start_expo_pref == "IM":
69 139 seq.t_prefered = -1;
70   - elif self.cleaned_data["start_expo_pref"] == "BE":
  140 + elif start_expo_pref == "BE":
71 141 seq.t_prefered = -1; #  TODO : calculer avec le programme de simulation ?
72   - elif self.cleaned_data["start_expo_pref"] == "MD":
  142 + elif start_expo_pref == "MD":
73 143 seq.t_prefered = (seq.jd1 + seq .jd2) / 2;
74 144 if self.cleaned_data["duration"]:
75   - seq.duration = self.cleaned_data["duration"] / 86400
  145 + seq.duration = self.cleaned_data.pop("duration") / 86400
  146 + if self.cleaned_data["target"]:
  147 + seq.target_coords = self.cleaned_data["target"]
  148 + # remove all key linked to form in order to get config_attributes
  149 + self.cleaned_data.pop("name")
  150 + self.cleaned_data.pop("start_date")
  151 + self.cleaned_data.pop("end_date")
  152 + self.cleaned_data.pop("scientific_program")
  153 + seq.config_attributes = self.cleaned_data
76 154 seq.save()
77 155 return seq
78 156  
... ... @@ -84,57 +162,175 @@ class AlbumForm(forms.ModelForm):
84 162  
85 163 class Meta:
86 164 model = Album
87   - fields = ("name", "detector")
  165 + fields = ("name", "name_of_channel_group", "name_of_channel")
88 166  
89   - def __init__(self, *args, readonly=False, **kwargs):
  167 + def __init__(self, *args, readonly=False, channel_groups_config, **kwargs):
90 168 super(AlbumForm, self).__init__(*args, **kwargs)
91 169 if (settings.DEBUG):
92 170 log.info("From __init__ of AlbumForm")
  171 +
93 172 for field in self.fields.values():
94 173 if readonly == True:
95 174 field.widget.attrs['readonly'] = True
96 175 field.widget.attrs['class'] = 'form-control'
97 176 field.required = True
  177 + channel_groups_choices = []
  178 + channel_choices = []
  179 + if channel_groups_config["global_groups_logic"] == "and":
  180 + channel_groups_choices.append(("All","All"))
  181 + else:
  182 + for group in channel_groups_config["groups"].values():
  183 + channel_groups_choices.append((group["name"],group["name"]))
  184 + self.fields["name_of_channel_group"] = forms.ChoiceField(choices=channel_groups_choices,label="Channel group ",widget=forms.RadioSelect,initial=channel_groups_choices[0])
  185 + if len(channel_groups_config["groups"]) == 1:
  186 + group_name = list(channel_groups_config["groups"].keys())[0]
  187 + if channel_groups_config["groups"][group_name]["logic"] == "and":
  188 + channel_choices.append(("All","All"))
  189 + else:
  190 + for channel in channel_groups_config["groups"][group_name]["channels"]:
  191 + channel_choices.append((channel,channel))
  192 + self.fields["name_of_channel"] = forms.ChoiceField(choices=channel_choices,label="Channel ",widget=forms.RadioSelect,initial=channel_choices[0])
98 193  
99 194  
100   -class PlanForm(forms.ModelForm):
101   - """
102   - Form for Plan edition
103   - """
  195 + for field in self.fields.values():
  196 + if readonly == True:
  197 + field.widget.attrs['readonly'] = True
104 198  
105   - duration = forms.IntegerField(label="Duration")
106 199  
107   - class Meta:
108   - model = Plan
109   - fields = ("name", "filter", "nb_images")
  200 +# class PlanForm(forms.ModelForm):
  201 +# """
  202 +# Form for Plan edition
  203 +# """
110 204  
111   - def __init__(self, *args, readonly=False, **kwargs):
112   - super(PlanForm, self).__init__(*args, **kwargs)
113   - if (settings.DEBUG):
114   - log.info("From __init__ of PlanForm")
115   - plan = kwargs["instance"]
  205 +# duration = forms.IntegerField(label="Duration")
116 206  
117   - self.fields["filter"].queryset = plan.album.detector.filter_wheel.filters
  207 +# class Meta:
  208 +# model = Plan
  209 +# fields = ("name", "filter", "nb_images")
118 210  
119   - for field in self.fields.values():
120   - if readonly == True:
121   - field.widget.attrs['readonly'] = True
122   - field.widget.attrs['class'] = 'form-control'
123   - field.required = True
  211 +# def __init__(self, *args, readonly=False, **kwargs):
  212 +# super(PlanForm, self).__init__(*args, **kwargs)
  213 +# if (settings.DEBUG):
  214 +# log.info("From __init__ of PlanForm")
  215 +# plan = kwargs["instance"]
124 216  
125   - if plan.duration:
126   - self.fields["duration"].initial = plan.duration * 86400
  217 +# self.fields["filter"].queryset = plan.album.detector.filter_wheel.filters
127 218  
128   - def clean_duration(self):
129   - '''
130   - Checks if duration is at least of 1s
131   - '''
  219 +# for field in self.fields.values():
  220 +# if readonly == True:
  221 +# field.widget.attrs['readonly'] = True
  222 +# field.widget.attrs['class'] = 'form-control'
  223 +# field.required = True
  224 +
  225 +# if plan.duration:
  226 +# self.fields["duration"].initial = plan.duration * 86400
  227 +
  228 +# def clean_duration(self):
  229 +# '''
  230 +# Checks if duration is at least of 1s
  231 +# '''
  232 +
  233 +# duration = int(self.cleaned_data.get("duration"))
  234 +# if duration < 1:
  235 +# raise forms.ValidationError("Duration (in seconds) must be at least of 1")
  236 +# return duration
  237 +
  238 +# def clean_nb_images(self):
  239 +# '''
  240 +# Checks if the is at least 1 image
  241 +# '''
  242 +
  243 +# nb_images = self.cleaned_data.get("nb_images")
  244 +# if nb_images < 1:
  245 +# raise forms.ValidationError("There must be at least 1 image")
  246 +# return nb_images
  247 +
  248 +# def save(self):
  249 +# plan = super(PlanForm, self).save()
  250 +# if (settings.DEBUG):
  251 +# log.info("From save of PlanForm")
  252 +# plan.complete = True
  253 +# plan.duration = self.cleaned_data["duration"] / 86400
  254 +# plan.save()
  255 +# check_album_validity(plan.album)
  256 +# return plan
  257 +
  258 +
  259 +
  260 +
  261 +class PlanForm(forms.ModelForm):
  262 + #plan is linked to an album_id and a channel (name)
  263 + #nb_image = PositiveIntegerField()
  264 + duration = PositiveIntegerField(editable=False)
  265 + class Meta:
  266 + model = Plan
  267 + fields = ("nb_images",)
132 268  
133   - duration = int(self.cleaned_data.get("duration"))
134   - if duration < 1:
135   - raise forms.ValidationError("Duration (in seconds) must be at least of 1")
136   - return duration
137 269  
  270 + def __init__(self, *args, data_from_config, edited_plan=None, readonly=False, **kwargs):
  271 + super(PlanForm, self).__init__(*args, **kwargs)
  272 + self.fields["nb_images"].initial = edited_plan.nb_images
  273 + for component in data_from_config:
  274 + for main_key in component.keys():
  275 + edited_plan_value = None
  276 + if edited_plan.config_attributes != None and edited_plan.config_attributes.get(main_key,None) != None :
  277 + edited_plan_value = edited_plan.config_attributes[main_key]
  278 + capability = component[main_key]
  279 + unit_of_field = capability.get('unit','None')
  280 + label_str = f"{capability['label']}"
  281 + if unit_of_field != "" and unit_of_field != "None":
  282 + label_str += f" ({unit_of_field})"
  283 + if capability.get("is_container",None) == True:
  284 + if type(capability["value"] == dict):
  285 + initial_index = 0
  286 + first_key = list(capability["value"].keys())[0]
  287 + number_of_element = len(capability["value"][first_key])
  288 + container_choices = []
  289 + for i in range(number_of_element):
  290 + choice_value = ""
  291 + for sub_key in capability["value"].keys():
  292 + if edited_plan_value:
  293 + edited_value = edited_plan_value[sub_key]
  294 + initial_index = capability["value"][sub_key].index(edited_value)
  295 + list_value_of_current_index = capability["value"][sub_key][i]
  296 + choice_value+=f"{sub_key}:{list_value_of_current_index};"
  297 + choice_value = choice_value[:-1]
  298 + container_choices.append((choice_value,""))
  299 +
  300 + self.fields[main_key] = forms.ChoiceField(choices=container_choices,label=capability["label"],widget=LinkedSelectWidget(attrs={"label":capability["label"],"sublists":capability["value"],"initial_index":initial_index,"name":main_key}))
  301 + else:
  302 + # Never happen ? container that isn't a dict
  303 + container_choices = []
  304 + for value in capability["value"]:
  305 + container_choices.append((value,value))
  306 + self.fields[main_key] = forms.ChoiceField(choices=container_choices,label=label_str,widget=forms.CheckboxSelectMultiple,initial=container_choices[0])
  307 + elif capability.get("is_enum",None) == True:
  308 + initial_index = 0
  309 + if edited_plan_value:
  310 + initial_index = capability["value"].index(edited_plan_value)
  311 + enum_choices = []
  312 + for value in capability["value"]:
  313 + enum_choices.append((value,value))
  314 + self.fields[main_key] = forms.ChoiceField(choices=enum_choices,label=label_str,widget=forms.RadioSelect,initial=enum_choices[initial_index])
  315 + else:
  316 + initial_value = capability["value"]
  317 + if edited_plan_value:
  318 + initial_value = edited_plan_value
  319 + if type(capability["value"]) == int:
  320 + max_value = capability.get("limsup",None)
  321 + min_value = capability.get("liminf",None)
  322 + self.fields[main_key] = forms.IntegerField(initial=initial_value,max_value=max_value,min_value=min_value,label=label_str)
  323 + elif type(capability["value"]) == float:
  324 + max_value = capability.get("limsup",None)
  325 + min_value = capability.get("liminf",None)
  326 + self.fields[main_key] = forms.FloatField(initial=initial_value,max_value=max_value,min_value=min_value,label=label_str)
  327 + else:
  328 + # type is str or anything else
  329 + self.fields[main_key] = forms.CharField(initial=initial_value,label=label_str)
  330 + for field in self.fields.values():
  331 + if readonly == True:
  332 + field.widget.attrs['readonly'] = True
  333 +
138 334 def clean_nb_images(self):
139 335 '''
140 336 Checks if the is at least 1 image
... ... @@ -145,12 +341,84 @@ class PlanForm(forms.ModelForm):
145 341 raise forms.ValidationError("There must be at least 1 image")
146 342 return nb_images
147 343  
  344 + def clean(self):
  345 + nb_images = self.cleaned_data.get("nb_images")
  346 + if nb_images < 1:
  347 + raise forms.ValidationError("There must be at least 1 image")
  348 + return self.cleaned_data
  349 +
148 350 def save(self):
149 351 plan = super(PlanForm, self).save()
150 352 if (settings.DEBUG):
151 353 log.info("From save of PlanForm")
152   - plan.complete = True
153   - plan.duration = self.cleaned_data["duration"] / 86400
  354 + self.cleaned_data.pop("csrfmiddlewaretoken")
  355 + nb_images = self.cleaned_data.pop("nb_images")[0]
  356 + config_attributes = {}
  357 + log.info(self.cleaned_data)
  358 + for key,value in self.cleaned_data.items():
  359 + if type(value) == str:
  360 + try:
  361 + # linked values
  362 + new_dict = {key:{}}
  363 + splitted_values = value.split(";")
  364 + config_attributes[key] = {}
  365 + for splitted_value in splitted_values:
  366 + subkey,subvalue = splitted_value.split(":")
  367 + config_attributes[key][subkey] = ast.literal_eval(subvalue)
  368 + except:
  369 + # Do nothing, normal string
  370 + config_attributes[key] = ast.literal_eval(value)
  371 + plan.nb_images = int(nb_images)
  372 + plan.config_attributes = config_attributes
154 373 plan.save()
155   - check_album_validity(plan.album)
156 374 return plan
  375 +
  376 +
  377 +class uneditablePlanForm(forms.Form):
  378 + def __init__(self, data_from_config, *args, **kwargs):
  379 + super(uneditablePlanForm, self).__init__(*args, **kwargs)
  380 + # Kwargs take as value the dict returned by ConfigPyros method : getEditableAttributesOfChannel
  381 + for component in data_from_config:
  382 + for key in component.keys():
  383 + capability = component[key]
  384 + unit_of_field = capability.get('unit','None')
  385 + label_str = f"{capability['label']}"
  386 + if unit_of_field != "" and unit_of_field != "None":
  387 + label_str += f" ({unit_of_field})"
  388 + if capability.get("is_container",None) == True:
  389 + if type(capability["value"] == dict):
  390 + first_key = list(capability["value"].keys())[0]
  391 + number_of_element = len(capability["value"][first_key])
  392 + container_choices = []
  393 + for i in range(number_of_element):
  394 + choice_value = ""
  395 + for key in capability["value"].keys():
  396 + list_value_of_current_index = capability["value"][key][i]
  397 + choice_value+=f"{key}:{list_value_of_current_index};"
  398 + choice_value = choice_value[:-1]
  399 + container_choices.append((choice_value,""))
  400 + self.fields[key] = forms.ChoiceField(choices=container_choices,label=capability["label"],widget=LinkedSelectWidget(attrs={"label":capability["label"],"sublists":capability["value"]}))
  401 + else:
  402 + container_choices = []
  403 + for value in capability["value"]:
  404 + container_choices.append((value,value))
  405 + self.fields[key] = forms.ChoiceField(choices=container_choices,label=label_str ,widget=forms.CheckboxSelectMultiple,initial=container_choices[0])
  406 + elif capability.get("is_enum",None) == True:
  407 + enum_choices = []
  408 + for value in capability["value"]:
  409 + enum_choices.append((value,value))
  410 + self.fields[key] = forms.ChoiceField(choices=enum_choices,label=label_str,widget=forms.RadioSelect,initial=enum_choices[0])
  411 + else:
  412 + if type(capability["value"]) == int:
  413 + max_value = capability.get("limsup",None)
  414 + min_value = capability.get("liminf",None)
  415 + self.fields[key] = forms.IntegerField(initial=capability["value"],max_value=max_value,min_value=min_value,label=label_str)
  416 + elif type(capability["value"]) == float:
  417 + max_value = capability.get("limsup",None)
  418 + min_value = capability.get("liminf",None)
  419 + self.fields[key] = forms.FloatField(initial=capability["value"],max_value=max_value,min_value=min_value,label=label_str)
  420 + else:
  421 + # type is str or anything else
  422 + self.fields[key] = forms.CharField(initial=capability["value"],label=label_str)
  423 + for field in self.fields.values():
  424 + field.widget.attrs['disabled'] = True
157 425 \ No newline at end of file
... ...
src/core/pyros_django/routine_manager/templates/routine_manager/edit_base.html
... ... @@ -11,21 +11,23 @@
11 11 <div class="row text-center" style="margin-top: 20px;">
12 12  
13 13 <ul class="nav nav-tabs col-lg-4" style="margin-bottom: 15px;">
14   -
  14 + {% comment %}
  15 +
15 16 {% if depth_level > 0 %}
16   - <li {% if depth_level == 1 %}class="active"{% endif %}><a href="{% url "action_request" req_id action%}" >Request Info</a></li>
  17 + <li {% if depth_level == 1 %}class="active"{% endif %}><a href="{% url "action_request" req_id action%}" >Request Info</a></li>
17 18 {% endif %}
  19 + {% endcomment %}
18 20  
19   - {% if depth_level > 1 %}
20   - <li {% if depth_level == 2 %}class="active"{% endif %}><a href="{% url "action_sequence" seq_id action%}" >Sequence Info</a></li>
  21 + {% if depth_level > 0 %}
  22 + <li {% if depth_level == 1 %}class="active"{% endif %}><a href="{% url "action_sequence" seq_id action%}" >Sequence Info</a></li>
21 23 {% endif %}
22 24  
23   - {% if depth_level > 2 %}
24   - <li {% if depth_level == 3 %}class="active"{% endif %}><a href="{% url "action_album" alb_id action%}" >Album Info</a></li>
  25 + {% if depth_level > 1 %}
  26 + <li {% if depth_level == 2 %}class="active"{% endif %}><a href="{% url "action_album" alb_id action%}" >Album Info</a></li>
25 27 {% endif %}
26 28  
27   - {% if depth_level > 3 %}
28   - <li {% if depth_level == 4 %}class="active"{% endif %}><a href="{% url "action_plan" plan_id action%}" >Plan Info</a></li>
  29 + {% if depth_level > 2 %}
  30 + <li {% if depth_level == 3 %}class="active"{% endif %}><a href="{% url "action_plan" plan_id action%}" >Plan Info</a></li>
29 31 {% endif %}
30 32 </ul>
31 33  
... ... @@ -41,17 +43,24 @@
41 43 title="The request must be complete to be submitted">Submit request for scheduling</a>
42 44 {% endif %}
43 45 {% else %}
44   - <a href="{% url "unsubmit_request" req.id %}" class="btn btn-warning" onclick="return confirm('The request will be unvalidated, and the unexecuted sequences, unscheduled')"
45   - title="The request will be unvalidated, and the unexecuted sequences, unscheduled">Unsubmit request</a>
  46 + {% comment %}
  47 +
  48 + <a href="{% url "unsubmit_request" req.id %}" class="btn btn-warning" onclick="return confirm('The request will be unvalidated, and the unexecuted sequences, unscheduled')"
  49 + title="The request will be unvalidated, and the unexecuted sequences, unscheduled">Unsubmit request</a>
  50 + {% endcomment %}
46 51 {% endif %}
47 52 </div>
48 53  
49 54 <div class="row col-lg-4">
50   - <a href="{% url "export_request" req.id %}" class="btn btn-info">Export request as XML file</a>
  55 + {# TODO : ajouter url export sequence as yaml file #}
  56 + <a href="#" class="btn btn-info">Export sequence as YAML file</a>
51 57 </div>
52 58  
53 59 <div class="row col-lg-4">
  60 + {% comment %}
  61 +
54 62 TODO : ajouter checkbox pour choisir en dynamique JD / GD
  63 + {% endcomment %}
55 64 </div>
56 65 </div>
57 66  
... ... @@ -70,7 +79,7 @@
70 79 {% endblock %}
71 80  
72 81 {% if action == "view" %}
73   - <a href="{{request.path|url_view_to_edit }}" class="btn btn-primary">Move to edition mode</a>
  82 + <a href="{{request.path|url_view_to_edit }}" class="btn btn-primary">Edit</a>
74 83  
75 84 {% endif %}
76 85 </div>
... ... @@ -79,34 +88,35 @@
79 88 <div class="row col-lg-12">
80 89 <div class="panel-heading"><h3>Summary</h3></div>
81 90 <div class="well" style="overflow:scroll; height:350px;">
  91 + {% comment %}
82 92 <div>
  93 +
83 94 <a class="btn btn-sm {% if req.complete %}btn-success{% else %}btn-danger{% endif %}" href="{% url "action_request" req_id action%}" >Request: {{ req.name }}</a>
84 95 {% if depth_level == 1 %}<span class="label label-info">{{action}}ing ...</span>{% endif %}
85 96 </div>
86   - {% for seq in req.sequences.all %}
  97 + {% endcomment %}
87 98  
88 99 <div>
89   - <a class="btn btn-sm {% if seq.status != "INCPL" %}btn-success{% else %}btn-danger{% endif %}" href="{% url "action_sequence" seq.id action%}" style="margin-left: 60px" >Sequence: {{ seq.name }}</a>
90   - {% if depth_level == 2 and seq_id == seq.id %}<span class="label label-info">{{action}}ing ...</span>{% endif %}
  100 + <a class="btn btn-sm {% if seq.status != "INCPL" %}btn-success{% else %}btn-danger{% endif %}" href="{% url "action_sequence" seq_id action%}" style="margin-left: 60px" >Sequence: {{ seq.name }}</a>
  101 + {% if depth_level == 1 %}<span class="label label-info">{{action}}ing ...</span>{% endif %}
91 102 </div>
92 103 {% for album in seq.albums.all %}
93   -
  104 +
94 105 <div>
95 106 <a class="btn btn-sm {% if album.complete %}btn-success{% else %}btn-danger{% endif %}" href="{% url "action_album" album.id action%}" style="margin-left: 120px" >Album: {{ album.name }}</a>
96   - {% if depth_level == 3 and alb_id == album.id %}<span class="label label-info">{{action}}ing ...</span>{% endif %}
  107 + {% if depth_level == 2 and alb_id == album.id %}<span class="label label-info">{{action}}ing ...</span>{% endif %}
97 108 </div>
98 109 {% for plan in album.plans.all %}
99   -
  110 +
100 111 <div>
101   - <a class="btn btn-sm {% if plan.complete %}btn-success{% else %}btn-danger{% endif %}" href="{% url "action_plan" plan.id action%}" style="margin-left: 180px" >Plan: {{ plan.name }}</a>
102   - {% if depth_level == 4 and plan_id == plan.id %}<span class="label label-info">{{action}}ing ...</span>{% endif %}
  112 + <a class="btn btn-sm {% if plan.complete %}btn-success{% else %}btn-danger{% endif %}" href="{% url "action_plan" plan.id action%}" style="margin-left: 180px" >Plan {{ forloop.counter }} ({{ plan.nb_images }} image(s)) : {{ plan.name }}</a>
  113 + {% if depth_level == 3 and plan_id == plan.id %}<span class="label label-info">{{action}}ing ...</span>{% endif %}
103 114 </div>
104 115  
105 116 {% endfor %}
106 117  
107 118 {% endfor %}
108 119  
109   - {% endfor %}
110 120 </div>
111 121 </div>
112 122  
... ...
src/core/pyros_django/routine_manager/templates/routine_manager/linkedSelectField_widget.html 0 → 100644
... ... @@ -0,0 +1,124 @@
  1 +{% comment %}
  2 +{% load tags %}
  3 +
  4 +{% with id=widget.attrs.id %}
  5 +
  6 + <table id="{{ group }}">
  7 + <thead>
  8 + <tr>
  9 + <th> {{ widget.label }} </th>
  10 + {% for key in widget.attrs.sublists.keys %}
  11 + <th> {{ key }} </th>
  12 +
  13 + {% endfor %}
  14 + </tr>
  15 + </thead>
  16 + <tbody>
  17 + {% for key in widget.attrs.sublists.keys %}
  18 + {% if forloop.counter0 == 0 %}
  19 + {% for element in widget.attrs.sublists|get_item:key %}
  20 + {% with forloop.counter0 as line %}
  21 + <tr>
  22 + <td>
  23 + {% if widget.attrs.disabled %}
  24 + {% if line == 0 %}
  25 + <input name="{{ widget.attrs.label }}" type="radio" id="{{ id }}" value="{{ line }}" disabled checked>
  26 + {% else %}
  27 + <input name="{{ widget.attrs.label }}" type="radio" id="{{ id }}" value="{{ line }}" disabled>
  28 + {% endif %}
  29 +
  30 + {% else %}
  31 + {% if line == 0 %}
  32 + <input name="{{ widget.attrs.label }}" type="radio" id="{{ id }}" value="{{ line }}" checked>
  33 + {% else %}
  34 + <input name="{{ widget.attrs.label }}" type="radio" id="{{ id }}" value="{{ line }}">
  35 + {% endif %}
  36 + {% endif %}
  37 + </td>
  38 + {% for key in widget.attrs.sublists.keys %}
  39 + {% with widget.attrs.sublists|get_item:key as list %}
  40 +
  41 + <td>{{ list | index:line }}</td>
  42 + {% endwith %}
  43 + {% endfor %}
  44 + </tr>
  45 +
  46 + {% endwith %}
  47 +
  48 + {% endfor %}
  49 + {% endif %}
  50 + {% endfor %}
  51 + </tbody>
  52 + </table>
  53 + {% endwith %}
  54 +
  55 +
  56 +
  57 +{% endcomment %}
  58 +{% load tags %}
  59 +
  60 +
  61 +<table id="{{ group }}">
  62 + <thead>
  63 + <tr>
  64 + <th> {{ widget.label }} </th>
  65 + {% for key in widget.attrs.sublists.keys %}
  66 + <th> {{ key }} </th>
  67 +
  68 + {% endfor %}
  69 + </tr>
  70 + </thead>
  71 + <tbody>
  72 + {% with id=widget.attrs.id %}
  73 + <div
  74 + {% if id %}
  75 + id="{{ id }}"
  76 + {% endif %}
  77 + {% if widget.attrs.class %}
  78 + class="{{ widget.attrs.class }}"
  79 + {% endif %}
  80 + >
  81 + {% for group, options, index in widget.optgroups %}
  82 + {% if group %}
  83 + <div><label>{{ group }}</label>
  84 + {% endif %}
  85 + {% for option in options %}
  86 +
  87 + <tr>
  88 +
  89 + <td>
  90 + {% if widget.attrs.readonly %}
  91 + {% if option.index|to_int == 0 or option.index|to_int == widget.attrs.initial_index|to_int %}
  92 + <input name="{{ widget.attrs.name }}" type="radio" id="{{ id }}" value="{{ option.value }}" disabled checked>
  93 + {% else %}
  94 + <input name="{{ widget.attrs.name }}" type="radio" id="{{ id }}" value="{{ option.value }}" disabled>
  95 + {% endif %}
  96 +
  97 + {% else %}
  98 + {% if option.index|to_int == 0 or option.index|to_int == widget.attrs.initial_index|to_int %}
  99 + <input name="{{ widget.attrs.name }}" type="radio" id="{{ id }}" value="{{ option.value }}" checked>
  100 + {% else %}
  101 + <input name="{{ widget.attrs.name }}" type="radio" id="{{ id }}" value="{{ option.value }}">
  102 + {% endif %}
  103 + {% endif %}
  104 + </td>
  105 + {% for key in widget.attrs.sublists.keys %}
  106 + {% with widget.attrs.sublists|get_item:key as list %}
  107 + {% with option.index|to_int as int_index %}
  108 + <td>{{ list | index:int_index }}</td>
  109 + {% endwith %}
  110 + {% endwith %}
  111 + {% endfor %}
  112 +
  113 + </tr>
  114 +
  115 + {% endfor %}
  116 + {% if group %}
  117 + </div>
  118 + {% endif %}
  119 + {% endfor %}
  120 + </div>
  121 + {% endwith %}
  122 +
  123 + </tbody>
  124 +</table>
0 125 \ No newline at end of file
... ...
src/core/pyros_django/routine_manager/templates/routine_manager/requests_list.html
... ... @@ -18,7 +18,7 @@
18 18 <div class="col-lg-5">
19 19 <form action="{% url "import_request" %}" method="post" enctype="multipart/form-data">
20 20 <label class="btn btn-primary btn-file">
21   - Import an YAML file as a request <input type="file" onchange="this.form.submit()" name="request_file" style="display:none;"/>
  21 + Import a YAML file as a request <input type="file" onchange="this.form.submit()" name="request_file" style="display:none;"/>
22 22 </label>
23 23 </form>
24 24 </div>
... ... @@ -26,8 +26,13 @@
26 26  
27 27 <div class="row" style="margin-top:10px;">
28 28 <div class="col-lg-12 text-left">
  29 + {% comment %}
  30 +
29 31 <a href="{% url "create_request" %}"><button class="btn btn-primary">Create manually a new
30   - observation request</button></a>
  32 + observation request</button></a>
  33 + {% endcomment %}
  34 + <a href="{% url "create_sequence" %}"><button class="btn btn-primary">Create manually a new
  35 + observation request</button></a>
31 36 </div>
32 37 </div>
33 38  
... ... @@ -61,7 +66,7 @@
61 66 <tbody>
62 67 {% for row in requests %}
63 68 <tr class={% if row.req.submitted %}"success"{% else %} {% if row.req.complete %}"info"{% else %}"warning"{% endif %} {% endif %}>
64   - <td><a href="{% url "action_request" row.req.id 'view' %}">{{ row.req.name }}</a></td>
  69 + <td><a href="{% url "action_sequence" row.id 'view' %}">{{ row.name }}</a></td>
65 70 <td>{{ row.req.created }}</td>
66 71 <td>{{ row.req.updated }}</td>
67 72 <td>{{ row.req.type }}</td>
... ... @@ -71,14 +76,17 @@
71 76 <td>
72 77 <center>
73 78 {% if row.req.submitted == False %}
74   - <a href="{% url "action_request" row.req.id 'edit' %}" class="btn btn-primary">Edit</a>
  79 + <a href="{% url "action_sequence" row.id 'edit' %}" class="btn btn-primary">Edit</a>
75 80 {% endif %}
76   - <a href="{% url "action_request" row.req.id 'view' %}" class="btn btn-primary">View</a>
  81 + {% comment %}
  82 +
  83 + <a href="{% url "action_sequence" row.id 'view' %}" class="btn btn-primary">View</a>
  84 + {% endcomment %}
77 85 {% if row.req.submitted == False and row.req.complete == True %}
78   - <a href="{% url "submit_request" row.req.id "requests_list" %}" class="btn btn-success" onclick="return confirm('The request will be validated, and the sequences scheduled')"
  86 + <a href="{% url "submit_request" row.id "requests_list" %}" class="btn btn-success" onclick="return confirm('The request will be validated, and the sequences scheduled')"
79 87 title="The request will be validated, and the sequences scheduled">Submit</a>
80 88 {% endif %}
81   - <a href="{% url "action_request" row.req.id 'delete' %}" class="btn btn-danger"
  89 + <a href="{% url "action_sequence" row.id 'delete' %}" class="btn btn-danger"
82 90 onclick="return confirm('Are you sure you want to delete this request ?')">Delete</a>
83 91 </center>
84 92 </td>
... ...
src/core/pyros_django/routine_manager/templates/routine_manager/testcreateplan.html 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 +{% extends "routine_manager/edit_base.html" %}
  2 +{% load static %}
  3 +
  4 +{% block content %}
  5 +<form action="" method="post">
  6 + {% csrf_token %}
  7 + <table class="table table-hover table-striped tablesorter">
  8 +
  9 + {% for field in form %}
  10 + <tr class="fieldWrapper">
  11 + <td>{{ field.label_tag }} {% if field.required %}&nbsp*{% endif %} </td>
  12 + <td>{{ field }}
  13 + {{ field.errors }}
  14 + </td>
  15 + </tr>
  16 + {% endfor %}
  17 +
  18 + </table>
  19 + <input id="submit" class="btn btn-info" type="submit" value="Submit" />
  20 +</form>
  21 +<button id="showUneditableForm" class="btn btn-info"> Show more informations </button>
  22 +<div id="uneditableForm">
  23 + <table class="table table-hover table-striped tablesorter">
  24 + {% for field in uneditableForm %}
  25 + <div class="form-group row">
  26 + <tr>
  27 + <td>{{ field.label_tag }} {% if field.required %}&nbsp*{% endif %} </td>
  28 + <td>{{ field }}
  29 + {{ field.errors }}
  30 + </td>
  31 + </tr>
  32 + </div>
  33 + {% endfor %}
  34 +
  35 + </table>
  36 +</div>
  37 +<script>
  38 + $("#uneditableForm").hide();
  39 + $('#showUneditableForm').click( function(){
  40 + $("#uneditableForm").toggle();
  41 + });
  42 +</script>
  43 +{% endblock %}
0 44 \ No newline at end of file
... ...
src/core/pyros_django/routine_manager/templates/routine_manager/view_album.html
... ... @@ -30,7 +30,7 @@
30 30 <td>&nbsp</td>
31 31 <td><button type="submit" class="btn btn-success" name="action" value="save">Save</button></td>
32 32 <td>&nbsp</td>
33   - <td><button type="submit" class="btn btn-success" name="action" value="save_and_add">Save and add new plan</button></td>
  33 + <td><button type="submit" class="btn btn-success" name="action" value="save_and_add">Add new plan</button></td>
34 34 <td>&nbsp</td>
35 35 <td><button type="submit" class="btn btn-danger" name="action" value="delete">Delete</button></td>
36 36 </tr>
... ... @@ -42,45 +42,48 @@
42 42 {% endblock %}
43 43  
44 44 {% block children %}
45   - <h3>List of Plans - <span class="label label-primary">{{alb.plans.count}}</span></h3>
46   - <div class="table-responsive">
47   - <table
  45 +{% comment %}
  46 +
  47 +<h3>List of Plans - <span class="label label-primary">{{alb.plans.count}}</span></h3>
  48 +<div class="table-responsive">
  49 + <table
48 50 class="table table-bordered table-hover table-striped tablesorter">
49 51 <thead>
50   - <tr>
51   - <th>Name <i class="fa fa-sort"></i></th>
  52 + <tr>
  53 + <th>Name <i class="fa fa-sort"></i></th>
52 54 <th>Created<i class="fa fa-sort"></i></th>
53 55 <th>Updated<i class="fa fa-sort"></i></th>
54 56 <th>Filter<i class="fa fa-sort"></i></th>
55   -{# <th>Duration<i class="fa fa-sort"></i></th>#}
56   -{# <th>Nb images<i class="fa fa-sort"></i></th>#}
  57 + {# <th>Duration<i class="fa fa-sort"></i></th>#}
  58 + {# <th>Nb images<i class="fa fa-sort"></i></th>#}
57 59 <th>Actions</th>
58   - </tr>
59   - </thead>
60   - <tbody>
  60 + </tr>
  61 + </thead>
  62 + <tbody>
61 63 {% for plan in alb.plans.all %}
62   - <tr class={% if not plan.complete %}"danger"{% else %}"success"{% endif %}>
63   - <td>{{ plan.name }}</td>
  64 + <tr class={% if not plan.complete %}"danger"{% else %}"success"{% endif %}>
  65 + <td>{{ plan.name }}</td>
64 66 <td>{{ plan.created }}</td>
65 67 <td>{{ plan.updated }}</td>
66 68 <td>{{ plan.filter }}</td>
67 69 {# <td>{{ plan.duration }}</td>#}
68 70 {# <td>{{ plan.nb_images }}</td>#}
69 71 <td>
70   - <center>
  72 + <center>
71 73 {% if action == 'edit' %}
72   - <a href="{% url "action_plan" plan.id 'edit' %}" class="btn btn-primary">Edit</a>
  74 + <a href="{% url "action_plan" plan.id 'edit' %}" class="btn btn-primary">Edit</a>
73 75 <a href="{% url "action_plan" plan.id 'delete' %}" class="btn btn-danger"
74 76 onclick="return confirm('Are you sure you want to delete this plan ?')">Delete</a>
75   - {% else %}
  77 + {% else %}
76 78 <a href="{% url "action_plan" plan.id 'view' %}" class="btn btn-primary"><span class="glyphicon glyphicon-e"></span></a>
77   - {% endif %}
  79 + {% endif %}
78 80 </center>
79 81 </td>
80 82  
81 83 </tr>
82   - {% endfor %}
83   - </tbody>
84   - </table>
85   - </div>
86   -{% endblock %}
87 84 \ No newline at end of file
  85 + {% endfor %}
  86 + </tbody>
  87 + </table>
  88 + </div>
  89 +{% endcomment %}
  90 + {% endblock %}
88 91 \ No newline at end of file
... ...
src/core/pyros_django/routine_manager/templates/routine_manager/view_plan.html
... ... @@ -5,20 +5,37 @@
5 5 {% endblock %}
6 6  
7 7 {% block fields %}
8   - <form action="{% url "plan_validate" plan.id %}" method="post" enctype="multipart/form-data">
  8 + <form action="{% url "plan_validate" plan.id %}" method="post" enctype="multipart/form-data">
9 9 {% csrf_token %}
10 10 <br>
11   -
  11 + {% comment %}
  12 +
12 13 <table class="table table-hover table-striped tablesorter">
13   - {% for field in form %}
  14 + {% for field in form %}
14 15 <tr>
15   - <td>{{ field.label_tag }} {% if field.field.required %}&nbsp*{% endif %} </td>
  16 + <td>{{ field.label_tag }} {% if field.field.required %}&nbsp*{% endif %} </td>
16 17 <td>{{ field }}
17 18 {{ field.errors }}
18 19 </td>
19 20 </tr>
20   - {% endfor %}
  21 + {% endfor %}
21 22 </table>
  23 + {% endcomment %}
  24 + <table class="table table-hover table-striped tablesorter">
  25 +
  26 + {% for field in form %}
  27 + <tr class="fieldWrapper">
  28 + <td>{{ field.label_tag }} {% if field.required %}&nbsp*{% endif %} </td>
  29 + <td>{{ field }}
  30 + {{ field.errors }}
  31 + </td>
  32 + </tr>
  33 + {% endfor %}
  34 +
  35 + </table>
  36 + <button id="showUneditableForm" class="btn btn-info"> Show more informations </button>
  37 +
  38 +
22 39  
23 40 {% if edit == True %}
24 41 <br>
... ... @@ -36,9 +53,30 @@
36 53 </div>
37 54 {% endif %}
38 55 </form>
39   -
  56 + <div id="uneditableForm">
  57 + <table class="table table-hover table-striped tablesorter">
  58 + {% for field in uneditableForm %}
  59 + <div class="form-group row">
  60 + <tr>
  61 + <td>{{ field.label_tag }} {% if field.required %}&nbsp*{% endif %} </td>
  62 + <td>{{ field }}
  63 + {{ field.errors }}
  64 + </td>
  65 + </tr>
  66 + </div>
  67 + {% endfor %}
  68 +
  69 + </table>
  70 + </div>
40 71 {% endblock %}
41 72  
42 73 {% block children %}
43 74  
  75 +<script>
  76 + $("#uneditableForm").hide();
  77 + $('#showUneditableForm').click( function(){
  78 + $("#uneditableForm").toggle();
  79 + });
  80 +</script>
  81 +
44 82 {% endblock %}
45 83 \ No newline at end of file
... ...
src/core/pyros_django/routine_manager/templates/routine_manager/view_sequence.html
... ... @@ -5,6 +5,7 @@
5 5 {% endblock %}
6 6  
7 7 {% block fields %}
  8 +<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
8 9 <form action="{% url "sequence_validate" seq.id %}" method="post" enctype="multipart/form-data">
9 10 {% csrf_token %}
10 11 <br>
... ... @@ -28,9 +29,11 @@
28 29 <tr>
29 30 <td><button type="submit" class="btn btn-warning" name="action" value="cancel">Cancel</button></td>
30 31 <td>&nbsp</td>
31   - <td><button type="submit" class="btn btn-success" name="action" value="save">Save</button></td>
  32 + <td><button type="submit" class="btn btn-success" name="action" value="save">Save and finish later</button></td>
32 33 <td>&nbsp</td>
33   - <td><button type="submit" class="btn btn-success" name="action" value="save_and_add">Save and add new album</button></td>
  34 + <td><button type="submit" class="btn btn-success" name="action" value="save_and_submit">Save and Submit</button></td>
  35 + <td>&nbsp</td>
  36 + <td><button type="submit" class="btn btn-success" name="action" value="save_and_add">Add new Album</button></td>
34 37 <td>&nbsp</td>
35 38 <td><button type="submit" class="btn btn-danger" name="action" value="delete">Delete</button></td>
36 39 </tr>
... ... @@ -39,44 +42,75 @@
39 42 {% endif %}
40 43 </form>
41 44  
  45 +
  46 +<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
  47 +<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
  48 +<script>
  49 +var $=jQuery.noConflict();
  50 +
  51 +
  52 +$(function(){
  53 +
  54 + var dateFormat = "yy-mm-dd";
  55 + start_date = $( "#id_start_date" ).datepicker({
  56 + defaultDate: Date.parse($("#id_start_date").val()),
  57 + changeMonth: true,
  58 + changeYear: true,
  59 + minDate: new Date(),
  60 + dateFormat: dateFormat,
  61 + constrainInput: false,
  62 + });
  63 + end_date = $( "#id_end_date" ).datepicker({
  64 + defaultDate: Date.parse($("#id_end_date").val()),
  65 + changeMonth: true,
  66 + changeYear: true,
  67 + minDate: new Date(),
  68 + dateFormat: dateFormat,
  69 + constrainInput: false,
  70 + });
  71 +})
  72 +</script>
42 73 {% endblock %}
43 74  
44 75 {% block children %}
45   - <h3>List of Albums - <span class="label label-primary">{{seq.albums.count}}</span></h3>
46   - <div class="table-responsive">
  76 +{% comment %}
  77 +
  78 +<h3>List of Albums - <span class="label label-primary">{{seq.albums.count}}</span></h3>
  79 +<div class="table-responsive">
47 80 <table
48   - class="table table-bordered table-hover table-striped tablesorter">
  81 + class="table table-bordered table-hover table-striped tablesorter">
49 82 <thead>
50   - <tr>
51   - <th>Name <i class="fa fa-sort"></i></th>
  83 + <tr>
  84 + <th>Name <i class="fa fa-sort"></i></th>
52 85 <th>Created<i class="fa fa-sort"></i></th>
53 86 <th>Updated<i class="fa fa-sort"></i></th>
54 87 <th>Detector<i class="fa fa-sort"></i></th>
55 88 <th>Actions</th>
56   - </tr>
57   - </thead>
58   - <tbody>
  89 + </tr>
  90 + </thead>
  91 + <tbody>
59 92 {% for alb in seq.albums.all %}
60   - <tr class={% if not alb.complete %}"danger"{% else %}"success"{% endif %}>
  93 + <tr class={% if not alb.complete %}"danger"{% else %}"success"{% endif %}>
61 94 <td>{{ alb.name }}</td>
62 95 <td>{{ alb.created }}</td>
63 96 <td>{{ alb.updated }}</td>
64 97 <td>{{ alb.detector }}</td>
65 98 <td>
66 99 <center>
67   - {% if action == 'edit' %}
68   - <a href="{% url "action_album" alb.id 'edit' %}" class="btn btn-primary">Edit</a>
  100 + {% if action == 'edit' %}
  101 + <a href="{% url "action_album" alb.id 'edit' %}" class="btn btn-primary">Edit</a>
69 102 <a href="{% url "action_album" alb.id 'delete' %}" class="btn btn-danger"
70 103 onclick="return confirm('Are you sure you want to delete this album ?')">Delete</a>
71   - {% else %}
  104 + {% else %}
72 105 <a href="{% url "action_album" alb.id 'view' %}" class="btn btn-primary">View</a>
73   - {% endif %}
  106 + {% endif %}
74 107 </center>
75 108 </td>
76 109  
77 110 </tr>
78   - {% endfor %}
  111 + {% endfor %}
79 112 </tbody>
80   - </table>
  113 + </table>
81 114 </div>
82   -{% endblock %}
83 115 \ No newline at end of file
  116 +{% endcomment %}
  117 + {% endblock %}
84 118 \ No newline at end of file
... ...
src/core/pyros_django/routine_manager/urls.py
1 1 from django.conf.urls import url
  2 +from django.urls import path
2 3 from . import views
3 4  
4 5 urlpatterns = [
... ... @@ -23,7 +24,8 @@ urlpatterns = [
23 24 url(r'^plan_validate/(?P<plan_id>\d+)$', views.plan_validate, name="plan_validate"),
24 25  
25 26 url(r'^create_request$', views.create_request, name="create_request"),
26   - url(r'^create_sequence/(?P<req_id>\d+)$', views.create_sequence, name="create_sequence"),
  27 + #url(r'^create_sequence/(?P<req_id>\d+)$', views.create_sequence, name="create_sequence"),
  28 + url(r'^create_sequence/', views.create_sequence, name="create_sequence"),
27 29 url(r'^create_album/(?P<seq_id>\d+)$', views.create_album, name="create_album"),
28 30 url(r'^create_plan/(?P<alb_id>\d+)$', views.create_plan, name="create_plan"),
29 31  
... ... @@ -32,6 +34,7 @@ urlpatterns = [
32 34  
33 35 url(r'^export_request/(?P<req_id>\d+)$', views.export_request, name="export_request"),
34 36 url(r'^import_request$', views.import_request, name="import_request"),
35   -
  37 + path("test_create_plan",views.test_create_plan,name="test_create_plan"),
  38 + path("edit_plan/<int:id>",views.edit_plan,name="edit_plan")
36 39 ]
37 40  
... ...
src/core/pyros_django/routine_manager/validators.py
... ... @@ -78,3 +78,20 @@ def check_request_validity(req):
78 78 req.complete = True
79 79 req.save()
80 80 return False
  81 +
  82 +def check_sequence_validity(seq):
  83 + """
  84 + Checks if a request is complete, and save its status in DB
  85 + A seq is complete if it has album and plan
  86 + :returns : a bool equal to True if the request was submitted but incomplete
  87 + """
  88 +
  89 + if seq.status ==Sequence.INCOMPLETE != 0 or seq.album.count() == 0:
  90 + seq.complete = False
  91 + seq.save()
  92 + if seq.submitted == True:
  93 + return True
  94 + return False
  95 + seq.complete = True
  96 + seq.save()
  97 + return False
... ...
src/core/pyros_django/routine_manager/views.py
1 1 from django.utils.decorators import method_decorator
2 2 from django.views.decorators.csrf import csrf_exempt
3   -from django.shortcuts import render, redirect
  3 +from django.shortcuts import get_object_or_404, render, redirect
4 4 from common.models import *
5 5 from django.db.models import Q
6 6 from django.contrib.auth.decorators import login_required
7   -from .forms import RequestForm, SequenceForm, AlbumForm, PlanForm
  7 +
  8 +from src.core.pyros_django.obsconfig.configpyros import ConfigPyros
  9 +from .forms import RequestForm, SequenceForm, AlbumForm, PlanForm, uneditablePlanForm
8 10 from .validators import check_plan_validity, check_album_validity, check_sequence_validity, check_request_validity
9 11 from .RequestSerializer import RequestSerializer
10 12 import scheduler
... ... @@ -19,7 +21,7 @@ from wsgiref.util import FileWrapper
19 21 from django.utils.encoding import smart_str
20 22 from django.http import HttpResponse
21 23 import mimetypes
22   -import os
  24 +import os, ast, datetime
23 25 from pprint import pprint
24 26  
25 27 SAVED_REQUESTS_FOLDER = "misc/saved_requests/"
... ... @@ -41,19 +43,23 @@ def requests_list(request, status=0, message=&quot;&quot;):
41 43  
42 44 # TODO: uncomment for alert filter
43 45 # requests_objs = Request.objects.filter(pyros_user=request.user).filter(is_alert=False).order_by("-updated")
44   -
45   - requests_objs = Request.objects.filter(pyros_user=request.user).order_by("-updated")
  46 + sequences_objs = Sequence.objects.filter(pyros_user=request.user).order_by("-updated")
46 47 print("REQ/views: Les requetes sont:")
47 48  
48   - requests = []
  49 + requests = sequences_objs
  50 +
  51 + # requests_objs = Request.objects.filter(pyros_user=request.user).order_by("-updated")
  52 + # print("REQ/views: Les requetes sont:")
49 53  
50   - for req in requests_objs:
51   - #print("- REQ/views: requete", req)
52   - sequences = req.sequences
53   - nb_executed = sequences.filter(status=Sequence.EXECUTED).count
54   - nb_cancelled = sequences.filter(Q(status=Sequence.INVALID) | Q(status=Sequence.CANCELLED)
55   - | Q(status=Sequence.UNPLANNABLE)).count()
56   - requests.append({'req': req, 'nb_seq': sequences.count(), 'nb_executed': nb_executed, 'nb_cancelled': nb_cancelled})
  54 + # requests = []
  55 +
  56 + # for req in requests_objs:
  57 + # #print("- REQ/views: requete", req)
  58 + # sequences = req.sequences
  59 + # nb_executed = sequences.filter(status=Sequence.EXECUTED).count
  60 + # nb_cancelled = sequences.filter(Q(status=Sequence.INVALID) | Q(status=Sequence.CANCELLED)
  61 + # | Q(status=Sequence.UNPLANNABLE)).count()
  62 + # requests.append({'req': req, 'nb_seq': sequences.count(), 'nb_executed': nb_executed, 'nb_cancelled': nb_cancelled})
57 63  
58 64 print("REQ/views: ICI:")
59 65 # opens template src/routine_manager/templates/routine_manager/requests_list.html with all local variables
... ... @@ -136,12 +142,11 @@ def action_sequence(request, seq_id, action, status=0, message=&quot;&quot;):
136 142 """
137 143 Apply actions (view, edit, delete) on a given sequence
138 144 """
139   -
  145 + unit_name = os.environ["unit_name"]
  146 + config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"], unit_name)
140 147 seq_id = int(seq_id)
141 148 seq = Sequence.objects.get(id=seq_id)
142   - req = seq.request
143   - req_id = req.id
144   - depth_level = 2
  149 + depth_level = 1
145 150  
146 151 if (settings.DEBUG):
147 152 log.info("From action_sequence")
... ... @@ -157,20 +162,22 @@ def action_sequence(request, seq_id, action, status=0, message=&quot;&quot;):
157 162 action = "view"
158 163  
159 164 if check_sequence_validity(seq) == True:
160   - return redirect(unsubmit_request, req_id)
  165 + #return redirect(unsubmit_request, req_id)
  166 + pass
161 167 if action == "edit":
162   - form = SequenceForm(instance=seq)
  168 + form = SequenceForm(instance=seq, data_from_config=config.getEditableAttributesOfMount(config.unit_name))
163 169 edit = True
164 170 return render(request, "routine_manager/view_sequence.html", locals())
165 171 elif action == "view":
166   - form = SequenceForm(instance=seq, readonly=True)
  172 + form = SequenceForm(instance=seq, readonly=True, data_from_config=config.getEditableAttributesOfMount(config.unit_name))
167 173 edit = False
168 174 return render(request, "routine_manager/view_sequence.html", locals())
169 175 elif action == "delete":
170 176 seq.delete()
171   - if check_request_validity(req) == True:
172   - return redirect(unsubmit_request, req_id)
173   - return redirect(action_request, req_id=seq.request.id, action="edit", status='1', message="Successfully deleted the sequence")
  177 + if check_sequence_validity(seq) == True:
  178 + #return redirect(unsubmit_request, req_id)
  179 + pass
  180 + return redirect(requests_list)
174 181  
175 182 return redirect(requests_list)
176 183  
... ... @@ -183,16 +190,17 @@ def sequence_validate(request, seq_id):
183 190  
184 191 if (settings.DEBUG):
185 192 log.info("From sequence_validate")
186   -
  193 + unit_name = os.environ["unit_name"]
  194 + config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"], unit_name)
187 195 seq_id = int(seq_id)
188 196 action = request.POST.get("action")
189 197 seq = Sequence.objects.get(id=seq_id)
190   - form = SequenceForm(instance=seq, data=request.POST)
  198 + form = SequenceForm(instance=seq, data=request.POST, data_from_config=config.getEditableAttributesOfMount(config.unit_name))
191 199  
192 200 if action == "cancel":
193 201 if seq.name == "New sequence":
194 202 seq.delete()
195   - return redirect(action_request, req_id=seq.request.id, action="edit", status='1', message="Cancelled sequence modification")
  203 + return redirect(requests_list)
196 204 elif action == "delete":
197 205 return redirect(action_sequence, seq_id, "delete")
198 206 elif form.is_valid():
... ... @@ -204,10 +212,8 @@ def sequence_validate(request, seq_id):
204 212 else:
205 213 error = True
206 214 message = "Please check your field's validity"
207   - depth_level = 2
  215 + depth_level = 1
208 216 edit = True
209   - req = seq.request
210   - req_id = req.id
211 217 return render(request, "routine_manager/view_sequence.html", locals())
212 218  
213 219 @login_required
... ... @@ -215,14 +221,14 @@ def action_album(request, alb_id, action, status=0, message=&quot;&quot;):
215 221 """
216 222 Apply actions (view, edit, delete) on a given album
217 223 """
  224 + unit_name = os.environ["unit_name"]
  225 + config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"], unit_name)
218 226  
219 227 alb_id = int(alb_id)
220 228 alb = Album.objects.get(id=alb_id)
221   - req = alb.sequence.request
222   - req_id = req.id
223 229 seq_id = alb.sequence.id
224   - depth_level = 3
225   -
  230 + depth_level = 2
  231 + seq = alb.sequence
226 232 if (settings.DEBUG):
227 233 log.info("From action_album")
228 234  
... ... @@ -236,14 +242,15 @@ def action_album(request, alb_id, action, status=0, message=&quot;&quot;):
236 242 message = "You can't edit a submitted request"
237 243 action = "view"
238 244  
239   - if check_album_validity(alb) == True:
240   - return redirect(unsubmit_request, req_id)
  245 + # if check_album_validity(alb) == True:
  246 + # #return redirect(unsubmit_request, req_id)
  247 + # pass
241 248 if action == "edit":
242   - form = AlbumForm(instance=alb)
  249 + form = AlbumForm(instance=alb, channel_groups_config=config.get_channel_groups(config.unit_name))
243 250 edit = True
244 251 return render(request, "routine_manager/view_album.html", locals())
245 252 elif action == "view":
246   - form = AlbumForm(instance=alb, readonly=True)
  253 + form = AlbumForm(instance=alb, readonly=True, channel_groups_config=config.get_channel_groups(config.unit_name))
247 254 edit = False
248 255 return render(request, "routine_manager/view_album.html", locals())
249 256 elif action == "delete":
... ... @@ -258,16 +265,16 @@ def album_validate(request, alb_id):
258 265 Called when the album form was validated.
259 266 Possible actions : Cancel, Save, Save and add plan, Delete
260 267 """
261   -
  268 + unit_name = os.environ["unit_name"]
  269 + config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"], unit_name)
262 270 alb_id = int(alb_id)
263 271 action = request.POST.get("action")
264 272 alb = Album.objects.get(id=alb_id)
265   - form = AlbumForm(instance=alb, data=request.POST)
266   -
  273 + form = AlbumForm(instance=alb, data=request.POST, channel_groups_config=config.get_channel_groups(config.unit_name))
  274 +
267 275 if (settings.DEBUG):
268 276 log.info("From album_validate")
269 277  
270   -
271 278 if action == "cancel":
272 279 if alb.name == "New album":
273 280 alb.delete()
... ... @@ -277,16 +284,15 @@ def album_validate(request, alb_id):
277 284 elif form.is_valid():
278 285 alb = form.save()
279 286 if action == "save":
280   - return redirect(action_album, alb_id=alb_id, action="edit", status=1, message="Album saved")
  287 + return redirect(action_sequence, seq_id=alb.sequence.id, action="edit")
  288 + #return redirect(action_album, alb_id=alb_id, action="edit", status=1, message="Album saved")
281 289 if action == "save_and_add":
282 290 return redirect(create_plan, alb_id=alb_id)
283 291 else:
284 292 error = True
285 293 message = "Please check your field's validity"
286   - depth_level = 3
  294 + depth_level = 2
287 295 edit = True
288   - req = alb.sequence.request
289   - req_id = req.id
290 296 seq_id = alb.sequence.id
291 297 action = "edit"
292 298 return render(request, "routine_manager/view_album.html", locals())
... ... @@ -296,14 +302,22 @@ def action_plan(request, plan_id, action, status=0, message=&quot;&quot;):
296 302 """
297 303 Apply actions (view, edit, delete) on a given plan
298 304 """
299   -
  305 + unit_name = os.environ["unit_name"]
  306 + config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"], unit_name)
300 307 plan_id = int(plan_id)
301 308 plan = Plan.objects.get(id=plan_id)
302   - req = plan.album.sequence.request
303   - req_id = req.id
  309 + name_of_channel_group = plan.album.name_of_channel_group
  310 + channel = None
  311 + if name_of_channel_group == "All":
  312 + channel = config.getGroupOfChannelByName(config.unit_name,config.get_channel_groups(config.unit_name)["groups"][0]["name"])
  313 + else:
  314 + # we retrieve config of the first channel of the group
  315 + channel = config.getGroupOfChannelByName(config.unit_name,name_of_channel_group)["channels"][0]
  316 + data_from_config = config.getEditableAttributesOfChannel(config.unit_name,channel)
304 317 seq_id = plan.album.sequence.id
  318 + seq = plan.album.sequence
305 319 alb_id = plan.album.id
306   - depth_level = 4
  320 + depth_level = 3
307 321  
308 322 if (settings.DEBUG):
309 323 log.info("From action_plan")
... ... @@ -318,14 +332,17 @@ def action_plan(request, plan_id, action, status=0, message=&quot;&quot;):
318 332 message = "You can't edit a submitted request"
319 333 action = "view"
320 334  
321   - if check_plan_validity(plan) == True:
322   - return redirect(unsubmit_request, req_id)
  335 + # if check_plan_validity(plan) == True:
  336 + # #return redirect(unsubmit_request, req_id)
  337 + # pass
323 338 if action == "edit":
324   - form = PlanForm(instance=plan)
  339 + form = PlanForm(edited_plan=plan, data_from_config=data_from_config)
  340 + uneditableForm = uneditablePlanForm(data_from_config=config.getUneditableAttributesOfChannel(config.unit_name,channel))
325 341 edit = True
326 342 return render(request, "routine_manager/view_plan.html", locals())
327 343 elif action == "view":
328   - form = PlanForm(instance=plan, readonly=True)
  344 + form = PlanForm(edited_plan=plan, data_from_config=data_from_config,readonly=True)
  345 + uneditableForm = uneditablePlanForm(data_from_config=config.getUneditableAttributesOfChannel(config.unit_name,channel))
329 346 edit = False
330 347 return render(request, "routine_manager/view_plan.html", locals())
331 348 elif action == "delete":
... ... @@ -340,35 +357,72 @@ def plan_validate(request, plan_id):
340 357 Called when the plan form was validated.
341 358 Possible actions : Cancel, Save, Delete
342 359 """
343   -
  360 + unit_name = os.environ["unit_name"]
  361 + config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"], unit_name)
344 362 plan_id = int(plan_id)
345 363 action = request.POST.get("action")
346 364 plan = Plan.objects.get(id=plan_id)
347   - form = PlanForm(instance=plan, data=request.POST)
  365 + seq = plan.album.sequence
  366 + name_of_channel_group = plan.album.name_of_channel_group
  367 + channel = None
  368 + if name_of_channel_group == "All":
  369 + channel = config.getGroupOfChannelByName(config.unit_name,config.get_channel_groups(config.unit_name)["groups"][0]["name"])
  370 + else:
  371 + # we retrieve config of the first channel of the group
  372 + channel = config.getGroupOfChannelByName(config.unit_name,name_of_channel_group)["channels"][0]
  373 + data_from_config = config.getEditableAttributesOfChannel(config.unit_name,channel)
  374 + #form = PlanForm(instance=plan, data=request.POST)
  375 + form = PlanForm(edited_plan=plan, data_from_config=data_from_config)
348 376  
349 377 if (settings.DEBUG):
350 378 log.info("From plan_validate")
351   -
352   -
353 379 if action == "cancel":
354   - if plan.name == "New plan":
355   - plan.delete()
  380 +
  381 + plan.delete()
356 382 return redirect(action_album, alb_id=plan.album.id, action="edit", status='1', message="Cancelled plan modification")
357 383 elif action == "delete":
358 384 return redirect(action_plan, plan_id, "delete")
359 385 elif form.is_valid():
  386 + # unused
360 387 plan = form.save()
361 388 if action == "save":
362   - return redirect(action_plan, plan_id=plan_id, action="edit", status=1, message="Plan saved")
  389 + action="edit"
  390 + status=1
  391 + message="Plan saved"
  392 + return redirect(action_plan, locals())
  393 + elif action == "save":
  394 + if request.POST:
  395 +
  396 + post_data = request.POST.copy()
  397 + post_data.pop("csrfmiddlewaretoken")
  398 + post_data.pop("action")
  399 + nb_images = post_data.pop("nb_images")[0]
  400 + config_attributes = {}
  401 + for key,value in post_data.items():
  402 + if type(value) == str:
  403 + try:
  404 + # linked values
  405 + new_dict = {key:{}}
  406 + splitted_values = value.split(";")
  407 + config_attributes[key] = {}
  408 + for splitted_value in splitted_values:
  409 + subkey,subvalue = splitted_value.split(":")
  410 + config_attributes[key][subkey] = ast.literal_eval(subvalue)
  411 + except:
  412 + # Do nothing, normal string
  413 + config_attributes[key] = ast.literal_eval(value)
  414 + plan.nb_images = int(nb_images)
  415 + plan.config_attributes = config_attributes
  416 + plan.save()
  417 + return redirect(action_album, plan.album.id, "edit")
  418 + #return redirect(action_plan, plan_id, "edit", 1, "Plan saved")
363 419 else:
364 420 error = True
365 421 message = "Please check your field's validity"
366 422 edit = True
367   - req = plan.album.sequence.request
368   - req_id = req.id
369 423 seq_id = plan.album.sequence.id
370 424 alb_id = plan.album.id
371   - depth_level = 4
  425 + depth_level = 3
372 426 return render(request, "routine_manager/view_plan.html", locals())
373 427  
374 428 @login_required
... ... @@ -381,13 +435,13 @@ def create_request(request):
381 435 return redirect(action_request, req.id, "edit")
382 436  
383 437 @login_required
384   -def create_sequence(request, req_id):
  438 +def create_sequence(request):
385 439 """
386 440 Create a new sequence and redirects to the editing action on it
387 441 """
388   -
389   - req = Request.objects.get(id=req_id)
390   - seq = Sequence.objects.create(request=req, name="New sequence", status=Sequence.INCOMPLETE)
  442 + date_of_sequence = datetime.datetime.utcnow()
  443 + date_of_sequence = date_of_sequence.strftime("%Y%m%dT%H%M%S")
  444 + seq = Sequence.objects.create(pyros_user=request.user, name=f"seq_{str(date_of_sequence)}", status=Sequence.INCOMPLETE)
391 445 return redirect(action_sequence, seq.id, "edit")
392 446  
393 447 @login_required
... ... @@ -407,7 +461,7 @@ def create_plan(request, alb_id):
407 461 """
408 462  
409 463 alb = Album.objects.get(id=alb_id)
410   - plan = Plan.objects.create(album=alb, name="New plan", complete=False)
  464 + plan = Plan.objects.create(album=alb,nb_images=1)
411 465 return redirect(action_plan, plan.id, "edit")
412 466  
413 467 @login_required
... ... @@ -548,3 +602,63 @@ def import_request(request):
548 602  
549 603 # Now, display the list of requests (updated with this new request)
550 604 return redirect(requests_list, status=status, message=message)
  605 +
  606 +@login_required
  607 +def test_create_plan(request):
  608 + config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
  609 + if request.POST:
  610 +
  611 + post_data = request.POST.copy()
  612 + post_data.pop("csrfmiddlewaretoken")
  613 + nb_images = post_data.pop("nb_images")[0]
  614 + config_attributes = {}
  615 + for key,value in post_data.items():
  616 + if type(value) == str:
  617 + try:
  618 + # linked values
  619 + new_dict = {key:{}}
  620 + splitted_values = value.split(";")
  621 + config_attributes[key] = {}
  622 + for splitted_value in splitted_values:
  623 + subkey,subvalue = splitted_value.split(":")
  624 + config_attributes[key][subkey] = ast.literal_eval(subvalue)
  625 + except:
  626 + # Do nothing, normal string
  627 + config_attributes[key] = ast.literal_eval(value)
  628 + plan = Plan()
  629 + plan.nb_images = int(nb_images)
  630 + plan.config_attributes = config_attributes
  631 + plan.save()
  632 + form = PlanForm(data_from_config=config.getEditableAttributesOfChannel(config.unit_name,list(config.get_channels(config.unit_name).keys())[0]))
  633 + uneditableForm = uneditablePlanForm(data_from_config=config.getUneditableAttributesOfChannel(config.unit_name,list(config.get_channels(config.unit_name).keys())[0]))
  634 + return render(request,"routine_manager/testcreateplan.html",{"form":form,"uneditableForm":uneditableForm})
  635 +
  636 +@login_required
  637 +def edit_plan(request,id):
  638 + plan = get_object_or_404(Plan,pk=id)
  639 + config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
  640 + if request.POST:
  641 +
  642 + post_data = request.POST.copy()
  643 + post_data.pop("csrfmiddlewaretoken")
  644 + nb_images = post_data.pop("nb_images")[0]
  645 + config_attributes = {}
  646 + for key,value in post_data.items():
  647 + if type(value) == str:
  648 + try:
  649 + # linked values
  650 + new_dict = {key:{}}
  651 + splitted_values = value.split(";")
  652 + config_attributes[key] = {}
  653 + for splitted_value in splitted_values:
  654 + subkey,subvalue = splitted_value.split(":")
  655 + config_attributes[key][subkey] = ast.literal_eval(subvalue)
  656 + except:
  657 + # Do nothing, normal string
  658 + config_attributes[key] = ast.literal_eval(value)
  659 + plan.nb_images = int(nb_images)
  660 + plan.config_attributes = config_attributes
  661 + plan.save()
  662 + form = PlanForm(data_from_config=config.getEditableAttributesOfChannel(config.unit_name,list(config.get_channels(config.unit_name).keys())[0]),edited_plan=plan)
  663 + uneditableForm = uneditablePlanForm(data_from_config=config.getUneditableAttributesOfChannel(config.unit_name,list(config.get_channels(config.unit_name).keys())[0]))
  664 + return render(request,"routine_manager/testcreateplan.html",{"form":form,"uneditableForm":uneditableForm})
551 665 \ No newline at end of file
... ...
src/core/pyros_django/routine_manager/widgets.py 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +from django.forms import widgets
  2 +from django.forms.renderers import get_default_renderer
  3 +
  4 +
  5 +class LinkedSelectWidget(widgets.RadioSelect):
  6 + def __init__(self,attrs=None,*args,**kwargs):
  7 + super(LinkedSelectWidget,self).__init__(attrs,*args,**kwargs)
  8 +
  9 + template_name = "routine_manager/linkedSelectField_widget.html"
  10 +
  11 +
0 12 \ No newline at end of file
... ...