Commit 43e6176dbc467485dd03fbc133356699f8c0220d

Authored by Etienne Pallier
1 parent 627f618c
Exists in dev

Model splitting goes on... : common/models.py, routine_manager/models.py, devices/models.py

privatedev/plugin/agent/AgentImagesCalibrator.py 0 → 100644
... ... @@ -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()
... ...
privatedev/plugin/agent/AgentImagesProcessor.py 0 → 100755
... ... @@ -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
1 1 from django.urls import re_path
2 2 from django.urls import path
  3 +
  4 +#from common.models import ScientificProgram
  5 +
3 6 from . import views
4 7  
5 8 urlpatterns = [
... ...
src/core/pyros_django/routine_manager/validators.py
1   -from common.models import Sequence, Album, Plan
  1 +from routine_manager.models import Sequence, Album, Plan
2 2 from django.conf import settings
3 3  
4 4 def check_plan_validity(plan):
... ...
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
1 1 from common.models import *
  2 +from routine_manager.models import Sequence, ScheduleHasSequences
2 3 # from decimal import Decimal
3 4 from utils.Logger import Logger
4 5 from utils.JDManipulator import *
... ...
src/core/pyros_django/scheduler/Scheduler.py
... ... @@ -5,6 +5,8 @@ from .UserManager import UserQuotaManager
5 5 from .Interval import *
6 6 from django.db.models import Q
7 7  
  8 +from routine_manager.models import Sequence, Schedule
  9 +
8 10 SIMULATION = False
9 11 DEBUG_FILE = False
10 12  
... ...
src/core/pyros_django/scheduler/UserManager.py
1 1 from common.models import *
  2 +from routine_manager.models import Sequence
  3 +
2 4 from utils.Logger import *
3 5 from decimal import *
4 6  
... ...
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
... ...