summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
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
commit09264888638ba506b33f6fc08350950b4629df0a (patch)
tree26aadf9f3582344e271826f437d5c6201a85b81f
parented72a1cbbdd35f0997bec4d2cf25d4a012ea79db (diff)
downloadIshtar-09264888638ba506b33f6fc08350950b4629df0a.tar.bz2
Ishtar-09264888638ba506b33f6fc08350950b4629df0a.zip
✨ sites - datings: manage datings (forms, sheet)develop-5.0-BPC
-rw-r--r--archaeological_context_records/forms.py65
-rw-r--r--archaeological_context_records/views.py79
-rw-r--r--archaeological_finds/views.py3
-rw-r--r--archaeological_operations/models.py10
-rw-r--r--archaeological_operations/templates/ishtar/sheet_site.html14
-rw-r--r--archaeological_operations/urls.py27
-rw-r--r--archaeological_operations/views.py26
-rw-r--r--ishtar_common/forms.py75
-rw-r--r--ishtar_common/views.py76
-rw-r--r--ishtar_common/views_item.py1
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)