Exemple #1
0
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"
Exemple #2
0
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"
Exemple #3
0
class ClosureDeclaration(Audit):
    prescription = models.OneToOneField(Prescription)
    closed = models.BooleanField(default=False)
Exemple #4
0
class SummaryCompletionState(AbstractState):
    """
    Stores state related to Part A (Summary & Approval) section of an
    electronic Prescribed Fire Plan. These flags are to indicate if each
    section has been fully completed.
    """
    prescription = models.OneToOneField(Prescription, related_name='pre_state')
    summary = models.BooleanField(choices=BOOL_CHOICES, default=False)
    context_statement = models.BooleanField(choices=BOOL_CHOICES,
                                            default=False)
    context_map = models.BooleanField(choices=BOOL_CHOICES, default=False)
    objectives = models.BooleanField(choices=BOOL_CHOICES, default=False)
    success_criteria = models.BooleanField(choices=BOOL_CHOICES, default=False)
    priority_justification = models.BooleanField(choices=BOOL_CHOICES,
                                                 default=False)
    complexity_analysis = models.BooleanField(choices=BOOL_CHOICES,
                                              default=False)
    risk_register = models.BooleanField(choices=BOOL_CHOICES, default=False)

    @property
    def complete_except_risk(self):
        """
        Return True if all sections of part A are marked complete except for
        risk_register.
        """
        return (self.summary and self.context_statement and self.context_map
                and self.objectives and self.success_criteria
                and self.priority_justification and self.complexity_analysis)

    def clean_context_map(self):
        """
        BR-16: For the Part A Stage of Completion Table, the Context map can
        not be marked as "Complete" unless a context map is uploaded.
        """
        documents = self.prescription.document_set
        count = documents.tag_names("Context Map").count()
        if self.context_map and count < 1:
            self.context_map = False
            self.save()
            raise ValidationError("To mark the context map as complete you "
                                  "first need to upload a context map")

    def clean_priority_justification(self):
        if self.priority_justification:
            justifications = self.prescription.priorityjustification_set
            justifications = justifications.filter(relevant=True)
            blankrationale = justifications.filter(rationale='').count()
            unratedpriority = justifications.filter(priority=0).count()
            if blankrationale > 0 or unratedpriority > 0:
                raise ValidationError("To mark the priority justification "
                                      "as complete you need to set the "
                                      "priority and enter a rationale for "
                                      "each relevant burn purpose.")
            if ((self.prescription.priority == 0
                 or self.prescription.rationale == '')):
                raise ValidationError("To mark the priority justification "
                                      "as complete you need to set the "
                                      "overall priority and rationale for "
                                      "the burn.")

    def clean_summary(self):
        if self.summary:
            if (self.prescription.priority < 1
                    or self.prescription.rationale == ''):
                raise ValidationError(
                    "To mark the summary and approval as "
                    "complete you must set the overall priority "
                    "and enter a rationale.")
            if not self.prescription.fuel_types.exists():
                raise ValidationError(
                    "To mark the summary and approval as "
                    "complete you must set the fuel type(s).")
            if not self.prescription.tenures.exists():
                raise ValidationError(
                    "To mark the summary and approval as "
                    "complete you must set the burn tenures(s).")
            if not self.prescription.forecast_areas.exists():
                raise ValidationError(
                    "To mark the summary and approval as "
                    "complete you must set the forecast area(s).")
            if not self.prescription.shires.exists():
                raise ValidationError("To mark the summary and approval as "
                                      "complete you must set the Shire(s).")
            if not self.prescription.prohibited_period:
                raise ValidationError(
                    "To mark the summary and approval as "
                    "complete you must input the Prohibited Period.")
            if not self.prescription.short_code:
                raise ValidationError(
                    "To mark the summary and approval as "
                    "complete you must input the Short Code.")

    def clean_objectives(self):
        objectives = self.prescription.objective_set.count()
        if self.objectives and objectives < 1:
            self.objectives = False
            self.save()
            raise ValidationError("To mark the burn objectives as complete "
                                  "you need to specify at least one burn "
                                  "objective.")

    def clean_complexity_analysis(self):
        not_rated = Complexity.objects.filter(
            prescription=self.prescription,
            rating=Complexity.RATING_UNRATED).exists()

        no_rationale = Complexity.objects.filter(
            prescription=self.prescription, rationale='').exists()

        if self.complexity_analysis and (not_rated or no_rationale):
            self.complexity_analysis = False
            self.save()
            raise ValidationError("To mark the complexity analysis as "
                                  "complete you need to rate all complexities "
                                  "and provide a rationale for your rating.")

    def clean_success_criteria(self):
        success_criterias = self.prescription.successcriteria_set.count()
        if self.success_criteria and success_criterias < 1:
            self.success_criteria = False
            self.save()
            raise ValidationError("To mark the success criterias as complete "
                                  "you need to specify at least one success "
                                  "criteria.")

    def clean_risk_register(self):
        if self.risk_register and self.prescription.register_set.count() < 1:
            self.risk_register = False
            self.save()
            raise ValidationError("To mark the risk register as complete "
                                  "you need to have at least one register "
                                  "item.")

    def clean_context_statement(self):
        contexts = Context.objects.filter(prescription=self.prescription)
        context = all(bool(c.statement) for c in contexts)
        actions = ContextRelevantAction.objects.filter(
            action__risk__prescription=self.prescription)
        complete = all(a.considered for a in actions)
        if self.context_statement and not (context and complete):
            self.context_statement = False
            self.save()
            raise ValidationError("To mark risk management context statement "
                                  "as complete, there must be at least one "
                                  "context statement and all relevant actions "
                                  "need to be considered.")
