From dd0b1b25f0e8fd4f513588bb7f9e87dbdbfc217e Mon Sep 17 00:00:00 2001 From: Oak McIlwain Date: Tue, 13 Aug 2024 11:23:42 +0800 Subject: [PATCH 01/26] Bug fix: Make sure to only query for non archived external referee invites. Also add handling for the case where there happen to be multiple. --- boranga/components/users/signals.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/boranga/components/users/signals.py b/boranga/components/users/signals.py index 08a7d7e8..dbf638f8 100644 --- a/boranga/components/users/signals.py +++ b/boranga/components/users/signals.py @@ -35,7 +35,15 @@ def ocr_process_ocr_external_referee_invite(sender, user, request, **kwargs): logger.info("External occurrence report referee invite found for user: %s", user) - ocr_external_referee_invite = OCRExternalRefereeInvite.objects.get(email=user.email) + ocr_external_referee_invites = OCRExternalRefereeInvite.objects.filter( + email=user.email, archived=False + ) + if ocr_external_referee_invites.count() > 1: + logger.warning( + "Multiple external occurrence report referee invites found for user: %s", + user, + ) + ocr_external_referee_invite = ocr_external_referee_invites.first() ocr_external_referee_invite.datetime_first_logged_in = user.last_login logger.info( "Saving datetime_first_logged_in for occurrence report external referee invite: %s", @@ -69,7 +77,15 @@ def cs_process_cs_external_referee_invite(sender, user, request, **kwargs): logger.info("External conservation status referee invite found for user: %s", user) - cs_external_referee_invite = CSExternalRefereeInvite.objects.get(email=user.email) + cs_external_referee_invites = CSExternalRefereeInvite.objects.filter( + email=user.email, archived=False + ) + if cs_external_referee_invites.count() > 1: + logger.warning( + "Multiple external conservation status referee invites found for user: %s", + user, + ) + cs_external_referee_invite = cs_external_referee_invites.first() cs_external_referee_invite.datetime_first_logged_in = user.last_login logger.info( "Saving datetime_first_logged_in for conservation status external referee invite: %s", From c90619716e824d3685dd62a393060017817f3650 Mon Sep 17 00:00:00 2001 From: Oak McIlwain Date: Tue, 13 Aug 2024 12:44:00 +0800 Subject: [PATCH 02/26] Making sure to exclude OCRs for related items of species and communities shown on OCC related items. --- boranga/components/occurrence/models.py | 8 +-- .../species_and_communities/models.py | 70 ++++++++++++++----- 2 files changed, 55 insertions(+), 23 deletions(-) diff --git a/boranga/components/occurrence/models.py b/boranga/components/occurrence/models.py index d34f887c..2f1e675b 100644 --- a/boranga/components/occurrence/models.py +++ b/boranga/components/occurrence/models.py @@ -3743,16 +3743,12 @@ def get_related_items(self, filter_type, **kwargs): # Add parent species related items to the list (limited to one degree of separation) if a_field.name == "species" and self.species: - return_list.extend( - self.species.get_related_items("all_except_occurrence_reports") - ) + return_list.extend(self.species.get_related_items("for_occurrence")) # Add renamed from / renamed to community related items to the list if a_field.name == "community" and self.community: return_list.extend( - self.community.get_related_items( - "all_except_occurrence_reports" - ) + self.community.get_related_items("for_occurrence") ) # Remove the occurrence itself from the list if it ended up there diff --git a/boranga/components/species_and_communities/models.py b/boranga/components/species_and_communities/models.py index d4b9e293..ea6ef259 100644 --- a/boranga/components/species_and_communities/models.py +++ b/boranga/components/species_and_communities/models.py @@ -687,7 +687,12 @@ def get_related_items(self, filter_type, **kwargs): "occurrences", "occurrence_report", ] - elif filter_type == "all_except_occurrence_reports": + elif filter_type == "conservation_status_and_occurrences": + related_field_names = [ + "conservation_status", + "occurrences", + ] + elif filter_type == "for_occurrence": related_field_names = [ "parent_species", "conservation_status", @@ -727,11 +732,18 @@ def get_related_items(self, filter_type, **kwargs): # Add parent species related items to the list (limited to one degree of separation) if a_field.name == "parent_species": for parent_species in self.parent_species.all(): - return_list.extend( - parent_species.get_related_items( - "all_except_parent_species" + if filter_type == "for_occurrence": + return_list.extend( + parent_species.get_related_items( + "conservation_status_and_occurrences" + ) + ) + else: + return_list.extend( + parent_species.get_related_items( + "all_except_parent_species" + ) ) - ) return return_list @@ -1425,7 +1437,12 @@ def get_related_items(self, filter_type, **kwargs): "occurrences", "occurrence_report", ] - elif filter_type == "all_except_occurrence_reports": + elif filter_type == "conservation_status_and_occurrences": + related_field_names = [ + "conservation_status", + "occurrences", + ] + elif filter_type == "for_occurrence": related_field_names = [ "renamed_from", "renamed_to", @@ -1465,18 +1482,36 @@ def get_related_items(self, filter_type, **kwargs): # Add renamed from related items to the list (limited to one degree of separation) if a_field.name == "renamed_from" and self.renamed_from: - return_list.extend( - self.renamed_from.get_related_items( - "all_except_renamed_community" + if filter_type == "for_occurrence": + return_list.extend( + self.renamed_from.get_related_items( + "conservation_status_and_occurrences" + ) + ) + else: + return_list.extend( + self.renamed_from.get_related_items( + "all_except_renamed_community" + ) ) - ) # Add renamed to related items to the list (limited to one degree of separation) - if a_field.name == "renamed_to" and self.renamed_to: - return_list.extend( - self.renamed_to.get_related_items( - "all_except_renamed_community" + if ( + a_field.name == "renamed_to" + and hasattr(self, "renamed_to") + and self.renamed_to + ): + if filter_type == "for_occurrence": + return_list.extend( + self.renamed_to.get_related_items( + "conservation_status_and_occurrences" + ) + ) + else: + return_list.extend( + self.renamed_to.get_related_items( + "all_except_renamed_community" + ) ) - ) return return_list @@ -1488,8 +1523,9 @@ def as_related_item(self): descriptor=self.related_item_descriptor, status=self.related_item_status, action_url=( - f'View ' + f'View ' + '' ), ) return related_item From e9f840163b3cf817bd0064112725c77a310fc7dd Mon Sep 17 00:00:00 2001 From: Oak McIlwain Date: Tue, 13 Aug 2024 14:43:36 +0800 Subject: [PATCH 03/26] Add OccurrenceReportCopyPermission --- boranga/components/occurrence/permissions.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/boranga/components/occurrence/permissions.py b/boranga/components/occurrence/permissions.py index 845d5ece..17174368 100644 --- a/boranga/components/occurrence/permissions.py +++ b/boranga/components/occurrence/permissions.py @@ -414,6 +414,17 @@ def has_object_permission(self, request, view, obj): return False +class OccurrenceReportCopyPermission(BasePermission): + def has_object_permission(self, request, view, obj): + if not request.user.is_authenticated: + return False + + if request.user.is_superuser: + return True + + return obj.submitter == request.user.id or is_occurrence_assessor(request) + + class OccurrencePermission(BasePermission): def has_permission(self, request, view): if not request.user.is_authenticated: From e2df7171a8184d6e783083e2bcecbfd8d8d06b78 Mon Sep 17 00:00:00 2001 From: Oak McIlwain Date: Tue, 13 Aug 2024 14:44:21 +0800 Subject: [PATCH 04/26] Add copy method to OccurrenceReport model --- boranga/components/occurrence/models.py | 120 ++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/boranga/components/occurrence/models.py b/boranga/components/occurrence/models.py index 2f1e675b..8dbd9a53 100644 --- a/boranga/components/occurrence/models.py +++ b/boranga/components/occurrence/models.py @@ -1119,6 +1119,126 @@ def external_referral_invites(self): archived=False, datetime_first_logged_in__isnull=True ) + @transaction.atomic + def copy(self, request_user_id): + ocr_copy = OccurrenceReport.objects.get(id=self.id) + ocr_copy.pk = None + ocr_copy.processing_status = OccurrenceReport.PROCESSING_STATUS_DRAFT + ocr_copy.customer_status = OccurrenceReport.CUSTOMER_STATUS_DRAFT + ocr_copy.occurrence_report_number = "" + ocr_copy.lodgement_date = None + ocr_copy.assigned_officer = None + ocr_copy.assigned_approver = None + ocr_copy.approved_by = None + if request_user_id != self.submitter: + self.submitter = request_user_id + ocr_copy.save(no_revision=True) + + # Clone all the associated models + location = clone_model( + OCRLocation, + OCRLocation, + self.location, + ) + if location: + location.occurrence_report = ocr_copy + location.save() + + habitat_composition = clone_model( + OCRHabitatComposition, + OCRHabitatComposition, + self.habitat_composition, + ) + if habitat_composition: + habitat_composition.occurrence_report = ocr_copy + habitat_composition.save() + + habitat_condition = clone_model( + OCRHabitatCondition, + OCRHabitatCondition, + self.habitat_condition, + ) + if habitat_condition: + habitat_condition.occurrence_report = ocr_copy + habitat_condition.save() + + vegetation_structure = clone_model( + OCRVegetationStructure, + OCRVegetationStructure, + self.vegetation_structure, + ) + if vegetation_structure: + vegetation_structure.occurrence_report = ocr_copy + vegetation_structure.save() + + fire_history = clone_model(OCRFireHistory, OCRFireHistory, self.fire_history) + if fire_history: + fire_history.occurrence_report = ocr_copy + fire_history.save() + + associated_species = clone_model( + OCRAssociatedSpecies, + OCRAssociatedSpecies, + self.associated_species, + ) + if associated_species: + associated_species.occurrence_report = ocr_copy + associated_species.save() + # copy over related species separately + for i in self.associated_species.related_species.all(): + associated_species.related_species.add(i) + + observation_detail = clone_model( + OCRObservationDetail, + OCRObservationDetail, + self.observation_detail, + ) + if observation_detail: + observation_detail.occurrence_report = ocr_copy + observation_detail.save() + + plant_count = clone_model(OCRPlantCount, OCRPlantCount, self.plant_count) + if plant_count: + plant_count.occurrence_report = ocr_copy + plant_count.save() + + animal_observation = clone_model( + OCRAnimalObservation, + OCRAnimalObservation, + self.animal_observation, + ) + if animal_observation: + animal_observation.occurrence_report = ocr_copy + animal_observation.save() + + identification = clone_model( + OCRIdentification, OCRIdentification, self.identification + ) + if identification: + identification.occurrence_report = ocr_copy + identification.save() + + # Clone the threats + for threat in self.ocr_threats.all(): + occ_threat = clone_model( + OCRConservationThreat, OCRConservationThreat, threat + ) + if occ_threat: + occ_threat.occurrence_report = ocr_copy + occ_threat.occurrence_report_threat = threat + occ_threat.save() + + # Clone the documents + for doc in self.documents.all(): + occ_doc = clone_model( + OccurrenceReportDocument, OccurrenceReportDocument, doc + ) + if occ_doc: + occ_doc.occurrence_report = ocr_copy + occ_doc.save() + + return ocr_copy + class OccurrenceReportDeclinedDetails(models.Model): occurrence_report = models.OneToOneField( From 85059c5e3e339dd50106ecbeef5084ef6dc80b3c Mon Sep 17 00:00:00 2001 From: Oak McIlwain Date: Tue, 13 Aug 2024 14:44:57 +0800 Subject: [PATCH 05/26] Add copy endpoint to OccurrenceReportViewSet --- boranga/components/occurrence/api.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/boranga/components/occurrence/api.py b/boranga/components/occurrence/api.py index 97eeb90e..7c53e271 100644 --- a/boranga/components/occurrence/api.py +++ b/boranga/components/occurrence/api.py @@ -110,6 +110,7 @@ IsOccurrenceReportReferee, OccurrenceObjectPermission, OccurrencePermission, + OccurrenceReportCopyPermission, OccurrenceReportObjectPermission, OccurrenceReportPermission, ) @@ -2418,6 +2419,20 @@ def update_show_on_map(self, request, *args, **kwargs): return Response(serializer.data) + @detail_route( + methods=[ + "POST", + ], + detail=True, + permission_classes=[OccurrenceReportCopyPermission], + ) + @renderer_classes((JSONRenderer,)) + def copy(self, request, *args, **kwargs): + instance = self.get_object() + ocr_copy = instance.copy(request.user.id) + serializer = self.get_serializer(ocr_copy) + return Response(serializer.data) + class ObserverDetailViewSet(viewsets.GenericViewSet, mixins.RetrieveModelMixin): queryset = OCRObserverDetail.objects.all() From 77584a37bc2e9838c9902ac7c89115f9f82cef53 Mon Sep 17 00:00:00 2001 From: Oak McIlwain Date: Tue, 13 Aug 2024 15:05:39 +0800 Subject: [PATCH 06/26] Make sure to set submitter_information to None when copying OCR. --- boranga/components/occurrence/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/boranga/components/occurrence/models.py b/boranga/components/occurrence/models.py index 8dbd9a53..cc943b10 100644 --- a/boranga/components/occurrence/models.py +++ b/boranga/components/occurrence/models.py @@ -1132,6 +1132,7 @@ def copy(self, request_user_id): ocr_copy.approved_by = None if request_user_id != self.submitter: self.submitter = request_user_id + ocr_copy.submitter_information = None ocr_copy.save(no_revision=True) # Clone all the associated models @@ -1334,6 +1335,9 @@ class OccurrenceReportUserAction(UserAction): ACTION_REINSTATE_PROPOSAL = "Reinstate occurrence report {}" ACTION_APPROVAL_LEVEL_DOCUMENT = "Assign Approval level document {}" ACTION_UPDATE_OBSERVER_DETAIL = "Update Observer {} on occurrence report {}" + ACTION_COPY = "Created occurrence report {} from a copy of occurrence report {}" + Action_COPY_TO = "Copy occurrence report to {}" + # Amendment ACTION_ID_REQUEST_AMENDMENTS = "Request amendments" From 4031f1f23c7d7f2e097a278bfbbf3787249262ee Mon Sep 17 00:00:00 2001 From: Oak McIlwain Date: Tue, 13 Aug 2024 15:06:02 +0800 Subject: [PATCH 07/26] Add logging to copy method on OccurrenceReportViewSet. --- boranga/components/occurrence/api.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/boranga/components/occurrence/api.py b/boranga/components/occurrence/api.py index 7c53e271..282b2520 100644 --- a/boranga/components/occurrence/api.py +++ b/boranga/components/occurrence/api.py @@ -2430,6 +2430,33 @@ def update_show_on_map(self, request, *args, **kwargs): def copy(self, request, *args, **kwargs): instance = self.get_object() ocr_copy = instance.copy(request.user.id) + + # Log the action + ocr_copy.log_user_action( + OccurrenceReportUserAction.ACTION_COPY.format( + ocr_copy.occurrence_report_number, instance.occurrence_report_number + ), + request, + ) + request.user.log_user_action( + OccurrenceReportUserAction.ACTION_COPY.format( + ocr_copy.occurrence_report_number, instance.occurrence_report_number + ), + request, + ) + instance.log_user_action( + OccurrenceReportUserAction.Action_COPY_TO.format( + ocr_copy.occurrence_report_number, + ), + request, + ) + request.user.log_user_action( + OccurrenceReportUserAction.Action_COPY_TO.format( + ocr_copy.occurrence_report_number, + ), + request, + ) + serializer = self.get_serializer(ocr_copy) return Response(serializer.data) From 27d2c083ced4dfdebcca53503245114495564ac7 Mon Sep 17 00:00:00 2001 From: Oak McIlwain Date: Tue, 13 Aug 2024 15:18:47 +0800 Subject: [PATCH 08/26] Correct case. --- boranga/components/occurrence/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boranga/components/occurrence/models.py b/boranga/components/occurrence/models.py index cc943b10..893c8501 100644 --- a/boranga/components/occurrence/models.py +++ b/boranga/components/occurrence/models.py @@ -1336,7 +1336,7 @@ class OccurrenceReportUserAction(UserAction): ACTION_APPROVAL_LEVEL_DOCUMENT = "Assign Approval level document {}" ACTION_UPDATE_OBSERVER_DETAIL = "Update Observer {} on occurrence report {}" ACTION_COPY = "Created occurrence report {} from a copy of occurrence report {}" - Action_COPY_TO = "Copy occurrence report to {}" + ACTION_COPY_TO = "Copy occurrence report to {}" # Amendment ACTION_ID_REQUEST_AMENDMENTS = "Request amendments" From 3273879087d317ffb18316c0447659e5c113bcc5 Mon Sep 17 00:00:00 2001 From: Oak McIlwain Date: Tue, 13 Aug 2024 15:19:00 +0800 Subject: [PATCH 09/26] Correct case. --- boranga/components/occurrence/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/boranga/components/occurrence/api.py b/boranga/components/occurrence/api.py index 282b2520..152a5e87 100644 --- a/boranga/components/occurrence/api.py +++ b/boranga/components/occurrence/api.py @@ -2445,13 +2445,13 @@ def copy(self, request, *args, **kwargs): request, ) instance.log_user_action( - OccurrenceReportUserAction.Action_COPY_TO.format( + OccurrenceReportUserAction.ACTION_COPY_TO.format( ocr_copy.occurrence_report_number, ), request, ) request.user.log_user_action( - OccurrenceReportUserAction.Action_COPY_TO.format( + OccurrenceReportUserAction.ACTION_COPY_TO.format( ocr_copy.occurrence_report_number, ), request, From 53a090beee77c7f4635ad413a275b2df4ef4b8b0 Mon Sep 17 00:00:00 2001 From: Oak McIlwain Date: Tue, 13 Aug 2024 15:28:51 +0800 Subject: [PATCH 10/26] Add user_is_assessor field to InternalOccurrenceReportSerializer as we need a check that doesn't care about the OCR processing status in order to know when to show the copy button. --- boranga/components/occurrence/serializers.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/boranga/components/occurrence/serializers.py b/boranga/components/occurrence/serializers.py index 04e6792c..77cc5633 100644 --- a/boranga/components/occurrence/serializers.py +++ b/boranga/components/occurrence/serializers.py @@ -1366,6 +1366,7 @@ class InternalOccurrenceReportSerializer(OccurrenceReportSerializer): can_user_assess = serializers.SerializerMethodField() can_user_action = serializers.SerializerMethodField() can_add_log = serializers.SerializerMethodField() + user_is_assessor = serializers.SerializerMethodField() current_assessor = serializers.SerializerMethodField(read_only=True) approval_details = OccurrenceReportApprovalDetailsSerializer( read_only=True, allow_null=True @@ -1455,6 +1456,7 @@ class Meta: "has_main_observer", "is_submitter", "migrated_from_id", + "user_is_assessor", ) def get_readonly(self, obj): @@ -1487,6 +1489,10 @@ def get_can_user_assess(self, obj): and obj.assigned_officer == request.user.id ) + def get_user_is_assessor(self, obj): + request = self.context["request"] + return is_occurrence_assessor(request) + def get_can_user_approve(self, obj): request = self.context["request"] return ( From eae92c91dce114aad719289108b031c7061f300c Mon Sep 17 00:00:00 2001 From: Oak McIlwain Date: Tue, 13 Aug 2024 16:01:30 +0800 Subject: [PATCH 11/26] Add further null checking to copy method on OccurrenceReport model. Also make sure to copy across any OccurrenceReportGeometry records. --- boranga/components/occurrence/models.py | 181 +++++++++++++----------- 1 file changed, 101 insertions(+), 80 deletions(-) diff --git a/boranga/components/occurrence/models.py b/boranga/components/occurrence/models.py index 893c8501..57752408 100644 --- a/boranga/components/occurrence/models.py +++ b/boranga/components/occurrence/models.py @@ -1136,88 +1136,100 @@ def copy(self, request_user_id): ocr_copy.save(no_revision=True) # Clone all the associated models - location = clone_model( - OCRLocation, - OCRLocation, - self.location, - ) - if location: - location.occurrence_report = ocr_copy - location.save() - - habitat_composition = clone_model( - OCRHabitatComposition, - OCRHabitatComposition, - self.habitat_composition, - ) - if habitat_composition: - habitat_composition.occurrence_report = ocr_copy - habitat_composition.save() - - habitat_condition = clone_model( - OCRHabitatCondition, - OCRHabitatCondition, - self.habitat_condition, - ) - if habitat_condition: - habitat_condition.occurrence_report = ocr_copy - habitat_condition.save() - - vegetation_structure = clone_model( - OCRVegetationStructure, - OCRVegetationStructure, - self.vegetation_structure, - ) - if vegetation_structure: - vegetation_structure.occurrence_report = ocr_copy - vegetation_structure.save() - - fire_history = clone_model(OCRFireHistory, OCRFireHistory, self.fire_history) - if fire_history: - fire_history.occurrence_report = ocr_copy - fire_history.save() - - associated_species = clone_model( - OCRAssociatedSpecies, - OCRAssociatedSpecies, - self.associated_species, - ) - if associated_species: - associated_species.occurrence_report = ocr_copy - associated_species.save() - # copy over related species separately - for i in self.associated_species.related_species.all(): - associated_species.related_species.add(i) - - observation_detail = clone_model( - OCRObservationDetail, - OCRObservationDetail, - self.observation_detail, - ) - if observation_detail: - observation_detail.occurrence_report = ocr_copy - observation_detail.save() - - plant_count = clone_model(OCRPlantCount, OCRPlantCount, self.plant_count) - if plant_count: - plant_count.occurrence_report = ocr_copy - plant_count.save() + if hasattr(self, "location") and self.location: + location = clone_model( + OCRLocation, + OCRLocation, + self.location, + ) + if location: + location.occurrence_report = ocr_copy + location.save() + + if hasattr(self, "habitat_composition") and self.habitat_composition: + habitat_composition = clone_model( + OCRHabitatComposition, + OCRHabitatComposition, + self.habitat_composition, + ) + if habitat_composition: + habitat_composition.occurrence_report = ocr_copy + habitat_composition.save() + + if hasattr(self, "habitat_condition") and self.habitat_condition: + habitat_condition = clone_model( + OCRHabitatCondition, + OCRHabitatCondition, + self.habitat_condition, + ) + if habitat_condition: + habitat_condition.occurrence_report = ocr_copy + habitat_condition.save() + + if hasattr(self, "vegetation_structure") and self.vegetation_structure: + vegetation_structure = clone_model( + OCRVegetationStructure, + OCRVegetationStructure, + self.vegetation_structure, + ) + if vegetation_structure: + vegetation_structure.occurrence_report = ocr_copy + vegetation_structure.save() - animal_observation = clone_model( - OCRAnimalObservation, - OCRAnimalObservation, - self.animal_observation, - ) - if animal_observation: - animal_observation.occurrence_report = ocr_copy - animal_observation.save() + if hasattr(self, "fire_history") and self.fire_history: + fire_history = clone_model( + OCRFireHistory, OCRFireHistory, self.fire_history + ) + if fire_history: + fire_history.occurrence_report = ocr_copy + fire_history.save() + + if hasattr(self, "associated_species") and self.associated_species: + associated_species = clone_model( + OCRAssociatedSpecies, + OCRAssociatedSpecies, + self.associated_species, + ) + if associated_species: + associated_species.occurrence_report = ocr_copy + associated_species.save() + # copy over related species separately + for i in self.associated_species.related_species.all(): + associated_species.related_species.add(i) + + if hasattr(self, "observation_detail") and self.observation_detail: + observation_detail = clone_model( + OCRObservationDetail, + OCRObservationDetail, + self.observation_detail, + ) + if observation_detail: + observation_detail.occurrence_report = ocr_copy + observation_detail.save() + + if hasattr(self, "plant_count") and self.plant_count: + plant_count = clone_model(OCRPlantCount, OCRPlantCount, self.plant_count) + if plant_count: + plant_count.occurrence_report = ocr_copy + plant_count.save() + + if hasattr(self, "animal_observation") and self.animal_observation: + animal_observation = clone_model( + OCRAnimalObservation, + OCRAnimalObservation, + self.animal_observation, + ) + if animal_observation: + animal_observation.occurrence_report = ocr_copy + animal_observation.save() - identification = clone_model( - OCRIdentification, OCRIdentification, self.identification - ) - if identification: - identification.occurrence_report = ocr_copy - identification.save() + if hasattr(self, "identification") and self.identification: + identification = clone_model( + OCRIdentification, OCRIdentification, self.identification + ) + if identification: + identification.occurrence_report = ocr_copy + identification.save() # Clone the threats for threat in self.ocr_threats.all(): @@ -1238,6 +1250,15 @@ def copy(self, request_user_id): occ_doc.occurrence_report = ocr_copy occ_doc.save() + # Clone any occurrence geometries + for geom in self.ocr_geometry.all(): + occ_geom = clone_model( + OccurrenceReportGeometry, OccurrenceReportGeometry, geom + ) + if occ_geom: + occ_geom.occurrence_report = ocr_copy + occ_geom.save() + return ocr_copy From c7b64c98e17e37f67d782281c1723186b999a3bf Mon Sep 17 00:00:00 2001 From: Oak McIlwain Date: Tue, 13 Aug 2024 16:01:52 +0800 Subject: [PATCH 12/26] Add copy action to datatable. --- .../occurrence/occurrence_report_table.vue | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/boranga/frontend/boranga/src/components/external/occurrence/occurrence_report_table.vue b/boranga/frontend/boranga/src/components/external/occurrence/occurrence_report_table.vue index 752b8a22..4b4b5385 100644 --- a/boranga/frontend/boranga/src/components/external/occurrence/occurrence_report_table.vue +++ b/boranga/frontend/boranga/src/components/external/occurrence/occurrence_report_table.vue @@ -46,8 +46,9 @@ @@ -281,8 +282,9 @@ export default { } } else if (full.can_user_view) { - links += `View`; + links += `View
`; } + links += `Copy`; return links; } } @@ -546,6 +548,46 @@ export default { console.log(error); }); }, + copyOCRProposal: function (occurrence_report_id) { + let vm = this; + swal.fire({ + title: "Copy Occurrence Report", + text: `Are you sure you want to make a copy of occurrence report OCR${occurrence_report_id}?`, + icon: "question", + showCancelButton: true, + confirmButtonText: 'Copy Occurrence Report', + customClass: { + confirmButton: 'btn btn-primary', + cancelButton: 'btn btn-secondary', + }, + reverseButtons: true, + }).then((swalresult) => { + if (swalresult.isConfirmed) { + this.$http.post(helpers.add_endpoint_json(api_endpoints.occurrence_report, occurrence_report_id + '/copy')).then(res => { + const ocr_copy = res.body; + swal.fire({ + title: 'Copied', + text: `The occurrence report has been copied to ${ocr_copy.occurrence_report_number}. When you click OK, the new occurrence report will open in a new window.`, + icon: 'success', + customClass: { + confirmButton: 'btn btn-primary', + }, + didClose: () => { + vm.$refs.occurrence_report_datatable.vmDataTable.ajax.reload(); + const routeData = this.$router.resolve({ + name: 'draft_ocr_proposal', + params: { occurrence_report_id: ocr_copy.id }, + query: { action: 'edit' } + }); + window.open(routeData.href, '_blank'); + } + }); + }, (error) => { + console.log(error); + }); + } + }); + }, addEventListeners: function () { let vm = this; // External Discard listener @@ -559,6 +601,11 @@ export default { var id = $(this).attr('data-reinstate-ocr-proposal'); vm.reinstateOCRProposal(id); }); + vm.$refs.occurrence_report_datatable.vmDataTable.on('click', 'a[data-copy-ocr-proposal]', function (e) { + e.preventDefault(); + var id = $(this).attr('data-copy-ocr-proposal'); + vm.copyOCRProposal(id); + }); vm.$refs.occurrence_report_datatable.vmDataTable.on('childRow.dt', function (e, settings) { helpers.enablePopovers(); }); From d292c11f1c249480cfc848445c4ec7a870d7967f Mon Sep 17 00:00:00 2001 From: Oak McIlwain Date: Tue, 13 Aug 2024 16:02:17 +0800 Subject: [PATCH 13/26] Incorporate copy action. --- .../internal/occurrence/occurrence_report.vue | 168 ++++++------------ 1 file changed, 50 insertions(+), 118 deletions(-) diff --git a/boranga/frontend/boranga/src/components/internal/occurrence/occurrence_report.vue b/boranga/frontend/boranga/src/components/internal/occurrence/occurrence_report.vue index c1198b82..9167e652 100644 --- a/boranga/frontend/boranga/src/components/internal/occurrence/occurrence_report.vue +++ b/boranga/frontend/boranga/src/components/internal/occurrence/occurrence_report.vue @@ -241,15 +241,17 @@ -
- + +
+
+
@@ -339,20 +341,17 @@ + :declined_details="occurrence_report.declined_details" @refreshFromResponse="refreshFromResponse"> + + :approval_details="occurrence_report.approval_details" @refreshFromResponse="refreshFromResponse"> +
- +