diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2024-09-16 12:45:00 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2024-09-16 12:46:09 +0200 |
commit | 12e9870e2aa0659de98a9122fbc4ab16b0877449 (patch) | |
tree | 36d375089b787fbc3649ad54bb9e7e3b2b5741e0 | |
parent | db5ecbb7b69ff668ca005bd9535bf595b79e649e (diff) | |
download | Ishtar-12e9870e2aa0659de98a9122fbc4ab16b0877449.tar.bz2 Ishtar-12e9870e2aa0659de98a9122fbc4ab16b0877449.zip |
✨ Context records relations: remove form from the wizard to put in a specific form
-rw-r--r-- | archaeological_context_records/forms.py | 80 | ||||
-rw-r--r-- | archaeological_context_records/models.py | 18 | ||||
-rw-r--r-- | archaeological_context_records/urls.py | 5 | ||||
-rw-r--r-- | archaeological_context_records/views.py | 122 | ||||
-rw-r--r-- | archaeological_context_records/wizards.py | 12 | ||||
-rw-r--r-- | archaeological_operations/forms.py | 12 | ||||
-rw-r--r-- | archaeological_operations/views.py | 1 | ||||
-rw-r--r-- | ishtar_common/models.py | 1 | ||||
-rw-r--r-- | ishtar_common/templates/ishtar/forms/modify_parcels.html | 6 | ||||
-rw-r--r-- | ishtar_common/templates/ishtar/forms/modify_relations.html | 59 |
10 files changed, 256 insertions, 60 deletions
diff --git a/archaeological_context_records/forms.py b/archaeological_context_records/forms.py index 55f27ddcd..8d41a7888 100644 --- a/archaeological_context_records/forms.py +++ b/archaeological_context_records/forms.py @@ -21,6 +21,7 @@ Context records forms definitions """ from collections import OrderedDict +from copy import copy from itertools import groupby from bootstrap_datepicker.widgets import DateField @@ -464,37 +465,6 @@ DatingFormSet.form_admin_name = _("Context record - 030 - Dating") DatingFormSet.form_slug = "contextrecord-030-datings" -class RecordRelationsForm(OpeRecordRelationsForm): - current_model = models.RelationType - current_related_model = models.ContextRecord - associated_models = { - "right_record": models.ContextRecord, - "relation_type": models.RelationType, - } - right_record = forms.ChoiceField( - label=_("Context record"), choices=[], required=False - ) - - def __init__(self, *args, **kwargs): - crs = None - if "data" in kwargs and "CONTEXT_RECORDS" in kwargs["data"]: - crs = kwargs["data"]["CONTEXT_RECORDS"] - super(RecordRelationsForm, self).__init__(*args, **kwargs) - self.fields["relation_type"].choices = models.RelationType.get_types( - initial=self.init_data.get("relation_type") - ) - if crs: - self.fields["right_record"].choices = [("", "-" * 2)] + crs - - -RecordRelationsFormSet = formset_factory( - RecordRelationsForm, can_delete=True, formset=RecordRelationsFormSetBase -) -RecordRelationsFormSet.form_label = _("Relations") -RecordRelationsFormSet.form_admin_name = _("Context record - 050 - Relations") -RecordRelationsFormSet.form_slug = "contextrecord-050-recordrelations" - - class RecordFormInterpretation(CustomForm, ManageOldType): HEADERS = {} form_label = _("Interpretation") @@ -554,6 +524,54 @@ class RecordDeletionForm(FinalForm): confirm_end_msg = _("Would you like to delete this context record?") +class RecordRelationsForm(OpeRecordRelationsForm): + current_model = models.RelationType + current_related_model = models.ContextRecord + associated_models = { + "right_record": models.ContextRecord, + "relation_type": models.RelationType, + } + ERROR_MISSING = _("You should select a context record and a relation type.") + + right_record = forms.ChoiceField( + label=_("Context record"), choices=[], required=False + ) + + def __init__(self, *args, **kwargs): + crs = None + if "data" in kwargs and "CONTEXT_RECORDS" in kwargs["data"]: + kwargs["data"] = copy(kwargs["data"]) + crs = kwargs["data"].pop("CONTEXT_RECORDS") + # clean data if not "real" data + prefix_value = kwargs['prefix'] + '-relation_type' + if not [k for k in kwargs['data'].keys() + if k.startswith(prefix_value) and kwargs['data'][k]]: + kwargs.pop('data') + if 'files' in kwargs: + kwargs.pop('files') + initial = kwargs.get("initial", {}) + if initial and initial.get("right_record", None): + if initial["right_record"] not in [cr_id for cr_id, cr_lbl in crs]: + try: + crs.append( + (initial["right_record"], + str(models.ContextRecord.objects.get(pk=initial["right_record"]))) + ) + except models.ContextRecord.DoesNotExist: + pass + super().__init__(*args, **kwargs) + if crs: + self.fields["right_record"].choices = [("", "-" * 2)] + crs + + +RecordRelationsFormSet = formset_factory( + RecordRelationsForm, can_delete=True, formset=RecordRelationsFormSetBase +) +RecordRelationsFormSet.form_label = _("Relations") +RecordRelationsFormSet.form_admin_name = _("Context record - 050 - Relations") +RecordRelationsFormSet.form_slug = "contextrecord-050-recordrelations" + + class QAOperationCR(IshtarForm): town = forms.ChoiceField(label=_("Town"), choices=[]) archaeological_site = forms.ChoiceField( diff --git a/archaeological_context_records/models.py b/archaeological_context_records/models.py index ee43183eb..3867997de 100644 --- a/archaeological_context_records/models.py +++ b/archaeological_context_records/models.py @@ -1056,9 +1056,10 @@ class ContextRecord( def get_extra_actions(self, request): # url, base_text, icon, extra_text, extra css class, is a quick action - actions = super(ContextRecord, self).get_extra_actions(request) + actions = super().get_extra_actions(request) + is_locked = hasattr(self, "is_locked") and self.is_locked(request.user) - # is_locked = hasattr(self, "is_locked") and self.is_locked(request.user) + can_edit_cr = self.can_do(request, "change_contextrecord") profile = get_current_profile() can_add_geo = profile.mapping and self.can_do(request, "add_geovectordata") if can_add_geo: @@ -1075,7 +1076,18 @@ class ContextRecord( False, ), ] - can_edit_cr = self.can_do(request, "change_contextrecord") + if can_edit_cr and not is_locked: + actions += [ + ( + reverse("context-record-relation-modify", args=[self.pk]), + _("Modify relations"), + "fa fa-retweet", + _("relations"), + "", + True, + ), + ] + if can_edit_cr: actions += [ ( diff --git a/archaeological_context_records/urls.py b/archaeological_context_records/urls.py index 446b3fbf9..5fd360fbb 100644 --- a/archaeological_context_records/urls.py +++ b/archaeological_context_records/urls.py @@ -150,6 +150,11 @@ urlpatterns = [ name="get-contextrecordrelationdetail", ), url( + r"^context-record-relations-modify/(?P<pk>.+)/$", + views.context_record_modify_relations, + name="context-record-relation-modify", + ), + url( r"^operation-qa-contextrecord/(?P<pks>[0-9]+)/$", check_rights(["add_contextrecord", "add_own_contextrecord"])( views.QAOperationContextRecordView.as_view() diff --git a/archaeological_context_records/views.py b/archaeological_context_records/views.py index 9d1d285e5..3695617f9 100644 --- a/archaeological_context_records/views.py +++ b/archaeological_context_records/views.py @@ -21,7 +21,7 @@ import json from django.db.models import Q from django.http import HttpResponse, HttpResponseRedirect, Http404 -from django.shortcuts import redirect +from django.shortcuts import render, redirect from django.urls import reverse from ishtar_common.utils import ugettext_lazy as _ from django.views.generic import RedirectView @@ -32,7 +32,6 @@ from archaeological_context_records import models from archaeological_operations.views import site_extra_context from archaeological_context_records import forms -from ishtar_common.utils import put_session_message from ishtar_common.views import ( IshtarMixin, @@ -42,7 +41,7 @@ from ishtar_common.views import ( wizard_is_available, QAItemEditForm, ) -from ishtar_common.views_item import display_item, get_item, show_item, revert_item +from ishtar_common.views_item import get_item, show_item, revert_item from archaeological_context_records import wizards show_contextrecord = show_item( @@ -122,7 +121,6 @@ record_creation_steps = [ ("general-record_creation", forms.RecordFormGeneral), ("datings-record_creation", forms.DatingFormSet), ("interpretation-record_creation", forms.RecordFormInterpretation), - ("relations-record_creation", forms.RecordRelationsFormSet), ("final-record_creation", forms.FinalForm), ] @@ -138,7 +136,6 @@ record_modification_steps = [ ("general-record_modification", forms.RecordFormGeneral), ("datings-record_modification", forms.DatingFormSet), ("interpretation-record_modification", forms.RecordFormInterpretation), - ("relations-record_modification", forms.RecordRelationsFormSet), ("final-record_modification", forms.FinalForm), ] @@ -196,6 +193,121 @@ def reset_wizards(request): wizard_class.session_reset(request, url_name) +RELATION_FORMSET_EXTRA_FORM = 3 + + +def get_relation_modify(model, model_relation, url_name): + def _modify_relation(request, pk): + formset_class = forms.RecordRelationsFormSet + item = model.objects.get(pk=pk) + relations = model_relation.objects.filter(left_record_id=pk).all() + + items = [ + (item.id, str(item)) + for item in model.objects.filter(operation=item.operation).all() + ] + current_items = [item[0] for item in items] + + initial = [] + for relation in relations: + initial.append({ + "pk": relation.pk, + "right_record": relation.right_record_id, + "relation_type": relation.relation_type_id, + }) + if relation.right_record_id not in current_items: + items.append((relation.right_record_id, str(relation.right_record))) + + data = { + 'form-TOTAL_FORMS': len(initial) + RELATION_FORMSET_EXTRA_FORM, + 'form-INITIAL_FORMS': 0, + 'form-MIN_NUM_FORMS': 0, + 'form-MAX_NUM_FORMS': 100, + "CONTEXT_RECORDS": items + } + + if request.method == 'POST': + new_data = dict(request.POST) + new_data = {k: new_data[k][0] for k in new_data} # convert POST to classic dict + + # remove empty lines and get deleted + no_values = list(range(data["form-TOTAL_FORMS"])) + deleted = {} + for k, value in new_data.items(): + if not value or not k.startswith("form-"): + continue + try: + form_number = int(k.split("-")[1]) + except (ValueError, IndexError) as __: + continue + if k.endswith("-DELETE") and new_data.get(f"form-{form_number}-pk", None): + deleted[form_number] = new_data[f"form-{form_number}-pk"] + if form_number not in no_values: # put it back in no values + no_values.append(form_number) + elif form_number in no_values and form_number not in deleted: + no_values.pop(no_values.index(form_number)) + for no_value in no_values: + for k in list(new_data.keys()): + if k.startswith(f"form-{no_value}-"): + new_data.pop(k) + data["form-TOTAL_FORMS"] = data["form-TOTAL_FORMS"] - len(no_values) + + new_data.update(data) + formset = formset_class(data=new_data) + + if formset.is_valid(): + is_valid = True + # delete + for deleted_id in deleted.values(): + try: + model_relation.objects.get(pk=deleted_id).delete() + except model_relation.DoesNotExist: + continue + + for idx_form, data in enumerate(formset.cleaned_data): + if not data.get('right_record') or not data.get('relation_type'): + continue + + if data.get("pk"): + try: + current_relation = model_relation.objects.get(pk=data.get("pk")) + except model_relation.DoesNotExist: + continue + not_deleted_or_associated = True + for key, value in data.items(): + if key == "DELETE" and value is True: + current_relation.delete() + not_deleted_or_associated = False + + if not_deleted_or_associated: + current_relation.right_record_id = data.get("right_record") + current_relation.relation_type_id = data.get("relation_type") + current_relation.save() + else: + model_relation.objects.create( + **{ + "left_record_id": item.pk, + "right_record_id": data.get("right_record"), + "relation_type_id": data.get("relation_type"), + } + ) + if is_valid: + return redirect(reverse(url_name, args=[pk])) + else: + formset = formset_class(initial=initial, data=data) + + return render(request, 'ishtar/forms/modify_relations.html', { + 'formset': formset, + "url": reverse(url_name, args=[pk]) + }) + return _modify_relation + + +context_record_modify_relations = get_relation_modify( + models.ContextRecord, models.RecordRelations, "context-record-relation-modify" +) + + class GenerateRelationImage(IshtarMixin, LoginRequiredMixin, RedirectView): upper_model = models.Operation model = models.ContextRecord diff --git a/archaeological_context_records/wizards.py b/archaeological_context_records/wizards.py index e92d9587e..35b4e02b4 100644 --- a/archaeological_context_records/wizards.py +++ b/archaeological_context_records/wizards.py @@ -119,21 +119,9 @@ class RecordWizard(Wizard): else: current_object = self.get_current_object() data["context_record"] = current_object - elif step.startswith("relations") and hasattr(form, "management_form"): - data["CONTEXT_RECORDS"] = self.get_other_context_records() form = super(RecordWizard, self).get_form(step, data, files) return form - def get_other_context_records(self): - operation = self.get_current_operation() - if not operation: - return [] - q = models.ContextRecord.objects.filter(operation_id=operation.pk) - obj = self.get_current_object() - if obj and obj.pk: - q = q.exclude(pk=obj.pk) - return [(cr.pk, cr.cached_label) for cr in q.all()] - class RecordModifWizard(RecordWizard): modification = True diff --git a/archaeological_operations/forms.py b/archaeological_operations/forms.py index cf9df8594..d3c797ff2 100644 --- a/archaeological_operations/forms.py +++ b/archaeological_operations/forms.py @@ -371,10 +371,13 @@ class ParcelFormSet(FormSet): class RecordRelationsForm(ManageOldType): base_model = 'right_relation' + ERROR_MISSING = _("You should select an operation.") current_model = models.RelationType current_related_model = models.Operation + associated_models = {'right_record': models.Operation, 'relation_type': models.RelationType} + pk = forms.IntegerField(required=False, widget=forms.HiddenInput) relation_type = forms.ChoiceField(label=_("Relation type"), choices=[], required=False) right_record = forms.IntegerField( @@ -388,9 +391,9 @@ class RecordRelationsForm(ManageOldType): self.left_record = None if 'left_record' in kwargs: self.left_record = kwargs.pop('left_record') - super(RecordRelationsForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['relation_type'].choices = \ - models.RelationType.get_types( + self.current_model.get_types( initial=self.init_data.get('relation_type')) @classmethod @@ -409,7 +412,7 @@ class RecordRelationsForm(ManageOldType): cleaned_data = self.cleaned_data if (cleaned_data.get('relation_type', None) and not cleaned_data.get('right_record', None)): - raise forms.ValidationError(_("You should select an operation.")) + raise forms.ValidationError(self.ERROR_MISSING) if (not cleaned_data.get('relation_type', None) and cleaned_data.get('right_record', None)): raise forms.ValidationError( @@ -455,6 +458,7 @@ class RecordRelationsForm(ManageOldType): class RecordRelationsFormSetBase(FormSet): + delete_widget = forms.CheckboxInput # passing left_record should be nicely done with form_kwargs with Django 1.9 # with no need of all these complications @@ -462,7 +466,7 @@ class RecordRelationsFormSetBase(FormSet): self.left_record = None if 'left_record' in kwargs: self.left_record = kwargs.pop('left_record') - super(RecordRelationsFormSetBase, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def _construct_forms(self): # instantiate all the forms and put them in self.forms diff --git a/archaeological_operations/views.py b/archaeological_operations/views.py index 08a1ee05c..028f311e6 100644 --- a/archaeological_operations/views.py +++ b/archaeological_operations/views.py @@ -495,7 +495,6 @@ def get_parcel_modify(model, key_for_parcel, url_name): return _modify_parcels - operation_modify_parcels = get_parcel_modify( models.Operation, "operation", "operation-parcels-modify" ) diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 48ed55e77..b9bd1351e 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -905,7 +905,6 @@ class RelationsViews(models.Model): if not settings.USE_BACKGROUND_TASK: return relation_view_update(cls, {"item_id": item_id}) else: - sender, kwargs = serialize_args_for_tasks(cls, None, {"item_id": item_id}) task_item = relation_view_update.delay(sender, kwargs) revoke_old_task(kwargs, "relation_view_update", task_item.id, cls) diff --git a/ishtar_common/templates/ishtar/forms/modify_parcels.html b/ishtar_common/templates/ishtar/forms/modify_parcels.html index 4c7c39120..c6d50fdef 100644 --- a/ishtar_common/templates/ishtar/forms/modify_parcels.html +++ b/ishtar_common/templates/ishtar/forms/modify_parcels.html @@ -20,11 +20,11 @@ <table class='inline-table'> <tr> {% for field in formset.forms.0 %} - {% if field.name != 'pk' %}{% if field.required %}<th{%else%}<td{% endif %}{% if not forloop.last %} rowspan='2'{% endif %}> - {{ field.label_tag }}{% if field.required %}</th>{%else%}</td>{% endif %}{% endif %}{% endfor %} + {% if field.name != 'pk' %}<th{% if not forloop.last %} rowspan='2'{% endif %}> + {{field.label}}</th>{% endif %}{% endfor %} </tr> <tr><td>({% trans "all"%} <input type='checkbox' name='check-all' class='check-all-parcel'/>)</td></tr> - {% inline_formset 'Parcels' formset.forms False %} + {% inline_formset _('Parcels') formset.forms False %} </table> {% endblock %} diff --git a/ishtar_common/templates/ishtar/forms/modify_relations.html b/ishtar_common/templates/ishtar/forms/modify_relations.html new file mode 100644 index 000000000..faec8b3fc --- /dev/null +++ b/ishtar_common/templates/ishtar/forms/modify_relations.html @@ -0,0 +1,59 @@ +{% load i18n l10n inline_formset table_form %} + +<div class="modal-dialog modal-lg"> + <div class="modal-content"> + <div class="modal-header"> + <h2>{{ formset.form_label }}</h2> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <form enctype="multipart/form-data" action="{{url}}" method="post" id="qa-action"> + {{ formset.management_form }} + {% csrf_token %} + <div class="modal-body body-scroll"> + <div class='form'> + {% block main_form %} + <table class='w-100 inline-table text-center'> + <tr> + {% for field in formset.forms.0 %} + {% if field.name != 'pk' %}<th{% if not forloop.last %} rowspan='2'{% endif %}> + {% if field.label %}{{field.label}}{% else %}{% trans "Delete" %}{% endif %}</th>{% endif %}{% endfor %} + </tr> + <tr><td>({% trans "all"%} <input type='checkbox' name='check-all' class='check-all-relations'/>)</td></tr> + {% inline_formset _("Relations") formset.forms False %} + </table> + {% endblock %} + </div> + </div> + <div class="modal-footer"> + {% block footer %} + <button type="submit" id="submit_form" name='validate' + value="validate" class="btn btn-success"> + {% if action_name %} + {{ action_name }} + {% else %} + {% trans "Add/Modify" %} + {% endif %} + </button> + <button type="button" data-dismiss="modal" + aria-label="Close" class="btn btn-secondary"> + {% trans "Close" %} + </button> + {% endblock %} + </div> + </form> + </div> +</div> +<script type="text/javascript">{% localize off %} + {% block js %} + {% endblock %} + $(document).ready(function(){ + qa_action_register("{{url}}"); + $(document).on("click", '.check-all-relations', function(){ + $('input[id$="-DELETE"]:checkbox').prop('checked', $(this).is(':checked')); + }); + {% block js_ready %} + {% endblock %} + }); +{% endlocalize %}</script> |