Commit 43e6176dbc467485dd03fbc133356699f8c0220d
1 parent
627f618c
Exists in
dev
Model splitting goes on... : common/models.py, routine_manager/models.py, devices/models.py
Showing
21 changed files
with
1327 additions
and
439 deletions
Show diff stats
... | ... | @@ -0,0 +1,338 @@ |
1 | +#!/usr/bin/env python3 | |
2 | +# | |
3 | +# To launch this agent from the root of Pyros: | |
4 | +# cd /srv/develop/pyros | |
5 | +# .\PYROS -t start AgentImagesCalibrator -o tnc -fg | |
6 | +# | |
7 | +# Edit the file pyros/pyros.py, | |
8 | +# Add a new entry in the dict AGENT: | |
9 | +# "AgentImagesCalibrator": "observation_manager", | |
10 | +# --------------------------------------------------- | |
11 | + | |
12 | +import sys | |
13 | +import time | |
14 | + | |
15 | +import os | |
16 | +pwd = os.environ['PROJECT_ROOT_PATH'] | |
17 | +if pwd not in sys.path: | |
18 | + sys.path.append(pwd) | |
19 | + | |
20 | +short_paths = ['src', 'src/core/pyros_django'] | |
21 | +for short_path in short_paths: | |
22 | + path = os.path.join(pwd, short_path) | |
23 | + if path not in sys.path: | |
24 | + sys.path.insert(0, path) | |
25 | + | |
26 | +from src.core.pyros_django.agent.Agent import Agent, build_agent, log | |
27 | + | |
28 | +# = Specials | |
29 | +import glob | |
30 | +import shutil | |
31 | +import guitastro | |
32 | + | |
33 | +class AgentImagesCalibrator(Agent): | |
34 | + | |
35 | + # - All possible running states | |
36 | + RUNNING_NOTHING = 0 | |
37 | + RUNNING_ONE_IMAGE_PROCESSING = 1 | |
38 | + RUNNING_COMPUTE_RON_GAIN = 2 | |
39 | + | |
40 | + # TODO: Redefine valid timeout | |
41 | + _AGENT_SPECIFIC_COMMANDS = { | |
42 | + # Format : “cmd_name” : (timeout, exec_mode) | |
43 | + | |
44 | + "do_create_test_images_1" : (60, Agent.EXEC_MODE.SEQUENTIAL, ''), | |
45 | + "do_create_test_images_2" : (60, Agent.EXEC_MODE.SEQUENTIAL, ''), | |
46 | + "do_stop_current_processing" : (60, Agent.EXEC_MODE.SEQUENTIAL, ''), | |
47 | + } | |
48 | + | |
49 | + | |
50 | + | |
51 | + | |
52 | + # Scenario to be executed | |
53 | + # "self do_stop_current_processing" | |
54 | + # AgentCmd.CMD_STATUS_CODE.CMD_EXECUTED | |
55 | + _TEST_COMMANDS_LIST = [ | |
56 | + # Format : ("self cmd_name cmd_args", timeout, "expected_result", expected_status), | |
57 | + (True, "self do_create_test_images_1", 200, '', Agent.CMD_STATUS.CMD_EXECUTED), | |
58 | + (True, "self do_exit", 500, "STOPPING", Agent.CMD_STATUS.CMD_EXECUTED), | |
59 | + ] | |
60 | + | |
61 | + """ | |
62 | + ================================================================= | |
63 | + Methods running inside main thread | |
64 | + ================================================================= | |
65 | + """ | |
66 | + | |
67 | + def __init__(self, name:str=None): | |
68 | + if name is None: | |
69 | + name = self.__class__.__name__ | |
70 | + super().__init__() | |
71 | + | |
72 | + def _init(self): | |
73 | + super()._init() | |
74 | + log.debug("end super init()") | |
75 | + log.info(f"self.TEST_MODE = {self.TEST_MODE}") | |
76 | + | |
77 | + # === Get config infos | |
78 | + agent_alias = self.name | |
79 | + log.info(f"agent_alias = {agent_alias}") | |
80 | + # === Get the config object | |
81 | + self.config = self._oc['config'] | |
82 | + # === Get self._path_data_root | |
83 | + self._path_data_root = self.config.get_agent_path_data_root(agent_alias) | |
84 | + # === Get self._home of current unit | |
85 | + self._home = self.config.getHome() | |
86 | + # === Get self._paths the directories for all data (images). See obsconfig_class.py to know keys | |
87 | + self._paths = self.config.get_agent_path_data_tree(agent_alias, True) | |
88 | + # === Get bias strategies | |
89 | + unit_name = "TNC" | |
90 | + channel_name = "OpticalChannel_up1" | |
91 | + category = "BI" | |
92 | + self_strategy_bias = self.config.get_image_calibrations(unit_name, channel_name, category) | |
93 | + log.debug(f"self_strategy_bias={self_strategy_bias}") | |
94 | + | |
95 | + # === Instanciate an object Ima to make image processing | |
96 | + self._ima = guitastro.Ima() | |
97 | + home = guitastro.Home(self._home) | |
98 | + | |
99 | + # === Instanciate an object Filenames to manage file names | |
100 | + self._filename_manager = guitastro.Filenames() | |
101 | + self._filename_manager.naming("PyROS.1") | |
102 | + | |
103 | + # === Set longitude to ima object to generate the night yyyymmdd and subdirectories yyyy/mm/dd | |
104 | + longitude = home.longitude | |
105 | + log.info(f"Longitude={longitude}") | |
106 | + self._ima.longitude(longitude) | |
107 | + log.info("Init done with success") | |
108 | + | |
109 | + # === Status of routine processing | |
110 | + self._routine_running = self.RUNNING_NOTHING | |
111 | + log.debug("end init()") | |
112 | + | |
113 | + # Note : called by _routine_process() in Agent | |
114 | + # @override | |
115 | + def _routine_process_iter_start_body(self): | |
116 | + log.debug("in routine_process_before_body()") | |
117 | + | |
118 | + # Note : called by _routine_process() in Agent | |
119 | + # @override | |
120 | + def _routine_process_iter_end_body(self): | |
121 | + log.debug("in routine_process_after_body()") | |
122 | + if self._routine_running == self.RUNNING_NOTHING: | |
123 | + # Get files to process | |
124 | + fitsfiles = self.glob_images_to_process() | |
125 | + n = len(fitsfiles) | |
126 | + log.info(f"There are {n} image{self._plural(n)} to process") | |
127 | + if n > 0: | |
128 | + # - We select the oldest image | |
129 | + fitsfile = fitsfiles[0] | |
130 | + log.info(f"Process the file {fitsfile}") | |
131 | + # - Thread TODO | |
132 | + self._routine_running = self.RUNNING_ONE_IMAGE_PROCESSING | |
133 | + self.process_one_image(fitsfile) | |
134 | + | |
135 | + """ | |
136 | + ================================================================= | |
137 | + Methods of specific commands | |
138 | + ================================================================= | |
139 | + """ | |
140 | + | |
141 | + def do_stop_current_processing(self): | |
142 | + pass | |
143 | + | |
144 | + def do_create_test_images_1(self): | |
145 | + self._create_test_images_1() | |
146 | + | |
147 | + def do_create_test_images_2(self): | |
148 | + self._create_test_images_2() | |
149 | + | |
150 | + """ | |
151 | + ================================================================= | |
152 | + Methods called by commands or routine. Overload these methods | |
153 | + ================================================================= | |
154 | + """ | |
155 | + | |
156 | + def glob_images_to_process(self): | |
157 | + | |
158 | + # - glob the incoming directory: | |
159 | + fitsfiles = glob.glob(f"{self._paths['ima_incoming']}/BI_*.fit") | |
160 | + # - Please sort list of files in increasing dates (TODO) | |
161 | + return fitsfiles | |
162 | + | |
163 | + def bias_correction(self): | |
164 | + | |
165 | + # - Search the bias | |
166 | + path_bias = os.path.join( self._paths['ima_bias'], self._date_night ) | |
167 | + fitsbiasfiles = glob.glob(f"{path_bias}/*.fit") | |
168 | + log.info(f"fitsbiasfiles = {fitsbiasfiles}") | |
169 | + if len(fitsbiasfiles) > 0: | |
170 | + | |
171 | + # - Select the bias | |
172 | + pass | |
173 | + | |
174 | + def dark_correction(self): | |
175 | + | |
176 | + # - Search the dark | |
177 | + path_darks = os.path.join( self._paths['ima_darks'], self._date_night ) | |
178 | + fitsdarkfiles = glob.glob(f"{path_darks}/*.fit") | |
179 | + log.info(f"fitsdarkfiles = {fitsdarkfiles}") | |
180 | + if len(fitsdarkfiles) > 0: | |
181 | + | |
182 | + # - Select two darks and compute the therm using exposure | |
183 | + # - Correction of dark | |
184 | + pass | |
185 | + | |
186 | + def flat_correction(self): | |
187 | + | |
188 | + # - Search the flat | |
189 | + path_flats = os.path.join( self._paths['ima_flats'], self._date_night ) | |
190 | + fitsflatfiles = glob.glob(f"{path_flats}/*.fit") | |
191 | + log.info(f"fitsflatfiles = {fitsflatfiles}") | |
192 | + if len(fitsflatfiles) > 0: | |
193 | + | |
194 | + # - Select the flat (with the filter) | |
195 | + # - Correction of flat | |
196 | + pass | |
197 | + | |
198 | + def inversion_correction(self): | |
199 | + pass | |
200 | + | |
201 | + def cosmetic_correction(self): | |
202 | + pass | |
203 | + | |
204 | + def wcs_calibration(self): | |
205 | + return 0 | |
206 | + | |
207 | + def process_one_image(self, fitsfile: str): | |
208 | + """This is the general algorithm of processing | |
209 | + | |
210 | + The processing consists to make corrections of dark, flat, inversions, cosmetic | |
211 | + and perform WCS calibration. | |
212 | + | |
213 | + Args: | |
214 | + fitsfile: The file of the FITS file to process. | |
215 | + | |
216 | + """ | |
217 | + | |
218 | + # - Load file in memory | |
219 | + log.info("Load the file in memory") | |
220 | + #self.set_infos("Load the file in memory") | |
221 | + f = self._ima.genename(self._ima.load(fitsfile)) | |
222 | + # log.info(f"f={f}") | |
223 | + | |
224 | + # - Save as tmp | |
225 | + self._ima.path(self._paths['ima_tmp']) | |
226 | + log.info("Save the temporary file as tmp name") | |
227 | + self._ima.save("tmp") | |
228 | + | |
229 | + # - Load tmp and get infos | |
230 | + self._ima.load("tmp") | |
231 | + date_obs = self._ima.getkwd("DATE-OBS") | |
232 | + self._date_night = self._ima.get_night(date_obs) | |
233 | + log.info(f"Date_obs = {date_obs}") | |
234 | + log.info(f"Night = {self._date_night}") | |
235 | + exposure = self._ima.getkwd("EXPOSURE") | |
236 | + log.info(f"Exposure = {exposure}") | |
237 | + | |
238 | + # - Bias correction | |
239 | + self.bias_correction() | |
240 | + | |
241 | + # - Dark correction | |
242 | + self.dark_correction() | |
243 | + | |
244 | + # - Flat correction | |
245 | + self.flat_correction() | |
246 | + | |
247 | + # - Save tmp corrected by dark and flat | |
248 | + self._ima.path(self._paths['ima_tmp']) | |
249 | + self._ima.save("tmp") | |
250 | + | |
251 | + # - Inversion of mirrors or mirorxy | |
252 | + self.inversion_correction() | |
253 | + | |
254 | + # - Cosmetic correction | |
255 | + self.cosmetic_correction() | |
256 | + | |
257 | + # - WCS calibration | |
258 | + nmatched = self.wcs_calibration() | |
259 | + | |
260 | + # - Prepare the output file name | |
261 | + log.info("Decode the filename") | |
262 | + fgen_in = f['genename'] + f['sep'] + f['indexes'][0] + f['suffix'] | |
263 | + fext_in = f['file_extension'] | |
264 | + fext_out = ".fits" | |
265 | + | |
266 | + # - Save in processed | |
267 | + yyyy = self._date_night[0:4] | |
268 | + mm = self._date_night[4:6] | |
269 | + dd = self._date_night[6:8] | |
270 | + path_processed = os.path.join( self._paths['ima_processed'], yyyy, mm, dd ) | |
271 | + self._ima.path(path_processed) | |
272 | + fname_out = fgen_in + fext_out | |
273 | + fname = self._ima.save(fname_out) | |
274 | + log.info(f"Save the processed image {fname}") | |
275 | + | |
276 | + # - Delete the file in incoming directory | |
277 | + os.remove(fitsfile) | |
278 | + log.info(f"Delete the raw image {fitsfile}") | |
279 | + | |
280 | + # - Update the running state | |
281 | + self._routine_running = self.RUNNING_NOTHING | |
282 | + | |
283 | + time.sleep(5) | |
284 | + print("\n ...End of image calibration\n") | |
285 | + | |
286 | + """ | |
287 | + ================================================================= | |
288 | + Internal methods | |
289 | + ================================================================= | |
290 | + """ | |
291 | + | |
292 | + def _create_test_images_1(self): | |
293 | + try: | |
294 | + # === Define an image to test the processing and copy it in incoming directory | |
295 | + self._file_ima_test = os.path.join(self._path_data_root,"vendor/guitastro/tests/data/m57.fit") | |
296 | + file_in = self._file_ima_test | |
297 | + file_out = f"{self._paths['ima_incoming']}/m57.fit" | |
298 | + shutil.copyfile(file_in, file_out) | |
299 | + self._filename_manager.naming("") | |
300 | + except: | |
301 | + raise | |
302 | + | |
303 | + def _create_test_images_2(self): | |
304 | + try: | |
305 | + self._ima.etc.camera("Kepler 4040") | |
306 | + self._ima.etc.optics("Takahashi_180ED") | |
307 | + self._ima.etc.params("msky",18) | |
308 | + ra = 132.84583 | |
309 | + dec = 11.81333 | |
310 | + at = self._ima.simulation("GAIA", "PHOTOM", shutter_mode="closed", t=50) | |
311 | + file_out = os.path.join(self._paths['ima_tmp'], "m67.ecsv") | |
312 | + print(f"STEP TOTO 1 = {at}") | |
313 | + at.t.write(file_out, format='astrotable', overwrite=True) | |
314 | + print(f"STEP TOTO 2") | |
315 | + date_obs = self.getkwd("DATE-OBS") | |
316 | + except: | |
317 | + raise | |
318 | + | |
319 | + def _plural(self, n: int) -> str: | |
320 | + """Return "s" if n>1 for plurals. | |
321 | + | |
322 | + Args: | |
323 | + n: Number of entities | |
324 | + | |
325 | + Returns: | |
326 | + The string "s" or "" | |
327 | + """ | |
328 | + if n > 1: | |
329 | + s = "s" | |
330 | + else: | |
331 | + s = "" | |
332 | + return s | |
333 | + | |
334 | +if __name__ == "__main__": | |
335 | + | |
336 | + agent = build_agent(AgentImagesCalibrator) | |
337 | + print(agent) | |
338 | + agent.run() | ... | ... |
... | ... | @@ -0,0 +1,328 @@ |
1 | +#!/usr/bin/env python3 | |
2 | +# | |
3 | +# To launch this agent from the root of Pyros: | |
4 | +# cd /srv/develop/pyros | |
5 | +# .\PYROS -t start AgentImagesProcessor -o tnc -fg | |
6 | +# | |
7 | +# --------------------------------------------------- | |
8 | + | |
9 | +import sys | |
10 | +import time | |
11 | + | |
12 | +import os | |
13 | +pwd = os.environ['PROJECT_ROOT_PATH'] | |
14 | +if pwd not in sys.path: | |
15 | + sys.path.append(pwd) | |
16 | + | |
17 | +short_paths = ['src', 'src/core/pyros_django'] | |
18 | +for short_path in short_paths: | |
19 | + path = os.path.join(pwd, short_path) | |
20 | + if path not in sys.path: | |
21 | + sys.path.insert(0, path) | |
22 | + | |
23 | +from src.core.pyros_django.agent.Agent import Agent, build_agent, log | |
24 | + | |
25 | +# = Specials | |
26 | +import glob | |
27 | +import shutil | |
28 | +import guitastro | |
29 | + | |
30 | +class AgentImagesProcessor(Agent): | |
31 | + | |
32 | + # - All possible running states | |
33 | + RUNNING_NOTHING = 0 | |
34 | + RUNNING_ONE_IMAGE_PROCESSING = 1 | |
35 | + RUNNING_COMPUTE_RON_GAIN = 2 | |
36 | + | |
37 | + # TODO: Redefine valid timeout | |
38 | + _AGENT_SPECIFIC_COMMANDS = { | |
39 | + # Format : “cmd_name” : (timeout, exec_mode) | |
40 | + | |
41 | + "do_create_test_images_1" : (60, Agent.EXEC_MODE.SEQUENTIAL, ''), | |
42 | + "do_create_test_images_2" : (60, Agent.EXEC_MODE.SEQUENTIAL, ''), | |
43 | + "do_stop_current_processing" : (60, Agent.EXEC_MODE.SEQUENTIAL, ''), | |
44 | + } | |
45 | + | |
46 | + # Scenario to be executed | |
47 | + # "self do_stop_current_processing" | |
48 | + # AgentCmd.CMD_STATUS_CODE.CMD_EXECUTED | |
49 | + _TEST_COMMANDS_LIST = [ | |
50 | + # Format : ("self cmd_name cmd_args", timeout, "expected_result", expected_status), | |
51 | + (True, "self do_create_test_images_1", 200, '', Agent.CMD_STATUS.CMD_EXECUTED), | |
52 | + (True, "self do_stop asap", 500, "STOPPING", Agent.CMD_STATUS.CMD_EXECUTED), | |
53 | + ] | |
54 | + | |
55 | + """ | |
56 | + ================================================================= | |
57 | + Methods running inside main thread | |
58 | + ================================================================= | |
59 | + """ | |
60 | + | |
61 | + def __init__(self, name:str=None,simulated_computer=None): | |
62 | + if name is None: | |
63 | + name = self.__class__.__name__ | |
64 | + super().__init__(simulated_computer=simulated_computer) | |
65 | + | |
66 | + def _init(self): | |
67 | + super()._init() | |
68 | + log.debug("end super init()") | |
69 | + log.info(f"self.TEST_MODE = {self.TEST_MODE}") | |
70 | + | |
71 | + # === Get config infos | |
72 | + agent_alias = self.name | |
73 | + log.info(f"agent_alias = {agent_alias}") | |
74 | + # === Get the config object | |
75 | + self.config = self._oc['config'] | |
76 | + # === Get self._path_data_root | |
77 | + self._path_data_root = self.config.get_agent_path_data_root(agent_alias) | |
78 | + # === Get self._home of current unit | |
79 | + self._home = self.config.getHome() | |
80 | + # === Get self._paths the directories for all data (images). See obsconfig_class.py to know keys | |
81 | + self._paths = self.config.get_agent_path_data_tree(agent_alias, True) | |
82 | + | |
83 | + # === Instanciate an object Ima to make image processing | |
84 | + self._ima = guitastro.Ima() | |
85 | + home = guitastro.Home(self._home) | |
86 | + | |
87 | + # === Instanciate an object Filenames to manage file names | |
88 | + self._filename_manager = guitastro.Filenames() | |
89 | + self._filename_manager.naming("PyROS.1") | |
90 | + | |
91 | + # === Set longitude to ima object to generate the night yyyymmdd and subdirectories yyyy/mm/dd | |
92 | + longitude = home.longitude | |
93 | + log.info(f"Longitude={longitude}") | |
94 | + self._ima.longitude(longitude) | |
95 | + log.info("Init done with success") | |
96 | + | |
97 | + # === Status of routine processing | |
98 | + self._routine_running = self.RUNNING_NOTHING | |
99 | + log.debug("end init()") | |
100 | + | |
101 | + # Note : called by _routine_process() in Agent | |
102 | + # @override | |
103 | + def _routine_process_iter_start_body(self): | |
104 | + log.debug("in routine_process_before_body()") | |
105 | + | |
106 | + # Note : called by _routine_process() in Agent | |
107 | + # @override | |
108 | + def _routine_process_iter_end_body(self): | |
109 | + log.debug("in routine_process_after_body()") | |
110 | + if self._routine_running == self.RUNNING_NOTHING: | |
111 | + # Get files to process | |
112 | + fitsfiles = self.glob_images_to_process() | |
113 | + n = len(fitsfiles) | |
114 | + log.info(f"There are {n} image{self._plural(n)} to process") | |
115 | + if n > 0: | |
116 | + # - We select the oldest image | |
117 | + fitsfile = fitsfiles[0] | |
118 | + log.info(f"Process the file {fitsfile}") | |
119 | + # - Thread TODO | |
120 | + self._routine_running = self.RUNNING_ONE_IMAGE_PROCESSING | |
121 | + self.process_one_image(fitsfile) | |
122 | + | |
123 | + """ | |
124 | + ================================================================= | |
125 | + Methods of specific commands | |
126 | + ================================================================= | |
127 | + """ | |
128 | + | |
129 | + def do_stop_current_processing(self): | |
130 | + pass | |
131 | + | |
132 | + def do_create_test_images_1(self): | |
133 | + self._create_test_images_1() | |
134 | + | |
135 | + def do_create_test_images_2(self): | |
136 | + self._create_test_images_2() | |
137 | + | |
138 | + """ | |
139 | + ================================================================= | |
140 | + Methods called by commands or routine. Overload these methods | |
141 | + ================================================================= | |
142 | + """ | |
143 | + | |
144 | + def glob_images_to_process(self): | |
145 | + | |
146 | + # - glob the incoming directory: | |
147 | + fitsfiles = glob.glob(f"{self._paths['ima_incoming']}/*.fit") | |
148 | + # - Please sort list of files in increasing dates (TODO) | |
149 | + return fitsfiles | |
150 | + | |
151 | + def bias_correction(self): | |
152 | + | |
153 | + # - Search the bias | |
154 | + path_bias = os.path.join( self._paths['ima_bias'], self._date_night ) | |
155 | + fitsbiasfiles = glob.glob(f"{path_bias}/*.fit") | |
156 | + log.info(f"fitsbiasfiles = {fitsbiasfiles}") | |
157 | + if len(fitsbiasfiles) > 0: | |
158 | + | |
159 | + # - Select the bias | |
160 | + pass | |
161 | + | |
162 | + def dark_correction(self): | |
163 | + | |
164 | + # - Search the dark | |
165 | + path_darks = os.path.join( self._paths['ima_darks'], self._date_night ) | |
166 | + fitsdarkfiles = glob.glob(f"{path_darks}/*.fit") | |
167 | + log.info(f"fitsdarkfiles = {fitsdarkfiles}") | |
168 | + if len(fitsdarkfiles) > 0: | |
169 | + | |
170 | + # - Select two darks and compute the therm using exposure | |
171 | + # - Correction of dark | |
172 | + pass | |
173 | + | |
174 | + def flat_correction(self): | |
175 | + | |
176 | + # - Search the flat | |
177 | + path_flats = os.path.join( self._paths['ima_flats'], self._date_night ) | |
178 | + fitsflatfiles = glob.glob(f"{path_flats}/*.fit") | |
179 | + log.info(f"fitsflatfiles = {fitsflatfiles}") | |
180 | + if len(fitsflatfiles) > 0: | |
181 | + | |
182 | + # - Select the flat (with the filter) | |
183 | + # - Correction of flat | |
184 | + pass | |
185 | + | |
186 | + def inversion_correction(self): | |
187 | + pass | |
188 | + | |
189 | + def cosmetic_correction(self): | |
190 | + pass | |
191 | + | |
192 | + def wcs_calibration(self): | |
193 | + return 0 | |
194 | + | |
195 | + def process_one_image(self, fitsfile: str): | |
196 | + """This is the general algorithm of processing | |
197 | + | |
198 | + The processing consists to make corrections of dark, flat, inversions, cosmetic | |
199 | + and perform WCS calibration. | |
200 | + | |
201 | + Args: | |
202 | + fitsfile: The file of the FITS file to process. | |
203 | + | |
204 | + """ | |
205 | + | |
206 | + # - Load file in memory | |
207 | + log.info("Load the file in memory") | |
208 | + #self.set_infos("Load the file in memory") | |
209 | + f = self._ima.genename(self._ima.load(fitsfile)) | |
210 | + # log.info(f"f={f}") | |
211 | + | |
212 | + # - Save as tmp | |
213 | + self._ima.path(self._paths['ima_tmp']) | |
214 | + log.info("Save the temporary file as tmp name") | |
215 | + self._ima.save("tmp") | |
216 | + | |
217 | + # - Load tmp and get infos | |
218 | + self._ima.load("tmp") | |
219 | + date_obs = self._ima.getkwd("DATE-OBS") | |
220 | + self._date_night = self._ima.get_night(date_obs) | |
221 | + log.info(f"Date_obs = {date_obs}") | |
222 | + log.info(f"Night = {self._date_night}") | |
223 | + exposure = self._ima.getkwd("EXPOSURE") | |
224 | + log.info(f"Exposure = {exposure}") | |
225 | + | |
226 | + # - Bias correction | |
227 | + self.bias_correction() | |
228 | + | |
229 | + # - Dark correction | |
230 | + self.dark_correction() | |
231 | + | |
232 | + # - Flat correction | |
233 | + self.flat_correction() | |
234 | + | |
235 | + # - Save tmp corrected by dark and flat | |
236 | + self._ima.path(self._paths['ima_tmp']) | |
237 | + self._ima.save("tmp") | |
238 | + | |
239 | + # - Inversion of mirrors or mirorxy | |
240 | + self.inversion_correction() | |
241 | + | |
242 | + # - Cosmetic correction | |
243 | + self.cosmetic_correction() | |
244 | + | |
245 | + # - WCS calibration | |
246 | + nmatched = self.wcs_calibration() | |
247 | + | |
248 | + # - Prepare the output file name | |
249 | + log.info("Decode the filename") | |
250 | + fgen_in = f['genename'] + f['sep'] + f['indexes'][0] + f['suffix'] | |
251 | + fext_in = f['file_extension'] | |
252 | + fext_out = ".fits" | |
253 | + | |
254 | + # - Save in processed | |
255 | + yyyy = self._date_night[0:4] | |
256 | + mm = self._date_night[4:6] | |
257 | + dd = self._date_night[6:8] | |
258 | + path_processed = os.path.join( self._paths['ima_processed'], yyyy, mm, dd ) | |
259 | + self._ima.path(path_processed) | |
260 | + fname_out = fgen_in + fext_out | |
261 | + fname = self._ima.save(fname_out) | |
262 | + log.info(f"Save the processed image {fname}") | |
263 | + | |
264 | + # - Delete the file in incoming directory | |
265 | + os.remove(fitsfile) | |
266 | + log.info(f"Delete the raw image {fitsfile}") | |
267 | + | |
268 | + # - Update the running state | |
269 | + self._routine_running = self.RUNNING_NOTHING | |
270 | + | |
271 | + time.sleep(5) | |
272 | + print("\n ...End of image calibration\n") | |
273 | + | |
274 | + """ | |
275 | + ================================================================= | |
276 | + Internal methods | |
277 | + ================================================================= | |
278 | + """ | |
279 | + | |
280 | + def _create_test_images_1(self): | |
281 | + try: | |
282 | + # === Define an image to test the processing and copy it in incoming directory | |
283 | + self._file_ima_test = os.path.join(self._path_data_root,"vendor/guitastro/tests/data/m57.fit") | |
284 | + file_in = self._file_ima_test | |
285 | + file_out = f"{self._paths['ima_incoming']}/m57.fit" | |
286 | + shutil.copyfile(file_in, file_out) | |
287 | + self._filename_manager.naming("") | |
288 | + except: | |
289 | + raise | |
290 | + | |
291 | + def _create_test_images_2(self): | |
292 | + try: | |
293 | + self._ima.etc.camera("Kepler 4040") | |
294 | + self._ima.etc.optics("Takahashi_180ED") | |
295 | + self._ima.etc.params("msky",18) | |
296 | + ra = 132.84583 | |
297 | + dec = 11.81333 | |
298 | + at = self._ima.simulation("GAIA", "PHOTOM", shutter_mode="closed", t=50, ra=ra, dec=dec) | |
299 | + file_out = os.path.join(self._paths['ima_tmp'], "m67.ecsv") | |
300 | + print(f"STEP TOTO 1 = {at}") | |
301 | + at.t.write(file_out, format='astrotable', overwrite=True) | |
302 | + print(f"STEP TOTO 2") | |
303 | + date_obs = self.getkwd("DATE-OBS") | |
304 | + except: | |
305 | + raise | |
306 | + | |
307 | + def _plural(self, n: int) -> str: | |
308 | + """Return "s" if n>1 for plurals. | |
309 | + | |
310 | + Args: | |
311 | + n: Number of entities | |
312 | + | |
313 | + Returns: | |
314 | + The string "s" or "" | |
315 | + """ | |
316 | + if n > 1: | |
317 | + s = "s" | |
318 | + else: | |
319 | + s = "" | |
320 | + return s | |
321 | + | |
322 | +if __name__ == "__main__": | |
323 | + parser = argparse.ArgumentParser(description='Start the agent.') | |
324 | + parser.add_argument("--computer",dest="computer",help='Launch agent with simulated computer hostname',action="store") | |
325 | + args = vars(parser.parse_args()) | |
326 | + agent = build_agent(AgentImagesProcessor,param_constr=args) | |
327 | + print(agent) | |
328 | + agent.run() | ... | ... |
src/core/pyros_django/agent/Agent.py
... | ... | @@ -384,8 +384,9 @@ class Agent: |
384 | 384 | # |
385 | 385 | #_TEST_COMMANDS_LIST: List[ Tuple[ bool, str, int, Union[str,None], Union[int,None] ] ] = [ |
386 | 386 | ##_TEST_COMMANDS_LIST: List[ Tuple[ bool, str, int, Optional[str], AgentCmd.CMD_STATUS_CODES ] ] = [ |
387 | + | |
388 | + # Alias type for _TEST_COMMANDS_LIST (for more readability) | |
387 | 389 | TestCommand = Tuple[ bool, str, Optional[int], Optional[str], Optional[int]] |
388 | - #_TEST_COMMANDS_LIST: List[ Tuple[ bool, str, Optional[int], Optional[str], Optional[int]] ] = [ | |
389 | 390 | _TEST_COMMANDS_LIST: List[ TestCommand ] = [ |
390 | 391 | # Format : (DO_IT, "self cmd_name cmd_args", validity, "expected_result", expected_status), |
391 | 392 | ... | ... |
src/core/pyros_django/agent/AgentDevice.py
... | ... | @@ -29,33 +29,6 @@ from device_controller.abstract_component.device_controller import ( |
29 | 29 | |
30 | 30 | |
31 | 31 | |
32 | -""" | |
33 | -================================================================= | |
34 | - class StoppableThread | |
35 | -================================================================= | |
36 | -""" | |
37 | -class StoppableThreadEvenWhenSleeping(threading.Thread): | |
38 | - # Thread class with a stop() method. The thread itself has to check | |
39 | - # regularly for the stopped() condition. | |
40 | - # It stops even if sleeping | |
41 | - # See https://python.developpez.com/faq/?page=Thread#ThreadKill | |
42 | - # See also https://www.oreilly.com/library/view/python-cookbook/0596001673/ch06s03.html | |
43 | - | |
44 | - def __init__(self, *args, **kwargs): | |
45 | - #super(StoppableThreadSimple, self).__init__(*args, **kwargs) | |
46 | - super().__init__(*args, **kwargs) | |
47 | - self._stop_event = threading.Event() | |
48 | - | |
49 | - #def stop(self): | |
50 | - def terminate(self): | |
51 | - self._stop_event.set() | |
52 | - | |
53 | - def stopped(self): | |
54 | - return self._stop_event.is_set() | |
55 | - | |
56 | - def wait(self, nbsec:float=2.0): | |
57 | - self._stop_event.wait(nbsec) | |
58 | - | |
59 | 32 | |
60 | 33 | |
61 | 34 | class AgentDevice(Agent): | ... | ... |
src/core/pyros_django/api/serializers.py
1 | -from common.models import AgentSurvey, Institute, Period, PyrosUser, SP_Period, ScienceTheme, ScientificProgram, Sequence, Plan, Album, AgentCmd | |
1 | +from common.models import AgentSurvey, Institute, Period, PyrosUser, SP_Period, ScienceTheme, ScientificProgram, AgentCmd | |
2 | +from routine_manager.models import Sequence, Plan, Album | |
2 | 3 | from rest_framework import serializers |
3 | 4 | import datetime |
4 | 5 | from datetime import timezone | ... | ... |
src/core/pyros_django/api/views.py
... | ... | @@ -7,7 +7,10 @@ from rest_framework.decorators import api_view, permission_classes, action |
7 | 7 | from django.core.validators import ValidationError |
8 | 8 | from src.core.pyros_django.user_manager import views as user_views |
9 | 9 | from api.serializers import AgentSurveySerializer, AlbumSerializer, FullSequenceSerializer, PlanSerializer, SPPeriodSerializer, ScientificProgramSerializer, SequenceSerializer, UserSerializer, AgentCmdSerializer |
10 | -from common.models import PyrosUser, SP_Period, ScientificProgram, Sequence, Album, Plan, UserLevel, SP_Period_User, AgentSurvey, AgentCmd | |
10 | + | |
11 | +from common.models import PyrosUser, SP_Period, ScientificProgram, UserLevel, SP_Period_User, AgentSurvey, AgentCmd | |
12 | +from routine_manager.models import Sequence, Album, Plan | |
13 | + | |
11 | 14 | from routine_manager.functions import check_sequence_file_validity |
12 | 15 | from rest_framework.request import Request |
13 | 16 | from django.db.models import Q | ... | ... |
src/core/pyros_django/common/admin.py
... | ... | @@ -5,10 +5,12 @@ from django.contrib.auth.models import User |
5 | 5 | # EP |
6 | 6 | from django.conf import settings |
7 | 7 | |
8 | +from common.models import ScientificProgram | |
8 | 9 | from common.models import * |
9 | 10 | |
10 | -# Necessary since new device/models.py file | |
11 | +# Other app*/models.py files | |
11 | 12 | from devices.models import Detector, Filter, AgentDeviceStatus, FilterWheel, Telescope, PlcDevice, PlcDeviceStatus |
13 | +from routine_manager.models import Image, StrategyObs, Schedule, Request, Alert, Sequence, Album, Plan, NrtAnalysis, ScheduleHasSequences | |
12 | 14 | |
13 | 15 | |
14 | 16 | # EP added | ... | ... |
src/core/pyros_django/common/models.py
... | ... | @@ -172,9 +172,11 @@ def printd(*args, **kwargs): |
172 | 172 | if os.environ.get('PYROS_DEBUG', '0') == '1': |
173 | 173 | print('(MODEL)', *args, **kwargs) |
174 | 174 | |
175 | +''' | |
175 | 176 | def get_or_create_unique_row_from_model(model: models.Model): |
176 | 177 | # return model.objects.get(id=1) if model.objects.exists() else model.objects.create(id=1) |
177 | 178 | return model.objects.first() if model.objects.exists() else model.objects.create(id=1) |
179 | +''' | |
178 | 180 | |
179 | 181 | |
180 | 182 | |
... | ... | @@ -186,85 +188,12 @@ def get_or_create_unique_row_from_model(model: models.Model): |
186 | 188 | ------------------------ |
187 | 189 | """ |
188 | 190 | |
189 | -class Request(models.Model): | |
190 | - pyros_user = models.ForeignKey( | |
191 | - 'PyrosUser', on_delete=models.DO_NOTHING, related_name="requests") | |
192 | - scientific_program = models.ForeignKey( | |
193 | - 'ScientificProgram', on_delete=models.DO_NOTHING, related_name="requests", blank=True, null=True) | |
194 | - name = models.CharField(max_length=45, blank=True, null=True) | |
195 | - desc = models.TextField(blank=True, null=True) | |
196 | - created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
197 | - updated = models.DateTimeField(blank=True, null=True, auto_now=True) | |
198 | - is_alert = models.BooleanField(default=False) | |
199 | - target_type = models.CharField(max_length=8, blank=True, null=True) | |
200 | - status = models.CharField(max_length=10, blank=True, null=True) | |
201 | - autodeposit = models.BooleanField(default=False) | |
202 | - checkpoint = models.CharField(max_length=45, blank=True, null=True) | |
203 | - flag = models.CharField(max_length=45, blank=True, null=True) | |
204 | - complete = models.BooleanField(default=False) | |
205 | - submitted = models.BooleanField(default=False) | |
206 | - | |
207 | - class Meta: | |
208 | - managed = True | |
209 | - db_table = 'request' | |
210 | - | |
211 | - def __str__(self): | |
212 | - return (str(self.name)) | |
213 | - | |
214 | - | |
215 | 191 | """ |
216 | 192 | ------------------------ |
217 | 193 | OTHER MODEL CLASSES |
218 | 194 | ------------------------ |
219 | 195 | """ |
220 | 196 | |
221 | - | |
222 | - | |
223 | -# TODO: A mettre dans device/models.py ? | |
224 | -class Image(models.Model): | |
225 | - plan = models.ForeignKey( | |
226 | - 'Plan', on_delete=models.CASCADE, related_name="images") | |
227 | - nrtanalysis = models.ForeignKey( | |
228 | - 'NrtAnalysis', models.DO_NOTHING, blank=True, null=True, related_name="images") | |
229 | - name = models.CharField(max_length=45, blank=True, null=True) | |
230 | - desc = models.TextField(blank=True, null=True) | |
231 | - created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
232 | - updated = models.DateTimeField(blank=True, null=True, auto_now=True) | |
233 | - date_from_gps = models.CharField(max_length=45, blank=True, null=True) | |
234 | - level = models.IntegerField(blank=True, null=True) | |
235 | - type = models.CharField(max_length=5, blank=True, null=True) | |
236 | - quality = models.CharField(max_length=45, blank=True, null=True) | |
237 | - flaggps = models.CharField(max_length=45, blank=True, null=True) | |
238 | - exposure = models.CharField(max_length=45, blank=True, null=True) | |
239 | - tempext = models.CharField(max_length=45, blank=True, null=True) | |
240 | - pressure = models.CharField(max_length=45, blank=True, null=True) | |
241 | - humidext = models.CharField(max_length=45, blank=True, null=True) | |
242 | - wind = models.CharField(max_length=45, blank=True, null=True) | |
243 | - wind_dir = models.CharField(max_length=45, blank=True, null=True) | |
244 | - dwnimg = models.CharField(max_length=45, blank=True, null=True) | |
245 | - dwncata = models.CharField(max_length=45, blank=True, null=True) | |
246 | - dwn = models.CharField(max_length=45, blank=True, null=True) | |
247 | - level0_fits_name = models.CharField(max_length=45, blank=True, null=True) | |
248 | - level1a_fits_name = models.CharField(max_length=45, blank=True, null=True) | |
249 | - level1b_fits_name = models.CharField(max_length=45, blank=True, null=True) | |
250 | - | |
251 | - class Meta: | |
252 | - managed = True | |
253 | - db_table = 'image' | |
254 | - | |
255 | - def __str__(self): | |
256 | - return (str(self.name)) | |
257 | - | |
258 | - | |
259 | - | |
260 | - | |
261 | - | |
262 | - | |
263 | - | |
264 | - | |
265 | - | |
266 | - | |
267 | - | |
268 | 197 | class AgentLogs(models.Model): |
269 | 198 | created = models.DateTimeField(blank=True, null=True, auto_now_add=True) |
270 | 199 | name = models.CharField(max_length=50) |
... | ... | @@ -352,61 +281,6 @@ class AgentSurvey(models.Model): |
352 | 281 | def is_stopping_or_restarting(self): return self.is_stopping() or self.is_restarting() |
353 | 282 | |
354 | 283 | |
355 | -class Album(models.Model): | |
356 | - sequence = models.ForeignKey( | |
357 | - 'Sequence', on_delete=models.CASCADE, related_name="albums") | |
358 | - # detector = models.ForeignKey( | |
359 | - # 'Detector', models.DO_NOTHING, related_name="albums", blank=True, null=True) | |
360 | - #name_of_channel = models.CharField(blank=True,null=True,max_length=150) | |
361 | - name = models.CharField(max_length=45, blank=True, null=True) | |
362 | - desc = models.TextField(blank=True, null=True) | |
363 | - created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
364 | - updated = models.DateTimeField(blank=True, null=True, auto_now=True) | |
365 | - complete = models.BooleanField(default=False) | |
366 | - | |
367 | - class Meta: | |
368 | - managed = True | |
369 | - db_table = 'album' | |
370 | - #verbose_name_plural = "Albums" | |
371 | - | |
372 | - def __str__(self): | |
373 | - return (str(self.name)) | |
374 | - | |
375 | - | |
376 | -class Alert(Request): | |
377 | - request = models.OneToOneField( | |
378 | - 'Request', on_delete=models.CASCADE, default='', parent_link=True) | |
379 | - strategyobs = models.ForeignKey( | |
380 | - 'StrategyObs', models.DO_NOTHING, related_name="alerts", blank=True, null=True) | |
381 | - voevent_file = models.CharField(max_length=45, blank=True, null=True) | |
382 | - author = models.CharField(max_length=45, blank=True, null=True) | |
383 | - burst_jd = models.DecimalField( | |
384 | - max_digits=15, decimal_places=8, blank=True, null=True) | |
385 | - burst_ra = models.FloatField(max_length=45, blank=True, null=True) | |
386 | - burst_dec = models.FloatField(max_length=45, blank=True, null=True) | |
387 | - astro_coord_system = models.CharField(max_length=45, blank=True, null=True) | |
388 | - jd_send = models.DecimalField( | |
389 | - max_digits=15, decimal_places=8, blank=True, null=True) | |
390 | - jd_received = models.DecimalField( | |
391 | - max_digits=15, decimal_places=8, blank=True, null=True) | |
392 | - trig_id = models.IntegerField(blank=True, null=True) | |
393 | - error_radius = models.FloatField(max_length=45, blank=True, null=True) | |
394 | - defly_not_grb = models.BooleanField(default=False) | |
395 | - editor = models.CharField(max_length=45, blank=True, null=True) | |
396 | - soln_status = models.CharField(max_length=45, blank=True, null=True) | |
397 | - pkt_ser_num = models.IntegerField(blank=True, null=True) | |
398 | - | |
399 | - class Meta: | |
400 | - managed = True | |
401 | - db_table = 'alert' | |
402 | - | |
403 | - def __str__(self): | |
404 | - return str(self.trig_id) | |
405 | - | |
406 | - def request_name(self): | |
407 | - return self.__str__() | |
408 | - | |
409 | - request_name.short_description = "Name" | |
410 | 284 | |
411 | 285 | |
412 | 286 | class AgentCmd(models.Model): |
... | ... | @@ -1352,63 +1226,6 @@ class Log(models.Model): |
1352 | 1226 | return (str(self.agent)) |
1353 | 1227 | |
1354 | 1228 | |
1355 | -class NrtAnalysis(models.Model): | |
1356 | - name = models.CharField(max_length=45, blank=True, null=True) | |
1357 | - desc = models.TextField(blank=True, null=True) | |
1358 | - created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
1359 | - updated = models.DateTimeField(blank=True, null=True, auto_now=True) | |
1360 | - analysis = models.TextField(blank=True, null=True) | |
1361 | - | |
1362 | - class Meta: | |
1363 | - managed = True | |
1364 | - db_table = 'nrtanalysis' | |
1365 | - verbose_name_plural = "Nrt analyzes" | |
1366 | - | |
1367 | - def __str__(self): | |
1368 | - return (str(self.name)) | |
1369 | - | |
1370 | - | |
1371 | -""" | |
1372 | -class Plan(models.Model): | |
1373 | - album = models.ForeignKey(Album, on_delete=models.CASCADE, related_name="plans") | |
1374 | - filter = models.ForeignKey(Filter, models.DO_NOTHING, related_name="plans", blank=True, null=True) | |
1375 | - name = models.CharField(max_length=45, blank=True, null=True) | |
1376 | - desc = models.CharField(max_length=45, blank=True, null=True) | |
1377 | - created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
1378 | - updated = models.DateTimeField(blank=True, null=True, auto_now=True) | |
1379 | - duration = models.FloatField(default=0, blank=True, null=True) | |
1380 | - position = models.CharField(max_length=45, blank=True, null=True) | |
1381 | - exposure_time = models.FloatField(blank=True, null=True) | |
1382 | - nb_images = models.IntegerField(blank=True, null=True) | |
1383 | - dithering = models.BooleanField(default=False) | |
1384 | - complete = models.BooleanField(default=False) | |
1385 | - | |
1386 | - class Meta: | |
1387 | - managed = True | |
1388 | - db_table = 'plan' | |
1389 | - | |
1390 | - def __str__(self): | |
1391 | - return (str(self.name)) | |
1392 | -""" | |
1393 | - | |
1394 | - | |
1395 | -class Plan(models.Model): | |
1396 | - album = models.ForeignKey( | |
1397 | - Album, on_delete=models.CASCADE, related_name="plans") | |
1398 | - created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
1399 | - updated = models.DateTimeField(blank=True, null=True, auto_now=True) | |
1400 | - duration = models.FloatField(default=0, blank=True, null=True) | |
1401 | - nb_images = models.PositiveIntegerField( | |
1402 | - blank=True, null=True, validators=[MinValueValidator(1)]) | |
1403 | - config_attributes = models.JSONField(blank=True, null=True) | |
1404 | - complete = models.BooleanField(default=False) | |
1405 | - | |
1406 | - class Meta: | |
1407 | - db_table = "plan" | |
1408 | - | |
1409 | - def __str__(self) -> str: | |
1410 | - return f"Plan of Album {self.album.name} has {self.nb_images} image(s)" | |
1411 | - | |
1412 | 1229 | |
1413 | 1230 | # class Plc(Device): |
1414 | 1231 | # last_update_status = models.DateTimeField(blank=True, null=True) |
... | ... | @@ -1448,6 +1265,44 @@ class PyrosUserManager(UserManager): |
1448 | 1265 | return PyrosUser.objects.filter(Q(user_level__name="Unit-PI") | Q(user_level__name="Unit-board")) |
1449 | 1266 | |
1450 | 1267 | |
1268 | + | |
1269 | + | |
1270 | +class ScientificProgramManager(models.Manager): | |
1271 | + def observable_programs(self): | |
1272 | + exploitable_sp = [] | |
1273 | + | |
1274 | + for sp_period in SP_Period.objects.all(): | |
1275 | + if sp_period.can_submit_sequence(): | |
1276 | + exploitable_sp.append(sp_period.scientific_program.id) | |
1277 | + return ScientificProgram.objects.filter(id__in=exploitable_sp) | |
1278 | + | |
1279 | +class ScientificProgram(models.Model): | |
1280 | + | |
1281 | + created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
1282 | + updated = models.DateTimeField(blank=True, null=True, auto_now=True) | |
1283 | + name = models.CharField(max_length=30, blank=False, | |
1284 | + null=False, default="", unique=True) | |
1285 | + description_short = models.TextField(default="", max_length=320) | |
1286 | + description_long = models.TextField(default="") | |
1287 | + institute = models.ForeignKey( | |
1288 | + Institute, on_delete=models.DO_NOTHING, related_name="scientific_programs") | |
1289 | + sp_pi = models.ForeignKey( | |
1290 | + "PyrosUser", on_delete=models.DO_NOTHING, related_name="Scientific_Program_Users") | |
1291 | + science_theme = models.ForeignKey( | |
1292 | + #"ScienceTheme", on_delete=models.DO_NOTHING, related_name="scientific_program_theme", default=1) | |
1293 | + ScienceTheme, on_delete=models.DO_NOTHING, related_name="scientific_program_theme", default=1) | |
1294 | + is_auto_validated = models.BooleanField(default=False) | |
1295 | + objects = ScientificProgramManager() | |
1296 | + | |
1297 | + class Meta: | |
1298 | + managed = True | |
1299 | + db_table = 'scientific_program' | |
1300 | + | |
1301 | + def __str__(self): | |
1302 | + return (str(self.name)) | |
1303 | + | |
1304 | + | |
1305 | + | |
1451 | 1306 | class PyrosUser(AbstractUser): |
1452 | 1307 | username = models.CharField( |
1453 | 1308 | max_length=255, blank=False, null=False, unique=True) |
... | ... | @@ -1483,7 +1338,8 @@ class PyrosUser(AbstractUser): |
1483 | 1338 | validator = models.ForeignKey( |
1484 | 1339 | "PyrosUser", on_delete=models.DO_NOTHING, null=True, related_name="pyros_users") |
1485 | 1340 | referee_themes = models.ManyToManyField( |
1486 | - "ScienceTheme", related_name="referee_themes", blank=True) | |
1341 | + #"ScienceTheme", related_name="referee_themes", blank=True) | |
1342 | + ScienceTheme, related_name="referee_themes", blank=True) | |
1487 | 1343 | |
1488 | 1344 | objects = PyrosUserManager() |
1489 | 1345 | |
... | ... | @@ -1577,42 +1433,6 @@ class PyrosUser(AbstractUser): |
1577 | 1433 | sp_pi=self.id) |
1578 | 1434 | return sp_where_user_is_sp_pi |
1579 | 1435 | |
1580 | -# class Schedule(models.Model): | |
1581 | -# created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
1582 | -# plan_start = models.DecimalField( | |
1583 | -# default=0.0, max_digits=15, decimal_places=8) | |
1584 | -# plan_end = models.DecimalField( | |
1585 | -# default=0.0, max_digits=15, decimal_places=8) | |
1586 | -# flag = models.CharField(max_length=45, blank=True, null=True) | |
1587 | -# | |
1588 | -# class Meta: | |
1589 | -# managed = True | |
1590 | -# db_table = 'schedule' | |
1591 | -# | |
1592 | -# def __str__(self): | |
1593 | -# return (str(self.created)) | |
1594 | - | |
1595 | - | |
1596 | -class Schedule(models.Model): | |
1597 | - sequences = models.ManyToManyField( | |
1598 | - 'Sequence', through='ScheduleHasSequences', related_name='schedules') | |
1599 | - created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
1600 | - plan_night_start = models.DecimalField( | |
1601 | - default=0.0, max_digits=15, decimal_places=8) | |
1602 | - plan_end = models.DecimalField( | |
1603 | - default=0.0, max_digits=15, decimal_places=8) | |
1604 | - plan_start = models.DecimalField( | |
1605 | - default=0.0, max_digits=15, decimal_places=8) | |
1606 | - flag = models.CharField(max_length=45, blank=True, null=True) | |
1607 | - | |
1608 | - class Meta: | |
1609 | - managed = True | |
1610 | - db_table = 'schedule' | |
1611 | - verbose_name_plural = "Schedules" | |
1612 | - | |
1613 | - def __str__(self): | |
1614 | - return (str(self.created)) | |
1615 | - | |
1616 | 1436 | |
1617 | 1437 | class PeriodManager(models.Manager): |
1618 | 1438 | # to get the currently active period, use exploitation_period() |
... | ... | @@ -1785,39 +1605,7 @@ class Period(models.Model): |
1785 | 1605 | db_table = "period" |
1786 | 1606 | |
1787 | 1607 | |
1788 | -class ScientificProgramManager(models.Manager): | |
1789 | - def observable_programs(self): | |
1790 | - exploitable_sp = [] | |
1791 | - | |
1792 | - for sp_period in SP_Period.objects.all(): | |
1793 | - if sp_period.can_submit_sequence(): | |
1794 | - exploitable_sp.append(sp_period.scientific_program.id) | |
1795 | - return ScientificProgram.objects.filter(id__in=exploitable_sp) | |
1796 | - | |
1797 | - | |
1798 | -class ScientificProgram(models.Model): | |
1799 | - | |
1800 | - created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
1801 | - updated = models.DateTimeField(blank=True, null=True, auto_now=True) | |
1802 | - name = models.CharField(max_length=30, blank=False, | |
1803 | - null=False, default="", unique=True) | |
1804 | - description_short = models.TextField(default="", max_length=320) | |
1805 | - description_long = models.TextField(default="") | |
1806 | - institute = models.ForeignKey( | |
1807 | - Institute, on_delete=models.DO_NOTHING, related_name="scientific_programs") | |
1808 | - sp_pi = models.ForeignKey( | |
1809 | - "PyrosUser", on_delete=models.DO_NOTHING, related_name="Scientific_Program_Users") | |
1810 | - science_theme = models.ForeignKey( | |
1811 | - "ScienceTheme", on_delete=models.DO_NOTHING, related_name="scientific_program_theme", default=1) | |
1812 | - is_auto_validated = models.BooleanField(default=False) | |
1813 | - objects = ScientificProgramManager() | |
1814 | - | |
1815 | - class Meta: | |
1816 | - managed = True | |
1817 | - db_table = 'scientific_program' | |
1818 | 1608 | |
1819 | - def __str__(self): | |
1820 | - return (str(self.name)) | |
1821 | 1609 | |
1822 | 1610 | |
1823 | 1611 | class SP_Period(models.Model): |
... | ... | @@ -1934,135 +1722,6 @@ class SP_PeriodWorkflow(models.Model): |
1934 | 1722 | class Meta: |
1935 | 1723 | db_table = "sp_period_workflow" |
1936 | 1724 | |
1937 | -class Sequence(models.Model): | |
1938 | - | |
1939 | - """ Definition of Status enum values """ | |
1940 | - INVALID = "INVL" | |
1941 | - DRAFT = "DRAFT" | |
1942 | - | |
1943 | - # INCOMPLETE = "INCPL" | |
1944 | - # COMPLETE = "CPL" | |
1945 | - TOBEPLANNED = "TBP" | |
1946 | - PLANNED = "PLND" | |
1947 | - UNPLANNABLE = "UNPLN" | |
1948 | - REJECTED = "RJTD" | |
1949 | - REC_RUNNING = "RUN" | |
1950 | - REC_FINISHED = "EXD" | |
1951 | - REC_CANCELED = "CNCLD" | |
1952 | - PROC_RUNNING = "PROC_RUN" | |
1953 | - PROC_CANCELED = "PROC_CNCLD" | |
1954 | - PROC_FINISHED = "PROC_EXD" | |
1955 | - # PENDING = "PNDG" | |
1956 | - # EXECUTING = "EXING" | |
1957 | - # EXECUTED = "EXD" | |
1958 | - CANCELLED = "CNCLD" | |
1959 | - STATUS_CHOICES = ( | |
1960 | - (INVALID, "Invalid"), | |
1961 | - (DRAFT, "DRAFT"), | |
1962 | - (TOBEPLANNED, "To be planned"), | |
1963 | - (PLANNED, "Planned"), | |
1964 | - (UNPLANNABLE, "Unplannable"), | |
1965 | - (REJECTED, "Rejected"), | |
1966 | - (REC_RUNNING, "Recording running"), | |
1967 | - (REC_FINISHED, "Recording finished"), | |
1968 | - (REC_CANCELED, "Recording canceled"), | |
1969 | - (PROC_RUNNING, "Processing running"), | |
1970 | - (PROC_FINISHED, "Processing finished"), | |
1971 | - (PROC_CANCELED, "Processing canceled"), | |
1972 | - (CANCELLED, "Cancelled"), | |
1973 | - ) | |
1974 | - START_EXPO_PREF_CHOICES = ( | |
1975 | - ('IMMEDIATE', 'IMMEDIATE'), | |
1976 | - ('BEST_ELEVATION', 'BEST_ELEVATION'), | |
1977 | - ('NO_CONSTRAINT', 'NO_CONSTRAINT'), | |
1978 | - ) | |
1979 | - | |
1980 | - start_expo_pref = models.CharField(max_length=50, blank=False, null=True, | |
1981 | - choices=START_EXPO_PREF_CHOICES, default=START_EXPO_PREF_CHOICES[0]) | |
1982 | - # request = models.ForeignKey( | |
1983 | - # Request, on_delete=models.CASCADE, related_name="sequences") | |
1984 | - pyros_user = models.ForeignKey( | |
1985 | - 'PyrosUser', on_delete=models.DO_NOTHING, related_name="sequences", blank=True, null=True) | |
1986 | - scientific_program = models.ForeignKey( | |
1987 | - 'ScientificProgram', on_delete=models.DO_NOTHING, related_name="sequences", blank=True, null=True) | |
1988 | - name = models.CharField(max_length=45, blank=True, null=True, unique=True) | |
1989 | - desc = models.TextField(blank=True, null=True) | |
1990 | - created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
1991 | - updated = models.DateTimeField(blank=True, null=True, auto_now=True) | |
1992 | - last_modified_by = models.ForeignKey( | |
1993 | - 'PyrosUser', on_delete=models.DO_NOTHING, related_name="+", blank=True, null=True) | |
1994 | - is_alert = models.BooleanField(default=False) | |
1995 | - status = models.CharField( | |
1996 | - max_length=11, blank=True, null=True, choices=STATUS_CHOICES) | |
1997 | - target_coords = models.CharField(max_length=100, blank=True, null=True) | |
1998 | - with_drift = models.BooleanField(default=False) | |
1999 | - priority = models.IntegerField(blank=True, null=True) | |
2000 | - analysis_method = models.CharField(max_length=45, blank=True, null=True) | |
2001 | - moon_min = models.IntegerField(blank=True, null=True) | |
2002 | - alt_min = models.IntegerField(blank=True, null=True) | |
2003 | - type = models.CharField(max_length=6, blank=True, null=True) | |
2004 | - img_current = models.CharField(max_length=45, blank=True, null=True) | |
2005 | - img_total = models.CharField(max_length=45, blank=True, null=True) | |
2006 | - not_obs = models.BooleanField(default=False) | |
2007 | - obsolete = models.BooleanField(default=False) | |
2008 | - processing = models.BooleanField(default=False) | |
2009 | - flag = models.CharField(max_length=45, blank=True, null=True) | |
2010 | - period = models.ForeignKey( | |
2011 | - "Period", on_delete=models.DO_NOTHING, related_name="sequences", blank=True, null=True) | |
2012 | - | |
2013 | - start_date = models.DateTimeField( | |
2014 | - blank=True, null=True, default=timezone.now, editable=True) | |
2015 | - end_date = models.DateTimeField( | |
2016 | - blank=True, null=True, default=timezone.now, editable=True) | |
2017 | - # jd1 et jd2 = julian day start / end | |
2018 | - jd1 = models.DecimalField(default=0.0, max_digits=15, decimal_places=8) | |
2019 | - jd2 = models.DecimalField(default=0.0, max_digits=15, decimal_places=8) | |
2020 | - tolerance_before = models.CharField( | |
2021 | - max_length=50, default="1s", blank=True, null=True) | |
2022 | - tolerance_after = models.CharField( | |
2023 | - max_length=50, default="1min", blank=True, null=True) | |
2024 | - | |
2025 | - #t_prefered = models.DecimalField(default=-1.0, max_digits=15, decimal_places=8) | |
2026 | - duration = models.DecimalField( | |
2027 | - default=-1.0, max_digits=15, decimal_places=8) | |
2028 | - # décomposer duration en duration pointing + duration album | |
2029 | - overhead = models.DecimalField(default=0, max_digits=15, decimal_places=8) | |
2030 | - submitted = models.BooleanField(default=False) | |
2031 | - config_attributes = models.JSONField(blank=True, null=True) | |
2032 | - | |
2033 | - ra = models.FloatField(blank=True, null=True) | |
2034 | - dec = models.FloatField(blank=True, null=True) | |
2035 | - complete = models.BooleanField(default=False, null=True, blank=True) | |
2036 | - | |
2037 | - class Meta: | |
2038 | - managed = True | |
2039 | - db_table = 'sequence' | |
2040 | - | |
2041 | - def __str__(self): | |
2042 | - return (str(self.name)) | |
2043 | - | |
2044 | - | |
2045 | -class ScheduleHasSequences(models.Model): | |
2046 | - # (EP) TODO: C'est pas un pb d'utiliser 2 fois le meme nom "shs" pour 2 choses differentes ???!!! | |
2047 | - schedule = models.ForeignKey( | |
2048 | - 'Schedule', on_delete=models.CASCADE, related_name="shs") | |
2049 | - sequence = models.ForeignKey( | |
2050 | - 'Sequence', on_delete=models.CASCADE, related_name="shs") | |
2051 | - | |
2052 | - status = models.CharField( | |
2053 | - max_length=11, blank=True, null=True, choices=Sequence.STATUS_CHOICES) | |
2054 | - desc = models.CharField(max_length=45, blank=True, null=True) | |
2055 | - tsp = models.DecimalField(default=-1.0, max_digits=15, decimal_places=8) | |
2056 | - tep = models.DecimalField(default=-1.0, max_digits=15, decimal_places=8) | |
2057 | - deltaTL = models.DecimalField( | |
2058 | - default=-1.0, max_digits=15, decimal_places=8) | |
2059 | - deltaTR = models.DecimalField( | |
2060 | - default=-1.0, max_digits=15, decimal_places=8) | |
2061 | - | |
2062 | - class Meta: | |
2063 | - managed = True | |
2064 | - db_table = 'schedule_has_sequences' | |
2065 | - | |
2066 | 1725 | |
2067 | 1726 | class SiteWatch(models.Model): |
2068 | 1727 | OPEN = "OPEN" |
... | ... | @@ -2131,21 +1790,6 @@ class SiteWatchHistory(models.Model): |
2131 | 1790 | verbose_name_plural = "Site watch histories" |
2132 | 1791 | |
2133 | 1792 | |
2134 | -class StrategyObs(models.Model): | |
2135 | - name = models.CharField(max_length=45, blank=True, null=True) | |
2136 | - desc = models.TextField(blank=True, null=True) | |
2137 | - xml_file = models.CharField(max_length=45, blank=True, null=True) | |
2138 | - is_default = models.BooleanField(default=False) | |
2139 | - | |
2140 | - class Meta: | |
2141 | - managed = True | |
2142 | - db_table = 'strategyobs' | |
2143 | - verbose_name_plural = "Strategy obs" | |
2144 | - | |
2145 | - def __str__(self): | |
2146 | - return (str(self.name)) | |
2147 | - | |
2148 | - | |
2149 | 1793 | # TODO: à virer car utilisé pour Celery (ou bien à utiliser pour les agents) |
2150 | 1794 | class TaskId(models.Model): |
2151 | 1795 | task = models.CharField(max_length=45, blank=True, null=True) | ... | ... |
src/core/pyros_django/devices/models.py
... | ... | @@ -180,19 +180,19 @@ class Company(models.Model): |
180 | 180 | """ |
181 | 181 | |
182 | 182 | |
183 | -''' | |
184 | 183 | # --- |
185 | 184 | # --- Utility functions |
186 | 185 | # --- |
187 | 186 | |
187 | +''' | |
188 | 188 | def printd(*args, **kwargs): |
189 | 189 | if os.environ.get('PYROS_DEBUG', '0') == '1': |
190 | 190 | print('(MODEL)', *args, **kwargs) |
191 | +''' | |
191 | 192 | |
192 | 193 | def get_or_create_unique_row_from_model(model: models.Model): |
193 | 194 | # return model.objects.get(id=1) if model.objects.exists() else model.objects.create(id=1) |
194 | 195 | return model.objects.first() if model.objects.exists() else model.objects.create(id=1) |
195 | -''' | |
196 | 196 | |
197 | 197 | |
198 | 198 | """ | ... | ... |
src/core/pyros_django/pyros/urls.py
... | ... | @@ -37,9 +37,11 @@ urlpatterns = [ |
37 | 37 | path('majordome/', include('majordome.urls')), |
38 | 38 | path('monitoring/', include('monitoring.urls')), |
39 | 39 | path('observation_manager/', include('observation_manager.urls')), |
40 | + | |
41 | + path('scientific_program/', include('scientific_program.urls')), | |
40 | 42 | path('routine_manager/', include('routine_manager.urls')), |
43 | + | |
41 | 44 | path('user_manager/', include('user_manager.urls')), |
42 | - path('scientific_program/', include('scientific_program.urls')), | |
43 | 45 | path('obs_config/', include('obsconfig.urls')), |
44 | 46 | path('api/', include('api.urls')), |
45 | 47 | path('', dashboard.views.index) | ... | ... |
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 | |
4 | 3 | from django.forms.widgets import RadioSelect |
4 | + | |
5 | +from django.db.models.fields import PositiveIntegerField | |
6 | + | |
5 | 7 | from common.models import * |
8 | +from routine_manager.models import Sequence, Album, Plan #, Request | |
9 | + | |
6 | 10 | from routine_manager.validators import check_album_validity |
7 | 11 | |
8 | 12 | import logging,ast |
9 | 13 | |
10 | 14 | from src.core.pyros_django.routine_manager.widgets import LinkedSelectWidget |
15 | + | |
11 | 16 | log = logging.getLogger("routine_manager-views") |
12 | 17 | |
18 | + | |
19 | +''' | |
13 | 20 | class RequestForm(forms.ModelForm): |
14 | 21 | """ |
15 | 22 | Form for Request edition |
... | ... | @@ -28,6 +35,7 @@ class RequestForm(forms.ModelForm): |
28 | 35 | field.widget.attrs['readonly'] = True |
29 | 36 | field.widget.attrs['class'] = 'form-control' |
30 | 37 | field.required = True |
38 | +''' | |
31 | 39 | |
32 | 40 | |
33 | 41 | class SequenceForm(forms.ModelForm): | ... | ... |
src/core/pyros_django/routine_manager/functions.py
1 | 1 | import ast |
2 | 2 | from src.core.pyros_django.obsconfig.obsconfig_class import OBSConfig |
3 | 3 | from .forms import SequenceForm, AlbumForm, PlanForm |
4 | -from common.models import PyrosUser, ScientificProgram, Sequence, Album, Plan, Period | |
4 | +from common.models import PyrosUser, ScientificProgram, Period | |
5 | +from routine_manager.models import Sequence, Album, Plan | |
5 | 6 | import datetime |
6 | 7 | import os, yaml |
7 | 8 | ... | ... |
src/core/pyros_django/routine_manager/models.py
1 | +# models_D3_Seq_submit_and_planning | |
2 | + | |
3 | +#from django.db import models | |
4 | + | |
5 | +##from __future__ import unicode_literals | |
6 | + | |
7 | +# (EP 21/9/22) To allow autoreferencing (ex: AgentCmd.create() returns a AgentCmd) | |
8 | +from __future__ import annotations | |
9 | + | |
10 | +# Stdlib imports | |
11 | +from numpy import False_ | |
12 | +from src.device_controller.abstract_component.device_controller import DeviceCmd | |
13 | +from enum import Enum | |
14 | +from datetime import datetime, timedelta, date | |
15 | +from dateutil.relativedelta import relativedelta | |
16 | +import os | |
17 | +import sys | |
18 | +from typing import Any, List, Tuple, Optional | |
19 | +import re | |
20 | + | |
21 | +# DJANGO imports | |
22 | +from django.core.validators import MaxValueValidator, MinValueValidator | |
23 | +from django.contrib.auth.models import AbstractUser, UserManager | |
1 | 24 | from django.db import models |
25 | +from django.db.models import Q, Max | |
26 | +from django.core.validators import MaxValueValidator, MinValueValidator | |
27 | +from django.db.models.deletion import DO_NOTHING | |
28 | +from django.db.models.expressions import F | |
29 | +from django.db.models.query import QuerySet | |
30 | +from model_utils import Choices | |
31 | +from django.utils import timezone | |
32 | + | |
33 | +# Project imports | |
34 | +from common.models import PyrosUser, ScientificProgram, Period | |
35 | +# DeviceCommand is used by class Command | |
36 | +sys.path.append("../../..") | |
37 | + | |
38 | +''' | |
39 | +NOT USED - to be removed | |
40 | +class PyrosState(Enum): | |
41 | + START = 'Starting' | |
42 | + PA = 'Passive' | |
43 | + INI = "INIT" | |
44 | + STAND = "Standby" | |
45 | + SCHED_START = 'Scheduler startup' | |
46 | + SCHED = 'Scheduler' | |
47 | + SCHED_CLOSE = 'Scheduler closing' | |
48 | +''' | |
49 | + | |
50 | + | |
51 | +""" | |
52 | +STYLE RULES | |
53 | +=========== | |
54 | +https://simpleisbetterthancomplex.com/tips/2018/02/10/django-tip-22-designing-better-models.html | |
55 | +https://steelkiwi.com/blog/best-practices-working-django-models-python/ | |
56 | + | |
57 | +- Model name => singular | |
58 | + Call it Company instead of Companies. | |
59 | + A model definition is the representation of a single object (the object in this example is a company), | |
60 | + and not a collection of companies | |
61 | + The model definition is a class, so always use CapWords convention (no underscores) | |
62 | + E.g. User, Permission, ContentType, etc. | |
63 | + | |
64 | +- For the model’s attributes use snake_case. | |
65 | + E.g. first_name, last_name, etc | |
66 | + | |
67 | +- Blank and Null Fields (https://simpleisbetterthancomplex.com/tips/2016/07/25/django-tip-8-blank-or-null.html) | |
68 | + - Null: It is database-related. Defines if a given database column will accept null values or not. | |
69 | + - Blank: It is validation-related. It will be used during forms validation, when calling form.is_valid(). | |
70 | + Do not use null=True for text-based fields that are optional. | |
71 | + Otherwise, you will end up having two possible values for “no data”, that is: None and an empty string. | |
72 | + Having two possible values for “no data” is redundant. | |
73 | + The Django convention is to use the empty string, not NULL. | |
74 | + Example: | |
75 | + # The default values of `null` and `blank` are `False`. | |
76 | + class Person(models.Model): | |
77 | + name = models.CharField(max_length=255) # Mandatory | |
78 | + bio = models.TextField(max_length=500, blank=True) # Optional (don't put null=True) | |
79 | + birth_date = models.DateField(null=True, blank=True) # Optional (here you may add null=True) | |
80 | + The default values of null and blank are False. | |
81 | + Special case, when you need to accept NULL values for a BooleanField, use NullBooleanField instead. | |
82 | + | |
83 | +- Choices : you can use Choices from the model_utils library. Take model Article, for instance: | |
84 | + from model_utils import Choices | |
85 | + class Article(models.Model): | |
86 | + STATUSES = Choices( | |
87 | + (0, 'draft', _('draft')), | |
88 | + (1, 'published', _('published')) ) | |
89 | + status = models.IntegerField(choices=STATUSES, default=STATUSES.draft) | |
90 | + | |
91 | +- Reverse Relationships | |
92 | + | |
93 | + - related_name : | |
94 | + Rule of thumb: if you are not sure what would be the related_name, | |
95 | + use the plural of the model holding the ForeignKey. | |
96 | + ex: | |
97 | + class Company: | |
98 | + name = models.CharField(max_length=30) | |
99 | + class Employee: | |
100 | + first_name = models.CharField(max_length=30) | |
101 | + last_name = models.CharField(max_length=30) | |
102 | + company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='employees') | |
103 | + usage: | |
104 | + google = Company.objects.get(name='Google') | |
105 | + google.employees.all() | |
106 | + You can also use the reverse relationship to modify the company field on the Employee instances: | |
107 | + vitor = Employee.objects.get(first_name='Vitor') | |
108 | + google = Company.objects.get(name='Google') | |
109 | + google.employees.add(vitor) | |
110 | + | |
111 | + - related_query_name : | |
112 | + This kind of relationship also applies to query filters. | |
113 | + For example, if I wanted to list all companies that employs people named ‘Vitor’, I could do the following: | |
114 | + companies = Company.objects.filter(employee__first_name='Vitor') | |
115 | + If you want to customize the name of this relationship, here is how we do it: | |
116 | + class Employee: | |
117 | + first_name = models.CharField(max_length=30) | |
118 | + last_name = models.CharField(max_length=30) | |
119 | + company = models.ForeignKey( | |
120 | + Company, | |
121 | + on_delete=models.CASCADE, | |
122 | + related_name='employees', | |
123 | + related_query_name='person' | |
124 | + ) | |
125 | + Then the usage would be: | |
126 | + companies = Company.objects.filter(person__first_name='Vitor') | |
127 | + | |
128 | + To use it consistently, related_name goes as plural and related_query_name goes as singular. | |
129 | + | |
130 | + | |
131 | +GENERAL EXAMPLE | |
132 | +======= | |
133 | + | |
134 | +from django.db import models | |
135 | +from django.urls import reverse | |
136 | + | |
137 | +class Company(models.Model): | |
138 | + # CHOICES | |
139 | + PUBLIC_LIMITED_COMPANY = 'PLC' | |
140 | + PRIVATE_COMPANY_LIMITED = 'LTD' | |
141 | + LIMITED_LIABILITY_PARTNERSHIP = 'LLP' | |
142 | + COMPANY_TYPE_CHOICES = ( | |
143 | + (PUBLIC_LIMITED_COMPANY, 'Public limited company'), | |
144 | + (PRIVATE_COMPANY_LIMITED, 'Private company limited by shares'), | |
145 | + (LIMITED_LIABILITY_PARTNERSHIP, 'Limited liability partnership'), | |
146 | + ) | |
147 | + | |
148 | + # DATABASE FIELDS | |
149 | + name = models.CharField('name', max_length=30) | |
150 | + vat_identification_number = models.CharField('VAT', max_length=20) | |
151 | + company_type = models.CharField('type', max_length=3, choices=COMPANY_TYPE_CHOICES) | |
152 | + | |
153 | + # MANAGERS | |
154 | + objects = models.Manager() | |
155 | + limited_companies = LimitedCompanyManager() | |
156 | + | |
157 | + # META CLASS | |
158 | + class Meta: | |
159 | + verbose_name = 'company' | |
160 | + verbose_name_plural = 'companies' | |
161 | + | |
162 | + # TO STRING METHOD | |
163 | + def __str__(self): | |
164 | + return self.name | |
165 | + | |
166 | + # SAVE METHOD | |
167 | + def save(self, *args, **kwargs): | |
168 | + do_something() | |
169 | + super().save(*args, **kwargs) # Call the "real" save() method. | |
170 | + do_something_else() | |
171 | + | |
172 | + # ABSOLUTE URL METHOD | |
173 | + def get_absolute_url(self): | |
174 | + return reverse('company_details', kwargs={'pk': self.id}) | |
175 | + | |
176 | + # OTHER METHODS | |
177 | + def process_invoices(self): | |
178 | + do_something() | |
179 | + | |
180 | +""" | |
181 | + | |
182 | + | |
183 | + | |
184 | + | |
185 | +# --- | |
186 | +# --- Utility functions | |
187 | +# --- | |
188 | + | |
189 | +''' | |
190 | +def printd(*args, **kwargs): | |
191 | + if os.environ.get('PYROS_DEBUG', '0') == '1': | |
192 | + print('(MODEL)', *args, **kwargs) | |
193 | + | |
194 | +def get_or_create_unique_row_from_model(model: models.Model): | |
195 | + # return model.objects.get(id=1) if model.objects.exists() else model.objects.create(id=1) | |
196 | + return model.objects.first() if model.objects.exists() else model.objects.create(id=1) | |
197 | +''' | |
198 | + | |
199 | + | |
200 | +""" | |
201 | +------------------------ | |
202 | + BASE MODEL CLASSES | |
203 | +------------------------ | |
204 | +""" | |
205 | + | |
206 | +class Request(models.Model): | |
207 | + pyros_user = models.ForeignKey(PyrosUser, on_delete=models.DO_NOTHING, related_name="requests") | |
208 | + #pyros_user = models.ForeignKey('PyrosUser', on_delete=models.DO_NOTHING, related_name="requests") | |
209 | + scientific_program = models.ForeignKey(ScientificProgram, on_delete=models.DO_NOTHING, related_name="requests", blank=True, null=True) | |
210 | + #scientific_program = models.ForeignKey('ScientificProgram', on_delete=models.DO_NOTHING, related_name="requests", blank=True, null=True) | |
211 | + name = models.CharField(max_length=45, blank=True, null=True) | |
212 | + desc = models.TextField(blank=True, null=True) | |
213 | + created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
214 | + updated = models.DateTimeField(blank=True, null=True, auto_now=True) | |
215 | + is_alert = models.BooleanField(default=False) | |
216 | + target_type = models.CharField(max_length=8, blank=True, null=True) | |
217 | + status = models.CharField(max_length=10, blank=True, null=True) | |
218 | + autodeposit = models.BooleanField(default=False) | |
219 | + checkpoint = models.CharField(max_length=45, blank=True, null=True) | |
220 | + flag = models.CharField(max_length=45, blank=True, null=True) | |
221 | + complete = models.BooleanField(default=False) | |
222 | + submitted = models.BooleanField(default=False) | |
223 | + | |
224 | + class Meta: | |
225 | + managed = True | |
226 | + db_table = 'request' | |
227 | + | |
228 | + def __str__(self): | |
229 | + return (str(self.name)) | |
230 | + | |
231 | + | |
232 | +""" | |
233 | +------------------------ | |
234 | + OTHER MODEL CLASSES | |
235 | +------------------------ | |
236 | +""" | |
237 | + | |
238 | + | |
239 | +class Album(models.Model): | |
240 | + sequence = models.ForeignKey('Sequence', on_delete=models.CASCADE, related_name="albums") | |
241 | + # detector = models.ForeignKey( | |
242 | + # 'Detector', models.DO_NOTHING, related_name="albums", blank=True, null=True) | |
243 | + #name_of_channel = models.CharField(blank=True,null=True,max_length=150) | |
244 | + name = models.CharField(max_length=45, blank=True, null=True) | |
245 | + desc = models.TextField(blank=True, null=True) | |
246 | + created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
247 | + updated = models.DateTimeField(blank=True, null=True, auto_now=True) | |
248 | + complete = models.BooleanField(default=False) | |
249 | + | |
250 | + class Meta: | |
251 | + managed = True | |
252 | + db_table = 'album' | |
253 | + #verbose_name_plural = "Albums" | |
254 | + | |
255 | + def __str__(self): | |
256 | + return (str(self.name)) | |
257 | + | |
258 | + | |
259 | +class Alert(Request): | |
260 | + request = models.OneToOneField(Request, on_delete=models.CASCADE, default='', parent_link=True) | |
261 | + strategyobs = models.ForeignKey('StrategyObs', models.DO_NOTHING, related_name="alerts", blank=True, null=True) | |
262 | + voevent_file = models.CharField(max_length=45, blank=True, null=True) | |
263 | + author = models.CharField(max_length=45, blank=True, null=True) | |
264 | + burst_jd = models.DecimalField( | |
265 | + max_digits=15, decimal_places=8, blank=True, null=True) | |
266 | + burst_ra = models.FloatField(max_length=45, blank=True, null=True) | |
267 | + burst_dec = models.FloatField(max_length=45, blank=True, null=True) | |
268 | + astro_coord_system = models.CharField(max_length=45, blank=True, null=True) | |
269 | + jd_send = models.DecimalField( | |
270 | + max_digits=15, decimal_places=8, blank=True, null=True) | |
271 | + jd_received = models.DecimalField( | |
272 | + max_digits=15, decimal_places=8, blank=True, null=True) | |
273 | + trig_id = models.IntegerField(blank=True, null=True) | |
274 | + error_radius = models.FloatField(max_length=45, blank=True, null=True) | |
275 | + defly_not_grb = models.BooleanField(default=False) | |
276 | + editor = models.CharField(max_length=45, blank=True, null=True) | |
277 | + soln_status = models.CharField(max_length=45, blank=True, null=True) | |
278 | + pkt_ser_num = models.IntegerField(blank=True, null=True) | |
279 | + | |
280 | + class Meta: | |
281 | + managed = True | |
282 | + db_table = 'alert' | |
283 | + | |
284 | + def __str__(self): | |
285 | + return str(self.trig_id) | |
286 | + | |
287 | + def request_name(self): | |
288 | + return self.__str__() | |
289 | + | |
290 | + request_name.short_description = "Name" | |
291 | + | |
292 | + | |
293 | + | |
294 | +class NrtAnalysis(models.Model): | |
295 | + name = models.CharField(max_length=45, blank=True, null=True) | |
296 | + desc = models.TextField(blank=True, null=True) | |
297 | + created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
298 | + updated = models.DateTimeField(blank=True, null=True, auto_now=True) | |
299 | + analysis = models.TextField(blank=True, null=True) | |
300 | + | |
301 | + class Meta: | |
302 | + managed = True | |
303 | + db_table = 'nrtanalysis' | |
304 | + verbose_name_plural = "Nrt analyzes" | |
305 | + | |
306 | + def __str__(self): | |
307 | + return (str(self.name)) | |
308 | + | |
309 | + | |
310 | +""" | |
311 | +class Plan(models.Model): | |
312 | + album = models.ForeignKey(Album, on_delete=models.CASCADE, related_name="plans") | |
313 | + filter = models.ForeignKey(Filter, models.DO_NOTHING, related_name="plans", blank=True, null=True) | |
314 | + name = models.CharField(max_length=45, blank=True, null=True) | |
315 | + desc = models.CharField(max_length=45, blank=True, null=True) | |
316 | + created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
317 | + updated = models.DateTimeField(blank=True, null=True, auto_now=True) | |
318 | + duration = models.FloatField(default=0, blank=True, null=True) | |
319 | + position = models.CharField(max_length=45, blank=True, null=True) | |
320 | + exposure_time = models.FloatField(blank=True, null=True) | |
321 | + nb_images = models.IntegerField(blank=True, null=True) | |
322 | + dithering = models.BooleanField(default=False) | |
323 | + complete = models.BooleanField(default=False) | |
324 | + | |
325 | + class Meta: | |
326 | + managed = True | |
327 | + db_table = 'plan' | |
328 | + | |
329 | + def __str__(self): | |
330 | + return (str(self.name)) | |
331 | +""" | |
332 | + | |
333 | + | |
334 | +class Plan(models.Model): | |
335 | + album = models.ForeignKey( | |
336 | + Album, on_delete=models.CASCADE, related_name="plans") | |
337 | + created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
338 | + updated = models.DateTimeField(blank=True, null=True, auto_now=True) | |
339 | + duration = models.FloatField(default=0, blank=True, null=True) | |
340 | + nb_images = models.PositiveIntegerField( | |
341 | + blank=True, null=True, validators=[MinValueValidator(1)]) | |
342 | + config_attributes = models.JSONField(blank=True, null=True) | |
343 | + complete = models.BooleanField(default=False) | |
344 | + | |
345 | + class Meta: | |
346 | + db_table = "plan" | |
347 | + | |
348 | + def __str__(self) -> str: | |
349 | + return f"Plan of Album {self.album.name} has {self.nb_images} image(s)" | |
350 | + | |
351 | + | |
352 | + | |
353 | +class Image(models.Model): | |
354 | + plan = models.ForeignKey( | |
355 | + 'Plan', on_delete=models.CASCADE, related_name="images") | |
356 | + nrtanalysis = models.ForeignKey( | |
357 | + 'NrtAnalysis', models.DO_NOTHING, blank=True, null=True, related_name="images") | |
358 | + name = models.CharField(max_length=45, blank=True, null=True) | |
359 | + desc = models.TextField(blank=True, null=True) | |
360 | + created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
361 | + updated = models.DateTimeField(blank=True, null=True, auto_now=True) | |
362 | + date_from_gps = models.CharField(max_length=45, blank=True, null=True) | |
363 | + level = models.IntegerField(blank=True, null=True) | |
364 | + type = models.CharField(max_length=5, blank=True, null=True) | |
365 | + quality = models.CharField(max_length=45, blank=True, null=True) | |
366 | + flaggps = models.CharField(max_length=45, blank=True, null=True) | |
367 | + exposure = models.CharField(max_length=45, blank=True, null=True) | |
368 | + tempext = models.CharField(max_length=45, blank=True, null=True) | |
369 | + pressure = models.CharField(max_length=45, blank=True, null=True) | |
370 | + humidext = models.CharField(max_length=45, blank=True, null=True) | |
371 | + wind = models.CharField(max_length=45, blank=True, null=True) | |
372 | + wind_dir = models.CharField(max_length=45, blank=True, null=True) | |
373 | + dwnimg = models.CharField(max_length=45, blank=True, null=True) | |
374 | + dwncata = models.CharField(max_length=45, blank=True, null=True) | |
375 | + dwn = models.CharField(max_length=45, blank=True, null=True) | |
376 | + level0_fits_name = models.CharField(max_length=45, blank=True, null=True) | |
377 | + level1a_fits_name = models.CharField(max_length=45, blank=True, null=True) | |
378 | + level1b_fits_name = models.CharField(max_length=45, blank=True, null=True) | |
379 | + | |
380 | + class Meta: | |
381 | + managed = True | |
382 | + db_table = 'image' | |
383 | + | |
384 | + def __str__(self): | |
385 | + return (str(self.name)) | |
386 | + | |
387 | + | |
388 | + | |
389 | + | |
390 | +# class Schedule(models.Model): | |
391 | +# created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
392 | +# plan_start = models.DecimalField( | |
393 | +# default=0.0, max_digits=15, decimal_places=8) | |
394 | +# plan_end = models.DecimalField( | |
395 | +# default=0.0, max_digits=15, decimal_places=8) | |
396 | +# flag = models.CharField(max_length=45, blank=True, null=True) | |
397 | +# | |
398 | +# class Meta: | |
399 | +# managed = True | |
400 | +# db_table = 'schedule' | |
401 | +# | |
402 | +# def __str__(self): | |
403 | +# return (str(self.created)) | |
404 | + | |
405 | +class Schedule(models.Model): | |
406 | + sequences = models.ManyToManyField( | |
407 | + 'Sequence', through='ScheduleHasSequences', related_name='schedules') | |
408 | + created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
409 | + plan_night_start = models.DecimalField( | |
410 | + default=0.0, max_digits=15, decimal_places=8) | |
411 | + plan_end = models.DecimalField( | |
412 | + default=0.0, max_digits=15, decimal_places=8) | |
413 | + plan_start = models.DecimalField( | |
414 | + default=0.0, max_digits=15, decimal_places=8) | |
415 | + flag = models.CharField(max_length=45, blank=True, null=True) | |
416 | + | |
417 | + class Meta: | |
418 | + managed = True | |
419 | + db_table = 'schedule' | |
420 | + verbose_name_plural = "Schedules" | |
421 | + | |
422 | + def __str__(self): | |
423 | + return (str(self.created)) | |
424 | + | |
425 | + | |
426 | +class Sequence(models.Model): | |
427 | + | |
428 | + """ Definition of Status enum values """ | |
429 | + INVALID = "INVL" | |
430 | + DRAFT = "DRAFT" | |
431 | + | |
432 | + # INCOMPLETE = "INCPL" | |
433 | + # COMPLETE = "CPL" | |
434 | + TOBEPLANNED = "TBP" | |
435 | + PLANNED = "PLND" | |
436 | + UNPLANNABLE = "UNPLN" | |
437 | + REJECTED = "RJTD" | |
438 | + REC_RUNNING = "RUN" | |
439 | + REC_FINISHED = "EXD" | |
440 | + REC_CANCELED = "CNCLD" | |
441 | + PROC_RUNNING = "PROC_RUN" | |
442 | + PROC_CANCELED = "PROC_CNCLD" | |
443 | + PROC_FINISHED = "PROC_EXD" | |
444 | + # PENDING = "PNDG" | |
445 | + # EXECUTING = "EXING" | |
446 | + # EXECUTED = "EXD" | |
447 | + CANCELLED = "CNCLD" | |
448 | + STATUS_CHOICES = ( | |
449 | + (INVALID, "Invalid"), | |
450 | + (DRAFT, "DRAFT"), | |
451 | + (TOBEPLANNED, "To be planned"), | |
452 | + (PLANNED, "Planned"), | |
453 | + (UNPLANNABLE, "Unplannable"), | |
454 | + (REJECTED, "Rejected"), | |
455 | + (REC_RUNNING, "Recording running"), | |
456 | + (REC_FINISHED, "Recording finished"), | |
457 | + (REC_CANCELED, "Recording canceled"), | |
458 | + (PROC_RUNNING, "Processing running"), | |
459 | + (PROC_FINISHED, "Processing finished"), | |
460 | + (PROC_CANCELED, "Processing canceled"), | |
461 | + (CANCELLED, "Cancelled"), | |
462 | + ) | |
463 | + START_EXPO_PREF_CHOICES = ( | |
464 | + ('IMMEDIATE', 'IMMEDIATE'), | |
465 | + ('BEST_ELEVATION', 'BEST_ELEVATION'), | |
466 | + ('NO_CONSTRAINT', 'NO_CONSTRAINT'), | |
467 | + ) | |
468 | + | |
469 | + start_expo_pref = models.CharField(max_length=50, blank=False, null=True, | |
470 | + choices=START_EXPO_PREF_CHOICES, default=START_EXPO_PREF_CHOICES[0]) | |
471 | + # request = models.ForeignKey( | |
472 | + # Request, on_delete=models.CASCADE, related_name="sequences") | |
473 | + pyros_user = models.ForeignKey(PyrosUser, on_delete=models.DO_NOTHING, related_name="sequences", blank=True, null=True) | |
474 | + #pyros_user = models.ForeignKey('PyrosUser', on_delete=models.DO_NOTHING, related_name="sequences", blank=True, null=True) | |
475 | + #scientific_program = models.ForeignKey('ScientificProgram', on_delete=models.DO_NOTHING, related_name="sequences", blank=True, null=True) | |
476 | + scientific_program = models.ForeignKey(ScientificProgram, on_delete=models.DO_NOTHING, related_name="sequences", blank=True, null=True) | |
477 | + name = models.CharField(max_length=45, blank=True, null=True, unique=True) | |
478 | + desc = models.TextField(blank=True, null=True) | |
479 | + created = models.DateTimeField(blank=True, null=True, auto_now_add=True) | |
480 | + updated = models.DateTimeField(blank=True, null=True, auto_now=True) | |
481 | + #last_modified_by = models.ForeignKey('PyrosUser', on_delete=models.DO_NOTHING, related_name="+", blank=True, null=True) | |
482 | + last_modified_by = models.ForeignKey(PyrosUser, on_delete=models.DO_NOTHING, related_name="+", blank=True, null=True) | |
483 | + is_alert = models.BooleanField(default=False) | |
484 | + status = models.CharField( | |
485 | + max_length=11, blank=True, null=True, choices=STATUS_CHOICES) | |
486 | + target_coords = models.CharField(max_length=100, blank=True, null=True) | |
487 | + with_drift = models.BooleanField(default=False) | |
488 | + priority = models.IntegerField(blank=True, null=True) | |
489 | + analysis_method = models.CharField(max_length=45, blank=True, null=True) | |
490 | + moon_min = models.IntegerField(blank=True, null=True) | |
491 | + alt_min = models.IntegerField(blank=True, null=True) | |
492 | + type = models.CharField(max_length=6, blank=True, null=True) | |
493 | + img_current = models.CharField(max_length=45, blank=True, null=True) | |
494 | + img_total = models.CharField(max_length=45, blank=True, null=True) | |
495 | + not_obs = models.BooleanField(default=False) | |
496 | + obsolete = models.BooleanField(default=False) | |
497 | + processing = models.BooleanField(default=False) | |
498 | + flag = models.CharField(max_length=45, blank=True, null=True) | |
499 | + period = models.ForeignKey(Period, on_delete=models.DO_NOTHING, related_name="sequences", blank=True, null=True) | |
500 | + #period = models.ForeignKey("Period", on_delete=models.DO_NOTHING, related_name="sequences", blank=True, null=True) | |
501 | + | |
502 | + start_date = models.DateTimeField( | |
503 | + blank=True, null=True, default=timezone.now, editable=True) | |
504 | + end_date = models.DateTimeField( | |
505 | + blank=True, null=True, default=timezone.now, editable=True) | |
506 | + # jd1 et jd2 = julian day start / end | |
507 | + jd1 = models.DecimalField(default=0.0, max_digits=15, decimal_places=8) | |
508 | + jd2 = models.DecimalField(default=0.0, max_digits=15, decimal_places=8) | |
509 | + tolerance_before = models.CharField( | |
510 | + max_length=50, default="1s", blank=True, null=True) | |
511 | + tolerance_after = models.CharField( | |
512 | + max_length=50, default="1min", blank=True, null=True) | |
513 | + | |
514 | + #t_prefered = models.DecimalField(default=-1.0, max_digits=15, decimal_places=8) | |
515 | + duration = models.DecimalField( | |
516 | + default=-1.0, max_digits=15, decimal_places=8) | |
517 | + # décomposer duration en duration pointing + duration album | |
518 | + overhead = models.DecimalField(default=0, max_digits=15, decimal_places=8) | |
519 | + submitted = models.BooleanField(default=False) | |
520 | + config_attributes = models.JSONField(blank=True, null=True) | |
521 | + | |
522 | + ra = models.FloatField(blank=True, null=True) | |
523 | + dec = models.FloatField(blank=True, null=True) | |
524 | + complete = models.BooleanField(default=False, null=True, blank=True) | |
525 | + | |
526 | + class Meta: | |
527 | + managed = True | |
528 | + db_table = 'sequence' | |
529 | + | |
530 | + def __str__(self): | |
531 | + return (str(self.name)) | |
532 | + | |
533 | + | |
534 | +class ScheduleHasSequences(models.Model): | |
535 | + # (EP) TODO: C'est pas un pb d'utiliser 2 fois le meme nom "shs" pour 2 choses differentes ???!!! | |
536 | + schedule = models.ForeignKey( | |
537 | + 'Schedule', on_delete=models.CASCADE, related_name="shs") | |
538 | + sequence = models.ForeignKey( | |
539 | + 'Sequence', on_delete=models.CASCADE, related_name="shs") | |
540 | + | |
541 | + status = models.CharField( | |
542 | + max_length=11, blank=True, null=True, choices=Sequence.STATUS_CHOICES) | |
543 | + desc = models.CharField(max_length=45, blank=True, null=True) | |
544 | + tsp = models.DecimalField(default=-1.0, max_digits=15, decimal_places=8) | |
545 | + tep = models.DecimalField(default=-1.0, max_digits=15, decimal_places=8) | |
546 | + deltaTL = models.DecimalField( | |
547 | + default=-1.0, max_digits=15, decimal_places=8) | |
548 | + deltaTR = models.DecimalField( | |
549 | + default=-1.0, max_digits=15, decimal_places=8) | |
550 | + | |
551 | + class Meta: | |
552 | + managed = True | |
553 | + db_table = 'schedule_has_sequences' | |
554 | + | |
555 | + | |
556 | +class StrategyObs(models.Model): | |
557 | + name = models.CharField(max_length=45, blank=True, null=True) | |
558 | + desc = models.TextField(blank=True, null=True) | |
559 | + xml_file = models.CharField(max_length=45, blank=True, null=True) | |
560 | + is_default = models.BooleanField(default=False) | |
561 | + | |
562 | + class Meta: | |
563 | + managed = True | |
564 | + db_table = 'strategyobs' | |
565 | + verbose_name_plural = "Strategy obs" | |
566 | + | |
567 | + def __str__(self): | |
568 | + return (str(self.name)) | |
569 | + | |
570 | + | |
2 | 571 | |
3 | -# Create your models here. | ... | ... |
src/core/pyros_django/routine_manager/urls.py
src/core/pyros_django/routine_manager/validators.py
src/core/pyros_django/routine_manager/views.py
... | ... | @@ -10,8 +10,11 @@ from collections import OrderedDict |
10 | 10 | from django.utils.decorators import method_decorator |
11 | 11 | from django.views.decorators.csrf import csrf_exempt |
12 | 12 | from django.shortcuts import get_object_or_404, render, redirect |
13 | + | |
13 | 14 | from common.models import * |
15 | +from routine_manager.models import * | |
14 | 16 | from django.db.models import Q |
17 | + | |
15 | 18 | from django.contrib.auth.decorators import login_required |
16 | 19 | from src.core.pyros_django.dashboard.decorator import level_required |
17 | 20 | import ast |
... | ... | @@ -20,7 +23,10 @@ import datetime |
20 | 23 | |
21 | 24 | from django.forms.models import model_to_dict |
22 | 25 | from src.core.pyros_django.obsconfig.obsconfig_class import OBSConfig |
23 | -from .forms import RequestForm, SequenceForm, AlbumForm, PlanForm, uneditablePlanForm | |
26 | + | |
27 | +from .forms import SequenceForm, AlbumForm, PlanForm, uneditablePlanForm | |
28 | +#from .forms import RequestForm, SequenceForm, AlbumForm, PlanForm, uneditablePlanForm | |
29 | + | |
24 | 30 | from .validators import check_plan_validity, check_album_validity, check_sequence_validity, check_request_validity |
25 | 31 | from .functions import check_sequence_file_validity |
26 | 32 | from .RequestSerializer import RequestSerializer | ... | ... |
src/core/pyros_django/scheduler/Interval.py
src/core/pyros_django/scheduler/Scheduler.py
src/core/pyros_django/scheduler/UserManager.py
src/core/pyros_django/scheduler/views.py
1 | 1 | from django.shortcuts import render |
2 | -from common.models import Sequence, Schedule | |
2 | +#from common.models import Sequence, Schedule | |
3 | +from routine_manager.models import Sequence, Schedule | |
4 | + | |
3 | 5 | from django.contrib.auth.decorators import login_required |
4 | 6 | from scheduler.simulator import Simulator |
5 | 7 | from operator import attrgetter | ... | ... |
src/core/pyros_django/scientific_program/views.py
... | ... | @@ -5,7 +5,10 @@ from django.contrib.auth import authenticate, login |
5 | 5 | from django.contrib.auth.decorators import login_required |
6 | 6 | from .forms import PeriodForm, ScienceThemeForm, ScientificProgramForm, InstituteForm, SP_PeriodForm,TACAssociationForm |
7 | 7 | from src.core.pyros_django.dashboard.decorator import level_required |
8 | -from common.models import ScientificProgram, Institute, Period, SP_Period_User, SP_Period, PyrosUser, UserLevel, SP_Period_Guest, ScienceTheme, Sequence | |
8 | + | |
9 | +from common.models import ScientificProgram, Institute, Period, SP_Period_User, SP_Period, PyrosUser, UserLevel, SP_Period_Guest, ScienceTheme | |
10 | +from routine_manager.models import Sequence | |
11 | + | |
9 | 12 | from django.http import HttpResponseRedirect |
10 | 13 | from datetime import date, datetime |
11 | 14 | from django.conf import settings | ... | ... |