Exemple #5
0
class BurnClosureState(AbstractState):
    """
    Stores state related to Part C (Burn Closure and Evaluation) of an
    electronic Prescribed Fire Plan. These flags are to indicate if each
    section has been fully completed.
    """
    prescription = models.OneToOneField(Prescription,
                                        related_name='post_state')
    post_actions = models.NullBooleanField(choices=NULL_CHOICES, default=False)
    evaluation_summary = models.BooleanField(choices=BOOL_CHOICES,
                                             default=False)
    evaluation = models.NullBooleanField(choices=NULL_CHOICES, default=False)
    post_ignitions = models.NullBooleanField(choices=NULL_CHOICES,
                                             default=False)
    aerial_intensity = models.NullBooleanField(choices=NULL_CHOICES,
                                               default=False)
    satellite_intensity = models.NullBooleanField(choices=NULL_CHOICES,
                                                  default=False)
    other = models.NullBooleanField(choices=NULL_CHOICES, default=False)
    post_burn_checklist = models.BooleanField(choices=BOOL_CHOICES,
                                              default=False)
    closure_declaration = models.BooleanField(choices=BOOL_CHOICES,
                                              default=False)
    signage = models.NullBooleanField(choices=NULL_CHOICES, default=False)

    def clean_evaluation(self):
        evaluations = self.prescription.proposedaction_set.all().count()
        incomplete_evaluations = self.prescription.proposedaction_set.filter(
            Q(observations='') | Q(observations=None) | Q(action='')
            | Q(action=None)).count()
        if self.evaluation and evaluations > 0 and incomplete_evaluations > 0:
            self.evaluation = False
            self.save()
            raise ValidationError("To mark burn evaluation - lessons learned "
                                  "as complete you must fill in the details "
                                  "of all lessons learned.")
        if self.evaluation and evaluations < 1:
            self.evaluation = False
            self.save()
            raise ValidationError("To mark burn evaluation - lessons learned "
                                  "as complete you must detail at least one "
                                  "lesson learned.")

    def clean_evaluation_summary(self):
        incomplete_evaluation_summary = Evaluation.objects.filter(
            Q(achieved=None) | Q(summary='') | Q(summary=None),
            criteria__prescription=self.prescription).count()
        if self.evaluation_summary and incomplete_evaluation_summary > 0:
            self.evaluation_summary = False
            self.save()
            raise ValidationError("To mark burn evaluation summary as "
                                  "complete, you must state the level of "
                                  "achievement and the evaluation rationale "
                                  "for each success criteria.")

    def clean_post_actions(self):
        post_burn_actions = Action.objects.filter(
            risk__prescription=self.prescription, post_burn=True).count()
        incomplete = Action.objects.filter(
            Q(post_burn_completed=None) | Q(post_burn_completer='')
            | Q(post_burn_completer=None),
            risk__prescription=self.prescription,
            post_burn=True).count()
        if self.post_actions and post_burn_actions > 0 and incomplete > 0:
            self.post_actions = False
            self.save()
            raise ValidationError("To mark post-burn actions as complete, you "
                                  "must enter a date actioned and actioned by "
                                  "whom for all post-burn actions.")

        if self.post_actions is None and post_burn_actions > 0:
            self.post_actions = False
            self.save()
            raise ValidationError("You cannot mark post-burn actions as "
                                  "not applicable if there are any post-burn "
                                  "actions.")

    def clean_post_burn_checklist(self):
        checklists = PostBurnChecklist.objects.filter(
            prescription=self.prescription, relevant=True)
        complete = all(c.completed_on and c.completed_by for c in checklists)
        if self.post_burn_checklist and not complete:
            self.post_burn_checklist = False
            self.save()
            raise ValidationError("To mark the post burn checklist as "
                                  "complete, you need to complete all "
                                  "relevant checklist items.")

    def clean_signage(self):
        documents = self.prescription.document_set
        doc_count = 0
        doc_count += self.prescription.way_set.filter(
            roadsegment__traffic_diagram__isnull=False).count()
        doc_count += documents.filter(tag__name="Traffic Diagrams").count()
        doc_count += documents.filter(tag__name="Diversion Map").count()
        sign_doc_count = documents.filter(
            tag__name="Sign Inspection and Surveillance Form").count()
        if self.signage is None and (doc_count > 0 or sign_doc_count > 0):
            raise ValidationError("Sign Inspection and Surveillance cannot "
                                  "be marked as incomplete if there are any "
                                  "traffic control diagrams, diversion maps "
                                  "or sign inspection and surveillance forms "
                                  "uploaded to the burn.")
        if self.signage and doc_count > 0 and sign_doc_count < 1:
            raise ValidationError("To mark Sign Inspection and "
                                  "Surveillance as complete there must "
                                  "be at least one traffic control "
                                  "diagram or diversion map "
                                  "AND a completed copy "
                                  "of the Sign Inspection and Surveillance "
                                  "Form uploaded to the burn.")
        if self.signage and doc_count < 1:
            raise ValidationError("Sign Inspection and Surveillance cannot "
                                  "be marked as complete if there are no "
                                  "traffic control diagrams or diversion maps "
                                  "uploaded to the burn.")

    def clean_closure_declaration(self):
        if self.closure_declaration and not self.prescription.can_close:
            raise ValidationError("Closure declaration cannot be marked as "
                                  "complete before the ePFP is ready to be "
                                  "closed.")
