From 1b5cda648e8273dc36ab7ce47f46daed631a78e7 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Wed, 9 Feb 2022 16:02:28 +0100 Subject: [PATCH] #86 Revocation edit * adds support for revocation edit * revocation document files will be replaced on an edit --- intervention/forms/modalForms.py | 57 ++++++++++++++++++- intervention/models/intervention.py | 37 ++++++++++++ .../detail/includes/revocation.html | 7 ++- intervention/urls.py | 3 +- intervention/views.py | 32 ++++++++++- konova/forms.py | 6 +- konova/models/document.py | 16 ++++++ konova/utils/message_templates.py | 4 ++ 8 files changed, 151 insertions(+), 11 deletions(-) diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py index 6a044c7c..8039b3ae 100644 --- a/intervention/forms/modalForms.py +++ b/intervention/forms/modalForms.py @@ -6,8 +6,11 @@ Created on: 27.09.21 """ from dal import autocomplete +from django.core.exceptions import ObjectDoesNotExist +from django.db.models.fields.files import FieldFile -from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED +from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED, \ + REVOCATION_EDITED, FILE_TYPE_UNSUPPORTED, FILE_SIZE_TOO_LARGE from user.models import User, UserActionLogEntry from django.db import transaction from django import forms @@ -15,7 +18,7 @@ from django.utils.translation import gettext_lazy as _ from compensation.models import EcoAccount, EcoAccountDeduction from intervention.inputs import TextToClipboardInput -from intervention.models import Intervention, InterventionDocument +from intervention.models import Intervention, InterventionDocument, RevocationDocument from konova.forms import BaseModalForm, NewDocumentForm, RemoveModalForm from konova.utils.general import format_german_float from konova.utils.user_checks import is_default_group_only @@ -157,6 +160,7 @@ class NewRevocationModalForm(BaseModalForm): } ) ) + document_model = RevocationDocument def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -166,12 +170,61 @@ class NewRevocationModalForm(BaseModalForm): "enctype": "multipart/form-data", # important for file upload } + def is_valid(self): + super_valid = super().is_valid() + + _file = self.cleaned_data.get("file", None) + + if isinstance(_file, FieldFile): + # FieldFile declares that no new file has been uploaded and we do not need to check on the file again + return super_valid + + mime_type_valid = self.document_model.is_mime_type_valid(_file) + if not mime_type_valid: + self.add_error( + "file", + FILE_TYPE_UNSUPPORTED + ) + + file_size_valid = self.document_model.is_file_size_valid(_file) + if not file_size_valid: + self.add_error( + "file", + FILE_SIZE_TOO_LARGE + ) + + file_valid = mime_type_valid and file_size_valid + return super_valid and file_valid + def save(self): revocation = self.instance.add_revocation(self) self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_ADDED) return revocation +class EditRevocationModalForm(NewRevocationModalForm): + revocation = None + + def __init__(self, *args, **kwargs): + self.revocation = kwargs.pop("revocation", None) + super().__init__(*args, **kwargs) + try: + doc = self.revocation.document.file + except ObjectDoesNotExist: + doc = None + form_data = { + "date": str(self.revocation.date), + "file": doc, + "comment": self.revocation.comment, + } + self.load_initial_data(form_data) + + def save(self): + revocation = self.instance.edit_revocation(self) + self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_EDITED) + return revocation + + class RemoveRevocationModalForm(RemoveModalForm): """ Removing modal form for Revocation diff --git a/intervention/models/intervention.py b/intervention/models/intervention.py index e2e736d7..167c27af 100644 --- a/intervention/models/intervention.py +++ b/intervention/models/intervention.py @@ -8,6 +8,8 @@ Created on: 15.11.21 import shutil from django.contrib import messages +from django.core.exceptions import ObjectDoesNotExist +from django.db.models.fields.files import FieldFile from django.urls import reverse from django.utils import timezone @@ -202,6 +204,41 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec ) return revocation + def edit_revocation(self, form): + """ Updates a revocation of the intervention + + Args: + form (EditRevocationModalForm): The form holding the data + + Returns: + + """ + form_data = form.cleaned_data + file = form_data.get("file", None) + + revocation = form.revocation + revocation.date = form_data.get("date", None) + revocation.comment = form_data.get("comment", None) + + with transaction.atomic(): + try: + revocation.document.date_of_creation = revocation.date + revocation.document.comment = revocation.comment + if not isinstance(file, FieldFile): + revocation.document.replace_file(file) + revocation.document.save() + except ObjectDoesNotExist: + revocation.document = RevocationDocument.objects.create( + title="revocation_of_{}".format(self.identifier), + date_of_creation=revocation.date, + comment=revocation.comment, + file=file, + instance=revocation + ) + revocation.save() + + return revocation + def remove_revocation(self, form): """ Removes a revocation from the intervention diff --git a/intervention/templates/intervention/detail/includes/revocation.html b/intervention/templates/intervention/detail/includes/revocation.html index d6b07b72..6eb68f99 100644 --- a/intervention/templates/intervention/detail/includes/revocation.html +++ b/intervention/templates/intervention/detail/includes/revocation.html @@ -63,9 +63,12 @@ {{ rev.comment }} - + {% if is_default_member and has_access %} - + {% endif %} diff --git a/intervention/urls.py b/intervention/urls.py index 8ad7f31e..45985587 100644 --- a/intervention/urls.py +++ b/intervention/urls.py @@ -10,7 +10,7 @@ from django.urls import path from intervention.views import index_view, new_view, detail_view, edit_view, remove_view, new_document_view, share_view, \ create_share_view, remove_revocation_view, new_revocation_view, check_view, log_view, new_deduction_view, \ record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view, report_view, \ - remove_deduction_view, remove_compensation_view, edit_deduction_view + remove_deduction_view, remove_compensation_view, edit_deduction_view, edit_revocation_view app_name = "intervention" urlpatterns = [ @@ -42,6 +42,7 @@ urlpatterns = [ # Revocation routes path('/revocation/new', new_revocation_view, name='new-revocation'), + path('/revocation//edit', edit_revocation_view, name='edit-revocation'), path('/revocation//remove', remove_revocation_view, name='remove-revocation'), path('revocation/', get_revocation_view, name='get-doc-revocation'), ] \ No newline at end of file diff --git a/intervention/views.py b/intervention/views.py index 6db35474..90447c91 100644 --- a/intervention/views.py +++ b/intervention/views.py @@ -7,7 +7,7 @@ from django.shortcuts import render from intervention.forms.forms import NewInterventionForm, EditInterventionForm from intervention.forms.modalForms import ShareModalForm, NewRevocationModalForm, \ CheckModalForm, NewDeductionModalForm, NewInterventionDocumentForm, RemoveEcoAccountDeductionModalForm, \ - RemoveRevocationModalForm, EditEcoAccountDeductionModalForm + RemoveRevocationModalForm, EditEcoAccountDeductionModalForm, EditRevocationModalForm from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument from intervention.tables import InterventionTable from konova.contexts import BaseContext @@ -18,7 +18,7 @@ from konova.utils.documents import remove_document, get_document from konova.utils.generators import generate_qr_code from konova.utils.message_templates import INTERVENTION_INVALID, FORM_INVALID, IDENTIFIER_REPLACED, \ CHECKED_RECORDED_RESET, DEDUCTION_REMOVED, DEDUCTION_ADDED, REVOCATION_ADDED, REVOCATION_REMOVED, \ - COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, DEDUCTION_EDITED + COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, DEDUCTION_EDITED, REVOCATION_EDITED from konova.utils.user_checks import in_group @@ -331,6 +331,31 @@ def remove_view(request: HttpRequest, id: str): ) +@login_required +@default_group_required +@shared_access_required(Intervention, "id") +def edit_revocation_view(request: HttpRequest, id: str, revocation_id: str): + """ Renders a edit view for a revocation + + Args: + request (HttpRequest): The incoming request + id (str): The intervention's id as string + revocation_id (str): The revocation's id as string + + Returns: + + """ + intervention = get_object_or_404(Intervention, id=id) + revocation = get_object_or_404(Revocation, id=revocation_id) + + form = EditRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention, revocation=revocation, request=request) + return form.process_request( + request, + REVOCATION_EDITED, + redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data" + ) + + @login_required @default_group_required @shared_access_required(Intervention, "id") @@ -339,7 +364,8 @@ def remove_revocation_view(request: HttpRequest, id: str, revocation_id: str): Args: request (HttpRequest): The incoming request - id (str): The revocation's id as string + id (str): The intervention's id as string + revocation_id (str): The revocation's id as string Returns: diff --git a/konova/forms.py b/konova/forms.py index c05eb422..a842e411 100644 --- a/konova/forms.py +++ b/konova/forms.py @@ -24,7 +24,7 @@ from konova.contexts import BaseContext from konova.models import BaseObject, Geometry, RecordableObjectMixin from konova.settings import DEFAULT_SRID from konova.tasks import celery_update_parcels -from konova.utils.message_templates import FORM_INVALID +from konova.utils.message_templates import FORM_INVALID, FILE_TYPE_UNSUPPORTED, FILE_SIZE_TOO_LARGE from user.models import UserActionLogEntry @@ -424,14 +424,14 @@ class NewDocumentForm(BaseModalForm): if not mime_type_valid: self.add_error( "file", - _("Unsupported file type") + FILE_TYPE_UNSUPPORTED ) file_size_valid = self.document_model.is_file_size_valid(_file) if not file_size_valid: self.add_error( "file", - _("File too large") + FILE_SIZE_TOO_LARGE ) file_valid = mime_type_valid and file_size_valid diff --git a/konova/models/document.py b/konova/models/document.py index 72b124d3..b465d70c 100644 --- a/konova/models/document.py +++ b/konova/models/document.py @@ -101,3 +101,19 @@ class AbstractDocument(BaseResource): def is_file_size_valid(cls, _file): max_size = cls._maximum_file_size * pow(1000, 2) return _file.size <= max_size + + def replace_file(self, new_file): + """ Replaces the old file on the hard drive with the new one + + Args: + new_file (File): The new file + + Returns: + + """ + try: + os.remove(self.file.file.name) + except FileNotFoundError: + pass + self.file = new_file + self.save() \ No newline at end of file diff --git a/konova/utils/message_templates.py b/konova/utils/message_templates.py index ea778ea2..2196dd9c 100644 --- a/konova/utils/message_templates.py +++ b/konova/utils/message_templates.py @@ -18,6 +18,10 @@ MISSING_GROUP_PERMISSION = _("You need to be part of another user group.") CHECKED_RECORDED_RESET = _("Status of Checked and Recorded reseted") +# FILES +FILE_TYPE_UNSUPPORTED = _("Unsupported file type") +FILE_SIZE_TOO_LARGE = _("File too large") + # ECO ACCOUNT CANCEL_ACC_RECORDED_OR_DEDUCTED = _("Action canceled. Eco account is recorded or deductions exist. Only conservation office member can perform this action.")