diff --git a/kustomize/overlays/prod/kustomization.yaml b/kustomize/overlays/prod/kustomization.yaml index ac3d84cd..b73b5758 100644 --- a/kustomize/overlays/prod/kustomization.yaml +++ b/kustomize/overlays/prod/kustomization.yaml @@ -25,5 +25,5 @@ patches: - path: service_patch.yaml images: - name: ghcr.io/dbca-wa/wastd - newTag: 2.1.4 + newTag: 2.1.5 diff --git a/pyproject.toml b/pyproject.toml index 6eb895fe..de92d90d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,6 @@ [tool.poetry] name = "wastd" -version = "2.1.4" - +version = "2.1.5" description = "Western Australian Sea Turtles Database" authors = ["Florian Mayer ", "Ashley Felton ","Evan Hallein ", "Rick Wang "] diff --git a/wamtram2/static/js/observation_management.js b/wamtram2/static/js/observation_management.js index fc72237a..b72e6e34 100644 --- a/wamtram2/static/js/observation_management.js +++ b/wamtram2/static/js/observation_management.js @@ -276,6 +276,12 @@ function setTagInfo() { const tagContainer = document.getElementById('tagContainer'); if (tagContainer && initialData.tag_info?.recorded_tags) { tagContainer.innerHTML = ''; + + if (initialData.tag_info.recorded_tags.length === 0) { + tagContainer.innerHTML = '

No flipper tags found

'; + return; + } + initialData.tag_info.recorded_tags.forEach(tag => { const tagHtml = `
@@ -331,6 +337,12 @@ function setTagInfo() { const pitTagContainer = document.getElementById('pitTagContainer'); if (pitTagContainer && initialData.tag_info?.recorded_pit_tags) { pitTagContainer.innerHTML = ''; + + if (initialData.tag_info.recorded_pit_tags.length === 0) { + pitTagContainer.innerHTML = '

No PIT tags found

'; + return; + } + initialData.tag_info.recorded_pit_tags.forEach(pitTag => { const pitTagHtml = `
@@ -344,7 +356,7 @@ function setTagInfo() {
- +
@@ -103,69 +107,92 @@
Create for a Tagged Turtle
- - {% if turtle %} -
-
-
Tag ID: {{ tag_id }}
-
- ID: + {% if turtle and not existing_turtle_entry %} +
+
+
Tag ID: {{ tag_id }}
+ +
Sex: {{ turtle.sex }}
+
Species: {{ turtle.species_code }}
+ {% if first_observation_date %} +
First Observed: {{ first_observation_date|perth_time|date:"Y-m-d" }}
+ {% endif %} + {% if latest_site %} +
Latest Site: {{ latest_site }}
+ {% endif %} +
+
+
+

Does the species and sex match the data sheet?

+ Yes, create a record for this turtle +
+ {% csrf_token %} + + + + + + + + {% comment %} {% endcomment %} + + No, create the record and review later + +
+
+ {% elif existing_turtle_entry %} +
+

You are searching for a new tag given to an existing turtle this season. Here is the turtle information:

+
+
+
+
Tag ID: {{ tag_id }}
+ +
Sex: {{ turtle.sex }}
+
Species: {{ turtle.species_code }}
+
Tag Added Date: {{ existing_turtle_entry.entry_batch.entry_date|date:"Y-m-d" }}
+
Location: {{ existing_turtle_entry.place_code.place_name }}
-
Sex: {{ turtle.sex }}
-
Species: {{ turtle.species_code }}
- {% if first_observation_date %} -
First Observed: {{ first_observation_date|perth_time|date:"Y-m-d" }}
- {% endif %} - {% if latest_site %} -
Latest Site: {{ latest_site }}
- {% endif %}
-
-
-

Does the species and sex match the data sheet?

- Yes, create a record for this turtle -
- {% csrf_token %} - - - - - - - - {% comment %} {% endcomment %} + + {% elif new_tag_entry %} +
+

No existing turtle found with this tag in the database, but an entry with this tag was found within this season:

+
+
+
+
Tag ID: {{ tag_id }}
+
Entry ID: {{ new_tag_entry.data_entry_id }}
+
Observation Date: {{ new_tag_entry.entry_batch.entry_date|date:"Y-m-d" }}
+
Place: {{ new_tag_entry.place_code.place_name }}
+
Species: {{ new_tag_entry.species_code.common_name }}
+
Sex: {{ new_tag_entry.sex }}
+
+
+
+

This may be a recapture of a new turtle from this season

+ Yes, Create New Data Entry + No, create the record and review later - -
- {% elif new_tag_entry %} -
-

No existing turtle found with this tag in the database, but an entry with this tag was found within this season:

-
-
-
-
Tag ID: {{ tag_id }}
-
Entry ID: {{ new_tag_entry.data_entry_id }}
-
Observation Date: {{ new_tag_entry.entry_batch.entry_date|date:"Y-m-d" }}
-
Place: {{ new_tag_entry.place_code.place_name }}
-
Species: {{ new_tag_entry.species_code.common_name }}
-
Sex: {{ new_tag_entry.sex }}
-
-
-

This may be a recapture of a new turtle from this season

- Yes, Create New Data Entry - - No, create the record and review later - -
{% endif %}
diff --git a/wamtram2/templates/wamtram2/observation_management.html b/wamtram2/templates/wamtram2/observation_management.html index 277a9a8f..e70c0c3b 100644 --- a/wamtram2/templates/wamtram2/observation_management.html +++ b/wamtram2/templates/wamtram2/observation_management.html @@ -349,6 +349,10 @@

Other Information

{% else %} const initialData = null; {% endif %} + + const tagStateChoices = {{ tag_states_choices|safe }}; + const pitTagStateChoices = {{ pit_tag_state_choices|safe }}; + const measurementTypeChoices = {{ measurement_type_choices|safe }}; {% endblock %} \ No newline at end of file diff --git a/wamtram2/templates/wamtram2/trtdataentry_form.html b/wamtram2/templates/wamtram2/trtdataentry_form.html index 56b98856..877f1c8a 100644 --- a/wamtram2/templates/wamtram2/trtdataentry_form.html +++ b/wamtram2/templates/wamtram2/trtdataentry_form.html @@ -518,6 +518,9 @@
NEW Flipper Tag(s)
OLD PIT Tag(s)
+

+ Note: If you searched using a RIGHT PIT tag, please manually copy it to the right PIT tag field. +

@@ -1139,12 +1142,14 @@ // Handle form changes to track dirty state form.addEventListener('input', function() { - isFormDirty = true; + if (!isSubmitting) { + isFormDirty = true; + } }); // Prompt the user if they try to leave the page with unsaved changes window.addEventListener('beforeunload', function(event) { - if (isFormDirty) { + if (isFormDirty && !isSubmitting) { event.preventDefault(); event.returnValue = ''; return ''; @@ -1154,15 +1159,28 @@ // Handle form submission form.addEventListener('submit', function(event) { if (form.checkValidity()) { - showLoadingOverlay(); - isFormDirty = false; + isSubmitting = true; + isFormDirty = false; + showLoadingOverlay(); + } else { - event.preventDefault(); - showLoadingOverlay(); - scrollToErrors(); + event.preventDefault(); + showLoadingOverlay(); + scrollToErrors(); } }); + + // Handle page reload on back navigation + window.addEventListener('pageshow', function(event) { + if (event.persisted) { + window.location.reload(); + isFormDirty = false; + isSubmitting = false; + } + }); + + function scrollToErrors() { const errorDiv = document.querySelector('.is-invalid'); if (errorDiv) { @@ -1178,13 +1196,6 @@ if (overlay) overlay.style.display = 'block'; if (spinner) spinner.style.display = 'block'; } - - // Handle page reload on back navigation - window.addEventListener('pageshow', function(event) { - if (event.persisted) { - window.location.reload(); - } - }); }); document.addEventListener('DOMContentLoaded', function() { const form = document.getElementById('dataEntryForm'); @@ -1216,6 +1227,8 @@ const confirmSaveButton = document.getElementById('confirmSaveButton'); const observationDateInput = document.querySelector('input[name="observation_date"]'); const dateErrorMessage = document.getElementById('date-error-message'); + let isFormDirty = false; + let isSubmitting = false; const labelMapping = { "search_entered_by": "Enterer", @@ -1378,9 +1391,12 @@ if (confirmSaveButton) { confirmSaveButton.addEventListener('click', function() { - $(summaryModal).modal('hide'); + isSubmitting = true; isFormDirty = false; - form.submit(); + $(summaryModal).modal('hide'); + setTimeout(() => { + form.submit(); + }, 100); }); } }); diff --git a/wamtram2/templates/wamtram2/trtentrybatch_detail.html b/wamtram2/templates/wamtram2/trtentrybatch_detail.html index dfe1e6c0..8d2ac68f 100644 --- a/wamtram2/templates/wamtram2/trtentrybatch_detail.html +++ b/wamtram2/templates/wamtram2/trtentrybatch_detail.html @@ -143,10 +143,10 @@
{% if request.user.is_superuser or request.user|has_group:"WAMTRAM2_STAFF" or request.user|has_group:"WAMTRAM2_TEAM_LEADER"%} - Validate this Batch + Validate this Batch {% endif %} {% if request.user.is_superuser %} - Add this batch to the database + Add this batch to the database {% endif %}
diff --git a/wamtram2/views.py b/wamtram2/views.py index 3b1eaf52..c6cd13af 100644 --- a/wamtram2/views.py +++ b/wamtram2/views.py @@ -705,6 +705,7 @@ def get_success_url(self): batch_id = self.kwargs['batch_id'] return reverse_lazy('wamtram2:entry_batch_detail', kwargs={'batch_id': batch_id}) + class ProcessDataEntryBatchView(LoginRequiredMixin, View): """ View class for processing a data entry batch. @@ -759,6 +760,7 @@ def get(self, request, *args, **kwargs): else: return redirect("wamtram2:entry_batch_detail", batch_id=self.kwargs["batch_id"]) + class FindTurtleView(LoginRequiredMixin, View): """ View class for finding a turtle based on tag and pit tag ID. @@ -812,7 +814,6 @@ def get(self, request, *args, **kwargs): Q(new_pittag_id_3__pittag_id=tag_id) | Q(new_pittag_id_4__pittag_id=tag_id), observation_id__isnull=True, - turtle_id__isnull=True ).select_related('entry_batch', 'place_code', 'species_code').order_by('-entry_batch__entry_date').first() if batch_id: @@ -891,6 +892,7 @@ def post(self, request, *args, **kwargs): new_tag_entry = None batch = None template_name = "No template associated" + existing_turtle_entry = None if batch_id: batch = TrtEntryBatches.objects.filter(entry_batch_id=batch_id).first() @@ -915,7 +917,7 @@ def post(self, request, *args, **kwargs): tag_type = "unknown_tag" if not turtle: - new_tag_entry = TrtDataEntry.objects.filter( + existing_turtle_entry = TrtDataEntry.objects.filter( Q(new_left_tag_id__tag_id=tag_id) | Q(new_left_tag_id_2__tag_id=tag_id) | Q(new_right_tag_id__tag_id=tag_id) | @@ -924,32 +926,86 @@ def post(self, request, *args, **kwargs): Q(new_pittag_id_2__pittag_id=tag_id) | Q(new_pittag_id_3__pittag_id=tag_id) | Q(new_pittag_id_4__pittag_id=tag_id), - observation_id__isnull=True, + turtle_id__isnull=False, + observation_id__isnull=True + ).select_related( + 'turtle_id', + 'entry_batch', + 'place_code', + 'species_code' ).order_by('-entry_batch__entry_date').first() - - if new_tag_entry: - if any([str(new_tag_entry.new_left_tag_id).upper() == str(tag_id).upper(), - str(new_tag_entry.new_left_tag_id_2).upper() == str(tag_id).upper()]): + + if existing_turtle_entry: + turtle = existing_turtle_entry.turtle_id + if any([ + str(existing_turtle_entry.new_left_tag_id).upper() == str(tag_id).upper(), + str(existing_turtle_entry.new_left_tag_id_2).upper() == str(tag_id).upper() + ]): tag_type = "recapture_tag" tag_side = "L" - elif any([str(new_tag_entry.new_right_tag_id).upper() == str(tag_id).upper(), - str(new_tag_entry.new_right_tag_id_2).upper() == str(tag_id).upper()]): + elif any([ + str(existing_turtle_entry.new_right_tag_id).upper() == str(tag_id).upper(), + str(existing_turtle_entry.new_right_tag_id_2).upper() == str(tag_id).upper() + ]): tag_type = "recapture_tag" tag_side = "R" else: tag_type = "recapture_pit_tag" tag_side = None else: - no_turtle_found = True + new_tag_entry = TrtDataEntry.objects.filter( + Q(new_left_tag_id__tag_id=tag_id) | + Q(new_left_tag_id_2__tag_id=tag_id) | + Q(new_right_tag_id__tag_id=tag_id) | + Q(new_right_tag_id_2__tag_id=tag_id) | + Q(new_pittag_id__pittag_id=tag_id) | + Q(new_pittag_id_2__pittag_id=tag_id) | + Q(new_pittag_id_3__pittag_id=tag_id) | + Q(new_pittag_id_4__pittag_id=tag_id), + observation_id__isnull=True, + turtle_id__isnull=True + ).select_related( + 'entry_batch', + 'place_code', + 'species_code' + ).order_by('-entry_batch__entry_date').first() + + if new_tag_entry: + if any([ + str(new_tag_entry.new_left_tag_id).upper() == str(tag_id).upper(), + str(new_tag_entry.new_left_tag_id_2).upper() == str(tag_id).upper() + ]): + tag_type = "recapture_tag" + tag_side = "L" + elif any([ + str(new_tag_entry.new_right_tag_id).upper() == str(tag_id).upper(), + str(new_tag_entry.new_right_tag_id_2).upper() == str(tag_id).upper() + ]): + tag_type = "recapture_tag" + tag_side = "R" + else: + tag_type = "recapture_pit_tag" + tag_side = None + else: + no_turtle_found = True if turtle: - # if request.POST.get('create_and_review_later'): - # response = redirect(reverse('wamtram2:existingtrtdataentry', kwargs={'batch_id': batch_id, 'turtle_id': turtle.turtle_id})) - # response = self.set_cookie(response, batch_id, tag_id, tag_type, tag_side, do_not_process=True) - # return response - # else: - response = redirect(reverse('wamtram2:find_turtle', kwargs={'batch_id': batch_id})) + if existing_turtle_entry: + response = render(request, "wamtram2/find_turtle.html", { + "form": form, + "turtle": turtle, + "existing_turtle_entry": existing_turtle_entry, + "tag_id": tag_id, + "tag_type": tag_type, + "tag_side": tag_side, + "batch_id": batch_id, + "batch": batch, + "template_name": template_name, + }) + else: + response = redirect(reverse('wamtram2:find_turtle', kwargs={'batch_id': batch_id})) return self.set_cookie(response, batch_id, tag_id, tag_type, tag_side) + elif new_tag_entry: response = render(request, "wamtram2/find_turtle.html", { "form": form, @@ -2582,12 +2638,17 @@ def handle_file_upload(self, request): class AvailableBatchesView(LoginRequiredMixin, View): def get(self, request): - user_orgs = request.user.organisations.all() + if request.user.is_superuser: + batches = TrtEntryBatches.objects.all() + else: + user_orgs = request.user.organisations.all() + batches = TrtEntryBatches.objects.filter( + batch_organisations__organisation__in=[org.code for org in user_orgs] + ) + current_batch_id = request.GET.get('current_batch_id') - batches = TrtEntryBatches.objects.filter( - batch_organisations__organisation__in=[org.code for org in user_orgs] - ).exclude( + batches = batches.exclude( entry_batch_id=current_batch_id ).distinct() @@ -3692,6 +3753,7 @@ def get_context_data(self, **kwargs): }) return context + def post(self, request, *args, **kwargs): if request.headers.get('X-Requested-With') == 'XMLHttpRequest': visible_columns = request.POST.getlist('columns[]') @@ -3879,6 +3941,20 @@ def _get_observation_data(self, observation): 'text': observation.place_code.get_full_name() if observation.place_code else None } + tag_info = { + 'recorded_tags': [{ + 'tag_id': tag.tag_id, + 'tag_side': tag.tag_side, + 'tag_position': tag.tag_position, + 'tag_state': tag.tag_state.tag_state if tag.tag_state else None, + } for tag in observation.trtrecordedtags_set.all()], + 'recorded_pit_tags': [{ + 'tag_id': tag.pittag_id, + 'tag_position': tag.pit_tag_position, + 'tag_state': tag.pit_tag_state.pit_tag_state if tag.pit_tag_state else None, + } for tag in observation.trtrecordedpittags_set.all()] + } + return { 'basic_info': { 'observation_id': observation.observation_id, @@ -3900,20 +3976,7 @@ def _get_observation_data(self, observation): 'latitude': str(observation.latitude), 'longitude': str(observation.longitude) }, - 'tag_info': { - 'recorded_tags': [{ - 'tag_id': str(tag.tag_id), - 'tag_side': str(tag.side), - 'tag_position': str(tag.tag_position), - 'tag_state': str(tag.tag_state), - 'barnacles': str(tag.barnacles) - } for tag in observation.trtrecordedtags_set.all()], - 'recorded_pit_tags': [{ - 'tag_id': str(tag.pittag_id), - 'tag_position': str(tag.pit_tag_position), - 'tag_state': str(tag.pit_tag_state) - } for tag in observation.trtrecordedpittags_set.all()] - }, + 'tag_info': tag_info, 'measurements': [{ 'measurement_type': str(measurement.measurement_type.description), 'measurement_value': str(measurement.measurement_value)