Exemple #6
0
class BurnImplementationState(AbstractState):
    """
    Stores state related to Part B (Implementation plan) of an electronic
    Prescribed Fire Plan. These flags are to indicate if each section has
    been fully completed.
    """
    prescription = models.OneToOneField(Prescription, related_name='day_state')
    overview = models.BooleanField(choices=BOOL_CHOICES, default=False)
    pre_actions = models.NullBooleanField(choices=NULL_CHOICES, default=False)
    actions = models.NullBooleanField(choices=NULL_CHOICES, default=False)
    roads = models.NullBooleanField(choices=NULL_CHOICES, default=False)
    traffic = models.NullBooleanField(choices=NULL_CHOICES, default=False)
    tracks = models.NullBooleanField(choices=NULL_CHOICES, default=False)
    burning_prescription = models.BooleanField(choices=BOOL_CHOICES,
                                               default=False)
    fuel_assessment = models.NullBooleanField(choices=NULL_CHOICES,
                                              default=False)
    edging_plan = models.NullBooleanField(choices=NULL_CHOICES, default=False)
    contingency_plan = models.BooleanField(choices=BOOL_CHOICES, default=False)
    lighting_sequence = models.BooleanField(choices=BOOL_CHOICES,
                                            default=False)
    exclusion_areas = models.NullBooleanField(choices=NULL_CHOICES,
                                              default=False)
    organisational_structure = models.BooleanField(choices=BOOL_CHOICES,
                                                   default=False)
    briefing = models.BooleanField(choices=BOOL_CHOICES, default=False)
    operation_maps = models.BooleanField(choices=BOOL_CHOICES, default=False)
    aerial_maps = models.NullBooleanField(choices=NULL_CHOICES, default=False)

    def clean_overview(self):
        overviews = self.prescription.operationaloverview_set.all()
        if self.overview and not all([x.overview for x in overviews]):
            self.overview = False
            self.save()
            raise ValidationError("Operational overview cannot be marked as "
                                  "complete unless the overview has been "
                                  "filled out and completed.")

    def clean_pre_actions(self):
        """
        There must be at least one pre burn action to complete this.
        The action details must also be complete.
        If there are no pre burn actions, user should select N/A.
        Should not allow user to mark N/A if pre burn action exists.
        """
        pre_burn_actions = Action.objects.filter(
            risk__prescription=self.prescription, relevant=True, pre_burn=True)
        incomplete_pre_burn_actions = 0

        for action in pre_burn_actions:
            if not action.details:
                incomplete_pre_burn_actions += 1

        if (self.pre_actions and pre_burn_actions.count() == 0):
            self.pre_actions = False
            self.save()
            raise ValidationError("Pre-burn actions cannot be marked as "
                                  "complete unless there is at least one "
                                  "pre-burn action associated with the burn.")
        if ((self.pre_actions and pre_burn_actions.count() > 0
             and incomplete_pre_burn_actions > 0)):
            self.pre_actions = False
            self.save()
            raise ValidationError("Pre-burn actions cannot be marked as "
                                  "complete unless all pre-burn actions have "
                                  "details.")
        if (self.pre_actions is None and pre_burn_actions.count() > 0):
            self.pre_actions = False
            self.save()
            raise ValidationError("Pre-burn actions cannot be marked as Not "
                                  "Applicable if there are pre-burn actions "
                                  "associated with the burn.")

    def clean_actions(self):
        """
        There must be at least one day of burn action to complete this.
        The action details must also be complete.
        If there are no day of burn actions, user should select N/A.
        Should not allow user to mark N/A if day of burn action exists.
        """
        dob_actions = Action.objects.filter(
            risk__prescription=self.prescription,
            relevant=True,
            day_of_burn=True)
        incomplete_dob_actions = 0
        for action in dob_actions:
            if (not action.details or (action.day_of_burn_include and not any([
                    action.day_of_burn_situation, action.day_of_burn_mission,
                    action.day_of_burn_execution,
                    action.day_of_burn_administration,
                    action.day_of_burn_command, action.day_of_burn_safety
            ]))):
                incomplete_dob_actions += 1
        if (self.actions and dob_actions.count() == 0):
            self.actions = False
            self.save()
            raise ValidationError("Day of burn actions cannot be marked as "
                                  "complete unless there is at least one day "
                                  "of burn action associated with the burn.")
        if ((self.actions and dob_actions.count() > 0
             and incomplete_dob_actions > 0)):
            self.actions = False
            self.save()
            raise ValidationError("Day of burn actions cannot be marked as "
                                  "complete unless all day of burn actions "
                                  "have details and all day of burn actions " +
                                  "that are to be included in briefing have " +
                                  "SMEAC locations(s) selected.")
        if (self.actions is None and dob_actions.count() > 0):
            self.actions = False
            self.save()
            raise ValidationError("Day of burn actions cannot be marked as "
                                  "Not Applicable if there are day of burn "
                                  "actions associated with the burn.")

    def clean_lighting_sequence(self):
        """
        There must be at least one lighting sequence
        """
        lightingsequences = self.prescription.lightingsequence_set.count()
        if self.lighting_sequence and lightingsequences < 1:
            self.lighting_sequence = False
            self.save()
            raise ValidationError("Lighting sequence cannot be "
                                  "marked as complete unless there is at "
                                  "least one lighting sequence.")

    def clean_contingency_plan(self):
        """
        BR-33: There must be at least one contingency plan.
        """
        contingencies = self.prescription.contingency_set.count()
        if self.contingency_plan and contingencies < 1:
            self.contingency_plan = False
            self.save()
            raise ValidationError("Contingency plan cannot be "
                                  "marked as complete unless there is at "
                                  "least one contingency plan.")

    def clean_exclusion_areas(self):
        """
        There must be at least one exclusion area to complete this.
        If there are no exclusion areas, user should select N/A.
        Should not allow user to mark N/A if exclusion area exists.
        """
        count = self.prescription.exclusionarea_set.all().count()
        if self.exclusion_areas and count < 1:
            self.exclusion_areas = False
            self.save()
            raise ValidationError("Exclusion areas cannot be marked as "
                                  "complete unless there is at least one "
                                  "exclusion area associated with the burn.")
        if self.exclusion_areas is None and count > 0:
            self.exclusion_areas = False
            self.save()
            raise ValidationError("Exclusion areas cannot be marked as Not "
                                  "Applicable if there are exclusion areas "
                                  "associated with the burn.")

    def clean_edging_plan(self):
        """
        BR-32: If there is an edging plan there must be at least one edge in
        the plan.
        """
        if self.edging_plan and self.prescription.edgingplan_set.count() < 1:
            self.edging_plan = False
            self.save()
            raise ValidationError("Edging plan cannot be marked as complete "
                                  "unless there is at least one edge in the "
                                  "plan.")

    def clean_briefing(self):
        """
        Briefing checklist cannot be marked as "Complete"
        unless either a briefing checklist document is uploaded
        or at least one item in the checklist has been entered into"
        """
        count = self.prescription.document_set.tag_names(
            "Briefing Checklist").count()
        bc_count = self.prescription.briefingchecklist_set.exclude(
            notes__isnull=True).exclude(notes__exact='').count()
        if (self.briefing and (count < 1 and bc_count < 1)):
            self.briefing = False
            self.save()
            raise ValidationError("Briefing checklist cannot be marked as "
                                  "complete unless either at least one item "
                                  "in the checklist has been entered or "
                                  "a briefing checklist document has been "
                                  "uploaded.")

    def clean_organisational_structure(self):
        """
        Organisational structure cannot be marked as "Complete"
        unless an organisational structure document is uploaded
        """
        count = self.prescription.document_set.tag_names(
            "Prescribed Burning Organisational Structure "
            "and Communications Plan").count()
        if self.organisational_structure and count < 1:
            self.organisational_structure = False
            self.save()
            raise ValidationError("Organisational structure and "
                                  "communications plan cannot be marked as "
                                  "complete unless an organisational "
                                  "structure and communications plan document "
                                  "has been uploaded.")

    def clean_aerial_maps(self):
        """
        BR-25: For the Part B Stage of Completion Table, the Aerial map can
        not be marked as "Complete" unless an aerial map is uploaded or it has
        been marked as "Not Applicable".
        """
        if self.aerial_maps and not self.prescription.aircraft_burn:
            self.aerial_maps = False
            self.save()
            raise ValidationError("Aerial burning map cannot be marked "
                                  "as complete unless the burn has been "
                                  "marked as an aircraft burn and an "
                                  "aerial burning map has been uploaded.")

        documents = self.prescription.document_set
        count = documents.tag_names("Aerial Burn Map").count()
        if ((self.aerial_maps and self.prescription.aircraft_burn
             and count < 1)):
            self.aerial_maps = False
            self.save()
            raise ValidationError("Aerial burning map cannot be marked as "
                                  "complete unless an aerial map has been "
                                  "uploaded.")

        if self.prescription.aircraft_burn and self.aerial_maps is None:
            self.aerial_maps = False
            self.save()
            raise ValidationError("Aerial burning map cannot marked as not "
                                  "applicable when the burn has been marked "
                                  "as an aircraft burn.")

    def clean_operation_maps(self):
        """
        BR-24: For the Part B Stage of Completion Table, the Operations map
        can not be marked as "Complete" unless an operations map is uploaded.
        The Operations Map can not be marked as "Not applicable".
        """
        documents = self.prescription.document_set
        count = documents.tag_names("Operations Map").count()
        if self.operation_maps and count < 1:
            self.operation_maps = False
            self.save()
            raise ValidationError("Operations map cannot be marked as "
                                  "complete unless an operations map has been "
                                  "uploaded.")

    def clean_burning_prescription(self):
        """
        BR-29: In the Contents section Part B - Burning Prescription can not
        be marked as complete unless there has been a document uploaded
        against it in Part D.
        There needs to be at least one burning prescription.
        """
        count = self.prescription.burningprescription_set.all().count()

        if self.burning_prescription and count < 1:
            self.burning_prescription = False
            self.save()
            raise ValidationError("Burning prescription cannot be marked as "
                                  "complete unless at least one burning "
                                  "prescription has been added to the burn.")

    def clean_fuel_assessment(self):
        count = self.prescription.document_set.tag_names(
            "Fuel Assessment Summary_FIRE 872").count()

        if self.fuel_assessment and count < 1:
            self.fuel_assessment = False
            self.save()
            raise ValidationError("Fuel Assessment cannot be marked as "
                                  "complete unless a fuel assessment "
                                  "summary has been uploaded against it.")

        if self.fuel_assessment is None and count > 0:
            self.fuel_assessment = False
            self.save()
            raise ValidationError(
                "Fuel Assessment cannot be marked as not applicable "
                "if there are Fuel Assessments associated with the "
                "burn.")

    def clean_roads(self):
        road_count = RoadSegment.objects.filter(
            way_ptr__prescription=self.prescription).count()
        incomplete_roads_count = RoadSegment.objects.filter(
            Q(road_type='') | Q(road_type=None) | Q(name='') | Q(name=None),
            way_ptr__prescription=self.prescription).count()

        if self.roads and road_count < 1:
            self.roads = False
            self.save()
            raise ValidationError("Roads cannot be marked as complete unless "
                                  "at least one road is associated with the "
                                  "burn.")

        if self.roads and road_count > 0 and incomplete_roads_count > 0:
            self.roads = False
            self.save()
            raise ValidationError("Roads cannot be marked as complete unless "
                                  "all roads associated with the burn have at "
                                  "least a road name and type.")

        if self.roads is None and road_count > 0:
            self.roads = False
            self.save()
            raise ValidationError("Roads cannot be marked as not applicable "
                                  "if there are roads associated with the "
                                  "burn.")

    def clean_traffic(self):
        """
        BR-23: For the Part B Stage of Completion Table, the Traffic Control
        Diagrams can not be marked as "Complete" unless a traffic control
        diagram is selected from the library or a custom map is uploaded or it
        has been marked as "Not Applicable".
        """
        documents = self.prescription.document_set
        traffic_diagrams = self.prescription.way_set.filter(
            roadsegment__traffic_diagram__isnull=False).count()
        count = (documents.filter(tag__name="Traffic Diagrams").count() +
                 traffic_diagrams)
        road_count = RoadSegment.objects.filter(
            way_ptr__prescription=self.prescription).count()

        if self.traffic and count < 1:
            self.traffic = False
            self.save()
            raise ValidationError("Traffic control diagrams cannot be "
                                  "marked as complete unless "
                                  "a traffic control diagram has been "
                                  "selected or a custom one uploaded.")

        if self.traffic and road_count < 1:
            self.traffic = False
            self.save()
            raise ValidationError("Traffic control diagrams cannot be "
                                  "marked as complete unless "
                                  "there is at least one road uploaded "
                                  "to the burn.")

        if self.traffic is None and count > 0:
            self.traffic = False
            self.save()
            raise ValidationError("Traffic control diagrams cannot be "
                                  "marked as not applicable "
                                  "if there are traffic control diagrams "
                                  "associated with the burn.")

    def clean_tracks(self):
        """
        BR-26: For the Part B Stage of Completion Table, the Tracks and Trails
        Maps can not be marked as "Complete" unless a diversion map
        uploaded or it has been marked as "Not Applicable". This only applies
        if there is a tracks/trails record stating that a diversion map
        exists.
        """
        documents = self.prescription.document_set
        ways = self.prescription.way_set
        doc_count = documents.filter(
            tag__name="Track Trail Diversion Map").count()
        count_trails_with_divmap = ways.filter(
            trailsegment__diversion=True).count()
        track_count = TrailSegment.objects.filter(
            way_ptr__prescription=self.prescription).count()

        if self.tracks and track_count < 1:
            self.tracks = False
            self.save()
            raise ValidationError("Tracks and trails cannot be "
                                  "marked as complete if there are no "
                                  "tracks and trails records associated with "
                                  "the burn.")

        if self.tracks and doc_count < 1 and count_trails_with_divmap > 0:
            self.tracks = False
            self.save()
            raise ValidationError("A user has indiciated that a diversion map "
                                  "is required for this burn. Tracks and "
                                  "trails cannot be marked as "
                                  "complete unless a diversion map has been "
                                  "uploaded.")

        if self.tracks and doc_count > 0 and count_trails_with_divmap < 1:
            self.tracks = False
            self.save()
            raise ValidationError("A diversion map has been uploaded "
                                  "but no tracks or trails requiring a "
                                  "diversion map have been "
                                  "associated with the burn. Tracks and "
                                  "trails cannot be marked as "
                                  "complete unless at least one track or "
                                  "trail requiring a diversion map is "
                                  "associated with the burn.")

        if self.tracks is None and track_count > 0:
            self.tracks = False
            self.save()
            raise ValidationError("Tracks and trails cannot be "
                                  "marked as not applicable as there are "
                                  "tracks and trails records associated with "
                                  "the burn.")

        if self.tracks is None and doc_count > 0:
            self.tracks = False
            self.save()
            raise ValidationError("Tracks and trails cannot be "
                                  "marked as not applicable as there are "
                                  "diversion maps uploaded to "
                                  "the burn.")
