diff options
| author | Étienne Loks <etienne.loks@iggdrasil.net> | 2026-03-31 18:45:10 +0200 |
|---|---|---|
| committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2026-03-31 18:45:10 +0200 |
| commit | 09264888638ba506b33f6fc08350950b4629df0a (patch) | |
| tree | 26aadf9f3582344e271826f437d5c6201a85b81f | |
| parent | ed72a1cbbdd35f0997bec4d2cf25d4a012ea79db (diff) | |
| download | Ishtar-09264888638ba506b33f6fc08350950b4629df0a.tar.bz2 Ishtar-09264888638ba506b33f6fc08350950b4629df0a.zip | |
✨ sites - datings: manage datings (forms, sheet)develop-5.0-BPC
| -rw-r--r-- | archaeological_context_records/forms.py | 65 | ||||
| -rw-r--r-- | archaeological_context_records/views.py | 79 | ||||
| -rw-r--r-- | archaeological_finds/views.py | 3 | ||||
| -rw-r--r-- | archaeological_operations/models.py | 10 | ||||
| -rw-r--r-- | archaeological_operations/templates/ishtar/sheet_site.html | 14 | ||||
| -rw-r--r-- | archaeological_operations/urls.py | 27 | ||||
| -rw-r--r-- | archaeological_operations/views.py | 26 | ||||
| -rw-r--r-- | ishtar_common/forms.py | 75 | ||||
| -rw-r--r-- | ishtar_common/views.py | 76 | ||||
| -rw-r--r-- | ishtar_common/views_item.py | 1 |
10 files changed, 228 insertions, 148 deletions
diff --git a/archaeological_context_records/forms.py b/archaeological_context_records/forms.py index bd59ddb8d..93ff65c25 100644 --- a/archaeological_context_records/forms.py +++ b/archaeological_context_records/forms.py @@ -898,68 +898,3 @@ class QAContextRecordFormMulti(QAForm): def _set_qa_relation_type(self, item, __): pass - - -class QADating(ManageOldType, forms.Form): - form_label = _("Dating") - associated_models = { - "dating_type_id": models.DatingType, - "quality_id": models.DatingQuality, - "period_id": models.Period, - } - pk = forms.IntegerField(required=False, widget=forms.HiddenInput) - reference = forms.CharField( - label=_("Reference"), validators=[validators.MaxLengthValidator(400)], - required=False - ) - period_id = widgets.Select2SimpleField( - label=_("Period"), required=False, modal="modal-dynamic-form" - ) - start_date = forms.IntegerField(label=_("Start date"), required=False) - end_date = forms.IntegerField(label=_("End date"), required=False) - quality_id = forms.ChoiceField(label=_("Quality"), required=False, choices=[]) - dating_type_id = forms.ChoiceField(label=_("Dating type"), required=False, choices=[]) - precise_dating = forms.CharField(label=_("Precise on this dating"), required=False, - widget=forms.Textarea) - - TYPES = [ - FieldType("dating_type_id", models.DatingType), - FieldType("quality_id", models.DatingQuality), - FieldType("period_id", models.Period), - ] - - def __init__(self, *args, **kwargs): - self.dating_model = kwargs.pop("dating_model") - self.current_item = kwargs.pop("current_item") - return super().__init__(*args, **kwargs) - - def clean(self): - data = self.cleaned_data - reference = data['reference'] - if reference: - q_attr = { - "reference": reference, - self.dating_model.CURRENT_MODEL_ATTR + "_id": self.current_item.pk - } - q = self.dating_model.objects.filter(**q_attr) - if data.get("pk", None): - q = q.exclude(pk=data["pk"]) - if q.count(): - raise forms.ValidationError( - _("This reference already exists for this item.") - ) - if any(1 for k in self.cleaned_data if self.cleaned_data[k]): - return data - raise forms.ValidationError(_("No data provided.")) - - def save(self): - data = copy(self.cleaned_data) - data[self.dating_model.CURRENT_MODEL_ATTR + "_id"] = self.current_item.pk - for attr in ['period_id', 'quality_id', 'dating_type_id']: - if not data.get(attr, None): - data[attr] = None - if data.get("pk", None): - pk = data.pop("pk") - self.dating_model.objects.filter(pk=pk).update(**data) - return - self.dating_model.objects.create(**data) diff --git a/archaeological_context_records/views.py b/archaeological_context_records/views.py index 0bc11031d..df7e83141 100644 --- a/archaeological_context_records/views.py +++ b/archaeological_context_records/views.py @@ -19,10 +19,9 @@ import json -from django.core.exceptions import PermissionDenied from django.db.models import Q from django.http import HttpResponse, HttpResponseRedirect, Http404 -from django.shortcuts import redirect, render +from django.shortcuts import redirect from django.urls import reverse from ishtar_common.utils import gettext_lazy as _ from django.views.generic import RedirectView @@ -35,6 +34,8 @@ from archaeological_operations.views import site_extra_context, get_relation_mod from archaeological_context_records import forms from ishtar_common.views import ( + get_dating_delete, + get_dating_form, IshtarMixin, LoginRequiredMixin, QAItemForm, @@ -194,58 +195,6 @@ context_record_modify_relations = get_relation_modify( filter_operations=True ) - -def get_dating_form(model, dating_model, url_name): - def _dating_form(request, pk, dating_pk=None, current_right=None): - try: - item = model.objects.get(pk=pk) - except model.DoesNotExist: - raise Http404() - # permission not provided - if not current_right: - raise PermissionDenied() - # specificaly check permission for own item, otherwise already checked - if "_own_" in current_right: - if not request.user.has_perm(current_right, item): - raise PermissionDenied() - initial = {} - if dating_pk: - try: - dating_item = dating_model.objects.get(pk=dating_pk) - initial = dict( - (attr, getattr(dating_item, attr)) - for attr in [ - "pk", "reference", "period_id", "start_date", "end_date", - "quality_id", "dating_type_id", "precise_dating"] - ) - except dating_model.DoesNotExist: - raise Http404() - if request.method == 'POST': - form = forms.QADating(request.POST, dating_model=dating_model, - current_item=item) - if form.is_valid(): - form.save() - return HttpResponseRedirect(reverse("success")) - else: - form = forms.QADating(dating_model=dating_model, - current_item=item, initial=initial) - button_name = _("Add") if not dating_pk else _("Modify") - icon = "fa fa-plus" if not dating_pk else "fa fa-pencil" - url_args = [pk] - if dating_pk: - url_args.append(dating_pk) - return render( - request, - "ishtar/forms/qa_form.html", { - "page_name": _("Dating"), - "icon": icon, - "action_name": button_name, - "form": form, - "url": reverse(url_name, args=url_args) - }) - return _dating_form - - context_record_dating_add = get_dating_form( models.ContextRecord, models.ContextRecordDating, "context-record-dating-add" ) @@ -256,28 +205,6 @@ context_record_dating_modify = get_dating_form( ) -def get_dating_delete(model, dating_model, url_name): - def _dating_form(request, dating_pk, current_right=None): - try: - dating_item = dating_model.objects.get(pk=dating_pk) - except dating_model.DoesNotExist: - raise Http404() - pk = getattr(dating_item, dating_model.CURRENT_MODEL_ATTR + "_id") - try: - item = model.objects.get(pk=pk) - except model.DoesNotExist: - raise Http404() - if "_own_" in current_right: - if not request.user.has_perm(current_right, item): - raise PermissionDenied() - elif current_right: - if not request.user.has_perm(current_right): - raise PermissionDenied() - dating_item.delete() - return HttpResponseRedirect(reverse("success")) - return _dating_form - - context_record_dating_delete = get_dating_delete( models.ContextRecord, models.ContextRecordDating, "context-record-dating-delete" ) diff --git a/archaeological_finds/views.py b/archaeological_finds/views.py index e294b4c6b..d11e820fa 100644 --- a/archaeological_finds/views.py +++ b/archaeological_finds/views.py @@ -45,6 +45,8 @@ from archaeological_finds import forms, forms_treatments from ishtar_common.views import ( get_autocomplete_generic, + get_dating_delete, + get_dating_form, IshtarMixin, LoginRequiredMixin, QAItemEditForm, @@ -61,7 +63,6 @@ from ishtar_common.views_item import ( get_autocomplete_queries, get_autocomplete_query ) -from archaeological_context_records.views import get_dating_delete, get_dating_form from archaeological_operations.wizards import AdministrativeActDeletionWizard from archaeological_finds import wizards diff --git a/archaeological_operations/models.py b/archaeological_operations/models.py index 62c0bcf93..7bf654639 100644 --- a/archaeological_operations/models.py +++ b/archaeological_operations/models.py @@ -920,6 +920,16 @@ class ArchaeologicalSite( if can_edit_site and not is_locked: actions += [ ( + reverse("site-dating-add", args=[self.pk]), + _("Add dating"), + "fa fa-plus", + _("dating"), + "", + True, + ), + ] + actions += [ + ( reverse("site-operation-relations-modify", args=[self.pk, window_id]), _("Modify site-operation relations"), "fa fa-retweet", diff --git a/archaeological_operations/templates/ishtar/sheet_site.html b/archaeological_operations/templates/ishtar/sheet_site.html index 7fda4efe0..1de4c367f 100644 --- a/archaeological_operations/templates/ishtar/sheet_site.html +++ b/archaeological_operations/templates/ishtar/sheet_site.html @@ -8,6 +8,11 @@ {% endblock %} {% block content %} +{# trick to set to null non existing variable #} +{% with permission_view_own_archaeologicalsite=permission_view_own_archaeologicalsite %} + +{% with can_change=permission_change_own_archaeologicalsite|or_:permission_change_archaeologicalsite %} +{% with dating_list=item|m2m_listing:"datings" %} {% with permission_change_own_geovectordata=permission_change_own_geovectordata %} {% with permission_change_geovectordata=permission_change_geovectordata %} @@ -52,6 +57,13 @@ {% endif %} {% field_flex_full _("Comment") item.comment "<pre>" "</pre>" %} </div> +{% if item.cached_periods or dating_list %} + <h3>{% trans "Periods / Datings" %}</h3> + {% field_flex_multiple_obj _("Periods") item 'periods' %} + {% with url_dating="site-dating" %} + {% include "ishtar/blocks/sheet_dating_list.html" %} + {% endwith %} +{% endif %} {% if item.cached_types or item.nature_of_site or item.interpretation_level or item.discovery_status or item.cached_current_states or item.cached_periods or item.cached_remains or item.cultural_attributions.count or item.discoverer %} <h3>{% trans "Scientific" %}</h3> <div class="row"> @@ -152,5 +164,5 @@ {% endif %} {% endif %} -{% endwith %} {% endwith %} {% endwith %} +{% endwith %}{% endwith %}{% endwith %}{% endwith %}{% endwith %}{% endwith %} {% endblock %} diff --git a/archaeological_operations/urls.py b/archaeological_operations/urls.py index 3082418d9..2af502f9f 100644 --- a/archaeological_operations/urls.py +++ b/archaeological_operations/urls.py @@ -424,6 +424,33 @@ urlpatterns = [ name="site-qa-link", kwargs={"model": models.ArchaeologicalSite, "url": "site-qa-link"}, ), + path( + "site-dating/<int:pk>/", + check_permissions( + ["archaeological_sites.change_archaeologicalsite", + "archaeological_sites.change_own_archaeologicalsite"])( + views.site_dating_add + ), + name="site-dating-add", + ), + path( + "site-dating/<int:pk>/<int:dating_pk>/", + check_permissions( + ["archaeological_sites.change_archaeologicalsite", + "archaeological_sites.change_own_archaeologicalsite"])( + views.site_dating_modify + ), + name="site-dating-modify", + ), + path( + "site-dating-delete/<int:dating_pk>/", + check_permissions( + ["archaeological_sites.change_archaeologicalsite", + "archaeological_sites.change_own_archaeologicalsite"])( + views.site_dating_delete + ), + name="site-dating-delete", + ), re_path( r"^site-qa-bulk-update/(?P<pks>[0-9-]+)?/$", check_permissions( diff --git a/archaeological_operations/views.py b/archaeological_operations/views.py index ecba76e05..3449290e0 100644 --- a/archaeological_operations/views.py +++ b/archaeological_operations/views.py @@ -43,7 +43,10 @@ from ishtar_common.models import ( ) from archaeological_context_records.models import ContextRecord from ishtar_common.utils import check_permissions_condition + from ishtar_common.views import ( + get_dating_delete, + get_dating_form, gen_generate_doc, QAItemEditForm, QABaseLockView, @@ -662,8 +665,27 @@ def operation_site_modify(model, related_model, related_key, formset_class, url_ 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") +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") + + +site_dating_add = get_dating_form( + models.ArchaeologicalSite, models.SiteDating, "site-dating-add" +) + + +site_dating_modify = get_dating_form( + models.ArchaeologicalSite, models.SiteDating, "site-dating-modify" +) + + +site_dating_delete = get_dating_delete( + models.ArchaeologicalSite, models.SiteDating, "site-dating-delete" +) # archaeological sites diff --git a/ishtar_common/forms.py b/ishtar_common/forms.py index 9558186bb..4db6a138b 100644 --- a/ishtar_common/forms.py +++ b/ishtar_common/forms.py @@ -21,6 +21,7 @@ Forms definition """ from collections import OrderedDict +from copy import copy import datetime from markdown import markdown import re @@ -534,6 +535,8 @@ class LockForm(object): if pk_key not in cleaned_data or not cleaned_data[pk_key]: raise forms.ValidationError(_("You should select an item.")) model = self.associated_models[pk_key] + if isinstance(model, tuple): + model = apps.get_model(*model) pks = self.cleaned_data[pk_key] pks = [pks] if isinstance(pks, int) else pks.split(",") for pk in pks: @@ -564,7 +567,10 @@ class MultiSearchForm(CustomFormSearch): @classmethod def get_current_model(cls): - return cls.associated_models[cls.pk_key] + model = cls.associated_models[cls.pk_key] + if isinstance(model, tuple): + model = apps.get_model(*model) + return model @classmethod def get_formated_datas(cls, cleaned_datas): @@ -691,6 +697,8 @@ class FieldType: def __init__(self, key, model, is_multiple=False, extra_args=None, empty_first=True, help_text=True): self.key = key + if isinstance(model, tuple): + model = apps.get_model(*model) self.model = model self.is_multiple = is_multiple self.extra_args = extra_args @@ -1560,3 +1568,68 @@ class GeoItemSelect(DocumentItemSelect): # all geo item can have documents FieldType("geodata__origin", models.GeoOriginType), FieldType("geodata__provider", models.GeoProviderType), ] + DocumentItemSelect.TYPES + + +class QADating(ManageOldType, forms.Form): + form_label = _("Dating") + associated_models = { + "dating_type_id": ("archaeological_context_records", "DatingType"), + "quality_id": ("archaeological_context_records", "DatingQuality"), + "period_id": ("archaeological_operations", "Period"), + } + pk = forms.IntegerField(required=False, widget=forms.HiddenInput) + reference = forms.CharField( + label=_("Reference"), validators=[validators.MaxLengthValidator(400)], + required=False + ) + period_id = widgets.Select2SimpleField( + label=_("Period"), required=False, modal="modal-dynamic-form" + ) + start_date = forms.IntegerField(label=_("Start date"), required=False) + end_date = forms.IntegerField(label=_("End date"), required=False) + quality_id = forms.ChoiceField(label=_("Quality"), required=False, choices=[]) + dating_type_id = forms.ChoiceField(label=_("Dating type"), required=False, choices=[]) + precise_dating = forms.CharField(label=_("Precise on this dating"), required=False, + widget=forms.Textarea) + + TYPES = [ + FieldType("dating_type_id", ("archaeological_context_records", "DatingType")), + FieldType("quality_id", ("archaeological_context_records", "DatingQuality")), + FieldType("period_id", ("archaeological_operations", "Period")), + ] + + def __init__(self, *args, **kwargs): + self.dating_model = kwargs.pop("dating_model") + self.current_item = kwargs.pop("current_item") + return super().__init__(*args, **kwargs) + + def clean(self): + data = self.cleaned_data + reference = data['reference'] + if reference: + q_attr = { + "reference": reference, + self.dating_model.CURRENT_MODEL_ATTR + "_id": self.current_item.pk + } + q = self.dating_model.objects.filter(**q_attr) + if data.get("pk", None): + q = q.exclude(pk=data["pk"]) + if q.count(): + raise forms.ValidationError( + _("This reference already exists for this item.") + ) + if any(1 for k in self.cleaned_data if self.cleaned_data[k]): + return data + raise forms.ValidationError(_("No data provided.")) + + def save(self): + data = copy(self.cleaned_data) + data[self.dating_model.CURRENT_MODEL_ATTR + "_id"] = self.current_item.pk + for attr in ['period_id', 'quality_id', 'dating_type_id']: + if not data.get(attr, None): + data[attr] = None + if data.get("pk", None): + pk = data.pop("pk") + self.dating_model.objects.filter(pk=pk).update(**data) + return + self.dating_model.objects.create(**data) diff --git a/ishtar_common/views.py b/ishtar_common/views.py index 9ffd4a4eb..4f37a0938 100644 --- a/ishtar_common/views.py +++ b/ishtar_common/views.py @@ -74,8 +74,7 @@ from archaeological_operations.models import Operation, ArchaeologicalSite from archaeological_warehouse.models import Warehouse from ishtar_common import forms_common as forms from ishtar_common import wizards -from ishtar_common.data_importer import ImporterError -from ishtar_common.forms import FinalForm, FinalDeleteForm, reverse_lazy +from ishtar_common.forms import FinalForm, FinalDeleteForm, reverse_lazy, QADating from ishtar_common.models import get_current_profile from ishtar_common.models_common import QuickAction from ishtar_common.templatetags.link_to_window import simple_link_to_window @@ -3909,3 +3908,76 @@ class GeoDeleteView(IshtarMixin, LoginRequiredMixin, DeleteView): if self.request.POST.get("back_url", None): return self.request.POST.get("back_url", None) return reverse("start") + + +def get_dating_form(model, dating_model, url_name): + def _dating_form(request, pk, dating_pk=None, current_right=None): + try: + item = model.objects.get(pk=pk) + except model.DoesNotExist: + raise Http404() + # permission not provided + if not current_right: + raise PermissionDenied() + # specificaly check permission for own item, otherwise already checked + if "_own_" in current_right: + if not request.user.has_perm(current_right, item): + raise PermissionDenied() + initial = {} + if dating_pk: + try: + dating_item = dating_model.objects.get(pk=dating_pk) + initial = dict( + (attr, getattr(dating_item, attr)) + for attr in [ + "pk", "reference", "period_id", "start_date", "end_date", + "quality_id", "dating_type_id", "precise_dating"] + ) + except dating_model.DoesNotExist: + raise Http404() + if request.method == 'POST': + form = QADating(request.POST, dating_model=dating_model, + current_item=item) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse("success")) + else: + form = QADating(dating_model=dating_model, + current_item=item, initial=initial) + button_name = _("Add") if not dating_pk else _("Modify") + icon = "fa fa-plus" if not dating_pk else "fa fa-pencil" + url_args = [pk] + if dating_pk: + url_args.append(dating_pk) + return render( + request, + "ishtar/forms/qa_form.html", { + "page_name": _("Dating"), + "icon": icon, + "action_name": button_name, + "form": form, + "url": reverse(url_name, args=url_args) + }) + return _dating_form + + +def get_dating_delete(model, dating_model, url_name): + def _dating_form(request, dating_pk, current_right=None): + try: + dating_item = dating_model.objects.get(pk=dating_pk) + except dating_model.DoesNotExist: + raise Http404() + pk = getattr(dating_item, dating_model.CURRENT_MODEL_ATTR + "_id") + try: + item = model.objects.get(pk=pk) + except model.DoesNotExist: + raise Http404() + if "_own_" in current_right: + if not request.user.has_perm(current_right, item): + raise PermissionDenied() + elif current_right: + if not request.user.has_perm(current_right): + raise PermissionDenied() + dating_item.delete() + return HttpResponseRedirect(reverse("success")) + return _dating_form diff --git a/ishtar_common/views_item.py b/ishtar_common/views_item.py index 18e3f5576..878d5361e 100644 --- a/ishtar_common/views_item.py +++ b/ishtar_common/views_item.py @@ -376,6 +376,7 @@ def modify_qa_item(model, frm, callback=None): return func + def get_default_permissions(dct): cache_key = f"{settings.PROJECT_SLUG}-default-perms" default_permissions = cache.get(cache_key) |
