class Job(models.Model): STATES = Choices("queued", "running", "completed") name = models.CharField(max_length=320, unique=True) args = models.TextField(null=True, blank=True) output = models.TextField(null=True, blank=True) state = models.CharField(choices=STATES, default=STATES.queued, max_length=64) class Meta: app_label = 'swingers'
class ExclusionArea(Audit): prescription = models.ForeignKey( Prescription, help_text="Prescription this exclusion area belongs to.", on_delete=models.PROTECT) description = models.TextField() location = models.TextField() detail = models.TextField(verbose_name="How will fire be excluded?") _required_fields = ('location', 'description', 'detail') def __str__(self): return "{0} - {1}".format(self.location, self.description)
class ProposedAction(Audit): prescription = models.ForeignKey(Prescription, on_delete=models.PROTECT) observations = models.TextField(blank=True, verbose_name='Observations Identified') action = models.TextField(blank=True, verbose_name='Proposed Action') _required_fields = ('observations', 'action') def __str__(self): return self.action class Meta: verbose_name = 'Lesson Learned' verbose_name_plural = "Lessons Learned"
class Evaluation(Audit): ACHIEVED_NO = 1 ACHIEVED_YES = 2 ACHIEVED_PARTIAL = 3 ACHIEVED_CHOICES = ( (ACHIEVED_NO, "No"), (ACHIEVED_YES, "Yes"), (ACHIEVED_PARTIAL, "Partially"), ) criteria = models.OneToOneField(SuccessCriteria, verbose_name="Success Criteria") achieved = models.PositiveSmallIntegerField( choices=ACHIEVED_CHOICES, blank=True, null=True, verbose_name="Success Criteria Achieved?") summary = models.TextField(verbose_name="Evaluation Rationale", blank=True) _required_fields = ('achieved', 'summary') @property def prescription(self): return self.criteria.prescription def clean_achieved(self): if self.summary != '' and not self.achieved: raise ValidationError("You need to indicate whether or not " "this success criteria has been met.") def clean_summary(self): if self.achieved is not None and not self.summary: raise ValidationError("You need to provide a rationale for this " "achievement.")
class Token(models.Model): link = models.ForeignKey(ApplicationLink) user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="%(app_label)s_%(class)s_user", help_text="User token authenticates as") url = models.TextField(help_text="Suburl this token is restricted to, " "relative e.g. (/my/single/service/entrypoint)", default="/") secret = models.CharField(max_length=320, help_text="Token Secret", unique=True) modified = models.DateTimeField(default=timezone.now, editable=False) timeout = models.IntegerField(default=600, help_text="Timeout token in " "seconds, 0 means never times out") class Meta: app_label = 'swingers' def save(self, *args, **kwargs): try: revision.unregister(self.__class__) except: pass super(Token, self).save(*args, **kwargs) def natural_key(self): return (self.secret, ) def get_by_natural_key(self, secret): return self.get(secret=secret) def __str__(self): return "{0} - {1}:{2}@{3}".format(self.pk, self.user, self.secret, self.link.client_name)[:320]
class DocumentAbstract(Audit, ActiveModel): ''' Generic class for supporting documents. ''' content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() content_object = generic.GenericForeignKey('content_type', 'object_id') uploaded_file = models.FileField( # max_length is maximum full path and filename length: max_length=255, upload_to=content_filename) description = models.TextField( blank=True, null=True, help_text='Name and/or description of the supporting document.') class Meta: abstract = True def __unicode__(self): return unicode(self.pk) @property def uploaded_file_name(self): """ Return the file name of the uploaded file, minus the server file path. """ try: return self.uploaded_file.name.rsplit('/', 1)[-1] except: # If the file has been deleted/is missing, return a warning. return '<missing_file>' @property def uploaded_file_ext(self): ''' Return the file extension of the uploaded file. ''' try: ext = os.path.splitext(self.uploaded_file.name)[1] return ext.replace('.', '').upper() except: # If the file has been deleted/is missing, return an empty string. return '' @property def filesize_str(self): ''' Return the filesize as a nicely human-readable string. ''' try: num = self.uploaded_file.size for x in ['bytes', 'KB', 'MB', 'GB']: if num < 1024.0: return '%3.1f%s' % (num, x) num /= 1024.0 except: # If the file has been deleted/is missing, return an empty string. return ''
class OperationalOverview(Audit): prescription = models.ForeignKey(Prescription, on_delete=models.PROTECT) overview = models.TextField(blank=True, null=True) def __str__(self): return self.overview class Meta: get_latest_by = 'id'
class RoadSegment(Way): road_type = models.TextField( verbose_name="Road Type", blank=True) traffic_considerations = models.TextField( blank=True, verbose_name="Special Traffic Considerations") traffic_diagram = models.ForeignKey( TrafficControlDiagram, null=True, blank=True, verbose_name="Select Traffic Control Diagram", on_delete=models.PROTECT) _required_fields = ('name', 'road_type',) def __str__(self): return self.name class Meta: ordering = ['id'] verbose_name = "Road" verbose_name_plural = "Roads"
class TrailSegment(Way): start = models.TextField(blank=True, verbose_name="Start Location") start_signage = models.TextField( blank=True, verbose_name="Description of Start Signage") stop = models.TextField(blank=True, verbose_name="Stop Location") stop_signage = models.TextField(blank=True, verbose_name="Description of Stop Signage") diversion = models.BooleanField(verbose_name="Is there a Diversion Map?", default=False) _required_fields = ('name', ) def __str__(self): return self.name class Meta: ordering = ['id'] verbose_name = "Track/Trail" verbose_name_plural = "Tracks/Trails"
class SignInspection(Audit): way = models.ForeignKey( Way, verbose_name="Road/Track/Trail Name", on_delete=models.PROTECT) inspected = models.DateTimeField( default=timezone.now, verbose_name="Date Inspected") comments = models.TextField() inspector = models.TextField(verbose_name="Inspecting Officer") def __str__(self): return "%s (%s)" % (self.way.name, date(self.inspected)) @property def prescription(self): return self.way.prescription class Meta: ordering = ['id'] verbose_name = "Sign Inspection" verbose_name_plural = "Sign Inspections"
class PostBurnChecklist(Audit): prescription = models.ForeignKey(Prescription, blank=True, null=True, on_delete=models.PROTECT) action = models.CharField(max_length=320) relevant = models.BooleanField(default=False) completed_on = models.DateField(default=timezone.now, blank=True, null=True) completed_by = models.TextField(verbose_name="Action completed by (name)", blank=True, null=True) _required_fields = ('completed_on', 'completed_by') def __str__(self): return self.action def clean_completed_on(self): if self.completed_on is not None: # The following line was causing validation failure before 0800. # Jira ref: PBS-1454 #if self.completed_on > timezone.now().date(): if self.completed_on > datetime.now().date(): raise ValidationError("This action could not be completed " "in the future.") if (self.prescription.approval_status_modified is None or self.completed_on < self.prescription.approval_status_modified.date()): raise ValidationError("This action could not be completed " "before its prescription was approved.") def clean(self, *args, **kwargs): super(PostBurnChecklist, self).clean(*args, **kwargs) if self.completed_on is not None and not self.completed_by: raise ValidationError("Please specify who completed this action.") if self.completed_by and self.completed_on is None: raise ValidationError("Please specify when was this action " "completed.") class Meta: ordering = ["pk"] verbose_name = "post burn checklist item" verbose_name_plural = "post burn checklist"
class RegionAbstract(Audit, ActiveModel): """ Abstract model to represent DEC regions and district areas, for use within other DEC corporate applications. """ name = models.CharField(max_length=320, unique=True) description = models.TextField(null=True, blank=True) slug = models.SlugField(unique=True, help_text='Must be unique.') class Meta: abstract = True ordering = ['name'] def __unicode__(self): return unicode(self.name) search_fields = ('name', 'slug', 'description')
class ApplicationLink(models.Audit): AUTH_METHOD = Choices('basic', 'md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512') client_name = models.CharField( max_length=320, help_text="project/host of client, this app is {0}".format( settings.SITE_NAME)) server_name = models.CharField( max_length=320, help_text="project/host of server, this app is {0}".format( settings.SITE_NAME)) server_url = models.TextField( help_text="URL service backend requests should be made to") identifier = models.CharField( max_length=320, null=True, blank=True, help_text="IP or Hostname, optional for added security") secret = models.CharField(max_length=320, help_text="Application secret") timeout = models.IntegerField(default=600, help_text="Timeout of oauth tokens in " "seconds") auth_method = models.CharField(choices=AUTH_METHOD, default=AUTH_METHOD.sha256, max_length=20) class Meta(Audit.Meta): unique_together = ("client_name", "server_name") app_label = 'swingers' def natural_key(self): return (self.client_name, self.server_name) def get_by_natural_key(self, client_name, server_name): return self.get(client_name=client_name, server_name=server_name) def get_access_token(self, user_id, expires=600): """ Returns an access token for with the current user. Note: uses a hardcoded URL when determining where to send the request. """ url = self.server_url + "/api/swingers/v1/{0}/request_token" nonce = make_nonce() r = requests.get(url.format(self.server_name), params={ "user_id": user_id, "nonce": nonce, "client_secret": self.get_client_secret(user_id, nonce), "client_id": self.client_name, "expires": expires }) if r.ok: return r.content else: r.raise_for_status() def get_client_secret(self, user_id, nonce): """ Returns the client secret based on a user and a nonce. """ stringtohash = "{0}{1}{2}".format(self.secret, user_id, nonce) return getattr(hashlib, self.auth_method)(stringtohash).hexdigest()
class LightingSequence(Audit): prescription = models.ForeignKey( Prescription, help_text="Prescription this lighting sequence belongs to.", on_delete=models.PROTECT) seqno = models.PositiveSmallIntegerField( verbose_name="Lighting Sequence Number", choices=Prescription.INT_CHOICES) cellname = models.TextField(verbose_name="Cell Name") strategies = models.TextField( help_text="Textual description of strategies for this sequence") wind_min = models.PositiveIntegerField( verbose_name="Min Wind Speed (km/h)", validators=[MaxValueValidator(200)], blank=True) wind_max = models.PositiveIntegerField( verbose_name="Max Wind Speed (km/h)", validators=[MinValueValidator(0), MaxValueValidator(200)], blank=True) wind_dir = models.TextField(verbose_name="Wind Direction") fuel_description = models.TextField( help_text="Textual description of the fuel for this sequence") fuel_age = models.PositiveSmallIntegerField(verbose_name="Fuel Age", help_text="Fuel Age in years", null=True, blank=True) fuel_age_unknown = models.BooleanField(verbose_name="Fuel Age Unknown?", default=False) ignition_types = models.ManyToManyField( IgnitionType, verbose_name="Planned Core Ignition Type") ffdi_min = models.PositiveIntegerField(verbose_name="FFDI Min", blank=True) ffdi_max = models.PositiveIntegerField(verbose_name="FFDI Max", blank=True) gfdi_min = models.PositiveIntegerField(verbose_name="GFDI Min", blank=True) gfdi_max = models.PositiveIntegerField(verbose_name="GFDI Max", blank=True) grassland_curing_min = models.PositiveIntegerField( verbose_name="Grassland Curing Min", blank=True) grassland_curing_max = models.PositiveIntegerField( verbose_name="Grassland Curing Max", blank=True) ros_min = models.PositiveIntegerField(verbose_name="ROS Min (m/h)", blank=True) ros_max = models.PositiveIntegerField(verbose_name="ROS Max (m/h)", blank=True) resources = models.TextField(verbose_name="Specialist Resources", blank=True) _required_fields = ('seqno', 'cellname', 'strategies', 'fuel_description', 'fuel_age', 'fuel_age_unknown', 'ignition_types', 'ffdi_min', 'ffdi_max', 'ros_min', 'ros_max', 'wind_min', 'wind_max', 'wind_dir') class Meta: verbose_name = "Lighting Sequence" verbose_name_plural = "Lighting Sequences" ordering = ['id'] def wind_speed(self): return field_range(self.wind_min, self.wind_max) wind_speed.short_description = mark_safe( '<abbr title="Wind Speed">Wind Speed</abbr> Range') def ffdi(self): return field_range(self.ffdi_min, self.ffdi_max) ffdi.short_description = mark_safe( '<abbr title="Forecast Fire Danger Index">FFDI</abbr> Range') def gfdi(self): return field_range(self.gfdi_min, self.gfdi_max) gfdi.short_description = mark_safe( '<abbr title="Grassland Fire Danger Index">FFDI</abbr> Range') gfdi.admin_order_field = "gfdi_max" def grassland_curing(self): return field_range(self.grassland_curing_min, self.grassland_curing_max) grassland_curing.short_description = mark_safe( '<abbr title="Grassland Curing Percent</abbr> Range') grassland_curing.admin_order_field = "grassland_curing_max" def ros(self): return field_range(self.ros_min, self.ros_max) ros.short_description = mark_safe( '<abbr title="Rate of Spread">ROS</abbr> Range') def clean_fuel_age(self): if self.fuel_age_unknown and self.fuel_age: raise ValidationError("You must either enter a fuel age or tick " "Fuel Age Unknown.") if not (self.fuel_age_unknown or self.fuel_age): raise ValidationError("You must enter a fuel age or tick Fuel Age " "Unknown.") def clean_ffdi_min(self): if self.ffdi_min is None: self.ffdi_min = 0 def clean_ffdi_max(self): if self.ffdi_max is None: self.ffdi_max = 0 def clean_gfdi_min(self): if self.gfdi_min is None: self.gfdi_min = 0 def clean_gfdi_max(self): if self.gfdi_max is None: self.gfdi_max = 0 def clean_grassland_curing_min(self): if self.grassland_curing_min is None: self.grassland_curing_min = 0 def clean_grassland_curing_max(self): if self.grassland_curing_max is None: self.grassland_curing_max = 0 def clean_ros_min(self): if self.ros_min is None: self.ros_min = 0 def clean_ros_max(self): if self.ros_max is None: self.ros_max = 0 def clean_wind_min(self): if self.wind_min is None: self.wind_min = 0 def clean_wind_max(self): if self.wind_max is None: self.wind_max = 0 def __str__(self): return "{0}. {1}".format(self.seqno, self.cellname)
class EdgingPlan(Audit): prescription = models.ForeignKey( Prescription, help_text="Prescription this edging plan belongs to.", on_delete=models.PROTECT) location = models.TextField( verbose_name="Edge Location", blank=True, null=True, help_text="Textual description of edge & its location") desirable_season = models.PositiveSmallIntegerField( verbose_name="Desirable Season", max_length=64, choices=Season.SEASON_CHOICES, blank=True, null=True) strategies = models.TextField( help_text="Textual description of strategies for this plan", blank=True, null=True) # NOTE: the fuel_type field will be deprecated in favour of a reference to # the VegetationType model in the prescription app. fuel_type = models.ForeignKey(FuelType, verbose_name="Fuel Type", blank=True, null=True, on_delete=models.PROTECT) ffdi_min = models.PositiveIntegerField(verbose_name="Min FFDI", blank=True, null=True) ffdi_max = models.PositiveIntegerField(verbose_name="Max FFDI", blank=True, null=True) gfdi_min = models.PositiveIntegerField(verbose_name="Min GFDI", blank=True, null=True) gfdi_max = models.PositiveIntegerField(verbose_name="Max GFDI", blank=True, null=True) sdi = models.TextField(verbose_name="SDI", blank=True, null=True) wind_min = models.PositiveIntegerField( verbose_name="Min Wind Speed (km/h)", blank=True, null=True) wind_max = models.PositiveIntegerField( verbose_name="Max Wind speed (km/h)", blank=True, null=True) wind_dir = models.TextField(verbose_name="Wind Direction", blank=True, null=True) ros_min = models.PositiveIntegerField(verbose_name="Min ROS (m/h)", blank=True, null=True) ros_max = models.PositiveIntegerField(verbose_name="Max ROS (m/h)", blank=True, null=True) grassland_curing_min = models.PositiveIntegerField( verbose_name="Grassland Curing % Min", blank=True, null=True) grassland_curing_max = models.PositiveIntegerField( verbose_name="Grassland Curing % Max", blank=True, null=True) def ffdi(self): return field_range(self.ffdi_min, self.ffdi_max) ffdi.short_description = mark_safe( '<abbr title="Forecast Fire Danger Index">FFDI</abbr> Range') ffdi.admin_order_field = 'ffdi_min' def gfdi(self): return field_range(self.gfdi_min, self.gfdi_max) gfdi.short_description = mark_safe( '<abbr title="Grassland Fire Danger Index">FFDI</abbr> Range') gfdi.admin_order_field = "gfdi_max" def wind(self): return "%d-%d" % (self.wind_min, self.wind_max) wind.short_description = "Wind Speed Range (km/h)" def grassland_curing(self): return field_range(self.grassland_curing_min, self.grassland_curing_max) grassland_curing.short_description = mark_safe( '<abbr title="Grassland Curing Percent</abbr> Range') grassland_curing.admin_order_field = "grassland_curing_max" def ros(self): return field_range(self.ros_min, self.ros_max) ros.short_description = mark_safe( '<abbr title="Rate of Spread">ROS</abbr> Range') def clean_sdi(self): if self.sdi == '': self.sdi = "N/A" def __str__(self): return self.location class Meta: verbose_name = "Edging Plan" verbose_name_plural = "Edging Plans" ordering = ['created']
class PrescribedBurn(Audit): BURN_ACTIVE = 1 BURN_INACTIVE = 2 BURN_MONITORED = 3 BURN_CHOICES = ( (BURN_ACTIVE, 'Yes'), (BURN_INACTIVE, 'No'), (BURN_MONITORED, 'Monitored'), ) IGNITION_STATUS_REQUIRED = 1 IGNITION_STATUS_COMPLETED = 2 IGNITION_STATUS_CHOICES = ( (IGNITION_STATUS_REQUIRED, 'Further ignitions required'), (IGNITION_STATUS_COMPLETED, 'Ignition now complete'), ) APPROVAL_DRAFT = 'DRAFT' APPROVAL_SUBMITTED = 'USER' APPROVAL_ENDORSED = 'SRM' APPROVAL_APPROVED = 'SDO' APPROVAL_CHOICES = ( (APPROVAL_DRAFT, 'Draft'), (APPROVAL_SUBMITTED, 'District Submitted'), (APPROVAL_ENDORSED, 'Region Endorsed'), (APPROVAL_APPROVED, 'State Approved'), ) FORM_268A = 1 FORM_268B = 2 FORM_NAME_CHOICES = ( (FORM_268A, 'Form 268a'), (FORM_268B, 'Form 268b'), ) ''' BUSHFIRE_DISTRICT_ALIASES can be used to override the District's original code from the model. Usage e.g.: BUSHFIRE_DISTRICT_ALIASES = { 'PHS' : 'PH', 'SWC' : 'SC', } ''' BUSHFIRE_DISTRICT_ALIASES = {} fmt = "%Y-%m-%d %H:%M" prescription = models.ForeignKey(Prescription, verbose_name="Burn ID", related_name='prescribed_burn', null=True, blank=True, on_delete=models.PROTECT) # prescription = ChainedForeignKey( # Prescription, chained_field="region", chained_model_field="region", # show_all=False, auto_choose=True, blank=True, null=True) # Required for Fire records fire_id = models.CharField(verbose_name="Fire Number", max_length=15, null=True, blank=True) fire_name = models.TextField(verbose_name="Name", null=True, blank=True) region = models.PositiveSmallIntegerField(choices=[ (r.id, r.name) for r in Region.objects.all() ], null=True, blank=True) district = ChainedForeignKey(District, chained_field="region", chained_model_field="region", show_all=False, auto_choose=True, blank=True, null=True, on_delete=models.PROTECT) fire_tenures = models.ManyToManyField(FireTenure, verbose_name="Tenures", blank=True) date = models.DateField(auto_now_add=False) form_name = models.PositiveSmallIntegerField( verbose_name="Form Name (268a / 268b)", choices=FORM_NAME_CHOICES, editable=True) status = models.PositiveSmallIntegerField(verbose_name="Active", choices=BURN_CHOICES, null=True, blank=True) ignition_status = models.PositiveSmallIntegerField( verbose_name="Ignition Status", choices=IGNITION_STATUS_CHOICES, null=True, blank=True) external_assist = models.ManyToManyField( ExternalAssist, verbose_name="Assistance received from", blank=True) planned_area = models.DecimalField( verbose_name="Today's treatment area (ha)", max_digits=12, decimal_places=1, validators=[MinValueValidator(0.0)], null=True, blank=True) area = models.DecimalField(verbose_name="Yesterday's treatment area (ha)", max_digits=12, decimal_places=1, validators=[MinValueValidator(0.0)], null=True, blank=True) planned_distance = models.DecimalField( verbose_name="Today's treatment distance (km)", max_digits=12, decimal_places=1, validators=[MinValueValidator(0.0)], null=True, blank=True) distance = models.DecimalField( verbose_name="Yesterday's treatment distance (km)", max_digits=12, decimal_places=1, validators=[MinValueValidator(0.0)], null=True, blank=True) tenures = models.TextField(verbose_name="Tenure") location = models.TextField(verbose_name="Location", null=True, blank=True) est_start = models.TimeField('Estimated Start Time', null=True, blank=True) conditions = models.TextField(verbose_name='SDO Special Conditions', null=True, blank=True) rolled = models.BooleanField(verbose_name="Fire Rolled from yesterday", editable=False, default=False) latitude = models.FloatField(default=0.0) longitude = models.FloatField(default=0.0) #aircraft_burn = models.BooleanField(verbose_name="Aircraft Burn", default=False) def clean(self): if not self.form_name: if self.prescription and self.area == None and self.distance == None: self.form_name = 1 else: self.form_name = 2 if self.prescription and not (self.region and self.district): self.region = self.prescription.region.id self.district = self.prescription.district def clean_fire_id(self): # set the Lat/Long to Zero, since Bushfire is not assigning these required fields self.latitude = 0.0 self.longitude = 0.0 # def clean_date(self): # today = date.today() # tomorrow = today + timedelta(days=1) # if not self.pk and (self.date < today or self.date > tomorrow): # raise ValidationError("You must enter burn plans for today or tommorow's date only.") def clean_planned_distance(self): if self.planned_area == None and self.planned_distance == None: raise ValidationError( "Must input at least one of Area or Distance") def clean_distance(self): if self.area == None and self.distance == None: raise ValidationError( "Must input at least one of Area or Distance") @property def is_acknowledged(self): if all(x in [i.acknow_type for i in self.acknowledgements.all()] for x in ['SDO_A', 'SDO_B']): return True else: return False @property def user_a_record(self): ack = self.acknowledgements.filter(acknow_type='USER_A') return ack[0].record if ack else None @property def srm_a_record(self): ack = self.acknowledgements.filter(acknow_type='SRM_A') return ack[0].record if ack else None @property def sdo_a_record(self): ack = self.acknowledgements.filter(acknow_type='SDO_A') return ack[0].record if ack else None @property def user_b_record(self): ack = self.acknowledgements.filter(acknow_type='USER_B') return ack[0].record if ack else None @property def srm_b_record(self): ack = self.acknowledgements.filter(acknow_type='SRM_B') return ack[0].record if ack else None @property def sdo_b_record(self): ack = self.acknowledgements.filter(acknow_type='SDO_B') return ack[0].record if ack else None @property def formA_isDraft(self): return not any( x in [i.acknow_type for i in self.acknowledgements.all()] for x in ['USER_A', 'SRM_A', 'SDO_A']) @property def formB_isDraft(self): return not any( x in [i.acknow_type for i in self.acknowledgements.all()] for x in ['USER_B', 'SRM_B', 'SDO_B']) @property def formA_user_acknowledged(self): return True if self.user_a_record else False @property def formA_srm_acknowledged(self): return True if self.srm_a_record else False @property def formA_sdo_acknowledged(self): return True if self.sdo_a_record else False @property def formB_user_acknowledged(self): return True if self.user_b_record else False @property def formB_srm_acknowledged(self): return True if self.srm_b_record else False @property def formB_sdo_acknowledged(self): return True if self.sdo_b_record else False @property def fire_type(self): return "Burn" if self.prescription else "Fire" @property def fire_idd(self): if self.prescription: return self.prescription.burn_id else: return self.fire_id @property def further_ignitions_req(self): if self.ignition_status == self.IGNITION_STATUS_REQUIRED: return True elif self.ignition_status == self.IGNITION_STATUS_COMPLETED: return False return None @property def active(self): # if self.status==self.BURN_ACTIVE: if self.status == self.BURN_ACTIVE or self.status == self.BURN_MONITORED: return True elif self.status == self.BURN_INACTIVE: return False return None @property def has_conditions(self): if self.conditions: return True return False @property def planned_area_str(self): _str = '' if self.planned_area: _str += str(self.planned_area) + " ha {} ".format( '-' if self.planned_distance else '') if self.planned_distance: _str += str(self.planned_distance) + " km" return _str @property def area_str(self): _str = '' if self.area >= 0: _str += str( self.area) + " ha {} ".format('-' if self.distance else '') if self.distance >= 0: _str += str(self.distance) + " km" return _str @property def tenures_str(self): if self.prescription: return self.tenures #', '.join([t.name for t in self.tenures.all()]) else: return ', '.join([i.name for i in self.fire_tenures.all()]) @property def had_external_assist(self): if self.external_assist.all().count() > 0: return True return False @property def external_assist_str(self): return ', '.join([i.name for i in self.external_assist.all()]) @property def name(self): if self.prescription: return self.prescription.name else: return self.fire_name @property def get_region(self): return self.prescription.region if self.prescription else self.region @property def get_district(self): return self.prescription.district if self.prescription else self.district @property def can_endorse(self): return (self.status == self.APPROVAL_SUBMITTED) @property def can_approve(self): return (self.status == self.APPROVAL_ENDORSED) @property def last_ignition(self): if self.prescription: area_achievements = self.prescription.areaachievement_set.all() if area_achievements: return max([i.ignition for i in area_achievements]) return None def short_str(self): return self.prescription.burn_id if self.prescription else self.fire_id def copy_ongoing_records(self, dt): """ Copy today's 'active' records to tomorrow 268b - (Automatically) copy all records from yesterday that were Active when 268a Region Endorsement occurs, except for Active and Area Burnt Yesterday dt = from date, copied to dt+1 """ tomorrow = dt + timedelta(days=1) # relative to dt #objects = [obj for obj in PrescribedBurn.objects.filter(date=dt, status=PrescribedBurn.BURN_ACTIVE)] objects = [self] now = timezone.now() admin = User.objects.get(username='******') count = 0 for obj in objects: if obj.fire_id and PrescribedBurn.objects.filter( fire_id=obj.fire_id, date=tomorrow, form_name=PrescribedBurn.FORM_268B): # don't copy if already exists - since record is unique on Prescription (not fire_id) logger.info( 'Ongoing Record Already Exists (Fire) - not copied (268b today to 268b tomorrow). Record {}, today {}, tomorrow {}' .format(obj.fire_idd, dt, tomorrow)) continue if obj.prescription and PrescribedBurn.objects.filter( prescription__burn_id=obj.prescription.burn_id, date=tomorrow, form_name=PrescribedBurn.FORM_268B, location=obj.location): # don't copy if already exists - since record is unique on Prescription (not fire_id) logger.info( 'Ongoing Record Already Exists (Burn) - not copied (268b today to 268b tomorrow). Record {}, today {}, tomorrow {}' .format(obj.fire_idd, dt, tomorrow)) continue try: obj.pk = None obj.date = tomorrow obj.area = None obj.status = None obj.approval_268a_status = PrescribedBurn.APPROVAL_DRAFT obj.approval_268a_status_modified = now obj.approval_268b_status = PrescribedBurn.APPROVAL_DRAFT obj.approval_268b_status_modified = now obj.acknowledgements.all().delete() obj.rolled = True obj.save() count += 1 logger.info( 'Ongoing Record copied (268b today to 268b tomorrow). Record {}, today {}, tomorrow {}' .format(obj.fire_idd, dt, tomorrow)) except: # records already exist - pk (pres, date) will not allow overwrite, so ignore the exception logger.warn( 'Ongoing Record not copied. Record {} already exists on day {}' .format(obj.fire_idd, tomorrow)) def copy_planned_approved_records_adhoc(self, dt): """ Copy today's 'planned' records (268a), that have been SDO approved. to tomorrow ongoing (268b) set Active and Area Burnt fields to None dt = from date, copied to dt+1 """ tomorrow = dt + timedelta(days=1) # relative to dt if not self.formA_sdo_acknowledged: logger.info( 'Only SDO Acknowledged record can be copied from dt {} to tomorrow {}' .format(dt, tomorrow)) return #objects = PrescribedBurn.objects.filter(date=dt, acknowledgements__acknow_type__in=['SDO_A'], form_name=PrescribedBurn.FORM_268A) objects = [self] now = timezone.now() count = 0 for obj in objects: if obj.fire_id and PrescribedBurn.objects.filter( fire_id=obj.fire_id, date=tomorrow, form_name=PrescribedBurn.FORM_268B): # don't copy if already exists - since record is unique on Prescription (not fire_id) logger.info( 'Planned Approved Record Already Exists (Fire) - not copied (268a today to 268b tomorrow). Record {}, today {}, tomorrow {}' .format(obj.fire_idd, dt, tomorrow)) continue if obj.prescription and PrescribedBurn.objects.filter( prescription__burn_id=obj.prescription.burn_id, date=tomorrow, form_name=PrescribedBurn.FORM_268B, location=obj.location): # don't copy if already exists - since record is unique on Prescription (not fire_id) logger.info( 'Planned Approved Record Already Exists (Burn) - not copied (268a today to 268b tomorrow). Record {}, today {}, tomorrow {}' .format(obj.fire_idd, dt, tomorrow)) continue try: obj.pk = None obj.date = tomorrow obj.area = None obj.distance = None obj.status = None obj.approval_268a_status = PrescribedBurn.APPROVAL_DRAFT obj.approval_268a_status_modified = now obj.approval_268b_status = PrescribedBurn.APPROVAL_DRAFT obj.approval_268b_status_modified = now #obj.acknowledgements.all().delete() obj.form_name = PrescribedBurn.FORM_268B obj.rolled = True obj.save() count += 1 logger.info( 'Planned Approved Record copied (268a today to 268b tomorrow). Record {}, today {}, tomorrow {}' .format(obj.fire_idd, dt, tomorrow)) except: # records already exist - pk (pres, date) will not allow overwrite, so ignore the exception logger.warn( 'Planned Approved Record not copied. Record {} already exists on day {}' .format(obj.fire_idd, tomorrow)) def __str__(self): return self.prescription.burn_id + ' (Burn)' if self.prescription else self.fire_id + ' (Fire)' class Meta: unique_together = ('prescription', 'date', 'form_name', 'location') verbose_name = 'Prescribed Burn or Bushfire' verbose_name_plural = 'Prescribed Burns and Bushfires' permissions = ( ("can_endorse", "Can endorse burns"), ("can_approve", "Can approve burns"), )
class BurningPrescription(Audit): prescription = models.ForeignKey( Prescription, help_text="Prescription this fuel schedule belongs to.", on_delete=models.PROTECT) # NOTE: the fuel_type field will be deprecated in favour of a reference to # the VegetationType model in the prescription app. fuel_type = models.ForeignKey(FuelType, verbose_name="Fuel Type", blank=True, null=True, on_delete=models.PROTECT) scorch = models.PositiveIntegerField( help_text="Maximum Scorch Height (m)", verbose_name="Scorch Height", validators=[MinValueValidator(1), MaxValueValidator(30)], blank=True, null=True) min_area = models.PositiveIntegerField( verbose_name="Min Area to be Burnt (%)", validators=[MinValueValidator(1), MaxValueValidator(100)], blank=True, null=True) max_area = models.PositiveIntegerField( verbose_name="Max Area to be Burnt (%)", validators=[MinValueValidator(1), MaxValueValidator(100)], blank=True, null=True) ros_min = models.PositiveIntegerField( verbose_name="Min ROS (m/h)", validators=[MinValueValidator(0), MaxValueValidator(10000)], blank=True, null=True) ros_max = models.PositiveIntegerField( verbose_name="Max ROS (m/h)", validators=[MinValueValidator(0), MaxValueValidator(10000)], blank=True, null=True) ffdi_min = models.PositiveIntegerField( verbose_name="Min FFDI", validators=[MinValueValidator(0), MaxValueValidator(100)], blank=True, null=True) ffdi_max = models.PositiveIntegerField( verbose_name="Max FFDI", validators=[MinValueValidator(0), MaxValueValidator(100)], blank=True, null=True) gfdi_min = models.PositiveIntegerField( verbose_name="Min GFDI", validators=[MinValueValidator(0), MaxValueValidator(100)], blank=True, null=True) gfdi_max = models.PositiveIntegerField( verbose_name="Max GFDI", validators=[MinValueValidator(0), MaxValueValidator(100)], blank=True, null=True) temp_min = models.PositiveIntegerField( verbose_name="Min Temp (degrees C)", validators=[MinValueValidator(0), MaxValueValidator(60)], blank=True, null=True) temp_max = models.PositiveIntegerField( verbose_name="Max Temp (degress C)", validators=[MinValueValidator(0), MaxValueValidator(60)], blank=True, null=True) rh_min = models.PositiveIntegerField( verbose_name="Min Relative Humidity (%)", validators=[MinValueValidator(0), MaxValueValidator(100)], blank=True, null=True) rh_max = models.PositiveIntegerField( verbose_name="Max Relative Humidity (%)", validators=[MinValueValidator(0), MaxValueValidator(100)], blank=True, null=True) sdi = models.TextField(verbose_name="SDI", blank=True, null=True) smc_min = models.PositiveIntegerField( verbose_name="Min Surface Moisture Content (%)", validators=[MinValueValidator(0), MaxValueValidator(100)], blank=True, null=True) smc_max = models.PositiveIntegerField( verbose_name="Max Surface Moisture Content (%)", validators=[MinValueValidator(0), MaxValueValidator(100)], blank=True, null=True) pmc_min = models.PositiveIntegerField( verbose_name="Min Profile Moisture Content (%)", validators=[MinValueValidator(0), MaxValueValidator(100)], blank=True, null=True) pmc_max = models.PositiveIntegerField( verbose_name="Max Profile Moisture Content (%)", validators=[MinValueValidator(0), MaxValueValidator(100)], blank=True, null=True) wind_min = models.PositiveIntegerField( verbose_name="Min Wind Speed (km/h)", blank=True, null=True, validators=[MinValueValidator(0), MaxValueValidator(200)]) wind_max = models.PositiveIntegerField( verbose_name="Max Wind Speed (km/h)", validators=[MinValueValidator(0), MaxValueValidator(200)], blank=True, null=True) wind_dir = models.TextField(verbose_name="Wind Direction", blank=True, null=True) grassland_curing_min = models.PositiveIntegerField( verbose_name="Grassland Curing % Min", validators=[MinValueValidator(0), MaxValueValidator(200)], blank=True, null=True) grassland_curing_max = models.PositiveIntegerField( verbose_name="Grassland Curing % Max", validators=[MinValueValidator(0), MaxValueValidator(200)], blank=True, null=True) def area(self): return field_range(self.min_area, self.max_area, True) area.short_description = "Area to be Burnt (%)" area.admin_order_field = "area_max" def ros(self): return field_range(self.ros_min, self.ros_max) ros.short_description = mark_safe( '<abbr title="Rate of Spread">ROS</abbr> Range') ros.admin_order_field = "ros_max" def ffdi(self): return field_range(self.ffdi_min, self.ffdi_max) ffdi.short_description = mark_safe( '<abbr title="Forecast Fire Danger Index">FFDI</abbr> Range') ffdi.admin_order_field = "ffdi_max" def gfdi(self): return field_range(self.gfdi_min, self.gfdi_max) gfdi.short_description = mark_safe( '<abbr title="Global Fire Danger Index">GFDI</abbr> Range') gfdi.admin_order_field = "gfdi_max" def temp(self): return field_range(self.temp_min, self.temp_max) temp.short_description = "Temperature Range" temp.admin_order_field = "temp_max" def rh(self): return field_range(self.rh_min, self.rh_max, True) rh.short_description = mark_safe( '<abbr title="Relative Humidity">RH</abbr> Range (%)') temp.admin_order_field = "temp_max" sdi.short_description = mark_safe( '<abbr title="Soil Dryness Index">SDI</abbr> Range') def smc(self): return field_range(self.smc_min, self.smc_max) smc.short_description = mark_safe( '<abbr title="Surface Moisture Content">SMC</abbr> Range') smc.admin_order_field = "smc_max" def pmc(self): return field_range(self.pmc_min, self.pmc_max) pmc.short_description = mark_safe( '<abbr title="Profile Moisture Content">PMC</abbr> Range') pmc.admin_order_field = "pmc_max" def wind(self): return field_range(self.wind_min, self.wind_max) wind.short_description = "Wind Speed Range (km/h)" wind.admin_order_field = "wind_max" def grassland_curing(self): return field_range(self.grassland_curing_min, self.grassland_curing_max) grassland_curing.short_description = mark_safe( '<abbr title="Grassland Curing Percent">GLC</abbr> Range') grassland_curing.admin_order_field = "grassland_curing_max" def clean_sdi(self): if self.sdi == '': self.sdi = "N/A" def __str__(self): return self.fuel_type.name class Meta: verbose_name = "Burning Prescription" verbose_name_plural = "Burning Prescriptions"
class AircraftBurn(Audit): APPROVAL_DRAFT = 'DRAFT' APPROVAL_SUBMITTED = 'USER' APPROVAL_ENDORSED = 'SRM' APPROVAL_APPROVED = 'SDO' APPROVAL_CHOICES = ( (APPROVAL_DRAFT, 'Draft'), (APPROVAL_SUBMITTED, 'District Submitted'), (APPROVAL_ENDORSED, 'Region Endorsed'), (APPROVAL_APPROVED, 'State Approved'), ) fmt = "%Y-%m-%d %H:%M" prescription = models.ForeignKey(Prescription, verbose_name="Burn ID", related_name='aircraft_burns', null=True, blank=True, on_delete=models.PROTECT) #prescribed_burn = models.ForeignKey(PrescribedBurn, verbose_name="Daily Burn ID", related_name='aircraft_burn', null=True, blank=True) date = models.DateField(auto_now_add=False) area = models.DecimalField(verbose_name="Area (ha)", max_digits=12, decimal_places=1, validators=[MinValueValidator(0.0)], null=True, blank=True) est_start = models.TimeField('Estimated Start Time', null=True, blank=True) bombing_duration = models.DecimalField( verbose_name="Bombing Duration (hrs)", max_digits=5, decimal_places=1, validators=[MinValueValidator(0.0)], null=True, blank=True) min_smc = models.DecimalField(verbose_name="Min SMC", max_digits=5, decimal_places=1, validators=[MinValueValidator(0.0)], null=True, blank=True) max_fdi = models.DecimalField(verbose_name="Max FDI", max_digits=5, decimal_places=1, validators=[MinValueValidator(0.0)], null=True, blank=True) sdi_per_day = models.DecimalField(verbose_name="SDI Each Day", max_digits=5, decimal_places=1, validators=[MinValueValidator(0.0)], null=True, blank=True) flight_seq = models.TextField(verbose_name="Flight Sequence", null=True, blank=True) aircraft_rego = models.TextField(verbose_name="Aircraft Rego", null=True, blank=True) arrival_time = models.TimeField(verbose_name="Arrival Time Over Burn", null=True, blank=True) program = models.TextField(verbose_name="Program", null=True, blank=True) aircrew = models.TextField(verbose_name="Aircrew", null=True, blank=True) rolled = models.BooleanField(verbose_name="Fire Rolled from yesterday", editable=False, default=False) #location= models.TextField(verbose_name="Location", null=True, blank=True) @property def regional_approval(self): return True @property def state_duty_approval(self): return True @property def state_aviation_approval(self): return True def __str__(self): return self.prescription.burn_id class Meta: unique_together = ('prescription', 'date') verbose_name = 'Aircraft Burn' verbose_name_plural = 'Aircraft Burns' permissions = ( ("can_endorse", "Can endorse burns"), ("can_approve", "Can approve burns"), )