From bd36ab5b6f959d99dc142ec834cf364098d4bf83 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Wed, 17 Nov 2021 12:09:49 +0100 Subject: [PATCH] #35 Sanity command * refactors "toggling" of recorded/checked state * introduces mark_as_edited() for RecordableObjectMixin --- compensation/forms/forms.py | 2 + compensation/models/compensation.py | 5 +++ compensation/models/eco_account.py | 1 + compensation/tests/test_workflow.py | 2 +- compensation/views/compensation.py | 10 ++++- ema/models/ema.py | 1 + intervention/forms/forms.py | 5 +-- intervention/forms/modalForms.py | 2 +- intervention/models/intervention.py | 66 +++++++++++++++++------------ konova/forms.py | 5 ++- konova/models/object.py | 40 ++++++++--------- 11 files changed, 80 insertions(+), 59 deletions(-) diff --git a/compensation/forms/forms.py b/compensation/forms/forms.py index 85e8fd67..bf4cca2d 100644 --- a/compensation/forms/forms.py +++ b/compensation/forms/forms.py @@ -284,6 +284,8 @@ class EditCompensationForm(NewCompensationForm): self.instance.save() self.instance.log.add(action) + + intervention.mark_as_edited(user) return self.instance diff --git a/compensation/models/compensation.py b/compensation/models/compensation.py index 7808187d..b9c9499e 100644 --- a/compensation/models/compensation.py +++ b/compensation/models/compensation.py @@ -271,6 +271,11 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin): ) return docs + def add_new_action(self, form) -> CompensationAction: + super().add_new_action(form) + self.intervention.set_as_edited(form.user) + + class CompensationDocument(AbstractDocument): """ Specializes document upload for revocations with certain path diff --git a/compensation/models/eco_account.py b/compensation/models/eco_account.py index ec612bc3..dad7bfff 100644 --- a/compensation/models/eco_account.py +++ b/compensation/models/eco_account.py @@ -7,6 +7,7 @@ Created on: 16.11.21 """ import shutil +from django.contrib.auth.models import User from django.core.exceptions import ValidationError from django.core.validators import MinValueValidator from django.db import models, transaction diff --git a/compensation/tests/test_workflow.py b/compensation/tests/test_workflow.py index 28bc62d1..adf5b197 100644 --- a/compensation/tests/test_workflow.py +++ b/compensation/tests/test_workflow.py @@ -274,7 +274,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase): # Now mock the eco account as it would be recorded (with invalid data) # Make sure the deductible surface is high enough for the request - self.eco_account.toggle_recorded(self.superuser) + self.eco_account.set_recorded(self.superuser) self.eco_account.refresh_from_db() self.eco_account.deductable_surface = test_surface + 1.00 self.eco_account.save() diff --git a/compensation/views/compensation.py b/compensation/views/compensation.py index 5bef5040..b5bee06c 100644 --- a/compensation/views/compensation.py +++ b/compensation/views/compensation.py @@ -15,7 +15,8 @@ from konova.decorators import * from konova.forms import RemoveModalForm, SimpleGeomForm from konova.utils.documents import get_document, remove_document from konova.utils.generators import generate_qr_code -from konova.utils.message_templates import FORM_INVALID, IDENTIFIER_REPLACED, DATA_UNSHARED_EXPLANATION +from konova.utils.message_templates import FORM_INVALID, IDENTIFIER_REPLACED, DATA_UNSHARED_EXPLANATION, \ + CHECKED_RECORDED_RESET from konova.utils.user_checks import in_group @@ -130,8 +131,15 @@ def edit_view(request: HttpRequest, id: str): geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp) if request.method == "POST": if data_form.is_valid() and geom_form.is_valid(): + # Preserve state of intervention recorded/checked to determine whether the user must be informed or not + # about a change of the recorded/checked state + intervention_recorded = comp.intervention.recorded is not None + intervention_checked = comp.intervention.checked is not None + # The data form takes the geom form for processing, as well as the performing user comp = data_form.save(request.user, geom_form) + if intervention_recorded or intervention_checked: + messages.info(request, CHECKED_RECORDED_RESET) messages.success(request, _("Compensation {} edited").format(comp.identifier)) return redirect("compensation:detail", id=comp.id) else: diff --git a/ema/models/ema.py b/ema/models/ema.py index bc7589d0..8c55bd4b 100644 --- a/ema/models/ema.py +++ b/ema/models/ema.py @@ -7,6 +7,7 @@ Created on: 15.11.21 """ import shutil +from django.contrib.auth.models import User from django.db import models from django.db.models import QuerySet diff --git a/intervention/forms/forms.py b/intervention/forms/forms.py index fdf88403..5340dd6e 100644 --- a/intervention/forms/forms.py +++ b/intervention/forms/forms.py @@ -348,10 +348,7 @@ class EditInterventionForm(NewInterventionForm): self.instance.save() # Uncheck and unrecord intervention due to changed data - if self.instance.checked: - self.instance.set_unchecked() - if self.instance.recorded: - self.instance.set_unrecorded(user) + self.instance.mark_as_edited(user) return self.instance diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py index 26aaa6b0..daa867e9 100644 --- a/intervention/forms/modalForms.py +++ b/intervention/forms/modalForms.py @@ -225,7 +225,7 @@ class CheckModalForm(BaseModalForm): """ with transaction.atomic(): - self.instance.toggle_checked(self.user) + self.instance.set_checked(self.user) class NewDeductionModalForm(BaseModalForm): diff --git a/intervention/models/intervention.py b/intervention/models/intervention.py index 225c6bb7..8d280232 100644 --- a/intervention/models/intervention.py +++ b/intervention/models/intervention.py @@ -138,40 +138,33 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec ) return revoc_docs, regular_docs - def toggle_recorded(self, user: User): - """ Toggle the recorded state + def set_unchecked(self): + log_entry = super().set_unchecked() + self.add_log_entry_to_compensations(log_entry) - For interventions the recorded action needs to be added to their compensation objects as well + def set_checked(self, user: User) -> UserActionLogEntry: + log_entry = super().set_checked(user) + self.add_log_entry_to_compensations(log_entry) + return log_entry + + def set_unrecorded(self, user: User): + log_entry = super().set_unrecorded(user) + self.add_log_entry_to_compensations(log_entry) + + def set_recorded(self, user: User) -> UserActionLogEntry: + log_entry = super().set_recorded(user) + self.add_log_entry_to_compensations(log_entry) + return log_entry + + def add_log_entry_to_compensations(self, log_entry: UserActionLogEntry): + """ Adds the log entry to related compensations Args: - user (User): The performing user + log_entry (UserActionLogEntry): The log entry Returns: """ - log_entry = super().toggle_recorded(user) - - # Add this action to the linked compensation logs as well - comps = self.compensations.all() - for comp in comps: - comp.log.add(log_entry) - - def toggle_checked(self, user: User) -> UserActionLogEntry: - """ Toggle the checked state - - For interventions the checked action needs to be added to their compensation objects as well - - Args: - user (User): The performing user - - Returns: - - """ - log_entry = super().toggle_checked(user) - # Leave if the log_entry is None (means "unchecked") - if log_entry is None: - return - # Add this action to the linked compensation logs as well comps = self.compensations.all() for comp in comps: comp.log.add(log_entry) @@ -202,7 +195,8 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec self.log.add(edited_action) self.modified = edited_action self.save() - return pay + self.mark_as_edited(user) + return pay def add_revocation(self, form): """ Adds a new revocation to the intervention @@ -237,6 +231,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec file=form_data["file"], instance=revocation ) + self.mark_as_edited(user) return revocation def add_deduction(self, form): @@ -266,8 +261,23 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec surface=form_data["surface"], created=user_action_create, ) + self.mark_as_edited(user) return deduction + def mark_as_edited(self, performing_user: User): + """ In case the object or a related object changed, internal processes need to be started, such as + unrecord and uncheck + + Args: + performing_user (User): The user which performed the editing action + + Returns: + + """ + super().mark_as_edited(performing_user) + if self.checked: + self.set_unchecked() + class InterventionDocument(AbstractDocument): """ diff --git a/konova/forms.py b/konova/forms.py index 5e644e5c..8d6fe39d 100644 --- a/konova/forms.py +++ b/konova/forms.py @@ -485,5 +485,8 @@ class RecordModalForm(BaseModalForm): def save(self): with transaction.atomic(): if self.cleaned_data["confirm"]: - self.instance.toggle_recorded(self.user) + if self.instance.recorded: + self.instance.set_unrecorded(self.user) + else: + self.instance.set_recorded(self.user) return self.instance \ No newline at end of file diff --git a/konova/models/object.py b/konova/models/object.py index 9eeb3fbc..84cb81c0 100644 --- a/konova/models/object.py +++ b/konova/models/object.py @@ -203,6 +203,8 @@ class RecordableObjectMixin(models.Model): Returns: """ + if not self.recorded: + return None action = UserActionLogEntry.get_unrecorded_action(user) self.recorded = None self.save() @@ -218,26 +220,27 @@ class RecordableObjectMixin(models.Model): Returns: """ + if self.recorded: + return None action = UserActionLogEntry.get_recorded_action(user) self.recorded = action self.save() self.log.add(action) return action - def toggle_recorded(self, user: User) -> UserActionLogEntry: - """ Un/Record intervention + def mark_as_edited(self, performing_user: User): + """ In case the object or a related object changed, internal processes need to be started, such as + unrecord and uncheck Args: - user (User): Performing user + performing_user (User): The user which performed the editing action Returns: """ - if not self.recorded: - ret_log_entry = self.set_recorded(user) - else: - ret_log_entry = self.set_unrecorded(user) - return ret_log_entry + + if self.recorded: + self.set_unrecorded(performing_user) class CheckableObjectMixin(models.Model): @@ -262,6 +265,9 @@ class CheckableObjectMixin(models.Model): Returns: """ + if not self.checked: + # Nothing to do + return # Do not .delete() the checked attribute! Just set it to None, since a delete() would kill it out of the # log history, which is not what we want! self.checked = None @@ -277,27 +283,15 @@ class CheckableObjectMixin(models.Model): Returns: """ + if self.checked: + # Nothing to do + return action = UserActionLogEntry.get_checked_action(user) self.checked = action self.save() self.log.add(action) return action - def toggle_checked(self, user: User) -> UserActionLogEntry: - """ Un/Record intervention - - Args: - user (User): Performing user - - Returns: - - """ - if not self.checked: - ret_log_entry = self.set_checked(user) - else: - ret_log_entry = self.set_unchecked() - return ret_log_entry - class ShareableObjectMixin(models.Model): # Users having access on this object