##from __future__ import unicode_literals from enum import Enum from datetime import datetime, timedelta from django.contrib.auth.models import AbstractUser from django.db import models from django.db.models import Q from django.core.validators import MaxValueValidator, MinValueValidator from model_utils import Choices ''' NOT USED - to be removed class PyrosState(Enum): START = 'Starting' PA = 'Passive' INI = "INIT" STAND = "Standby" SCHED_START = 'Scheduler startup' SCHED = 'Scheduler' SCHED_CLOSE = 'Scheduler closing' ''' """ STYLE RULES =========== https://simpleisbetterthancomplex.com/tips/2018/02/10/django-tip-22-designing-better-models.html https://steelkiwi.com/blog/best-practices-working-django-models-python/ - Model name => singular Call it Company instead of Companies. A model definition is the representation of a single object (the object in this example is a company), and not a collection of companies The model definition is a class, so always use CapWords convention (no underscores) E.g. User, Permission, ContentType, etc. - For the model’s attributes use snake_case. E.g. first_name, last_name, etc - Blank and Null Fields (https://simpleisbetterthancomplex.com/tips/2016/07/25/django-tip-8-blank-or-null.html) - Null: It is database-related. Defines if a given database column will accept null values or not. - Blank: It is validation-related. It will be used during forms validation, when calling form.is_valid(). Do not use null=True for text-based fields that are optional. Otherwise, you will end up having two possible values for “no data”, that is: None and an empty string. Having two possible values for “no data” is redundant. The Django convention is to use the empty string, not NULL. Example: # The default values of `null` and `blank` are `False`. class Person(models.Model): name = models.CharField(max_length=255) # Mandatory bio = models.TextField(max_length=500, blank=True) # Optional (don't put null=True) birth_date = models.DateField(null=True, blank=True) # Optional (here you may add null=True) The default values of null and blank are False. Special case, when you need to accept NULL values for a BooleanField, use NullBooleanField instead. - Choices : you can use Choices from the model_utils library. Take model Article, for instance: from model_utils import Choices class Article(models.Model): STATUSES = Choices( (0, 'draft', _('draft')), (1, 'published', _('published')) ) status = models.IntegerField(choices=STATUSES, default=STATUSES.draft) - Reverse Relationships - related_name : Rule of thumb: if you are not sure what would be the related_name, use the plural of the model holding the ForeignKey. ex: class Company: name = models.CharField(max_length=30) class Employee: first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='employees') usage: google = Company.objects.get(name='Google') google.employees.all() You can also use the reverse relationship to modify the company field on the Employee instances: vitor = Employee.objects.get(first_name='Vitor') google = Company.objects.get(name='Google') google.employees.add(vitor) - related_query_name : This kind of relationship also applies to query filters. For example, if I wanted to list all companies that employs people named ‘Vitor’, I could do the following: companies = Company.objects.filter(employee__first_name='Vitor') If you want to customize the name of this relationship, here is how we do it: class Employee: first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) company = models.ForeignKey( Company, on_delete=models.CASCADE, related_name='employees', related_query_name='person' ) Then the usage would be: companies = Company.objects.filter(person__first_name='Vitor') To use it consistently, related_name goes as plural and related_query_name goes as singular. GENERAL EXAMPLE ======= from django.db import models from django.urls import reverse class Company(models.Model): # CHOICES PUBLIC_LIMITED_COMPANY = 'PLC' PRIVATE_COMPANY_LIMITED = 'LTD' LIMITED_LIABILITY_PARTNERSHIP = 'LLP' COMPANY_TYPE_CHOICES = ( (PUBLIC_LIMITED_COMPANY, 'Public limited company'), (PRIVATE_COMPANY_LIMITED, 'Private company limited by shares'), (LIMITED_LIABILITY_PARTNERSHIP, 'Limited liability partnership'), ) # DATABASE FIELDS name = models.CharField('name', max_length=30) vat_identification_number = models.CharField('VAT', max_length=20) company_type = models.CharField('type', max_length=3, choices=COMPANY_TYPE_CHOICES) # MANAGERS objects = models.Manager() limited_companies = LimitedCompanyManager() # META CLASS class Meta: verbose_name = 'company' verbose_name_plural = 'companies' # TO STRING METHOD def __str__(self): return self.name # SAVE METHOD def save(self, *args, **kwargs): do_something() super().save(*args, **kwargs) # Call the "real" save() method. do_something_else() # ABSOLUTE URL METHOD def get_absolute_url(self): return reverse('company_details', kwargs={'pk': self.id}) # OTHER METHODS def process_invoices(self): do_something() """ """ ------------------------ BASE MODEL CLASSES ------------------------ """ class Device(models.Model): name = models.CharField(max_length=45, blank=True, null=True) desc = models.TextField(blank=True, null=True) created = models.DateTimeField(blank=True, null=True, auto_now_add=True) updated = models.DateTimeField(blank=True, null=True, auto_now=True) is_online = models.BooleanField(default=False) status = models.CharField(max_length=11, blank=True, null=True) maintenance_date = models.DateTimeField(blank=True, null=True) class Meta: abstract = True def __str__(self): return (str(self.name)) class Request(models.Model): pyros_user = models.ForeignKey( 'PyrosUser', on_delete=models.DO_NOTHING, related_name="requests") scientific_program = models.ForeignKey( 'ScientificProgram', on_delete=models.DO_NOTHING, related_name="requests", blank=True, null=True) name = models.CharField(max_length=45, blank=True, null=True) desc = models.TextField(blank=True, null=True) created = models.DateTimeField(blank=True, null=True, auto_now_add=True) updated = models.DateTimeField(blank=True, null=True, auto_now=True) is_alert = models.BooleanField(default=False) target_type = models.CharField(max_length=8, blank=True, null=True) status = models.CharField(max_length=10, blank=True, null=True) autodeposit = models.BooleanField(default=False) checkpoint = models.CharField(max_length=45, blank=True, null=True) flag = models.CharField(max_length=45, blank=True, null=True) complete = models.BooleanField(default=False) submitted = models.BooleanField(default=False) class Meta: managed = True db_table = 'request' def __str__(self): return (str(self.name)) """ ------------------------ OTHER MODEL CLASSES ------------------------ """ class Command(models.Model): """ | id | sender | receiver | name | validity_duration_sec (default=60) | sender_deposit_time | receiver_read_time """ # -------------- Command CONSTANTS -------------- # Receiver status codes """ RSCODE_RUNNING = -1 # en cours d’exécution => une fois la cde lue RSCODE_EXECUTED = 0 # cde exécutée => simulé par un sleep(3) dans AgentX.core_process()) RSCODE_PENDING = 1 # cde en attente d'exécution RSCODE_SKIPPED = 2 # cde ignorée (je suis idle… et j’ai ignoré cette commande, et je passe à la cde suivante) RSCODE_OUTOFDATE = 3 # cde périmée CMD_STATUS_CODES = ( RSCODE_RUNNING, # en cours d’exécution => une fois la cde lue RSCODE_EXECUTED, # cde exécutée => simulé par un sleep(3) dans AgentX.core_process()) RSCODE_PENDING, # cde en attente d'exécution RSCODE_SKIPPED, # cde ignorée (je suis idle… et j’ai ignoré cette commande, et je passe à la cde suivante) RSCODE_OUTOFDATE, # cde périmée ) """ CMD_STATUS_CODES = Choices( "CMD_RUNNING", # en cours d’exécution => une fois la cde lue "CMD_EXECUTED", # cde exécutée => simulé par un sleep(3) dans AgentX.core_process()) "CMD_PENDING", # cde en attente d'exécution "CMD_SKIPPED", # cde ignorée (je suis idle… et j’ai ignoré cette commande, et je passe à la cde suivante) "CMD_KILLED", # cde ignorée (je suis idle… et j’ai ignoré cette commande, et je passe à la cde suivante) "CMD_OUTOFDATE" # cde périmée ) GENERIC_COMMANDS = ["go_idle", "go_active", "abort", "exit"] #COMMANDS_PEREMPTION_HOURS = 48 COMMANDS_PEREMPTION_HOURS = 60/60 COMMANDS_VALIDITY_DURATION_SEC_DEFAULT = 30 # -------------- Command FIELDS -------------- #sender = models.CharField(max_length=50, blank=True, null=True, unique=True) sender = models.CharField(max_length=50, help_text='sender agent name') receiver = models.CharField(max_length=50) name = models.CharField(max_length=400) validity_duration_sec = models.PositiveIntegerField(default=COMMANDS_VALIDITY_DURATION_SEC_DEFAULT) # Automatically set at table line creation (line created by the sender) sender_deposit_time = models.DateTimeField(blank=True, null=True, auto_now_add=True) # Set by the receiver : # - at reading time receiver_read_time = models.DateTimeField(null=True) # - after execution receiver_processed_time = models.DateTimeField(null=True) receiver_status_code = models.CharField(choices = CMD_STATUS_CODES, default=CMD_STATUS_CODES.CMD_PENDING, max_length=20) #receiver_status_code = models.IntegerField(choices=CMD_STATUS_CODES, default=RSCODE_PENDING) # TODO: maybe à mettre au format json (key:value) result = models.CharField(max_length=400, blank=True) class Meta: managed = True db_table = 'command' #verbose_name = "agent survey" #verbose_name_plural = "agents survey" # -------------- Command CLASS (static) METHODS -------------- @classmethod def get_peremption_date_from_now(cls): return datetime.utcnow().astimezone() - timedelta(hours = cls.COMMANDS_PEREMPTION_HOURS) @classmethod def delete_commands_with_running_status_if_exists_for_agent(cls, agent_name): running_commands = cls.objects.filter( # only commands for agent agent_name receiver = agent_name, # only running commands receiver_status_code = cls.CMD_STATUS_CODES.CMD_RUNNING, # only not expired commands #sender_deposit_time__gte = cls.get_peremption_date_from_now(), ) if running_commands: print("Delete (false) 'running' command:") Command.show_commands(running_commands) running_commands.delete() @classmethod def get_pending_commands_for_agent(cls, agent_name): #print("peremption date", COMMAND_PEREMPTION_DATE_FROM_NOW) return cls.objects.filter( # only pending commands # but also including the currently running command Q(receiver_status_code = cls.CMD_STATUS_CODES.CMD_PENDING) | Q(receiver_status_code = cls.CMD_STATUS_CODES.CMD_RUNNING), # only commands for agent agent_name receiver = agent_name, # only not expired commands #sender_deposit_time__gte = cls.get_peremption_date_from_now(), ).order_by("sender_deposit_time") @classmethod def purge_old_commands_for_agent(cls, agent_name): """ Delete commands (which agent_name is recipient of) older than COMMANDS_PEREMPTION_HOURS (like 48h) ATTENTION !!! EXCEPT the RUNNING command !!! NB: datetime.utcnow() is equivalent to datetime.now(timezone.utc) """ print(f"Looking for old commands to purge... (commands that are not executing and older than {cls.COMMANDS_PEREMPTION_HOURS} hour(s))") """ COMMAND_PEREMPTION_DATE_FROM_NOW = datetime.utcnow() - timedelta(hours = self.COMMANDS_PEREMPTION_HOURS) #print("peremption date", COMMAND_PEREMPTION_DATE_FROM_NOW) old_commands = Command.objects.filter( # only commands for me receiver = self.name, # only pending commands sender_deposit_time__lt = COMMAND_PEREMPTION_DATE_FROM_NOW, ) """ #COMMAND_PEREMPTION_DATE_FROM_NOW = datetime.utcnow() - timedelta(hours = cls.COMMANDS_PEREMPTION_HOURS) #print("peremption date", COMMAND_PEREMPTION_DATE_FROM_NOW) old_commands = cls.objects.filter( # only commands for agent agent_name receiver = agent_name, # only expired commands sender_deposit_time__lt = cls.get_peremption_date_from_now(), ).exclude( receiver_status_code = cls.CMD_STATUS_CODES.CMD_RUNNING ) if old_commands.exists(): print("Found old commands to delete:") #for cmd in old_commands: print(cmd) cls.show_commands(old_commands) old_commands.delete() @classmethod def show_commands(cls, commands:models.query): for cmd in commands: print("-", cmd) # -------------- Command INSTANCE METHODS -------------- def __str__(self): return (f"Commmand '{self.name}' ({self.receiver_status_code}) sent by agent {self.sender} to agent {self.receiver} at {self.sender_deposit_time}") def is_generic(self): """ Is this a generic command ? It is the case if command is of style "go_idle" or "go_active" or "stop"... """ return self.name in self.GENERIC_COMMANDS def is_running(self): #return (datetime.utcnow() - self.sender_deposit_time) > timedelta(seconds = self.validity_duration_sec) return self.receiver_status_code == self.CMD_STATUS_CODES.CMD_RUNNING def is_pending(self): return self.receiver_status_code == self.CMD_STATUS_CODES.CMD_PENDING def is_read(self): return self.receiver_read_time is not None def is_expired(self): #return (datetime.utcnow() - self.sender_deposit_time) > timedelta(seconds = self.validity_duration_sec) return (datetime.utcnow().astimezone() - self.sender_deposit_time) > timedelta(seconds = self.validity_duration_sec) """ elapsed_time = cmd.receiver_read_time - cmd.sender_deposit_time max_time = timedelta(seconds = cmd.validity_duration_sec) print(f"Elapsed time is {elapsed_time}, (max is {max_time})") if elapsed_time > max_time: """ def set_read_time(self): self.receiver_read_time = datetime.utcnow().astimezone() self.save() def set_as_processed(self): print(f"- Set command {self.name} as processed") self.receiver_status_code = self.CMD_STATUS_CODES.CMD_EXECUTED self.receiver_processed_time = datetime.utcnow().astimezone() self.save() def set_as_outofdate(self): print(f"- Set this command as expired (older than its validity duration of {self.validity_duration_sec}s): {self}") self.set_status_to(self.CMD_STATUS_CODES.CMD_OUTOFDATE) def set_as_skipped(self): self.set_status_to(self.CMD_STATUS_CODES.CMD_SKIPPED) def set_as_killed(self): print(f"- Set command {self.name} as killed") #print(f"- Set this command as killed: {self}") self.set_status_to(self.CMD_STATUS_CODES.CMD_KILLED) def set_as_running(self): print(f"- Set command {self.name} as running") self.set_status_to(self.CMD_STATUS_CODES.CMD_RUNNING) ''' def set_as_executed(self): self.set_status_to(self.CMD_STATUS_CODES.CMD_EXECUTED) ''' def set_status_to(self, status:str): self.receiver_status_code = status self.save(update_fields=["receiver_status_code"]) class AgentSurvey(models.Model): """ | id | name | created | updated | validity_duration_sec (default=1mn) | mode (active/idle) | status (launch/init/loop/exit/...) | """ # Statuses #STATUSES = Choices('new', 'verified', 'published') STATUS_LAUNCH = "LAUNCHED" STATUS_INIT = "INITIALIZING" STATUS_MAIN_LOOP = "IN_MAIN_LOOP" STATUS_PROCESS_LOOP = "IN_PROCESS_LOOP" STATUS_EXIT = "EXITING" # Modes MODE_ACTIVE = "ACTIVE" MODE_IDLE = "IDLE" MODE_CHOICES = ( (MODE_ACTIVE, 'Active mode'), (MODE_IDLE, 'Idle mode'), ) STATUS_CHOICES = ( (STATUS_LAUNCH, "LAUNCHED"), (STATUS_INIT, "INITIALIZING"), (STATUS_MAIN_LOOP, "IN_MAIN_LOOP"), (STATUS_PROCESS_LOOP, "IN_PROCESS_LOOP"), (STATUS_EXIT, "EXITING"), ) name = models.CharField(max_length=50, unique=True) #name = models.CharField(max_length=50, blank=True, null=True, unique=True) #created = models.DateTimeField(blank=True, null=True, auto_now_add=True) created = models.DateTimeField(blank=True, null=True, auto_now_add=True) updated = models.DateTimeField(blank=True, null=True, auto_now=True) validity_duration_sec = models.PositiveIntegerField(default=90) #validity_duration_sec = models.DurationField(default=90) mode = models.CharField('agent mode', max_length=15, blank=True, choices=MODE_CHOICES) status = models.CharField(max_length=15, blank=True, choices=STATUS_CHOICES) class Meta: managed = True db_table = 'agent_survey' #verbose_name = "agent survey" #verbose_name_plural = "agents survey" def __str__(self): return (f"Agent {self.name} at {self.updated} in mode {self.mode} and status {self.status}") class Album(models.Model): sequence = models.ForeignKey( 'Sequence', on_delete=models.CASCADE, related_name="albums") detector = models.ForeignKey( 'Detector', models.DO_NOTHING, related_name="albums", blank=True, null=True) name = models.CharField(max_length=45, blank=True, null=True) desc = models.TextField(blank=True, null=True) created = models.DateTimeField(blank=True, null=True, auto_now_add=True) updated = models.DateTimeField(blank=True, null=True, auto_now=True) complete = models.BooleanField(default=False) class Meta: managed = True db_table = 'album' #verbose_name_plural = "Albums" def __str__(self): return (str(self.name)) class Alert(Request): request = models.OneToOneField('Request', on_delete=models.CASCADE, default='', parent_link=True) strategyobs = models.ForeignKey( 'StrategyObs', models.DO_NOTHING, related_name="alerts", blank=True, null=True) voevent_file = models.CharField(max_length=45, blank=True, null=True) author = models.CharField(max_length=45, blank=True, null=True) burst_jd = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) burst_ra = models.FloatField(max_length=45, blank=True, null=True) burst_dec = models.FloatField(max_length=45, blank=True, null=True) astro_coord_system = models.CharField(max_length=45, blank=True, null=True) jd_send = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) jd_received = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) trig_id = models.IntegerField(blank=True, null=True) error_radius = models.FloatField(max_length=45, blank=True, null=True) defly_not_grb = models.BooleanField(default=False) editor = models.CharField(max_length=45, blank=True, null=True) soln_status = models.CharField(max_length=45, blank=True, null=True) pkt_ser_num = models.IntegerField(blank=True, null=True) class Meta: managed = True db_table = 'alert' def __str__(self): return str(self.trig_id) def request_name(self): return self.__str__() request_name.short_description = "Name" class Config(models.Model): PYROS_STATE = ["Starting", "Passive", "Standby", "Remote", "Startup", "Scheduler", "Closing" ] id = models.IntegerField(default='1', primary_key=True) #latitude = models.FloatField(default=1) latitude = models.DecimalField( max_digits=4, decimal_places=2, default=1, validators=[ MaxValueValidator(90), MinValueValidator(-90) ] ) local_time_zone = models.FloatField(default=1) #longitude = models.FloatField(default=1) longitude = models.DecimalField( max_digits=5, decimal_places=2, default=1, validators=[ MaxValueValidator(360), MinValueValidator(-360) ] ) altitude = models.FloatField(default=1) horizon_line = models.FloatField(default=1) row_data_save_frequency = models.IntegerField(default='300') request_frequency = models.IntegerField(default='300') analysed_data_save = models.IntegerField(default='300') telescope_ip_address = models.CharField(max_length=45, default="127.0.0.1") camera_ip_address = models.CharField(max_length=45, default="127.0.0.1") plc_ip_address = models.CharField(max_length=45, default="127.0.0.1") # TODO: changer ça, c'est pas clair du tout... # True = mode Scheduler-standby, False = mode Remote !!!! global_mode = models.BooleanField(default='True') ack = models.BooleanField(default='False') bypass = models.BooleanField(default='True') lock = models.BooleanField(default='False') pyros_state = models.CharField(max_length=25, default=PYROS_STATE[0]) force_passive_mode = models.BooleanField(default='False') plc_timeout_seconds = models.PositiveIntegerField(default=60) majordome_state = models.CharField(max_length=25, default="") ntc = models.BooleanField(default='False') majordome_restarted = models.BooleanField(default='False') class Meta: managed = True db_table = 'config' verbose_name_plural = "Config" def __str__(self): return (str(self.__dict__)) class Country(models.Model): name = models.CharField(max_length=45, blank=True, null=True) desc = models.TextField(blank=True, null=True) quota = models.FloatField(blank=True, null=True) class Meta: managed = True db_table = 'country' verbose_name_plural = "Countries" def __str__(self): return (str(self.name)) class Detector(Device): VIS = "Visible camera" NIR = "Cagire" telescope = models.ForeignKey( 'Telescope', models.DO_NOTHING, related_name="detectors") nb_photo_x = models.IntegerField(blank=True, null=True) nb_photo_y = models.IntegerField(blank=True, null=True) photo_size_x = models.IntegerField(blank=True, null=True) photo_size_y = models.IntegerField(blank=True, null=True) has_shutter = models.BooleanField(default=False) equivalent_foc_len = models.CharField(max_length=45, blank=True, null=True) acq_start = models.DateTimeField(blank=True, null=True) acq_stop = models.DateTimeField(blank=True, null=True) check_temp = models.FloatField(blank=True, null=True) gain = models.FloatField(blank=True, null=True) readout_noise = models.FloatField(blank=True, null=True) readout_time = models.FloatField(blank=True, null=True) idcam_readout_mode = models.IntegerField(blank=True, null=True) class Meta: managed = True db_table = 'detector' def __str__(self): return str(self.name) def device_name(self): return self.__str__() device_name.short_description = "Name" class Dome(Device): DOME = "Dome" open = models.BooleanField(default=False, blank=True) class Meta: managed = True db_table = 'dome' def __str__(self): return str(self.name) def device_name(self): return self.__str__() device_name.short_description = "Name" class Filter(Device): VIS_FILTER_1 = "First visible filter" VIS_FILTER_2 = "Second visible filter" NIR_FILTER_1 = "First infrared filter" NIR_FILTER_2 = "Second infrared filter" filter_wheel = models.ForeignKey( "FilterWheel", models.DO_NOTHING, related_name="filters", blank=True, null=True) category = models.CharField(max_length=1, blank=True, null=True) transmission_curve_doc = models.CharField( max_length=45, blank=True, null=True) class Meta: managed = True db_table = 'filter' def __str__(self): return (str(self.name)) def device_name(self): return self.__str__() device_name.short_description = "Name" class FilterWheel(Device): detector = models.OneToOneField(Detector, on_delete=models.CASCADE, related_name="filter_wheel", blank=True, null=True) class Meta: managed = True db_table = 'filter_wheel' def __str__(self): return (str(self.name)) def device_name(self): return self.__str__() device_name.short_description = "Name" class Image(models.Model): plan = models.ForeignKey('Plan', on_delete=models.CASCADE, related_name="images") nrtanalysis = models.ForeignKey( 'NrtAnalysis', models.DO_NOTHING, blank=True, null=True, related_name="images") name = models.CharField(max_length=45, blank=True, null=True) desc = models.TextField(blank=True, null=True) created = models.DateTimeField(blank=True, null=True, auto_now_add=True) updated = models.DateTimeField(blank=True, null=True, auto_now=True) date_from_gps = models.CharField(max_length=45, blank=True, null=True) level = models.IntegerField(blank=True, null=True) type = models.CharField(max_length=5, blank=True, null=True) quality = models.CharField(max_length=45, blank=True, null=True) flaggps = models.CharField(max_length=45, blank=True, null=True) exposure = models.CharField(max_length=45, blank=True, null=True) tempext = models.CharField(max_length=45, blank=True, null=True) pressure = models.CharField(max_length=45, blank=True, null=True) humidext = models.CharField(max_length=45, blank=True, null=True) wind = models.CharField(max_length=45, blank=True, null=True) wind_dir = models.CharField(max_length=45, blank=True, null=True) dwnimg = models.CharField(max_length=45, blank=True, null=True) dwncata = models.CharField(max_length=45, blank=True, null=True) dwn = models.CharField(max_length=45, blank=True, null=True) level0_fits_name = models.CharField(max_length=45, blank=True, null=True) level1a_fits_name = models.CharField(max_length=45, blank=True, null=True) level1b_fits_name = models.CharField(max_length=45, blank=True, null=True) class Meta: managed = True db_table = 'image' def __str__(self): return (str(self.name)) class Log(models.Model): agent = models.CharField(max_length=45, blank=True, null=True) created = models.DateTimeField(blank=True, null=True, auto_now_add=True) message = models.TextField(blank=True, null=True) class Meta: managed = True db_table = 'log' def __str__(self): return (str(self.agent)) class NrtAnalysis(models.Model): name = models.CharField(max_length=45, blank=True, null=True) desc = models.TextField(blank=True, null=True) created = models.DateTimeField(blank=True, null=True, auto_now_add=True) updated = models.DateTimeField(blank=True, null=True, auto_now=True) analysis = models.TextField(blank=True, null=True) class Meta: managed = True db_table = 'nrtanalysis' verbose_name_plural = "Nrt analyzes" def __str__(self): return (str(self.name)) class Plan(models.Model): album = models.ForeignKey(Album, on_delete=models.CASCADE, related_name="plans") filter = models.ForeignKey(Filter, models.DO_NOTHING, related_name="plans", blank=True, null=True) name = models.CharField(max_length=45, blank=True, null=True) desc = models.CharField(max_length=45, blank=True, null=True) created = models.DateTimeField(blank=True, null=True, auto_now_add=True) updated = models.DateTimeField(blank=True, null=True, auto_now=True) duration = models.FloatField(default=0, blank=True, null=True) position = models.CharField(max_length=45, blank=True, null=True) exposure_time = models.FloatField(blank=True, null=True) nb_images = models.IntegerField(blank=True, null=True) dithering = models.BooleanField(default=False) complete = models.BooleanField(default=False) class Meta: managed = True db_table = 'plan' def __str__(self): return (str(self.name)) class PlcDeviceStatus(models.Model): device = models.ForeignKey('PlcDevice', on_delete=models.CASCADE, related_name='current_status') created = models.DateTimeField(auto_now_add=True, editable=False, blank=True) outside_temp = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) outside_temp_unit = models.CharField(max_length=45, blank=True, null=True) outside_humidity = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) outside_humidity_unit = models.CharField(max_length=45, blank=True, null=True) pressure = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) pressure_unit = models.CharField(max_length=45, blank=True, null=True) rain_rate = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) rain_rate_unit = models.CharField(max_length=45, blank=True, null=True) wind_speed = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) wind_speed_unit = models.CharField(max_length=45, blank=True, null=True) wind_dir = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) wind_dir_unit = models.CharField(max_length=45, blank=True, null=True) dew_point = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) dew_point_unit = models.CharField(max_length=45, blank=True, null=True) analog = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) analog_unit = models.CharField(max_length=45, blank=True, null=True) digital = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) digital_unit = models.CharField(max_length=45, blank=True, null=True) inside_temp = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) inside_temp_unit = models.CharField(max_length=45, blank=True, null=True) inside_humidity = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) inside_humidity_unit = models.CharField(max_length=45, blank=True, null=True) wind_dir_cardinal = models.CharField(max_length=45, blank=True, null=True) wind_dir_cardinal_unit = models.CharField(max_length=45, blank=True, null=True) sensor_temperature = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) sensor_temperature_unit = models.CharField(max_length=45, blank=True, null=True) sky_temperature = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) sky_temperature_unit = models.CharField(max_length=45, blank=True, null=True) status = models.CharField(max_length=45, blank=True, null=True) current = models.DecimalField(max_digits=15, decimal_places=8, blank=True, null=True) current_unit = models.CharField(max_length=45, blank=True, null=True) is_safe = models.BooleanField(default=True) plc_mode = models.CharField(max_length=4, null=True) lights = models.CharField(max_length=3, null=True) shutters = models.CharField(max_length=5, null=True) class Meta: managed = True db_table = 'plc_devices_status' def __str__(self): return (str(self.__dict__)) ''' TODO : This function is Ugly, we should change this with a function pointer array and setters getters for each attribute ''' def setValue(self, key, value, unit=""): if key == "Temperature_outside": self.outside_temp = value self.outside_temp_unit = unit elif key == "Humidity_outside": self.outside_humidity = value self.outside_humidity_unit = unit elif key == "_Pressure": self.pressure = value self.pressure_unit = unit elif key == "Rain_boolean": # RainRate self.rain_rate = value self.rain_rate_unit = 'boulean' elif key == "Wind_speed": self.wind_speed = value self.wind_speed_unit = unit elif key == "Wind_dir": self.wind_dir = value self.wind_dir_unit = unit elif key == "_DewPoint": self.dew_point = value self.dew_point_unit = unit elif key == "_analog": self.analog = value self.analog_unit = unit elif key == "_digital": self.digital = value self.digital_unit = unit elif key == "_InsideTemp": self.inside_temp = value self.inside_temp_unit = unit elif key == "_InsideHumidity": self.inside_humidity = value self.inside_humidity_unit = unit elif key == "_WindDirCardinal": self.wind_dir_cardinal = value self.wind_dir_cardinal_unit = unit elif key == "_SensorTemperature": self.sensor_temperature = value self.sensor_temperature_unit = unit elif key == "_SkyTemperature": self.sky_temperature = value self.sky_temperature_unit = unit # PM 20190222 try patch elif key == "Error_code": self.status = value elif key == "current": self.current = value self.current_unit = unit elif key == "mode": self.plc_mode = value elif key == "is_safe": self.is_safe = value elif key == "LIGHTS": self.lights = value elif key == "SHUTTERS": self.shutters = value else: # PM 20190222 ignore unrecognized #raise KeyError("Key " + str(key) + " unrecognized") pass class PlcDevice(Device): #device = models.ForeignKey('Plc', on_delete=models.CASCADE, related_name='plc_devices') name = models.CharField(max_length=45, blank=True, null=True) desc = models.TextField(blank=True, null=True) created = models.DateTimeField(blank=True, null=True, auto_now_add=True) updated = models.DateTimeField(blank=True, null=True, auto_now=True) class Meta: managed = True db_table = 'plc_devices' def __str__(self): return str(self.name) #class Plc(Device): # last_update_status = models.DateTimeField(blank=True, null=True) # i # class Meta: # managed = True # db_table = 'plc' class PyrosUser(AbstractUser): username = models.CharField(max_length=255, blank=False, null=False, unique=True) is_active = models.BooleanField(default='False') first_time = models.BooleanField(default='False') country = models.ForeignKey( Country, on_delete=models.DO_NOTHING, related_name="pyros_users") user_level = models.ForeignKey( 'UserLevel', on_delete=models.DO_NOTHING, related_name="pyros_users") desc = models.TextField(blank=True, null=True) created = models.DateTimeField(blank=True, null=True, auto_now_add=True) updated = models.DateTimeField(blank=True, null=True, auto_now=True) tel = models.CharField(max_length=45, blank=True, null=True) address = models.TextField(max_length=100, blank=True, null=True) laboratory = models.CharField(max_length=45, blank=True, null=True) last_connect = models.DateTimeField(blank=True, null=True) cur_connect = models.DateTimeField(blank=True, null=True) putvalid_beg = models.DateTimeField(blank=True, null=True) putvalid_end = models.DateTimeField(blank=True, null=True) acqvalid_beg = models.CharField(max_length=45, blank=True, null=True) acqvalid_end = models.CharField(max_length=45, blank=True, null=True) quota = models.FloatField(blank=True, null=True) quota_rea = models.FloatField(blank=True, null=True) u_priority = models.IntegerField(blank=True, null=True) p_priority = models.IntegerField(blank=True, null=True) dir_level = models.IntegerField(blank=True, null=True) can_del_void_req = models.BooleanField(default=False) class Meta: managed = True db_table = 'pyros_user' def __str__(self): return (str(self.get_username())) def user_username(self): return self.__str__() user_username.short_description = "Username" # class Schedule(models.Model): # created = models.DateTimeField(blank=True, null=True, auto_now_add=True) # plan_start = models.DecimalField( # default=0.0, max_digits=15, decimal_places=8) # plan_end = models.DecimalField( # default=0.0, max_digits=15, decimal_places=8) # flag = models.CharField(max_length=45, blank=True, null=True) # # class Meta: # managed = True # db_table = 'schedule' # # def __str__(self): # return (str(self.created)) class Schedule(models.Model): sequences = models.ManyToManyField( 'Sequence', through='ScheduleHasSequences', related_name='schedules') created = models.DateTimeField(blank=True, null=True, auto_now_add=True) plan_night_start = models.DecimalField( default=0.0, max_digits=15, decimal_places=8) plan_end = models.DecimalField( default=0.0, max_digits=15, decimal_places=8) plan_start = models.DecimalField( default=0.0, max_digits=15, decimal_places=8) flag = models.CharField(max_length=45, blank=True, null=True) class Meta: managed = True db_table = 'schedule' verbose_name_plural = "Schedules" def __str__(self): return (str(self.created)) class ScientificProgram(models.Model): pyros_users = models.ManyToManyField( 'PyrosUser', related_name="scientific_programs") name = models.CharField(max_length=45, blank=True, null=True) desc = models.TextField(blank=True, null=True) quota = models.FloatField(blank=True, null=True) priority = models.IntegerField(blank=True, null=True) class Meta: managed = True db_table = 'scientific_program' def __str__(self): return (str(self.name)) class Sequence(models.Model): """ Definition of Status enum values """ INCOMPLETE = "INCPL" COMPLETE = "CPL" TOBEPLANNED = "TBP" PLANNED = "PLND" PENDING = "PNDG" EXECUTING = "EXING" EXECUTED = "EXD" REJECTED = "RJTD" INVALID = "INVL" CANCELLED = "CNCLD" UNPLANNABLE = "UNPLN" STATUS_CHOICES = ( (INCOMPLETE, "Incomplete"), (COMPLETE, "Complete"), (TOBEPLANNED, "To be planned"), (PLANNED, "Planned"), (UNPLANNABLE, "Unplannable"), (PENDING, "Pending"), (EXECUTED, "Executed"), (EXECUTING, "Executing"), (REJECTED, "Rejected"), (CANCELLED, "Cancelled"), (INVALID, "Invalid"), ) request = models.ForeignKey( Request, on_delete=models.CASCADE, related_name="sequences") name = models.CharField(max_length=45, blank=True, null=True) desc = models.TextField(blank=True, null=True) created = models.DateTimeField(blank=True, null=True, auto_now_add=True) updated = models.DateTimeField(blank=True, null=True, auto_now=True) is_alert = models.BooleanField(default=False) status = models.CharField( max_length=11, blank=True, null=True, choices=STATUS_CHOICES) target_coords = models.CharField(max_length=100, blank=True, null=True) with_drift = models.BooleanField(default=False) priority = models.IntegerField(blank=True, null=True) analysis_method = models.CharField(max_length=45, blank=True, null=True) moon_min = models.IntegerField(blank=True, null=True) alt_min = models.IntegerField(blank=True, null=True) type = models.CharField(max_length=6, blank=True, null=True) img_current = models.CharField(max_length=45, blank=True, null=True) img_total = models.CharField(max_length=45, blank=True, null=True) not_obs = models.BooleanField(default=False) obsolete = models.BooleanField(default=False) processing = models.BooleanField(default=False) flag = models.CharField(max_length=45, blank=True, null=True) jd1 = models.DecimalField(default=0.0, max_digits=15, decimal_places=8) jd2 = models.DecimalField(default=0.0, max_digits=15, decimal_places=8) t_prefered = models.DecimalField( default=-1.0, max_digits=15, decimal_places=8) duration = models.DecimalField( default=-1.0, max_digits=15, decimal_places=8) overhead = models.DecimalField(default=0, max_digits=15, decimal_places=8) ra = models.FloatField(blank=True, null=True) dec = models.FloatField(blank=True, null=True) class Meta: managed = True db_table = 'sequence' def __str__(self): return (str(self.name)) class ScheduleHasSequences(models.Model): # (EP) TODO: C'est pas un pb d'utiliser 2 fois le meme nom "shs" pour 2 choses differentes ???!!! schedule = models.ForeignKey( 'Schedule', on_delete=models.CASCADE, related_name="shs") sequence = models.ForeignKey( 'Sequence', on_delete=models.CASCADE, related_name="shs") status = models.CharField( max_length=11, blank=True, null=True, choices=Sequence.STATUS_CHOICES) desc = models.CharField(max_length=45, blank=True, null=True) tsp = models.DecimalField(default=-1.0, max_digits=15, decimal_places=8) tep = models.DecimalField(default=-1.0, max_digits=15, decimal_places=8) deltaTL = models.DecimalField( default=-1.0, max_digits=15, decimal_places=8) deltaTR = models.DecimalField( default=-1.0, max_digits=15, decimal_places=8) class Meta: managed = True db_table = 'schedule_has_sequences' class SiteWatch(models.Model): OPEN = "OPEN" CLOSE = "CLOSE" ON = "ON" OFF = "OFF" global_status = models.CharField(max_length=255, blank=True, null=True) updated = models.DateTimeField(blank=True, null=True, auto_now=True) lights = models.CharField(max_length=45, blank=True, null=True) dome = models.CharField(max_length=45, blank=True, null=True) doors = models.CharField(max_length=45, blank=True, null=True) temperature = models.FloatField(blank=True, null=True) shutter = models.FloatField(blank=True, null=True) pressure = models.FloatField(blank=True, null=True) humidity = models.FloatField(blank=True, null=True) class Meta: managed = True db_table = 'sitewatch' verbose_name_plural = "Site watches" def __str__(self): return (str(self.__dict__)) # TODO def setGlobalStatus(self): self.global_status = "" if self.doors and self.doors.find("open") != -1: self.global_status += "DOOR_OPEN " if self.lights and self.lights == "on": self.global_status += "LIGHTS_ON " if self.temperature and float(self.temperature) > 40: self.global_status += "TOO_HOT " if self.humidity and float(self.humidity) > 80: self.global_status += "HUMIDITY_TOO_HIGH " if self.global_status == "": self.global_status = "OK" return 0 # TODO HANDLE FLAT LAMPS ... def setAttribute(self, key, value): self.doors = "" if key == "InsideHumidity": self.humidity = value elif key == "Pressure": self.pressure = value elif key == "InsideTemp": self.temperature = value else: return 1 return 0 class SiteWatchHistory(models.Model): id = models.IntegerField(primary_key=True) class Meta: managed = True db_table = 'sitewatchhistory' verbose_name_plural = "Site watch histories" class StrategyObs(models.Model): name = models.CharField(max_length=45, blank=True, null=True) desc = models.TextField(blank=True, null=True) xml_file = models.CharField(max_length=45, blank=True, null=True) is_default = models.BooleanField(default=False) class Meta: managed = True db_table = 'strategyobs' verbose_name_plural = "Strategy obs" def __str__(self): return (str(self.name)) #TODO: à virer car utilisé pour Celery (ou bien à utiliser pour les agents) class TaskId(models.Model): task = models.CharField(max_length=45, blank=True, null=True) created = models.DateTimeField(blank=True, null=True, auto_now_add=True) task_id = models.CharField(max_length=45, blank=True, null=True) class Meta: managed = True db_table = 'task_id' def __str__(self): return (str(self.task) + " - " + str(self.task_id)) class Telescope(Device): TELESCOPE = "Telescope" mount_type = models.CharField(max_length=9, blank=True, null=True) diameter = models.FloatField(blank=True, null=True) latitude = models.FloatField(blank=True, null=True) longitude = models.FloatField(blank=True, null=True) sens = models.CharField(max_length=1, blank=True, null=True) altitude = models.FloatField(blank=True, null=True) readout_time = models.IntegerField(blank=True, null=True) slew_time = models.IntegerField(blank=True, null=True) slew_dead = models.IntegerField(blank=True, null=True) slew_rate_max = models.FloatField(blank=True, null=True) horizon_type = models.CharField(max_length=45, blank=True, null=True) horizon_def = models.FloatField(blank=True, null=True) lim_dec_max = models.FloatField(blank=True, null=True) lim_dec_min = models.FloatField(blank=True, null=True) lim_ha_rise = models.FloatField(blank=True, null=True) lim_ha_set = models.FloatField(blank=True, null=True) address = models.CharField(max_length=45, blank=True, null=True) night_elev_sun = models.FloatField(blank=True, null=True) mpc_code = models.CharField(max_length=45, blank=True, null=True) class Meta: managed = True db_table = 'telescope' def __str__(self): return (self.name) class TelescopeCommand(models.Model): created = models.DateTimeField(blank=True, null=True, auto_now_add=True) answered = models.DateTimeField(blank=True, null=True) request = models.CharField(blank=False, null=False, max_length=255) answer = models.TextField(null=True, blank=True) class Meta: managed = True db_table = "telescopecommand" def __str__(self): return str(self.request) + str(self.created) class UserLevel(models.Model): name = models.CharField(max_length=45, blank=True, null=True) desc = models.TextField(blank=True, null=True) priority = models.IntegerField(blank=True, null=True) quota = models.FloatField(blank=True, null=True) class Meta: managed = True db_table = 'user_level' def __str__(self): return (str(self.name)) class Version(models.Model): module_name = models.CharField(max_length=45, blank=True, null=True) version = models.CharField(max_length=15, blank=True, null=True) created = models.DateTimeField(blank=True, null=True, auto_now_add=True) updated = models.DateTimeField(blank=True, null=True, auto_now=True) class Meta: managed = True db_table = 'version' def __str__(self): return (str(self.module_name) + " - " + str(self.version)) class WeatherWatch(models.Model): WIND_LIMIT = 100 RAIN_LIMIT = 5 global_status = models.CharField(max_length=255, blank=True, null=True) updated = models.DateTimeField(blank=True, null=True, auto_now=True) humidity = models.FloatField(blank=True, null=True) wind = models.FloatField(blank=True, null=True) wind_dir = models.CharField(max_length=45, blank=True, null=True) temperature = models.FloatField(blank=True, null=True) pressure = models.FloatField(blank=True, null=True) rain = models.FloatField(blank=True, null=True) cloud = models.FloatField(blank=True, null=True) class Meta: managed = True db_table = 'weatherwatch' verbose_name_plural = "Weather watches" def __str__(self): return (str(self.__dict__)) # TODO def setGlobalStatus(self): #print(self.rain) self.global_status = "" if self.rain and float(self.rain) > 0: self.global_status += "RAINING " if self.wind and float(self.wind) > 80: self.global_status += "WIND_TOO_STRONG " if self.humidity and float(self.humidity) > 80: self.global_status += "HUMIDITY_TOO_HIGH " if self.cloud and float(self.cloud) > 10: self.global_status += "TOO_MUCH_CLOUDY " if self.global_status == "": self.global_status = "OK" return 0 def setAttribute(self, key, value): if key == "Rain_boolean": self.rain = value elif key == "_CloudRate": self.cloud = value elif key == "Wind_speed": self.wind = value elif key == "Wind_direction": self.wind_dir = value elif key == "Temperature_outside": self.temperature = value elif key == "Humidity_outside": self.humidity = value elif key == "_Pressure": self.pressure = value else: return 1 return 0 class WeatherWatchHistory(models.Model): datetime = models.DateTimeField(blank=True, null=True, auto_now_add=True) humid_int = models.FloatField(blank=True, null=True) humid_ext = models.CharField(max_length=45, blank=True, null=True) wind = models.CharField(max_length=45, blank=True, null=True) wind_dir = models.CharField(max_length=45, blank=True, null=True) temp_int = models.CharField(max_length=45, blank=True, null=True) temp_ext = models.CharField(max_length=45, blank=True, null=True) pressure = models.CharField(max_length=45, blank=True, null=True) rain = models.CharField(max_length=45, blank=True, null=True) dwn = models.CharField(max_length=45, blank=True, null=True) class Meta: managed = True db_table = 'weatherwatchhistory' verbose_name_plural = "Weather watch histories" def __str__(self): return (str(self.datetime))