diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2024-10-10 13:42:18 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2024-10-14 16:48:37 +0200 |
commit | 435797a54e4d322a46711f303c2fc1fd5286330e (patch) | |
tree | 7c209135a17fe9b12fa2e2e844f345debed45d97 /archaeological_operations | |
parent | 69df0a88eeaafeee81d76a3307e79fe5cad8ecf8 (diff) | |
download | Ishtar-435797a54e4d322a46711f303c2fc1fd5286330e.tar.bz2 Ishtar-435797a54e4d322a46711f303c2fc1fd5286330e.zip |
✨ site and operation relations forms refactoring
Diffstat (limited to 'archaeological_operations')
-rw-r--r-- | archaeological_operations/forms.py | 70 | ||||
-rw-r--r-- | archaeological_operations/models.py | 30 | ||||
-rw-r--r-- | archaeological_operations/urls.py | 34 | ||||
-rw-r--r-- | archaeological_operations/views.py | 144 |
4 files changed, 215 insertions, 63 deletions
diff --git a/archaeological_operations/forms.py b/archaeological_operations/forms.py index 433f3fd54..1a41503d4 100644 --- a/archaeological_operations/forms.py +++ b/archaeological_operations/forms.py @@ -425,30 +425,59 @@ 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 - - def __init__(self, *args, **kwargs): - self.left_record = None - if 'left_record' in kwargs: - self.left_record = kwargs.pop('left_record') - super().__init__(*args, **kwargs) - - def _construct_forms(self): - # instantiate all the forms and put them in self.forms - self.forms = [] - for i in range(self.total_form_count()): - self.forms.append(self._construct_form( - i, left_record=self.left_record)) RecordRelationsFormSet = formset_factory( - RecordRelationsForm, can_delete=True, formset=RecordRelationsFormSetBase) + RecordRelationsForm, can_delete=True, formset=RecordRelationsFormSetBase, + extra=3 +) RecordRelationsFormSet.form_label = _("Operations - Relations") RecordRelationsFormSet.form_admin_name = _("Operation - Relations") RecordRelationsFormSet.form_slug = "operation-relations" +class OpeSiteRelationsForm(ManageOldType): + associated_models = {'right_record': models.ArchaeologicalSite} + + right_record = forms.IntegerField( + label="Site", + widget=widgets.JQueryAutoComplete( + reverse_lazy('autocomplete-archaeologicalsite'), + associated_model=models.ArchaeologicalSite), + validators=[valid_id(models.ArchaeologicalSite)], required=False) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["right_record"].label = get_current_profile().get_site_label() + +OpeSiteRelationsFormSet = formset_factory( + OpeSiteRelationsForm, can_delete=True, formset=RecordRelationsFormSetBase, + extra=3 +) +OpeSiteRelationsFormSet.form_label = _("Operations - Sites relations") +OpeSiteRelationsFormSet.form_admin_name = _("Operation - Sites - relations") +OpeSiteRelationsFormSet.form_slug = "operation-site-relations" + + +class SiteOpeRelationsForm(ManageOldType): + associated_models = {'right_record': models.Operation} + + right_record = forms.IntegerField( + label=_("Operation"), + widget=widgets.JQueryAutoComplete( + reverse_lazy('autocomplete-operation'), + associated_model=models.Operation), + validators=[valid_id(models.Operation)], required=False) + +SiteOpeRelationsFormSet = formset_factory( + SiteOpeRelationsForm, can_delete=True, formset=RecordRelationsFormSetBase, + extra=3 +) +SiteOpeRelationsFormSet.form_label = _("Sites - Operations relations") +SiteOpeRelationsFormSet.form_admin_name = _("Sites - Operation relations") +SiteOpeRelationsFormSet.form_slug = "siteoperation-relations" + + class OperationSelect(GeoItemSelect): _model = models.Operation form_admin_name = _("Operation - 001 - Search") @@ -671,7 +700,6 @@ class OperationFormGeneral(CustomForm, ManageOldType): 'collaborator': Person, 'remain': models.RemainType, 'period': models.Period, - 'archaeological_site': models.ArchaeologicalSite, 'town': Town, } HEADERS['code_patriarche'] = FormHeader(_("General")) @@ -691,9 +719,6 @@ class OperationFormGeneral(CustomForm, ManageOldType): label=_("Towns"), model=Town, required=False, remote=True) - archaeological_site = widgets.Select2MultipleField( - model=models.ArchaeologicalSite, - required=False, remote=True) year = forms.IntegerField(label=_("Year"), initial=lambda: datetime.datetime.now().year, validators=[validators.MinValueValidator(1000), @@ -845,15 +870,10 @@ class OperationFormGeneral(CustomForm, ManageOldType): if not profile.underwater: self._remove_fields(("drassm_code",)) data = kwargs.get("data", {}) or kwargs.get("initial", {}) - has_site = any(1 for k, v in data.items() if v and k.split("-")[-1] == "archaeological_site") - if not has_site and not profile.archaeological_site: - self._remove_fields(("archaeological_site",)) if 'collaborator' in self.fields: self.fields['collaborator'].widget.attrs['full-width'] = True if towns and towns != -1: self.fields['town'].choices = [('', '--')] + towns - if 'archaeological_site' in self.fields: - self.fields['archaeological_site'].label = get_current_profile().get_site_label() def clean(self): cleaned_data = self.cleaned_data diff --git a/archaeological_operations/models.py b/archaeological_operations/models.py index f8c8f20a0..63904bebe 100644 --- a/archaeological_operations/models.py +++ b/archaeological_operations/models.py @@ -759,18 +759,31 @@ class ArchaeologicalSite( base_finds__context_record__archaeological_site__pk=self.pk ) - def get_extra_actions(self, request): + def get_extra_actions(self, request, window_id=None): """ For sheet template """ # url, base_text, icon, extra_text, extra css class, is a quick action actions = super(ArchaeologicalSite, self).get_extra_actions(request) - # is_locked = self.is_locked(request.user) + is_locked = self.is_locked(request.user) profile = get_current_profile() + can_edit_site = self.can_do(request, "change_archaeologicalsite") can_add_geo = profile.mapping and self.can_do(request, "add_geovectordata") if can_add_geo: actions.append(self.get_add_geo_action()) + if can_edit_site and not is_locked: + actions += [ + ( + reverse("site-operation-relations-modify", args=[self.pk, window_id]), + _("Modify site-operation relations"), + "fa fa-retweet", + _("operations"), + "", + True, + ), + ] + can_create_operation = self.can_do(request, "change_operation") if can_create_operation and not self.operations.count(): actions.append( @@ -796,7 +809,6 @@ class ArchaeologicalSite( ) ) - can_edit_site = self.can_do(request, "change_archaeologicalsite") if can_edit_site: actions += [ ( @@ -2096,7 +2108,7 @@ class Operation( ).distinct("location", "index") return q - def get_extra_actions(self, request): + def get_extra_actions(self, request, window_id=None): """ For sheet template """ @@ -2160,6 +2172,16 @@ class Operation( True, ), ] + actions += [ + ( + reverse("operation-site-relations-modify", args=[self.pk, window_id]), + _("Modify operation-site relations"), + "fa fa-retweet", + profile.get_site_label("plural").lower(), + "", + True, + ), + ] if can_edit_operation: actions += [ diff --git a/archaeological_operations/urls.py b/archaeological_operations/urls.py index b87d6c5fd..7f5080c39 100644 --- a/archaeological_operations/urls.py +++ b/archaeological_operations/urls.py @@ -18,13 +18,17 @@ # See the file COPYING for details. from django.conf.urls import url -from django.urls import path +from django.urls import path, register_converter +from ishtar_common import urls_converters from ishtar_common.utils import check_rights from archaeological_operations import views from archaeological_operations import views_api from archaeological_operations import models + +register_converter(urls_converters.UnderscoreSlug, "uslug") + # be carreful: each check_rights must be relevant with ishtar_menu # forms @@ -311,6 +315,34 @@ urlpatterns = [ ), name="operation-relation-modify", ), + path( + "operation-site-relations-modify/<int:pk>/", + check_rights(["change_operation", "change_own_operation"])( + views.operation_site_modify_relations + ), + name="operation-site-relations-modify", + ), + path( + "operation-site-relations-modify/<int:pk>/<uslug:window_id>/", + check_rights(["change_operation", "change_own_operation"])( + views.operation_site_modify_relations + ), + name="operation-site-relations-modify", + ), + path( + "site-operation-relations-modify/<int:pk>/", + check_rights(["change_operation", "change_own_operation"])( + views.site_operation_modify_relations + ), + name="site-operation-relations-modify", + ), + path( + "site-operation-relations-modify/<int:pk>/<uslug:window_id>/", + check_rights(["change_operation", "change_own_operation"])( + views.site_operation_modify_relations + ), + name="site-operation-relations-modify", + ), url( r"^operation-qa-bulk-update/(?P<pks>[0-9-]+)?/$", check_rights(["change_operation", "change_own_operation"])( diff --git a/archaeological_operations/views.py b/archaeological_operations/views.py index 70732d04e..bf33e45b6 100644 --- a/archaeological_operations/views.py +++ b/archaeological_operations/views.py @@ -502,7 +502,37 @@ operation_modify_parcels = get_parcel_modify( RELATION_FORMSET_EXTRA_FORM = 3 -def get_relation_modify(model, model_relation, url_name, formset_class, filter_operations=False): +def _formset_get_deleted(request, data, pk_key): + new_data = dict(request.POST) + new_data = {k: new_data[k][0] for k in new_data} # convert POST to classic dict + no_values = list(range(data["form-TOTAL_FORMS"])) + deleted = {} + for k, value in list(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"): + if new_data.get(f"form-{form_number}-{pk_key}", None): + deleted[form_number] = new_data[f"form-{form_number}-{pk_key}"] + if form_number not in no_values: # put it back in no values + no_values.append(form_number) + else: + new_data.pop(k) + 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) + return new_data, deleted + + +def get_relation_modify(model, model_relation, formset_class, url_name, filter_operations=False): def _modify_relation(request, pk, current_right=None): try: item = model.objects.get(pk=pk) @@ -512,6 +542,7 @@ def get_relation_modify(model, model_relation, url_name, formset_class, filter_o if not item.is_own(request.user): raise PermissionDenied() relations = model_relation.objects.filter(left_record_id=pk).all() + form_kwargs = {"left_record": item} items, current_items = [], [] if filter_operations: @@ -539,37 +570,9 @@ def get_relation_modify(model, model_relation, url_name, formset_class, filter_o if filter_operations: data["CURRENT_ITEMS"] = 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 list(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"): - if 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) - else: - new_data.pop(k) - 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) - + new_data, deleted = _formset_get_deleted(request, data, "pk") + formset = formset_class(data=new_data, form_kwargs=form_kwargs) if formset.is_valid(): is_valid = True # delete @@ -609,7 +612,7 @@ def get_relation_modify(model, model_relation, url_name, formset_class, filter_o if is_valid: return redirect(reverse(url_name, args=[pk])) else: - formset = formset_class(initial=initial, data=data) + formset = formset_class(initial=initial, data=data, form_kwargs=form_kwargs) return render(request, 'ishtar/forms/modify_relations.html', { 'formset': formset, @@ -620,10 +623,85 @@ def get_relation_modify(model, model_relation, url_name, formset_class, filter_o operation_modify_relations = get_relation_modify( models.Operation, models.RecordRelations, - "operation-relation-modify", forms.RecordRelationsFormSet + forms.RecordRelationsFormSet, "operation-relation-modify" ) +RELATION_LIMIT = 50 + + +def operation_site_modify(model, related_model, related_key, formset_class, url_name): + + def _get_initial(q_relations): + initial = [] + current_relation_ids = q_relations.values_list("id", flat=True) + excess = current_relation_ids.count() > RELATION_LIMIT and RELATION_LIMIT + for relation_id in current_relation_ids[:RELATION_LIMIT]: + initial.append({ + "right_record": relation_id, + }) + return initial, current_relation_ids, excess + get_initial = _get_initial + + def view(request, pk, window_id=None, current_right=None): + try: + item = model.objects.get(pk=pk) + except model.DoesNotExist: + raise Http404() + if "_own_" in current_right: + if not item.is_own(request.user): + raise PermissionDenied() + q_relations = getattr(item, related_key) + initial, current_relation_ids, excess = get_initial(q_relations) + if request.method == 'POST': + formset = formset_class(data=request.POST) + if formset.is_valid(): + deleted_ids = [] + for form in formset.deleted_forms: + deleted_id = form.cleaned_data.get("right_record", None) + try: + deleted_id = int(deleted_id) + except (ValueError, TypeError): + continue + deleted_ids.append(deleted_id) + if deleted_id in current_relation_ids: + try: + del_item = related_model.objects.get(pk=deleted_id) + except related_model.DoesNotExist: + continue + q_relations.remove(del_item) + + for idx_form, data in enumerate(formset.cleaned_data): + if not data.get('right_record'): + continue + try: + new_item_id = int(data.get("right_record")) + if new_item_id in deleted_ids or new_item_id in current_relation_ids: + continue + new_item = related_model.objects.get(pk=new_item_id) + except (ValueError, TypeError, related_model.DoesNotExist): + continue + q_relations.add(new_item) + initial, __, __ = get_initial(q_relations) + formset = formset_class(initial=initial) + else: + formset = formset_class(initial=initial) + + return render(request, 'ishtar/forms/modify_relations.html', { + 'formset': formset, + "url": reverse(url_name, args=[pk]), + "window_id": window_id, + "excess": str( + _("Too many relations. Only the first {limit} items are displayed.") + ).format(limit=excess) if excess else None + }) + return view + + +operation_site_modify_relations = operation_site_modify(models.Operation, models.ArchaeologicalSite, "archaeological_sites", forms.OpeSiteRelationsFormSet, "operation-site-relations-modify") +site_operation_modify_relations = operation_site_modify(models.ArchaeologicalSite, models.Operation, "operations", forms.SiteOpeRelationsFormSet, "site-operation-relations-modify") + + # archaeological sites |