Exemple #7
0
class Document(Audit):
    prescription = models.ForeignKey(
        Prescription, null=True,
        help_text="Prescription that this document belongs to", on_delete=models.PROTECT)
    category = models.ForeignKey(DocumentCategory, related_name="documents", on_delete=models.PROTECT)
    tag = ChainedForeignKey(
        DocumentTag, chained_field="category", chained_model_field="category",
        show_all=False, auto_choose=True, verbose_name="Descriptor", on_delete=models.PROTECT)
    custom_tag = models.CharField(
        max_length=64, blank=True, verbose_name="Custom Descriptor")
    document = ContentTypeRestrictedFileField(
        upload_to=content_file_name, max_length=200,
        content_types=['application/pdf', 'image/tiff', 'image/tif',
                       'image/jpeg', 'image/jpg', 'image/gif', 'image/png',
                       'application/zip', 'application/x-zip-compressed'],
        help_text='Acceptable file types: pdf, tiff, jpg, gif, png, zip')

    document_created = models.DateTimeField(
        verbose_name="Date Document Created", default=timezone.now, editable=True, null=True, blank=True)

    document_archived = models.BooleanField(default=False, verbose_name="Archived Document")

    objects = TagManager()

    def save(self, *args, **kwargs):
        super(Document, self).save(*args, **kwargs)
        # confirm that file is written to filesystem, if not remove the record
        if not self.exists:
            fname = self.document.name
            Document.objects.get(id=self.id).delete()
            raise Exception('ERROR: File not created on filesystem {}'.format(fname))
        return

    @property
    def descriptor(self):
        if self.custom_tag:
            return "Other ({0})".format(self.custom_tag)
        else:
            return self.tag.name

    @property
    def dimensions(self):
        return get_dimensions(self.document.path)

    @property
    def filename(self):
        try:
            return os.path.basename(self.document.path)
        except:
            return None

    @property
    def exists(self):
        """
        Check if file exists on the file system
        """
        try:
            return os.path.exists(self.document.file.name)
        except:
            return False


    @property
    def is_zipped(self):
        return self.filename.endswith('.zip')

    class Meta:
        ordering = ['tag', 'document']
        permissions = (
            ("archive_document", "Can archive documents")
        )

    def __str__(self):
        return "{0} - {1}".format(self.prescription, self.document.name)
Exemple #8
0
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)
Exemple #9
0
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"),
        )
Exemple #10
0
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"),
        )