summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
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
commit12e9870e2aa0659de98a9122fbc4ab16b0877449 (patch)
tree36d375089b787fbc3649ad54bb9e7e3b2b5741e0
parentdb5ecbb7b69ff668ca005bd9535bf595b79e649e (diff)
downloadIshtar-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.py80
-rw-r--r--archaeological_context_records/models.py18
-rw-r--r--archaeological_context_records/urls.py5
-rw-r--r--archaeological_context_records/views.py122
-rw-r--r--archaeological_context_records/wizards.py12
-rw-r--r--archaeological_operations/forms.py12
-rw-r--r--archaeological_operations/views.py1
-rw-r--r--ishtar_common/models.py1
-rw-r--r--ishtar_common/templates/ishtar/forms/modify_parcels.html6
-rw-r--r--ishtar_common/templates/ishtar/forms/modify_relations.html59
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">&times;</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>