Merge pull request 'master' (#113) from master into Docker
Reviewed-on: SGD-Nord/konova#113
This commit is contained in:
		
						commit
						2fcc41bf4a
					
				@ -12,15 +12,17 @@ class BaseAPIV1TestCase(BaseTestCase):
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
        cls.superuser.get_API_token()
 | 
			
		||||
        cls.superuser.api_token.is_active = True
 | 
			
		||||
        cls.superuser.api_token.save()
 | 
			
		||||
        default_group = cls.groups.get(name=DEFAULT_GROUP)
 | 
			
		||||
        cls.superuser.groups.add(default_group)
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.superuser.get_API_token()
 | 
			
		||||
        self.superuser.api_token.is_active = True
 | 
			
		||||
        self.superuser.api_token.save()
 | 
			
		||||
        default_group = self.groups.get(name=DEFAULT_GROUP)
 | 
			
		||||
        self.superuser.groups.add(default_group)
 | 
			
		||||
 | 
			
		||||
        cls.header_data = {
 | 
			
		||||
            "HTTP_ksptoken": cls.superuser.api_token.token,
 | 
			
		||||
            "HTTP_kspuser": cls.superuser.username,
 | 
			
		||||
        self.header_data = {
 | 
			
		||||
            "HTTP_ksptoken": self.superuser.api_token.token,
 | 
			
		||||
            "HTTP_kspuser": self.superuser.username,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -18,10 +18,10 @@ from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_COMPENSATION_ACTION
 | 
			
		||||
    CODELIST_COMPENSATION_ACTION_DETAIL_ID
 | 
			
		||||
from compensation.models import CompensationDocument, EcoAccountDocument
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
from konova.forms import BaseModalForm, NewDocumentForm
 | 
			
		||||
from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm
 | 
			
		||||
from konova.models import DeadlineType
 | 
			
		||||
from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, ADDED_DEADLINE, \
 | 
			
		||||
    ADDED_COMPENSATION_ACTION, PAYMENT_ADDED
 | 
			
		||||
    ADDED_COMPENSATION_ACTION, PAYMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewPaymentForm(BaseModalForm):
 | 
			
		||||
@ -100,10 +100,52 @@ class NewPaymentForm(BaseModalForm):
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        pay = self.instance.add_payment(self)
 | 
			
		||||
        self.instance.mark_as_edited(self.user, self.request, edit_comment=PAYMENT_ADDED)
 | 
			
		||||
        return pay
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EditPaymentModalForm(NewPaymentForm):
 | 
			
		||||
    """ Form handling edit for Payment
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    payment = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.payment = kwargs.pop("payment", None)
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        form_date = {
 | 
			
		||||
            "amount": self.payment.amount,
 | 
			
		||||
            "due": str(self.payment.due_on),
 | 
			
		||||
            "comment": self.payment.comment,
 | 
			
		||||
        }
 | 
			
		||||
        self.load_initial_data(form_date, disabled_fields=[])
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        payment = self.payment
 | 
			
		||||
        payment.amount = self.cleaned_data.get("amount", None)
 | 
			
		||||
        payment.due_on = self.cleaned_data.get("due", None)
 | 
			
		||||
        payment.comment = self.cleaned_data.get("comment", None)
 | 
			
		||||
        payment.save()
 | 
			
		||||
        self.instance.mark_as_edited(self.user, self.request, edit_comment=PAYMENT_EDITED)
 | 
			
		||||
        return payment
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RemovePaymentModalForm(RemoveModalForm):
 | 
			
		||||
    """ Removing modal form for Payment
 | 
			
		||||
 | 
			
		||||
    Can be used for anything, where removing shall be confirmed by the user a second time.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    payment = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        payment = kwargs.pop("payment", None)
 | 
			
		||||
        self.payment = payment
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        self.instance.remove_payment(self)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewStateModalForm(BaseModalForm):
 | 
			
		||||
    """ Form handling state related input
 | 
			
		||||
 | 
			
		||||
@ -219,6 +261,63 @@ class NewStateModalForm(BaseModalForm):
 | 
			
		||||
            raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EditCompensationStateModalForm(NewStateModalForm):
 | 
			
		||||
    state = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.state = kwargs.pop("state", None)
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        form_data = {
 | 
			
		||||
            "biotope_type": self.state.biotope_type,
 | 
			
		||||
            "biotope_extra": self.state.biotope_type_details.all(),
 | 
			
		||||
            "surface": self.state.surface,
 | 
			
		||||
        }
 | 
			
		||||
        self.load_initial_data(form_data)
 | 
			
		||||
 | 
			
		||||
    def save(self, is_before_state: bool = False):
 | 
			
		||||
        state = self.state
 | 
			
		||||
        state.biotope_type = self.cleaned_data.get("biotope_type", None)
 | 
			
		||||
        state.biotope_type_details.set(self.cleaned_data.get("biotope_extra", []))
 | 
			
		||||
        state.surface = self.cleaned_data.get("surface", None)
 | 
			
		||||
        state.save()
 | 
			
		||||
        self.instance.mark_as_edited(self.user, self.request, edit_comment=COMPENSATION_STATE_EDITED)
 | 
			
		||||
        return state
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RemoveCompensationStateModalForm(RemoveModalForm):
 | 
			
		||||
    """ Removing modal form for CompensationState
 | 
			
		||||
 | 
			
		||||
    Can be used for anything, where removing shall be confirmed by the user a second time.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    state = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        state = kwargs.pop("state", None)
 | 
			
		||||
        self.state = state
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        self.instance.remove_state(self)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RemoveCompensationActionModalForm(RemoveModalForm):
 | 
			
		||||
    """ Removing modal form for CompensationAction
 | 
			
		||||
 | 
			
		||||
    Can be used for anything, where removing shall be confirmed by the user a second time.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    action = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        action = kwargs.pop("action", None)
 | 
			
		||||
        self.action = action
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        self.instance.remove_action(self)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewDeadlineModalForm(BaseModalForm):
 | 
			
		||||
    """ Form handling deadline related input
 | 
			
		||||
 | 
			
		||||
@ -271,7 +370,29 @@ class NewDeadlineModalForm(BaseModalForm):
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        deadline = self.instance.add_deadline(self)
 | 
			
		||||
        self.instance.mark_as_edited(self.user, self.request, ADDED_DEADLINE)
 | 
			
		||||
        return deadline
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EditDeadlineModalForm(NewDeadlineModalForm):
 | 
			
		||||
    deadline = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.deadline = kwargs.pop("deadline", None)
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        form_data = {
 | 
			
		||||
            "type": self.deadline.type,
 | 
			
		||||
            "date": str(self.deadline.date),
 | 
			
		||||
            "comment": self.deadline.comment,
 | 
			
		||||
        }
 | 
			
		||||
        self.load_initial_data(form_data)
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        deadline = self.deadline
 | 
			
		||||
        deadline.type = self.cleaned_data.get("type", None)
 | 
			
		||||
        deadline.date = self.cleaned_data.get("date", None)
 | 
			
		||||
        deadline.comment = self.cleaned_data.get("comment", None)
 | 
			
		||||
        deadline.save()
 | 
			
		||||
        self.instance.mark_as_edited(self.user, self.request, edit_comment=DEADLINE_EDITED)
 | 
			
		||||
        return deadline
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -368,9 +489,36 @@ class NewActionModalForm(BaseModalForm):
 | 
			
		||||
        return action
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewCompensationDocumentForm(NewDocumentForm):
 | 
			
		||||
class EditCompensationActionModalForm(NewActionModalForm):
 | 
			
		||||
    action = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.action = kwargs.pop("action", None)
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        form_data = {
 | 
			
		||||
            "action_type": self.action.action_type,
 | 
			
		||||
            "action_type_details": self.action.action_type_details.all(),
 | 
			
		||||
            "amount": self.action.amount,
 | 
			
		||||
            "unit": self.action.unit,
 | 
			
		||||
            "comment": self.action.comment,
 | 
			
		||||
        }
 | 
			
		||||
        self.load_initial_data(form_data)
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        action = self.action
 | 
			
		||||
        action.action_type = self.cleaned_data.get("action_type", None)
 | 
			
		||||
        action.action_type_details.set(self.cleaned_data.get("action_type_details", []))
 | 
			
		||||
        action.amount = self.cleaned_data.get("amount", None)
 | 
			
		||||
        action.unit = self.cleaned_data.get("unit", None)
 | 
			
		||||
        action.comment = self.cleaned_data.get("comment", None)
 | 
			
		||||
        action.save()
 | 
			
		||||
        self.instance.mark_as_edited(self.user, self.request, edit_comment=COMPENSATION_ACTION_EDITED)
 | 
			
		||||
        return action
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewCompensationDocumentModalForm(NewDocumentModalForm):
 | 
			
		||||
    document_model = CompensationDocument
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewEcoAccountDocumentForm(NewDocumentForm):
 | 
			
		||||
class NewEcoAccountDocumentModalForm(NewDocumentModalForm):
 | 
			
		||||
    document_model = EcoAccountDocument
 | 
			
		||||
@ -12,6 +12,7 @@ from codelist.models import KonovaCode
 | 
			
		||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_DETAIL_ID
 | 
			
		||||
from compensation.managers import CompensationActionManager
 | 
			
		||||
from konova.models import BaseResource
 | 
			
		||||
from konova.utils.message_templates import COMPENSATION_ACTION_REMOVED
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UnitChoices(models.TextChoices):
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,8 @@ from konova.models import BaseObject, AbstractDocument, Deadline, generate_docum
 | 
			
		||||
    GeoReferencedMixin
 | 
			
		||||
from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
 | 
			
		||||
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, COMPENSATION_REMOVED_TEMPLATE, \
 | 
			
		||||
    DOCUMENT_REMOVED_TEMPLATE
 | 
			
		||||
    DOCUMENT_REMOVED_TEMPLATE, COMPENSATION_EDITED_TEMPLATE, DEADLINE_REMOVED, ADDED_DEADLINE, \
 | 
			
		||||
    COMPENSATION_ACTION_REMOVED, COMPENSATION_STATE_REMOVED, INTERVENTION_HAS_REVOCATIONS_TEMPLATE
 | 
			
		||||
from user.models import UserActionLogEntry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -61,7 +62,6 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
 | 
			
		||||
        user = form.user
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            created_action = UserActionLogEntry.get_created_action(user)
 | 
			
		||||
            edited_action = UserActionLogEntry.get_edited_action(user, _("Added deadline"))
 | 
			
		||||
 | 
			
		||||
            deadline = Deadline.objects.create(
 | 
			
		||||
                type=form_data["type"],
 | 
			
		||||
@ -70,12 +70,26 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
 | 
			
		||||
                created=created_action,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            self.modified = edited_action
 | 
			
		||||
            self.save()
 | 
			
		||||
            self.log.add(edited_action)
 | 
			
		||||
            self.deadlines.add(deadline)
 | 
			
		||||
            self.mark_as_edited(user, edit_comment=ADDED_DEADLINE)
 | 
			
		||||
            return deadline
 | 
			
		||||
 | 
			
		||||
    def remove_deadline(self, form):
 | 
			
		||||
        """ Removes a deadline from the abstract compensation
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            form (RemoveDeadlineModalForm): The form holding all relevant data
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        deadline = form.deadline
 | 
			
		||||
        user = form.user
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            deadline.delete()
 | 
			
		||||
            self.mark_as_edited(user, edit_comment=DEADLINE_REMOVED)
 | 
			
		||||
 | 
			
		||||
    def add_action(self, form) -> CompensationAction:
 | 
			
		||||
        """ Adds a new action to the compensation
 | 
			
		||||
 | 
			
		||||
@ -101,6 +115,21 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
 | 
			
		||||
            self.actions.add(comp_action)
 | 
			
		||||
            return comp_action
 | 
			
		||||
 | 
			
		||||
    def remove_action(self, form):
 | 
			
		||||
        """ Removes a CompensationAction from the abstract compensation
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            form (RemoveCompensationActionModalForm): The form holding all relevant data
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        action = form.action
 | 
			
		||||
        user = form.user
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            action.delete()
 | 
			
		||||
            self.mark_as_edited(user, edit_comment=COMPENSATION_ACTION_REMOVED)
 | 
			
		||||
 | 
			
		||||
    def add_state(self, form, is_before_state: bool) -> CompensationState:
 | 
			
		||||
        """ Adds a new compensation state to the compensation
 | 
			
		||||
 | 
			
		||||
@ -125,6 +154,21 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
 | 
			
		||||
                self.after_states.add(state)
 | 
			
		||||
            return state
 | 
			
		||||
 | 
			
		||||
    def remove_state(self, form):
 | 
			
		||||
        """ Removes a CompensationState from the abstract compensation
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            form (RemoveCompensationStateModalForm): The form holding all relevant data
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        state = form.state
 | 
			
		||||
        user = form.user
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            state.delete()
 | 
			
		||||
            self.mark_as_edited(user, edit_comment=COMPENSATION_STATE_REMOVED)
 | 
			
		||||
 | 
			
		||||
    def get_surface_after_states(self) -> float:
 | 
			
		||||
        """ Calculates the compensation's/account's surface
 | 
			
		||||
 | 
			
		||||
@ -287,28 +331,6 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
 | 
			
		||||
        """
 | 
			
		||||
        return self.intervention.users.all()
 | 
			
		||||
 | 
			
		||||
    def get_LANIS_link(self) -> str:
 | 
			
		||||
        """ Generates a link for LANIS depending on the geometry
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            geom = self.geometry.geom.transform(DEFAULT_SRID_RLP, clone=True)
 | 
			
		||||
            x = geom.centroid.x
 | 
			
		||||
            y = geom.centroid.y
 | 
			
		||||
            zoom_lvl = 16
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            # If no geometry has been added, yet.
 | 
			
		||||
            x = 1
 | 
			
		||||
            y = 1
 | 
			
		||||
            zoom_lvl = 6
 | 
			
		||||
        return LANIS_LINK_TEMPLATE.format(
 | 
			
		||||
            zoom_lvl,
 | 
			
		||||
            x,
 | 
			
		||||
            y,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def get_documents(self) -> QuerySet:
 | 
			
		||||
        """ Getter for all documents of a compensation
 | 
			
		||||
 | 
			
		||||
@ -332,7 +354,9 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        return self.intervention.mark_as_edited(user, request, edit_comment, reset_recorded)
 | 
			
		||||
        self.intervention.unrecord(user, request)
 | 
			
		||||
        action = super().mark_as_edited(user, edit_comment=edit_comment)
 | 
			
		||||
        return action
 | 
			
		||||
 | 
			
		||||
    def is_ready_for_publish(self) -> bool:
 | 
			
		||||
        """ Not inherited by RecordableObjectMixin
 | 
			
		||||
@ -344,6 +368,26 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
 | 
			
		||||
        """
 | 
			
		||||
        return self.intervention.is_ready_for_publish()
 | 
			
		||||
 | 
			
		||||
    def set_status_messages(self, request: HttpRequest):
 | 
			
		||||
        """ Setter for different information that need to be rendered
 | 
			
		||||
 | 
			
		||||
        Adds messages to the given HttpRequest
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            request (HttpRequest): The incoming request
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            request (HttpRequest): The modified request
 | 
			
		||||
        """
 | 
			
		||||
        if self.intervention.legal.revocations.exists():
 | 
			
		||||
            messages.error(
 | 
			
		||||
                request,
 | 
			
		||||
                INTERVENTION_HAS_REVOCATIONS_TEMPLATE.format(self.intervention.legal.revocations.count()),
 | 
			
		||||
                extra_tags="danger",
 | 
			
		||||
            )
 | 
			
		||||
        super().set_status_messages(request)
 | 
			
		||||
        return request
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CompensationDocument(AbstractDocument):
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
@ -123,28 +123,6 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
 | 
			
		||||
 | 
			
		||||
        return ret_val_total, ret_val_relative
 | 
			
		||||
 | 
			
		||||
    def get_LANIS_link(self) -> str:
 | 
			
		||||
        """ Generates a link for LANIS depending on the geometry
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            geom = self.geometry.geom.transform(DEFAULT_SRID_RLP, clone=True)
 | 
			
		||||
            x = geom.centroid.x
 | 
			
		||||
            y = geom.centroid.y
 | 
			
		||||
            zoom_lvl = 16
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            # If no geometry has been added, yet.
 | 
			
		||||
            x = 1
 | 
			
		||||
            y = 1
 | 
			
		||||
            zoom_lvl = 6
 | 
			
		||||
        return LANIS_LINK_TEMPLATE.format(
 | 
			
		||||
            zoom_lvl,
 | 
			
		||||
            x,
 | 
			
		||||
            y,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def quality_check(self) -> EcoAccountQualityChecker:
 | 
			
		||||
        """ Quality check
 | 
			
		||||
 | 
			
		||||
@ -273,5 +251,5 @@ class EcoAccountDeduction(BaseResource):
 | 
			
		||||
    def delete(self, user=None, *args, **kwargs):
 | 
			
		||||
        if user is not None:
 | 
			
		||||
            self.intervention.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED)
 | 
			
		||||
            self.account.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED, reset_recorded=False)
 | 
			
		||||
            self.account.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED)
 | 
			
		||||
        super().delete(*args, **kwargs)
 | 
			
		||||
@ -37,8 +37,3 @@ class Payment(BaseResource):
 | 
			
		||||
        ordering = [
 | 
			
		||||
            "-amount",
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def delete(self, user=None, *args, **kwargs):
 | 
			
		||||
        if user is not None:
 | 
			
		||||
            self.intervention.mark_as_edited(user, edit_comment=PAYMENT_REMOVED)
 | 
			
		||||
        super().delete(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
@ -6,11 +6,13 @@ Created on: 16.11.21
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.db.models import Q
 | 
			
		||||
 | 
			
		||||
from codelist.models import KonovaCode
 | 
			
		||||
from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID
 | 
			
		||||
from compensation.managers import CompensationStateManager
 | 
			
		||||
from konova.models import UuidModel
 | 
			
		||||
from konova.utils.message_templates import COMPENSATION_STATE_REMOVED
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CompensationState(UuidModel):
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,11 @@ class CompensationTable(BaseTable, TableRenderMixin):
 | 
			
		||||
        orderable=True,
 | 
			
		||||
        accessor="title",
 | 
			
		||||
    )
 | 
			
		||||
    d = tables.Column(
 | 
			
		||||
        verbose_name=_("Parcel gmrkng"),
 | 
			
		||||
        orderable=True,
 | 
			
		||||
        accessor="geometry",
 | 
			
		||||
    )
 | 
			
		||||
    c = tables.Column(
 | 
			
		||||
        verbose_name=_("Checked"),
 | 
			
		||||
        orderable=True,
 | 
			
		||||
@ -80,14 +85,17 @@ class CompensationTable(BaseTable, TableRenderMixin):
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        html = ""
 | 
			
		||||
        html += self.render_link(
 | 
			
		||||
            tooltip=_("Open {}").format(_("Compensation")),
 | 
			
		||||
            href=reverse("compensation:detail", args=(record.id,)),
 | 
			
		||||
            txt=value,
 | 
			
		||||
            new_tab=False,
 | 
			
		||||
        context = {
 | 
			
		||||
            "tooltip": _("Open {}").format(_("Intervention")),
 | 
			
		||||
            "content": value,
 | 
			
		||||
            "url": reverse("compensation:detail", args=(record.id,)),
 | 
			
		||||
            "has_revocations": record.intervention.legal.revocations.exists()
 | 
			
		||||
        }
 | 
			
		||||
        html = render_to_string(
 | 
			
		||||
            "table/revocation_warning_col.html",
 | 
			
		||||
            context
 | 
			
		||||
        )
 | 
			
		||||
        return format_html(html)
 | 
			
		||||
        return html
 | 
			
		||||
 | 
			
		||||
    def render_c(self, value, record: Compensation):
 | 
			
		||||
        """ Renders the checked column for a compensation
 | 
			
		||||
@ -115,6 +123,28 @@ class CompensationTable(BaseTable, TableRenderMixin):
 | 
			
		||||
        )
 | 
			
		||||
        return format_html(html)
 | 
			
		||||
 | 
			
		||||
    def render_d(self, value, record: Compensation):
 | 
			
		||||
        """ Renders the parcel district column for a compensation
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            value (str): The geometry
 | 
			
		||||
            record (Compensation): The compensation record
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        parcels = value.get_underlying_parcels().values_list(
 | 
			
		||||
            "gmrkng",
 | 
			
		||||
            flat=True
 | 
			
		||||
        ).distinct()
 | 
			
		||||
        html = render_to_string(
 | 
			
		||||
            "table/gmrkng_col.html",
 | 
			
		||||
            {
 | 
			
		||||
                "entries": parcels
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
        return html
 | 
			
		||||
 | 
			
		||||
    def render_r(self, value, record: Compensation):
 | 
			
		||||
        """ Renders the registered column for a compensation
 | 
			
		||||
 | 
			
		||||
@ -173,10 +203,20 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
 | 
			
		||||
        orderable=True,
 | 
			
		||||
        accessor="title",
 | 
			
		||||
    )
 | 
			
		||||
    d = tables.Column(
 | 
			
		||||
        verbose_name=_("Parcel gmrkng"),
 | 
			
		||||
        orderable=True,
 | 
			
		||||
        accessor="geometry",
 | 
			
		||||
    )
 | 
			
		||||
    av = tables.Column(
 | 
			
		||||
        verbose_name=_("Available"),
 | 
			
		||||
        orderable=True,
 | 
			
		||||
        empty_values=[],
 | 
			
		||||
        attrs={
 | 
			
		||||
            "th": {
 | 
			
		||||
                "class": "w-20",
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    r = tables.Column(
 | 
			
		||||
        verbose_name=_("Recorded"),
 | 
			
		||||
@ -244,6 +284,28 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
 | 
			
		||||
        html = render_to_string("konova/widgets/progressbar.html", {"value": value_relative})
 | 
			
		||||
        return format_html(html)
 | 
			
		||||
 | 
			
		||||
    def render_d(self, value, record: Compensation):
 | 
			
		||||
        """ Renders the parcel district column for a compensation
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            value (str): The geometry
 | 
			
		||||
            record (Compensation): The compensation record
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        parcels = value.get_underlying_parcels().values_list(
 | 
			
		||||
            "gmrkng",
 | 
			
		||||
            flat=True
 | 
			
		||||
        ).distinct()
 | 
			
		||||
        html = render_to_string(
 | 
			
		||||
            "table/gmrkng_col.html",
 | 
			
		||||
            {
 | 
			
		||||
                "entries": parcels
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
        return html
 | 
			
		||||
 | 
			
		||||
    def render_r(self, value, record: EcoAccount):
 | 
			
		||||
        """ Renders the recorded column for an eco account
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,7 @@
 | 
			
		||||
                    {% trans 'Comment' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                {% if is_default_member and has_access %}
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                <th class="w-10" scope="col">
 | 
			
		||||
                    <span class="float-right">
 | 
			
		||||
                        {% trans 'Action' %}
 | 
			
		||||
                    </span>
 | 
			
		||||
@ -61,9 +61,12 @@
 | 
			
		||||
                        {{ action.comment }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="">
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:action-remove' obj.id action.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove action' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:action-edit' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit action' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:action-remove' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove action' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@
 | 
			
		||||
                    {% trans 'Comment' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                {% if is_default_member and has_access %}
 | 
			
		||||
                    <th scope="col">
 | 
			
		||||
                    <th class="w-10" scope="col">
 | 
			
		||||
                        <span class="float-right">
 | 
			
		||||
                            {% trans 'Action' %}
 | 
			
		||||
                        </span>
 | 
			
		||||
@ -54,9 +54,12 @@
 | 
			
		||||
                        {{ deadline.comment }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td>
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:deadline-remove' obj.id deadline.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove deadline' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:deadline-edit' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit deadline' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:deadline-remove' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove deadline' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -27,11 +27,14 @@
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                    {% trans 'Title' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                    {% trans 'Created on' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                    {% trans 'Comment' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                {% if is_default_member and has_access %}
 | 
			
		||||
                    <th scope="col">
 | 
			
		||||
                    <th class="w-10" scope="col">
 | 
			
		||||
                        <span class="float-right">
 | 
			
		||||
                            {% trans 'Action' %}
 | 
			
		||||
                        </span>
 | 
			
		||||
@ -43,18 +46,24 @@
 | 
			
		||||
            {% for doc in obj.documents.all %}
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                    <a href="{% url 'compensation:get-doc' doc.id %}">
 | 
			
		||||
                    <a href="{% url 'compensation:get-doc' obj.id doc.id %}">
 | 
			
		||||
                        {{ doc.title }}
 | 
			
		||||
                    </a>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                    {{ doc.date_of_creation }}
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                    <div class="scroll-150">
 | 
			
		||||
                        {{ doc.comment }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:remove-doc' doc.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove document' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:edit-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit document' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:remove-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove document' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,7 @@
 | 
			
		||||
                    {% trans 'Surface' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                {% if is_default_member and has_access %}
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                <th class="w-10" scope="col">
 | 
			
		||||
                    <span class="float-right">
 | 
			
		||||
                        {% trans 'Action' %}
 | 
			
		||||
                    </span>
 | 
			
		||||
@ -57,9 +57,12 @@
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </td>
 | 
			
		||||
                <td>{{ state.surface|floatformat:2 }} m²</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:state-remove' obj.id state.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove state' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:state-remove' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove state' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,7 @@
 | 
			
		||||
                    {% trans 'Surface' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                {% if is_default_member and has_access %}
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                <th class="w-10" scope="col">
 | 
			
		||||
                    <span class="float-right">
 | 
			
		||||
                        {% trans 'Action' %}
 | 
			
		||||
                    </span>
 | 
			
		||||
@ -57,9 +57,12 @@
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </td>
 | 
			
		||||
                <td>{{ state.surface|floatformat:2 }} m²</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:state-remove' obj.id state.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove state' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:state-remove' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove state' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@
 | 
			
		||||
                    {% trans 'Comment' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                {% if is_default_member and has_access %}
 | 
			
		||||
                    <th scope="col">
 | 
			
		||||
                    <th class="w-10" scope="col">
 | 
			
		||||
                        <span class="float-right">
 | 
			
		||||
                            {% trans 'Action' %}
 | 
			
		||||
                        </span>
 | 
			
		||||
@ -60,9 +60,12 @@
 | 
			
		||||
                        {{ action.comment }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="">
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:action-remove' obj.id action.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove action' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:action-edit' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit action' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:action-remove' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove action' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                    {% trans 'Comment' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                <th class="w-10" scope="col">
 | 
			
		||||
                    <span class="float-right">
 | 
			
		||||
                        {% trans 'Action' %}
 | 
			
		||||
                    </span>
 | 
			
		||||
@ -52,9 +52,12 @@
 | 
			
		||||
                        {{ deadline.comment }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:deadline-remove' obj.id deadline.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove deadline' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:deadline-edit' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit deadline' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:deadline-remove' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove deadline' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,7 @@
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                    {% trans 'Created' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                <th class="w-10" scope="col">
 | 
			
		||||
                    <span class="float-right">
 | 
			
		||||
                        {% trans 'Action' %}
 | 
			
		||||
                    </span>
 | 
			
		||||
@ -60,9 +60,12 @@
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="align-middle">{{ deduction.surface|floatformat:2|intcomma }} m²</td>
 | 
			
		||||
                <td class="align-middle">{{ deduction.created.timestamp|default_if_none:""|naturalday}}</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:remove-deduction' deduction.account.id deduction.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove Deduction' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:edit-deduction' deduction.account.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit Deduction' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:remove-deduction' deduction.account.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove Deduction' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -28,9 +28,12 @@
 | 
			
		||||
                    {% trans 'Title' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                    {% trans 'Comment' %}
 | 
			
		||||
                    {% trans 'Created on' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                    {% trans 'Comment' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                <th class="w-10" scope="col">
 | 
			
		||||
                    <span class="float-right">
 | 
			
		||||
                        {% trans 'Action' %}
 | 
			
		||||
                    </span>
 | 
			
		||||
@ -41,18 +44,24 @@
 | 
			
		||||
            {% for doc in obj.documents.all %}
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                    <a href="{% url 'compensation:acc:get-doc' doc.id %}">
 | 
			
		||||
                    <a href="{% url 'compensation:acc:get-doc' obj.id doc.id %}">
 | 
			
		||||
                        {{ doc.title }}
 | 
			
		||||
                    </a>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                    {{ doc.date_of_creation }}
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                    <div class="scroll-150">
 | 
			
		||||
                        {{ doc.comment }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:remove-doc' doc.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove document' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:edit-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit document' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:remove-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove document' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,7 @@
 | 
			
		||||
                    {% trans 'Surface' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                {% if is_default_member and has_access %}
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                <th class="w-10" scope="col">
 | 
			
		||||
                    <span class="float-right">
 | 
			
		||||
                        {% trans 'Action' %}
 | 
			
		||||
                    </span>
 | 
			
		||||
@ -57,9 +57,12 @@
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </td>
 | 
			
		||||
                <td>{{ state.surface|floatformat:2 }} m²</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:state-remove' obj.id state.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove state' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:state-remove' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove state' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,7 @@
 | 
			
		||||
                    {% trans 'Surface' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                {% if is_default_member and has_access %}
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                <th class="w-10" scope="col">
 | 
			
		||||
                    <span class="float-right">
 | 
			
		||||
                        {% trans 'Action' %}
 | 
			
		||||
                    </span>
 | 
			
		||||
@ -57,9 +57,12 @@
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </td>
 | 
			
		||||
                <td>{{ state.surface|floatformat:2 }} m²</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:state-remove' obj.id state.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove state' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:acc:state-remove' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove state' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										7
									
								
								compensation/tests/compensation/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								compensation/tests/compensation/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 07.02.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										248
									
								
								compensation/tests/compensation/test_views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								compensation/tests/compensation/test_views.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,248 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 07.02.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.test.client import Client
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from konova.models import Deadline, DeadlineType
 | 
			
		||||
from konova.settings import DEFAULT_GROUP
 | 
			
		||||
from konova.tests.test_views import BaseViewTestCase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CompensationViewTestCase(BaseViewTestCase):
 | 
			
		||||
    """
 | 
			
		||||
    These tests focus on proper returned views depending on the user's groups privileges and login status
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls) -> None:
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
        
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        state = self.create_dummy_states()
 | 
			
		||||
        self.compensation.before_states.set([state])
 | 
			
		||||
        self.compensation.after_states.set([state])
 | 
			
		||||
 | 
			
		||||
        action = self.create_dummy_action()
 | 
			
		||||
        self.compensation.actions.set([action])
 | 
			
		||||
 | 
			
		||||
        self.deadline = Deadline.objects.get_or_create(
 | 
			
		||||
            type=DeadlineType.FINISHED,
 | 
			
		||||
            date="2020-01-01",
 | 
			
		||||
            comment="TESTDEADDLINECOMMENT"
 | 
			
		||||
        )[0]
 | 
			
		||||
        self.compensation.deadlines.add(self.deadline)
 | 
			
		||||
 | 
			
		||||
        # Prepare urls
 | 
			
		||||
        self.index_url = reverse("compensation:index", args=())
 | 
			
		||||
        self.new_url = reverse("compensation:new", args=(self.intervention.id,))
 | 
			
		||||
        self.new_id_url = reverse("compensation:new-id", args=())
 | 
			
		||||
        self.detail_url = reverse("compensation:detail", args=(self.compensation.id,))
 | 
			
		||||
        self.log_url = reverse("compensation:log", args=(self.compensation.id,))
 | 
			
		||||
        self.edit_url = reverse("compensation:edit", args=(self.compensation.id,))
 | 
			
		||||
        self.remove_url = reverse("compensation:remove", args=(self.compensation.id,))
 | 
			
		||||
        self.report_url = reverse("compensation:report", args=(self.compensation.id,))
 | 
			
		||||
        self.state_new_url = reverse("compensation:new-state", args=(self.compensation.id,))
 | 
			
		||||
        self.action_new_url = reverse("compensation:new-action", args=(self.compensation.id,))
 | 
			
		||||
        self.deadline_new_url = reverse("compensation:new-deadline", args=(self.compensation.id,))
 | 
			
		||||
        self.deadline_edit_url = reverse("compensation:deadline-edit", args=(self.compensation.id, self.deadline.id))
 | 
			
		||||
        self.deadline_remove_url = reverse("compensation:deadline-remove", args=(self.compensation.id, self.deadline.id))
 | 
			
		||||
        self.new_doc_url = reverse("compensation:new-doc", args=(self.compensation.id,))
 | 
			
		||||
 | 
			
		||||
        self.state_remove_url = reverse("compensation:state-remove", args=(self.compensation.id, self.comp_state.id,))
 | 
			
		||||
        self.action_remove_url = reverse("compensation:action-remove", args=(self.compensation.id, self.comp_action.id,))
 | 
			
		||||
 | 
			
		||||
    def test_anonymous_user(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User not logged in
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.report_url,
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.deadline_edit_url,
 | 
			
		||||
            self.deadline_remove_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_no_groups_shared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in and has no groups and data is shared
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        self.superuser.groups.set([])
 | 
			
		||||
        self.intervention.share_with_list([self.superuser])
 | 
			
		||||
 | 
			
		||||
        # Since the user has no groups, it does not matter that data has been shared. There SHOULD not be any difference
 | 
			
		||||
        # to a user without access, since the important permissions are missing
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.report_url,
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.deadline_edit_url,
 | 
			
		||||
            self.deadline_remove_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_no_groups_unshared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in and has no groups and data is not shared
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        self.superuser.groups.set([])
 | 
			
		||||
        # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
 | 
			
		||||
        self.intervention.share_with_list([])
 | 
			
		||||
 | 
			
		||||
        # Since the user has no groups, it does not matter that data is unshared. There SHOULD not be any difference
 | 
			
		||||
        # to a user having shared access, since all important permissions are missing
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.report_url,
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.deadline_edit_url,
 | 
			
		||||
            self.deadline_remove_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_default_group_shared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in, is default group member and data is shared
 | 
			
		||||
        --> Default group necessary since all base functionalities depend on this group membership
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        group = self.groups.get(name=DEFAULT_GROUP)
 | 
			
		||||
        self.superuser.groups.set([group])
 | 
			
		||||
        # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
 | 
			
		||||
        self.intervention.share_with_list([self.superuser])
 | 
			
		||||
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.report_url,
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.deadline_edit_url,
 | 
			
		||||
            self.deadline_remove_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_default_group_unshared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in, is default group member and data is NOT shared
 | 
			
		||||
        --> Default group necessary since all base functionalities depend on this group membership
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        group = self.groups.get(name=DEFAULT_GROUP)
 | 
			
		||||
        self.superuser.groups.set([group])
 | 
			
		||||
        # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
 | 
			
		||||
        self.intervention.share_with_list([])
 | 
			
		||||
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.report_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.deadline_edit_url,
 | 
			
		||||
            self.deadline_remove_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 11.11.21
 | 
			
		||||
Created on: 07.02.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
import datetime
 | 
			
		||||
@ -11,7 +11,7 @@ from django.contrib.gis.geos import MultiPolygon
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from compensation.models import Compensation
 | 
			
		||||
from konova.settings import ETS_GROUP, ZB_GROUP
 | 
			
		||||
from konova.settings import ZB_GROUP, ETS_GROUP
 | 
			
		||||
from konova.tests.test_views import BaseWorkflowTestCase
 | 
			
		||||
from user.models import UserAction
 | 
			
		||||
 | 
			
		||||
@ -21,17 +21,18 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
        # Give the user shared access to the dummy intervention -> inherits the access to the compensation
 | 
			
		||||
        cls.intervention.share_with(cls.superuser)
 | 
			
		||||
 | 
			
		||||
        # Make sure the intervention itself would be fine with valid data
 | 
			
		||||
        cls.intervention = cls.fill_out_intervention(cls.intervention)
 | 
			
		||||
 | 
			
		||||
        # Make sure the compensation is linked to the intervention
 | 
			
		||||
        cls.intervention.compensations.set([cls.compensation])
 | 
			
		||||
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        super().setUp()
 | 
			
		||||
 | 
			
		||||
        # Give the user shared access to the dummy intervention -> inherits the access to the compensation
 | 
			
		||||
        self.intervention.share_with(self.superuser)
 | 
			
		||||
 | 
			
		||||
        # Make sure the intervention itself would be fine with valid data
 | 
			
		||||
        self.intervention = self.fill_out_intervention(self.intervention)
 | 
			
		||||
 | 
			
		||||
        # Make sure the compensation is linked to the intervention
 | 
			
		||||
        self.intervention.compensations.set([self.compensation])
 | 
			
		||||
        
 | 
			
		||||
        # Delete all existing compensations, which might be created by tests
 | 
			
		||||
        Compensation.objects.all().delete()
 | 
			
		||||
 | 
			
		||||
@ -55,6 +56,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
            "geom": test_geom.geojson,
 | 
			
		||||
            "intervention": self.intervention.id,
 | 
			
		||||
        }
 | 
			
		||||
        pre_creation_intervention_log_count = self.intervention.log.count()
 | 
			
		||||
 | 
			
		||||
        # Preserve the current number of intervention's compensations
 | 
			
		||||
        num_compensations = self.intervention.compensations.count()
 | 
			
		||||
@ -66,6 +68,13 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
        self.assertEqual(new_compensation.identifier, test_id)
 | 
			
		||||
        self.assertEqual(new_compensation.title, test_title)
 | 
			
		||||
        self.assert_equal_geometries(new_compensation.geometry.geom, test_geom)
 | 
			
		||||
        self.assertEqual(new_compensation.log.count(), 1)
 | 
			
		||||
 | 
			
		||||
        # Expect logs to be set
 | 
			
		||||
        self.assertEqual(pre_creation_intervention_log_count + 1, self.intervention.log.count())
 | 
			
		||||
        self.assertEqual(new_compensation.log.count(), 1)
 | 
			
		||||
        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
 | 
			
		||||
        self.assertEqual(new_compensation.log.first().action, UserAction.CREATED)
 | 
			
		||||
 | 
			
		||||
    def test_new_from_intervention(self):
 | 
			
		||||
        """ Test the creation of a compensation from a given intervention
 | 
			
		||||
@ -83,6 +92,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
            "title": test_title,
 | 
			
		||||
            "geom": test_geom.geojson,
 | 
			
		||||
        }
 | 
			
		||||
        pre_creation_intervention_log_count = self.intervention.log.count()
 | 
			
		||||
 | 
			
		||||
        # Preserve the current number of intervention's compensations
 | 
			
		||||
        num_compensations = self.intervention.compensations.count()
 | 
			
		||||
@ -95,6 +105,12 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
        self.assertEqual(new_compensation.title, test_title)
 | 
			
		||||
        self.assert_equal_geometries(new_compensation.geometry.geom, test_geom)
 | 
			
		||||
 | 
			
		||||
        # Expect logs to be set
 | 
			
		||||
        self.assertEqual(new_compensation.log.count(), 1)
 | 
			
		||||
        self.assertEqual(new_compensation.log.first().action, UserAction.CREATED)
 | 
			
		||||
        self.assertEqual(pre_creation_intervention_log_count + 1, self.intervention.log.count())
 | 
			
		||||
        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
 | 
			
		||||
 | 
			
		||||
    def test_edit(self):
 | 
			
		||||
        """ Checks that the editing of a compensation works
 | 
			
		||||
 | 
			
		||||
@ -103,6 +119,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
        """
 | 
			
		||||
        url = reverse("compensation:edit", args=(self.compensation.id,))
 | 
			
		||||
        self.compensation = self.fill_out_compensation(self.compensation)
 | 
			
		||||
        pre_edit_log_count = self.compensation.log.count()
 | 
			
		||||
 | 
			
		||||
        new_title = self.create_dummy_string()
 | 
			
		||||
        new_identifier = self.create_dummy_string()
 | 
			
		||||
@ -138,6 +155,10 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
 | 
			
		||||
        self.assert_equal_geometries(self.compensation.geometry.geom, new_geometry)
 | 
			
		||||
 | 
			
		||||
        # Expect logs to be set
 | 
			
		||||
        self.assertEqual(pre_edit_log_count + 1, self.compensation.log.count())
 | 
			
		||||
        self.assertEqual(self.compensation.log.first().action, UserAction.EDITED)
 | 
			
		||||
 | 
			
		||||
    def test_checkability(self):
 | 
			
		||||
        """
 | 
			
		||||
        This tests if the checkability of the compensation (which is defined by the linked intervention's checked
 | 
			
		||||
@ -152,6 +173,8 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
        # Add proper privilege for the user
 | 
			
		||||
        self.superuser.groups.add(self.groups.get(name=ZB_GROUP))
 | 
			
		||||
 | 
			
		||||
        pre_check_log_count = self.compensation.log.count()
 | 
			
		||||
 | 
			
		||||
        # Prepare url and form data
 | 
			
		||||
        url = reverse("intervention:check", args=(self.intervention.id,))
 | 
			
		||||
        post_data = {
 | 
			
		||||
@ -186,6 +209,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
 | 
			
		||||
        # Expect the user action to be in the log
 | 
			
		||||
        self.assertIn(checked, self.compensation.log.all())
 | 
			
		||||
        self.assertEqual(pre_check_log_count + 1, self.compensation.log.count())
 | 
			
		||||
 | 
			
		||||
    def test_recordability(self):
 | 
			
		||||
        """
 | 
			
		||||
@ -200,6 +224,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
        """
 | 
			
		||||
        # Add proper privilege for the user
 | 
			
		||||
        self.superuser.groups.add(self.groups.get(name=ETS_GROUP))
 | 
			
		||||
        pre_record_log_count = self.compensation.log.count()
 | 
			
		||||
 | 
			
		||||
        # Prepare url and form data
 | 
			
		||||
        record_url = reverse("intervention:record", args=(self.intervention.id,))
 | 
			
		||||
@ -234,62 +259,5 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
 | 
			
		||||
        # Expect the user action to be in the log
 | 
			
		||||
        self.assertIn(recorded, self.compensation.log.all())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
        # Add user to conservation office group and give shared access to the account
 | 
			
		||||
        cls.superuser.groups.add(cls.groups.get(name=ETS_GROUP))
 | 
			
		||||
        cls.eco_account.share_with_list([cls.superuser])
 | 
			
		||||
 | 
			
		||||
    def test_deductability(self):
 | 
			
		||||
        """
 | 
			
		||||
        This tests the deductability of an eco account.
 | 
			
		||||
 | 
			
		||||
        An eco account should only be deductible if it is recorded.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        # Give user shared access to the dummy intervention, which will be needed here
 | 
			
		||||
        self.intervention.share_with(self.superuser)
 | 
			
		||||
 | 
			
		||||
        # Prepare data for deduction creation
 | 
			
		||||
        deduct_url = reverse("compensation:acc:new-deduction", args=(self.eco_account.id,))
 | 
			
		||||
        test_surface = 10.00
 | 
			
		||||
        post_data = {
 | 
			
		||||
            "surface": test_surface,
 | 
			
		||||
            "account": self.id,
 | 
			
		||||
            "intervention": self.intervention.id,
 | 
			
		||||
        }
 | 
			
		||||
        # Perform request --> expect to fail
 | 
			
		||||
        self.client_user.post(deduct_url, post_data)
 | 
			
		||||
 | 
			
		||||
        # Expect that no deduction has been created
 | 
			
		||||
        self.assertEqual(0, self.eco_account.deductions.count())
 | 
			
		||||
        self.assertEqual(0, self.intervention.deductions.count())
 | 
			
		||||
 | 
			
		||||
        # 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.set_recorded(self.superuser)
 | 
			
		||||
        self.eco_account.refresh_from_db()
 | 
			
		||||
        self.eco_account.deductable_surface = test_surface + 1.00
 | 
			
		||||
        self.eco_account.save()
 | 
			
		||||
        self.assertIsNotNone(self.eco_account.recorded)
 | 
			
		||||
        self.assertGreater(self.eco_account.deductable_surface, test_surface)
 | 
			
		||||
 | 
			
		||||
        # Rerun the request
 | 
			
		||||
        self.client_user.post(deduct_url, post_data)
 | 
			
		||||
 | 
			
		||||
        # Expect that the deduction has been created
 | 
			
		||||
        self.assertEqual(1, self.eco_account.deductions.count())
 | 
			
		||||
        self.assertEqual(1, self.intervention.deductions.count())
 | 
			
		||||
        deduction = self.eco_account.deductions.first()
 | 
			
		||||
        self.assertEqual(deduction.surface, test_surface)
 | 
			
		||||
        self.assertEqual(deduction.account, self.eco_account)
 | 
			
		||||
        self.assertEqual(deduction.intervention, self.intervention)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(pre_record_log_count + 1, self.compensation.log.count())
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										7
									
								
								compensation/tests/ecoaccount/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								compensation/tests/ecoaccount/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 07.02.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										229
									
								
								compensation/tests/ecoaccount/test_views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								compensation/tests/ecoaccount/test_views.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,229 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 27.10.21
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.test import Client
 | 
			
		||||
 | 
			
		||||
from compensation.tests.compensation.test_views import CompensationViewTestCase
 | 
			
		||||
from konova.models import DeadlineType, Deadline
 | 
			
		||||
from konova.settings import DEFAULT_GROUP
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EcoAccountViewTestCase(CompensationViewTestCase):
 | 
			
		||||
    """
 | 
			
		||||
    These tests focus on proper returned views depending on the user's groups privileges and login status
 | 
			
		||||
 | 
			
		||||
    EcoAccounts can inherit the same tests used for compensations.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    comp_state = None
 | 
			
		||||
    comp_action = None
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls) -> None:
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
        
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        state = self.create_dummy_states()
 | 
			
		||||
        self.eco_account.before_states.set([state])
 | 
			
		||||
        self.eco_account.after_states.set([state])
 | 
			
		||||
 | 
			
		||||
        action = self.create_dummy_action()
 | 
			
		||||
        self.eco_account.actions.set([action])
 | 
			
		||||
 | 
			
		||||
        # Prepare urls
 | 
			
		||||
        self.index_url = reverse("compensation:acc:index", args=())
 | 
			
		||||
        self.new_url = reverse("compensation:acc:new", args=())
 | 
			
		||||
        self.new_id_url = reverse("compensation:acc:new-id", args=())
 | 
			
		||||
        self.detail_url = reverse("compensation:acc:detail", args=(self.eco_account.id,))
 | 
			
		||||
        self.log_url = reverse("compensation:acc:log", args=(self.eco_account.id,))
 | 
			
		||||
        self.edit_url = reverse("compensation:acc:edit", args=(self.eco_account.id,))
 | 
			
		||||
        self.remove_url = reverse("compensation:acc:remove", args=(self.eco_account.id,))
 | 
			
		||||
        self.report_url = reverse("compensation:acc:report", args=(self.eco_account.id,))
 | 
			
		||||
 | 
			
		||||
        self.state_new_url = reverse("compensation:acc:new-state", args=(self.eco_account.id,))
 | 
			
		||||
        self.state_edit_url = reverse("compensation:acc:state-edit", args=(self.eco_account.id, self.comp_state.id))
 | 
			
		||||
        self.state_remove_url = reverse("compensation:acc:state-remove", args=(self.eco_account.id, self.comp_state.id,))
 | 
			
		||||
 | 
			
		||||
        self.action_new_url = reverse("compensation:acc:new-action", args=(self.eco_account.id,))
 | 
			
		||||
        self.action_edit_url = reverse("compensation:acc:action-edit", args=(self.eco_account.id, self.comp_action.id))
 | 
			
		||||
        self.action_remove_url = reverse("compensation:acc:action-remove", args=(self.eco_account.id, self.comp_action.id,))
 | 
			
		||||
 | 
			
		||||
        self.deadline = Deadline.objects.get_or_create(
 | 
			
		||||
            type=DeadlineType.FINISHED,
 | 
			
		||||
            date="2020-01-01",
 | 
			
		||||
            comment="DEADLINE COMMENT"
 | 
			
		||||
        )[0]
 | 
			
		||||
        self.eco_account.deadlines.add(self.deadline)
 | 
			
		||||
 | 
			
		||||
        self.deadline_new_url = reverse("compensation:acc:new-deadline", args=(self.eco_account.id,))
 | 
			
		||||
        self.deadline_edit_url = reverse("compensation:acc:deadline-edit", args=(self.eco_account.id, self.deadline.id))
 | 
			
		||||
        self.deadline_remove_url = reverse("compensation:acc:deadline-remove", args=(self.eco_account.id, self.deadline.id))
 | 
			
		||||
 | 
			
		||||
        self.new_doc_url = reverse("compensation:acc:new-doc", args=(self.eco_account.id,))
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_no_groups_shared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in and has no groups and data is shared
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        self.superuser.groups.set([])
 | 
			
		||||
        self.eco_account.share_with_list([self.superuser])
 | 
			
		||||
 | 
			
		||||
        # Since the user has no groups, it does not matter that data has been shared. There SHOULD not be any difference
 | 
			
		||||
        # to a user without access, since the important permissions are missing
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.report_url,
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.state_edit_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.action_edit_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.deadline_edit_url,
 | 
			
		||||
            self.deadline_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_no_groups_unshared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in and has no groups and data is shared
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        self.superuser.groups.set([])
 | 
			
		||||
        self.eco_account.share_with_list([])
 | 
			
		||||
 | 
			
		||||
        # Since the user has no groups, it does not matter that data is unshared. There SHOULD not be any difference
 | 
			
		||||
        # to a user having shared access, since all important permissions are missing
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.report_url,
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.state_edit_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.action_edit_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.deadline_edit_url,
 | 
			
		||||
            self.deadline_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_default_group_shared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in, is default group member and data is shared
 | 
			
		||||
        --> Default group necessary since all base functionalities depend on this group membership
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        group = self.groups.get(name=DEFAULT_GROUP)
 | 
			
		||||
        self.superuser.groups.set([group])
 | 
			
		||||
        # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
 | 
			
		||||
        self.eco_account.share_with_list([self.superuser])
 | 
			
		||||
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.report_url,
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.state_edit_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.action_edit_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.deadline_edit_url,
 | 
			
		||||
            self.deadline_remove_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_default_group_unshared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in, is default group member and data is NOT shared
 | 
			
		||||
        --> Default group necessary since all base functionalities depend on this group membership
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        group = self.groups.get(name=DEFAULT_GROUP)
 | 
			
		||||
        self.superuser.groups.set([group])
 | 
			
		||||
        self.eco_account.share_with_list([])
 | 
			
		||||
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.report_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.new_url,
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.state_edit_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.action_edit_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.deadline_edit_url,
 | 
			
		||||
            self.deadline_remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										302
									
								
								compensation/tests/ecoaccount/test_workflow.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										302
									
								
								compensation/tests/ecoaccount/test_workflow.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,302 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 11.11.21
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
import datetime
 | 
			
		||||
 | 
			
		||||
from django.contrib.gis.geos import MultiPolygon
 | 
			
		||||
from django.core.exceptions import ObjectDoesNotExist
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from compensation.models import EcoAccount, EcoAccountDeduction
 | 
			
		||||
from konova.settings import ETS_GROUP, DEFAULT_GROUP
 | 
			
		||||
from konova.tests.test_views import BaseWorkflowTestCase
 | 
			
		||||
from user.models import UserAction
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        # Add user to conservation office group and give shared access to the account
 | 
			
		||||
        self.superuser.groups.add(self.groups.get(name=DEFAULT_GROUP))
 | 
			
		||||
        self.superuser.groups.add(self.groups.get(name=ETS_GROUP))
 | 
			
		||||
        self.eco_account.share_with_list([self.superuser])
 | 
			
		||||
 | 
			
		||||
    def test_new(self):
 | 
			
		||||
        """ Test the creation of an EcoAccount
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        # Prepare url and form data to be posted
 | 
			
		||||
        new_url = reverse("compensation:acc:new")
 | 
			
		||||
        test_id = self.create_dummy_string()
 | 
			
		||||
        test_title = self.create_dummy_string()
 | 
			
		||||
        test_geom = self.create_dummy_geometry()
 | 
			
		||||
        test_deductable_surface = 1000
 | 
			
		||||
        test_conservation_office = self.get_conservation_office_code()
 | 
			
		||||
        post_data = {
 | 
			
		||||
            "identifier": test_id,
 | 
			
		||||
            "title": test_title,
 | 
			
		||||
            "geom": test_geom.geojson,
 | 
			
		||||
            "deductable_surface": test_deductable_surface,
 | 
			
		||||
            "conservation_office": test_conservation_office.id
 | 
			
		||||
        }
 | 
			
		||||
        self.client_user.post(new_url, post_data)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            acc = EcoAccount.objects.get(
 | 
			
		||||
                identifier=test_id
 | 
			
		||||
            )
 | 
			
		||||
        except ObjectDoesNotExist:
 | 
			
		||||
            self.fail(msg="EcoAccount not created")
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(acc.identifier, test_id)
 | 
			
		||||
        self.assertEqual(acc.title, test_title)
 | 
			
		||||
        self.assert_equal_geometries(acc.geometry.geom, test_geom)
 | 
			
		||||
        self.assertEqual(acc.log.count(), 1)
 | 
			
		||||
 | 
			
		||||
        # Expect logs to be set
 | 
			
		||||
        self.assertEqual(acc.log.count(), 1)
 | 
			
		||||
        self.assertEqual(acc.log.first().action, UserAction.CREATED)
 | 
			
		||||
 | 
			
		||||
    def test_edit(self):
 | 
			
		||||
        """ Checks that the editing of an EcoAccount works
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.eco_account.share_with(self.superuser)
 | 
			
		||||
 | 
			
		||||
        url = reverse("compensation:acc:edit", args=(self.eco_account.id,))
 | 
			
		||||
        pre_edit_log_count = self.eco_account.log.count()
 | 
			
		||||
 | 
			
		||||
        new_title = self.create_dummy_string()
 | 
			
		||||
        new_identifier = self.create_dummy_string()
 | 
			
		||||
        new_comment = self.create_dummy_string()
 | 
			
		||||
        new_geometry = MultiPolygon(srid=4326)  # Create an empty geometry
 | 
			
		||||
        test_conservation_office = self.get_conservation_office_code()
 | 
			
		||||
        test_deductable_surface = 10005
 | 
			
		||||
 | 
			
		||||
        check_on_elements = {
 | 
			
		||||
            self.eco_account.title: new_title,
 | 
			
		||||
            self.eco_account.identifier: new_identifier,
 | 
			
		||||
            self.eco_account.comment: new_comment,
 | 
			
		||||
            self.eco_account.deductable_surface: test_deductable_surface,
 | 
			
		||||
        }
 | 
			
		||||
        for k, v in check_on_elements.items():
 | 
			
		||||
            self.assertNotEqual(k, v)
 | 
			
		||||
 | 
			
		||||
        post_data = {
 | 
			
		||||
            "identifier": new_identifier,
 | 
			
		||||
            "title": new_title,
 | 
			
		||||
            "comment": new_comment,
 | 
			
		||||
            "geom": new_geometry.geojson,
 | 
			
		||||
            "surface": test_deductable_surface,
 | 
			
		||||
            "conservation_office": test_conservation_office.id
 | 
			
		||||
        }
 | 
			
		||||
        self.client_user.post(url, post_data)
 | 
			
		||||
        self.eco_account.refresh_from_db()
 | 
			
		||||
 | 
			
		||||
        check_on_elements = {
 | 
			
		||||
            self.eco_account.title: new_title,
 | 
			
		||||
            self.eco_account.identifier: new_identifier,
 | 
			
		||||
            self.eco_account.deductable_surface: test_deductable_surface,
 | 
			
		||||
            self.eco_account.comment: new_comment,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for k, v in check_on_elements.items():
 | 
			
		||||
            self.assertEqual(k, v)
 | 
			
		||||
 | 
			
		||||
        self.assert_equal_geometries(self.eco_account.geometry.geom, new_geometry)
 | 
			
		||||
 | 
			
		||||
        # Expect logs to be set
 | 
			
		||||
        self.assertEqual(pre_edit_log_count + 1, self.eco_account.log.count())
 | 
			
		||||
        self.assertEqual(self.eco_account.log.first().action, UserAction.EDITED)
 | 
			
		||||
 | 
			
		||||
    def test_recordability(self):
 | 
			
		||||
        """
 | 
			
		||||
        This tests if the recordability of the EcoAccount is triggered by the quality of it's data (e.g. not all fields filled)
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        # Add proper privilege for the user
 | 
			
		||||
        self.eco_account.share_with(self.superuser)
 | 
			
		||||
        pre_record_log_count = self.eco_account.log.count()
 | 
			
		||||
 | 
			
		||||
        # Prepare url and form data
 | 
			
		||||
        record_url = reverse("compensation:acc:record", args=(self.eco_account.id,))
 | 
			
		||||
        post_data = {
 | 
			
		||||
            "confirm": True,
 | 
			
		||||
        }
 | 
			
		||||
        self.eco_account.refresh_from_db()
 | 
			
		||||
 | 
			
		||||
        # Make sure the account is not recorded
 | 
			
		||||
        self.assertIsNone(self.eco_account.recorded)
 | 
			
		||||
 | 
			
		||||
        # Run the request --> expect fail, since the account is not valid, yet
 | 
			
		||||
        self.client_user.post(record_url, post_data)
 | 
			
		||||
 | 
			
		||||
        # Check that the account is still not recorded
 | 
			
		||||
        self.assertIsNone(self.eco_account.recorded)
 | 
			
		||||
 | 
			
		||||
        # Now fill out the data for an ecoaccount
 | 
			
		||||
        self.eco_account = self.fill_out_eco_account(self.eco_account)
 | 
			
		||||
 | 
			
		||||
        # Rerun the request
 | 
			
		||||
        self.client_user.post(record_url, post_data)
 | 
			
		||||
 | 
			
		||||
        # Expect the EcoAccount now to be recorded
 | 
			
		||||
        # Attention: We can only test the date part of the timestamp,
 | 
			
		||||
        # since the delay in microseconds would lead to fail
 | 
			
		||||
        self.eco_account.refresh_from_db()
 | 
			
		||||
        recorded = self.eco_account.recorded
 | 
			
		||||
        self.assertIsNotNone(recorded)
 | 
			
		||||
        self.assertEqual(self.superuser, recorded.user)
 | 
			
		||||
        self.assertEqual(UserAction.RECORDED, recorded.action)
 | 
			
		||||
        self.assertEqual(datetime.date.today(), recorded.timestamp.date())
 | 
			
		||||
 | 
			
		||||
        # Expect the user action to be in the log
 | 
			
		||||
        self.assertIn(recorded, self.eco_account.log.all())
 | 
			
		||||
        self.assertEqual(pre_record_log_count + 1, self.eco_account.log.count())
 | 
			
		||||
 | 
			
		||||
    def test_new_deduction(self):
 | 
			
		||||
        """
 | 
			
		||||
        This tests the deductability of an eco account.
 | 
			
		||||
 | 
			
		||||
        An eco account should only be deductible if it is recorded.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        # Give user shared access to the dummy intervention, which will be needed here
 | 
			
		||||
        self.intervention.share_with(self.superuser)
 | 
			
		||||
        pre_deduction_acc_log_count = self.eco_account.log.count()
 | 
			
		||||
        pre_deduction_int_log_count = self.intervention.log.count()
 | 
			
		||||
 | 
			
		||||
        # Prepare data for deduction creation
 | 
			
		||||
        deduct_url = reverse("compensation:acc:new-deduction", args=(self.eco_account.id,))
 | 
			
		||||
        test_surface = 10.00
 | 
			
		||||
        post_data = {
 | 
			
		||||
            "surface": test_surface,
 | 
			
		||||
            "account": self.eco_account.id,
 | 
			
		||||
            "intervention": self.intervention.id,
 | 
			
		||||
        }
 | 
			
		||||
        # Perform request --> expect to fail
 | 
			
		||||
        self.client_user.post(deduct_url, post_data)
 | 
			
		||||
 | 
			
		||||
        # Expect that no deduction has been created
 | 
			
		||||
        self.assertEqual(0, self.eco_account.deductions.count())
 | 
			
		||||
        self.assertEqual(0, self.intervention.deductions.count())
 | 
			
		||||
        self.assertEqual(pre_deduction_acc_log_count, 0)
 | 
			
		||||
        self.assertEqual(pre_deduction_int_log_count, 0)
 | 
			
		||||
 | 
			
		||||
        # 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.set_recorded(self.superuser)
 | 
			
		||||
        self.eco_account.refresh_from_db()
 | 
			
		||||
        self.eco_account.deductable_surface = test_surface + 1.00
 | 
			
		||||
        self.eco_account.save()
 | 
			
		||||
        self.assertIsNotNone(self.eco_account.recorded)
 | 
			
		||||
        self.assertGreater(self.eco_account.deductable_surface, test_surface)
 | 
			
		||||
        # Expect the recorded entry in the log
 | 
			
		||||
        self.assertEqual(pre_deduction_acc_log_count + 1, self.eco_account.log.count())
 | 
			
		||||
        self.assertTrue(self.eco_account.log.first().action == UserAction.RECORDED)
 | 
			
		||||
 | 
			
		||||
        # Rerun the request
 | 
			
		||||
        self.client_user.post(deduct_url, post_data)
 | 
			
		||||
 | 
			
		||||
        # Expect that the deduction has been created
 | 
			
		||||
        self.assertEqual(1, self.eco_account.deductions.count())
 | 
			
		||||
        self.assertEqual(1, self.intervention.deductions.count())
 | 
			
		||||
        deduction = self.eco_account.deductions.first()
 | 
			
		||||
        self.assertEqual(deduction.surface, test_surface)
 | 
			
		||||
        self.assertEqual(deduction.account, self.eco_account)
 | 
			
		||||
        self.assertEqual(deduction.intervention, self.intervention)
 | 
			
		||||
 | 
			
		||||
        # Expect entries in the log
 | 
			
		||||
        self.assertEqual(pre_deduction_acc_log_count + 2, self.eco_account.log.count())
 | 
			
		||||
        self.assertTrue(self.eco_account.log.first().action == UserAction.EDITED)
 | 
			
		||||
        self.assertEqual(pre_deduction_int_log_count + 1, self.intervention.log.count())
 | 
			
		||||
        self.assertTrue(self.intervention.log.first().action == UserAction.EDITED)
 | 
			
		||||
 | 
			
		||||
    def test_edit_deduction(self):
 | 
			
		||||
        test_surface = self.eco_account.get_available_rest()[0]
 | 
			
		||||
        self.eco_account.set_recorded(self.superuser)
 | 
			
		||||
        self.eco_account.refresh_from_db()
 | 
			
		||||
 | 
			
		||||
        deduction = EcoAccountDeduction.objects.create(
 | 
			
		||||
            intervention=self.intervention,
 | 
			
		||||
            account=self.eco_account,
 | 
			
		||||
            surface=0
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(1, self.intervention.deductions.count())
 | 
			
		||||
        self.assertEqual(1, self.eco_account.deductions.count())
 | 
			
		||||
 | 
			
		||||
        # Prepare url and form data to be posted
 | 
			
		||||
        new_url = reverse("compensation:acc:edit-deduction", args=(self.eco_account.id, deduction.id))
 | 
			
		||||
        post_data = {
 | 
			
		||||
            "intervention": deduction.intervention.id,
 | 
			
		||||
            "account": deduction.account.id,
 | 
			
		||||
            "surface": test_surface,
 | 
			
		||||
        }
 | 
			
		||||
        pre_edit_intervention_log_count = self.intervention.log.count()
 | 
			
		||||
        pre_edit_account_log_count = self.eco_account.log.count()
 | 
			
		||||
        num_deductions_intervention = self.intervention.deductions.count()
 | 
			
		||||
        num_deductions_account = self.eco_account.deductions.count()
 | 
			
		||||
 | 
			
		||||
        self.client_user.post(new_url, post_data)
 | 
			
		||||
 | 
			
		||||
        self.intervention.refresh_from_db()
 | 
			
		||||
        self.eco_account.refresh_from_db()
 | 
			
		||||
        deduction.refresh_from_db()
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(num_deductions_intervention, self.intervention.deductions.count())
 | 
			
		||||
        self.assertEqual(num_deductions_account, self.eco_account.deductions.count())
 | 
			
		||||
        self.assertEqual(deduction.surface, test_surface)
 | 
			
		||||
 | 
			
		||||
        # Expect logs to be set
 | 
			
		||||
        self.assertEqual(pre_edit_intervention_log_count + 1, self.intervention.log.count())
 | 
			
		||||
        self.assertEqual(pre_edit_account_log_count + 1, self.eco_account.log.count())
 | 
			
		||||
        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
 | 
			
		||||
        self.assertEqual(self.eco_account.log.first().action, UserAction.EDITED)
 | 
			
		||||
 | 
			
		||||
    def test_remove_deduction(self):
 | 
			
		||||
        intervention = self.deduction.intervention
 | 
			
		||||
        account = self.deduction.account
 | 
			
		||||
 | 
			
		||||
        # Prepare url and form data to be posted
 | 
			
		||||
        new_url = reverse("compensation:acc:remove-deduction", args=(account.id, self.deduction.id))
 | 
			
		||||
        post_data = {
 | 
			
		||||
            "confirm": True,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        intervention.share_with(self.superuser)
 | 
			
		||||
        account.share_with(self.superuser)
 | 
			
		||||
 | 
			
		||||
        pre_edit_intervention_log_count = intervention.log.count()
 | 
			
		||||
        pre_edit_account_log_count = account.log.count()
 | 
			
		||||
        num_deductions_intervention = intervention.deductions.count()
 | 
			
		||||
        num_deductions_account = account.deductions.count()
 | 
			
		||||
 | 
			
		||||
        self.client_user.post(new_url, post_data)
 | 
			
		||||
 | 
			
		||||
        intervention.refresh_from_db()
 | 
			
		||||
        account.refresh_from_db()
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(num_deductions_intervention - 1, intervention.deductions.count())
 | 
			
		||||
        self.assertEqual(num_deductions_account - 1, account.deductions.count())
 | 
			
		||||
 | 
			
		||||
        # Expect logs to be set
 | 
			
		||||
        self.assertEqual(pre_edit_intervention_log_count + 1, intervention.log.count())
 | 
			
		||||
        self.assertEqual(pre_edit_account_log_count + 1, account.log.count())
 | 
			
		||||
        self.assertEqual(intervention.log.first().action, UserAction.EDITED)
 | 
			
		||||
        self.assertEqual(account.log.first().action, UserAction.EDITED)
 | 
			
		||||
							
								
								
									
										7
									
								
								compensation/tests/payment/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								compensation/tests/payment/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 09.02.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										156
									
								
								compensation/tests/payment/test_views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								compensation/tests/payment/test_views.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,156 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 09.02.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.test.client import Client
 | 
			
		||||
 | 
			
		||||
from compensation.models import Payment
 | 
			
		||||
from konova.settings import DEFAULT_GROUP
 | 
			
		||||
from konova.tests.test_views import BaseViewTestCase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PaymentViewTestCase(BaseViewTestCase):
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls) -> None:
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.payment = Payment.objects.get_or_create(
 | 
			
		||||
            intervention=self.intervention,
 | 
			
		||||
            amount=1,
 | 
			
		||||
            due_on="2020-01-01",
 | 
			
		||||
            comment="Testcomment"
 | 
			
		||||
        )[0]
 | 
			
		||||
 | 
			
		||||
        self.new_url = reverse("compensation:pay:new", args=(self.intervention.id,))
 | 
			
		||||
        self.edit_url = reverse("compensation:pay:edit", args=(self.intervention.id, self.payment.id))
 | 
			
		||||
        self.remove_url = reverse("compensation:pay:remove", args=(self.intervention.id, self.payment.id))
 | 
			
		||||
 | 
			
		||||
    def test_anonymous_user(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User not logged in
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
 | 
			
		||||
        success_urls = [
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_no_groups_shared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in and has no groups and data is shared
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        self.superuser.groups.set([])
 | 
			
		||||
        self.intervention.share_with_list([self.superuser])
 | 
			
		||||
 | 
			
		||||
        # Since the user has no groups, it does not matter that data has been shared. There SHOULD not be any difference
 | 
			
		||||
        # to a user without access, since the important permissions are missing
 | 
			
		||||
        success_urls = [
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_no_groups_unshared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in and has no groups and data is not shared
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        self.superuser.groups.set([])
 | 
			
		||||
        # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
 | 
			
		||||
        self.intervention.share_with_list([])
 | 
			
		||||
 | 
			
		||||
        # Since the user has no groups, it does not matter that data is unshared. There SHOULD not be any difference
 | 
			
		||||
        # to a user having shared access, since all important permissions are missing
 | 
			
		||||
        success_urls = [
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_default_group_shared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in, is default group member and data is shared
 | 
			
		||||
        --> Default group necessary since all base functionalities depend on this group membership
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        group = self.groups.get(name=DEFAULT_GROUP)
 | 
			
		||||
        self.superuser.groups.set([group])
 | 
			
		||||
        # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
 | 
			
		||||
        self.intervention.share_with_list([self.superuser])
 | 
			
		||||
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_default_group_unshared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in, is default group member and data is NOT shared
 | 
			
		||||
        --> Default group necessary since all base functionalities depend on this group membership
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        group = self.groups.get(name=DEFAULT_GROUP)
 | 
			
		||||
        self.superuser.groups.set([group])
 | 
			
		||||
        # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
 | 
			
		||||
        self.intervention.share_with_list([])
 | 
			
		||||
 | 
			
		||||
        success_urls = [
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
							
								
								
									
										127
									
								
								compensation/tests/payment/test_workflow.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								compensation/tests/payment/test_workflow.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,127 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 09.02.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.core.exceptions import ObjectDoesNotExist
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from compensation.models import Payment
 | 
			
		||||
from konova.tests.test_views import BaseWorkflowTestCase
 | 
			
		||||
from user.models import UserAction
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PaymentWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        # Give the user shared access to the dummy intervention
 | 
			
		||||
        self.intervention.share_with(self.superuser)
 | 
			
		||||
 | 
			
		||||
        self.payment = Payment.objects.get_or_create(
 | 
			
		||||
            intervention=self.intervention,
 | 
			
		||||
            amount=1,
 | 
			
		||||
            due_on="2020-01-01",
 | 
			
		||||
            comment="Testcomment"
 | 
			
		||||
        )[0]
 | 
			
		||||
 | 
			
		||||
    def test_new(self):
 | 
			
		||||
        """ Test the creation of a payment
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        # Prepare url and form data to be posted
 | 
			
		||||
        new_url = reverse("compensation:pay:new", args=(self.intervention.id,))
 | 
			
		||||
        test_amount = 12345
 | 
			
		||||
        test_due_on = "1970-01-01"
 | 
			
		||||
        test_comment = self.create_dummy_string()
 | 
			
		||||
        post_data = {
 | 
			
		||||
            "amount": test_amount,
 | 
			
		||||
            "due": test_due_on,
 | 
			
		||||
            "comment": test_comment,
 | 
			
		||||
        }
 | 
			
		||||
        pre_creation_intervention_log_count = self.intervention.log.count()
 | 
			
		||||
        num_payments = self.intervention.payments.count()
 | 
			
		||||
 | 
			
		||||
        self.client_user.post(new_url, post_data)
 | 
			
		||||
 | 
			
		||||
        self.intervention.refresh_from_db()
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(num_payments + 1, self.intervention.payments.count())
 | 
			
		||||
        new_payment = self.intervention.payments.get(amount=test_amount)
 | 
			
		||||
        self.assertEqual(new_payment.amount, test_amount)
 | 
			
		||||
        self.assertEqual(str(new_payment.due_on), test_due_on)
 | 
			
		||||
        self.assertEqual(new_payment.comment, test_comment)
 | 
			
		||||
 | 
			
		||||
        # Expect logs to be set
 | 
			
		||||
        self.assertEqual(pre_creation_intervention_log_count + 1, self.intervention.log.count())
 | 
			
		||||
        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
 | 
			
		||||
 | 
			
		||||
    def test_edit(self):
 | 
			
		||||
        """ Test edit of a payment
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        # Prepare url and form data to be posted
 | 
			
		||||
        new_url = reverse("compensation:pay:edit", args=(self.intervention.id, self.payment.id))
 | 
			
		||||
        test_amount = self.payment.amount * 2
 | 
			
		||||
        test_due_on = "1970-01-01"
 | 
			
		||||
        test_comment = self.create_dummy_string()
 | 
			
		||||
        post_data = {
 | 
			
		||||
            "amount": test_amount,
 | 
			
		||||
            "due": test_due_on,
 | 
			
		||||
            "comment": test_comment,
 | 
			
		||||
        }
 | 
			
		||||
        pre_edit_intervention_log_count = self.intervention.log.count()
 | 
			
		||||
        num_payments = self.intervention.payments.count()
 | 
			
		||||
 | 
			
		||||
        self.client_user.post(new_url, post_data)
 | 
			
		||||
 | 
			
		||||
        self.intervention.refresh_from_db()
 | 
			
		||||
        self.payment.refresh_from_db()
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(num_payments, self.intervention.payments.count())
 | 
			
		||||
        self.assertEqual(self.payment.amount, test_amount)
 | 
			
		||||
        self.assertEqual(str(self.payment.due_on), test_due_on)
 | 
			
		||||
        self.assertEqual(self.payment.comment, test_comment)
 | 
			
		||||
 | 
			
		||||
        # Expect logs to be set
 | 
			
		||||
        self.assertEqual(pre_edit_intervention_log_count + 1, self.intervention.log.count())
 | 
			
		||||
        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
 | 
			
		||||
 | 
			
		||||
    def test_remove(self):
 | 
			
		||||
        """ Test remove of a payment
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        # Prepare url and form data to be posted
 | 
			
		||||
        new_url = reverse("compensation:pay:remove", args=(self.intervention.id, self.payment.id))
 | 
			
		||||
        post_data = {
 | 
			
		||||
            "confirm": True,
 | 
			
		||||
        }
 | 
			
		||||
        pre_remove_intervention_log_count = self.intervention.log.count()
 | 
			
		||||
        num_payments = self.intervention.payments.count()
 | 
			
		||||
 | 
			
		||||
        self.client_user.post(new_url, post_data)
 | 
			
		||||
 | 
			
		||||
        self.intervention.refresh_from_db()
 | 
			
		||||
        try:
 | 
			
		||||
            self.payment.refresh_from_db()
 | 
			
		||||
            self.fail(msg="Payment still exists after delete")
 | 
			
		||||
        except ObjectDoesNotExist:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(num_payments - 1, self.intervention.payments.count())
 | 
			
		||||
 | 
			
		||||
        # Expect logs to be set
 | 
			
		||||
        self.assertEqual(pre_remove_intervention_log_count + 1, self.intervention.log.count())
 | 
			
		||||
        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
 | 
			
		||||
 | 
			
		||||
@ -1,406 +0,0 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 27.10.21
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.test import Client
 | 
			
		||||
 | 
			
		||||
from konova.settings import DEFAULT_GROUP
 | 
			
		||||
from konova.tests.test_views import BaseViewTestCase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CompensationViewTestCase(BaseViewTestCase):
 | 
			
		||||
    """
 | 
			
		||||
    These tests focus on proper returned views depending on the user's groups privileges and login status
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls) -> None:
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
        state = cls.create_dummy_states()
 | 
			
		||||
        cls.compensation.before_states.set([state])
 | 
			
		||||
        cls.compensation.after_states.set([state])
 | 
			
		||||
 | 
			
		||||
        action = cls.create_dummy_action()
 | 
			
		||||
        cls.compensation.actions.set([action])
 | 
			
		||||
 | 
			
		||||
        # Prepare urls
 | 
			
		||||
        cls.index_url = reverse("compensation:index", args=())
 | 
			
		||||
        cls.new_url = reverse("compensation:new", args=(cls.intervention.id,))
 | 
			
		||||
        cls.new_id_url = reverse("compensation:new-id", args=())
 | 
			
		||||
        cls.detail_url = reverse("compensation:detail", args=(cls.compensation.id,))
 | 
			
		||||
        cls.log_url = reverse("compensation:log", args=(cls.compensation.id,))
 | 
			
		||||
        cls.edit_url = reverse("compensation:edit", args=(cls.compensation.id,))
 | 
			
		||||
        cls.remove_url = reverse("compensation:remove", args=(cls.compensation.id,))
 | 
			
		||||
        cls.report_url = reverse("compensation:report", args=(cls.compensation.id,))
 | 
			
		||||
        cls.state_new_url = reverse("compensation:new-state", args=(cls.compensation.id,))
 | 
			
		||||
        cls.action_new_url = reverse("compensation:new-action", args=(cls.compensation.id,))
 | 
			
		||||
        cls.deadline_new_url = reverse("compensation:new-deadline", args=(cls.compensation.id,))
 | 
			
		||||
        cls.new_doc_url = reverse("compensation:new-doc", args=(cls.compensation.id,))
 | 
			
		||||
 | 
			
		||||
        cls.state_remove_url = reverse("compensation:state-remove", args=(cls.compensation.id, cls.comp_state.id,))
 | 
			
		||||
        cls.action_remove_url = reverse("compensation:action-remove", args=(cls.compensation.id, cls.comp_action.id,))
 | 
			
		||||
 | 
			
		||||
    def test_anonymous_user(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User not logged in
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.report_url,
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_no_groups_shared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in and has no groups and data is shared
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        self.superuser.groups.set([])
 | 
			
		||||
        self.intervention.share_with_list([self.superuser])
 | 
			
		||||
 | 
			
		||||
        # Since the user has no groups, it does not matter that data has been shared. There SHOULD not be any difference
 | 
			
		||||
        # to a user without access, since the important permissions are missing
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.report_url,
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_no_groups_unshared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in and has no groups and data is shared
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        self.superuser.groups.set([])
 | 
			
		||||
        # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
 | 
			
		||||
        self.intervention.share_with_list([])
 | 
			
		||||
 | 
			
		||||
        # Since the user has no groups, it does not matter that data is unshared. There SHOULD not be any difference
 | 
			
		||||
        # to a user having shared access, since all important permissions are missing
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.report_url,
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_default_group_shared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in, is default group member and data is shared
 | 
			
		||||
        --> Default group necessary since all base functionalities depend on this group membership
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        group = self.groups.get(name=DEFAULT_GROUP)
 | 
			
		||||
        self.superuser.groups.set([group])
 | 
			
		||||
        # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
 | 
			
		||||
        self.intervention.share_with_list([self.superuser])
 | 
			
		||||
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.report_url,
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_default_group_unshared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in, is default group member and data is NOT shared
 | 
			
		||||
        --> Default group necessary since all base functionalities depend on this group membership
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        group = self.groups.get(name=DEFAULT_GROUP)
 | 
			
		||||
        self.superuser.groups.set([group])
 | 
			
		||||
        # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
 | 
			
		||||
        self.intervention.share_with_list([])
 | 
			
		||||
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.report_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EcoAccountViewTestCase(CompensationViewTestCase):
 | 
			
		||||
    """
 | 
			
		||||
    These tests focus on proper returned views depending on the user's groups privileges and login status
 | 
			
		||||
 | 
			
		||||
    EcoAccounts can inherit the same tests used for compensations.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    comp_state = None
 | 
			
		||||
    comp_action = None
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls) -> None:
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
        state = cls.create_dummy_states()
 | 
			
		||||
        cls.eco_account.before_states.set([state])
 | 
			
		||||
        cls.eco_account.after_states.set([state])
 | 
			
		||||
 | 
			
		||||
        action = cls.create_dummy_action()
 | 
			
		||||
        cls.eco_account.actions.set([action])
 | 
			
		||||
 | 
			
		||||
        # Prepare urls
 | 
			
		||||
        cls.index_url = reverse("compensation:acc:index", args=())
 | 
			
		||||
        cls.new_url = reverse("compensation:acc:new", args=())
 | 
			
		||||
        cls.new_id_url = reverse("compensation:acc:new-id", args=())
 | 
			
		||||
        cls.detail_url = reverse("compensation:acc:detail", args=(cls.eco_account.id,))
 | 
			
		||||
        cls.log_url = reverse("compensation:acc:log", args=(cls.eco_account.id,))
 | 
			
		||||
        cls.edit_url = reverse("compensation:acc:edit", args=(cls.eco_account.id,))
 | 
			
		||||
        cls.remove_url = reverse("compensation:acc:remove", args=(cls.eco_account.id,))
 | 
			
		||||
        cls.report_url = reverse("compensation:acc:report", args=(cls.eco_account.id,))
 | 
			
		||||
        cls.state_new_url = reverse("compensation:acc:new-state", args=(cls.eco_account.id,))
 | 
			
		||||
        cls.action_new_url = reverse("compensation:acc:new-action", args=(cls.eco_account.id,))
 | 
			
		||||
        cls.deadline_new_url = reverse("compensation:acc:new-deadline", args=(cls.eco_account.id,))
 | 
			
		||||
        cls.new_doc_url = reverse("compensation:acc:new-doc", args=(cls.eco_account.id,))
 | 
			
		||||
        cls.state_remove_url = reverse("compensation:acc:state-remove", args=(cls.eco_account.id, cls.comp_state.id,))
 | 
			
		||||
        cls.action_remove_url = reverse("compensation:acc:action-remove", args=(cls.eco_account.id, cls.comp_action.id,))
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_no_groups_shared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in and has no groups and data is shared
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        self.superuser.groups.set([])
 | 
			
		||||
        self.eco_account.share_with_list([self.superuser])
 | 
			
		||||
 | 
			
		||||
        # Since the user has no groups, it does not matter that data has been shared. There SHOULD not be any difference
 | 
			
		||||
        # to a user without access, since the important permissions are missing
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.report_url,
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_no_groups_unshared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in and has no groups and data is shared
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        self.superuser.groups.set([])
 | 
			
		||||
        self.eco_account.share_with_list([])
 | 
			
		||||
 | 
			
		||||
        # Since the user has no groups, it does not matter that data is unshared. There SHOULD not be any difference
 | 
			
		||||
        # to a user having shared access, since all important permissions are missing
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.report_url,
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_default_group_shared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in, is default group member and data is shared
 | 
			
		||||
        --> Default group necessary since all base functionalities depend on this group membership
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        group = self.groups.get(name=DEFAULT_GROUP)
 | 
			
		||||
        self.superuser.groups.set([group])
 | 
			
		||||
        # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
 | 
			
		||||
        self.eco_account.share_with_list([self.superuser])
 | 
			
		||||
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.report_url,
 | 
			
		||||
            self.new_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
 | 
			
		||||
    def test_logged_in_default_group_unshared(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
 | 
			
		||||
        Assumption: User logged in, is default group member and data is NOT shared
 | 
			
		||||
        --> Default group necessary since all base functionalities depend on this group membership
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        client.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        group = self.groups.get(name=DEFAULT_GROUP)
 | 
			
		||||
        self.superuser.groups.set([group])
 | 
			
		||||
        self.eco_account.share_with_list([])
 | 
			
		||||
 | 
			
		||||
        success_urls = [
 | 
			
		||||
            self.index_url,
 | 
			
		||||
            self.detail_url,
 | 
			
		||||
            self.report_url,
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.new_url,
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
        self.assert_url_fail(client, fail_urls)
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
 | 
			
		||||
@ -18,17 +18,24 @@ urlpatterns = [
 | 
			
		||||
    path('<id>/log', log_view, name='log'),
 | 
			
		||||
    path('<id>/edit', edit_view, name='edit'),
 | 
			
		||||
    path('<id>/remove', remove_view, name='remove'),
 | 
			
		||||
 | 
			
		||||
    path('<id>/state/new', state_new_view, name='new-state'),
 | 
			
		||||
    path('<id>/action/new', action_new_view, name='new-action'),
 | 
			
		||||
    path('<id>/state/<state_id>/edit', state_edit_view, name='state-edit'),
 | 
			
		||||
    path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),
 | 
			
		||||
 | 
			
		||||
    path('<id>/action/new', action_new_view, name='new-action'),
 | 
			
		||||
    path('<id>/action/<action_id>/edit', action_edit_view, name='action-edit'),
 | 
			
		||||
    path('<id>/action/<action_id>/remove', action_remove_view, name='action-remove'),
 | 
			
		||||
 | 
			
		||||
    path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
 | 
			
		||||
    path('<id>/deadline/<deadline_id>/edit', deadline_edit_view, name='deadline-edit'),
 | 
			
		||||
    path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
 | 
			
		||||
    path('<id>/report', report_view, name='report'),
 | 
			
		||||
 | 
			
		||||
    # Documents
 | 
			
		||||
    path('<id>/document/new/', new_document_view, name='new-doc'),
 | 
			
		||||
    path('document/<doc_id>', get_document_view, name='get-doc'),
 | 
			
		||||
    path('document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
 | 
			
		||||
    path('<id>/document/<doc_id>', get_document_view, name='get-doc'),
 | 
			
		||||
    path('<id>/document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
 | 
			
		||||
    path('<id>/document/<doc_id>/edit/', edit_document_view, name='edit-doc'),
 | 
			
		||||
 | 
			
		||||
]
 | 
			
		||||
@ -19,22 +19,31 @@ urlpatterns = [
 | 
			
		||||
    path('<id>/report', report_view, name='report'),
 | 
			
		||||
    path('<id>/edit', edit_view, name='edit'),
 | 
			
		||||
    path('<id>/remove', remove_view, name='remove'),
 | 
			
		||||
 | 
			
		||||
    path('<id>/state/new', state_new_view, name='new-state'),
 | 
			
		||||
    path('<id>/action/new', action_new_view, name='new-action'),
 | 
			
		||||
    path('<id>/state/<state_id>/edit', state_edit_view, name='state-edit'),
 | 
			
		||||
    path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),
 | 
			
		||||
 | 
			
		||||
    path('<id>/action/new', action_new_view, name='new-action'),
 | 
			
		||||
    path('<id>/action/<action_id>/edit', action_edit_view, name='action-edit'),
 | 
			
		||||
    path('<id>/action/<action_id>/remove', action_remove_view, name='action-remove'),
 | 
			
		||||
    path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
 | 
			
		||||
 | 
			
		||||
    path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
 | 
			
		||||
    path('<id>/deadline/<deadline_id>/edit', deadline_edit_view, name='deadline-edit'),
 | 
			
		||||
    path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
 | 
			
		||||
 | 
			
		||||
    path('<id>/share/<token>', share_view, name='share'),
 | 
			
		||||
    path('<id>/share', create_share_view, name='share-create'),
 | 
			
		||||
 | 
			
		||||
    # Documents
 | 
			
		||||
    path('<id>/document/new/', new_document_view, name='new-doc'),
 | 
			
		||||
    path('document/<doc_id>', get_document_view, name='get-doc'),
 | 
			
		||||
    path('document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
 | 
			
		||||
    path('<id>/document/<doc_id>', get_document_view, name='get-doc'),
 | 
			
		||||
    path('<id>/document/<doc_id>/edit', edit_document_view, name='edit-doc'),
 | 
			
		||||
    path('<id>/document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
 | 
			
		||||
 | 
			
		||||
    # Eco-account deductions
 | 
			
		||||
    path('<id>/remove/<deduction_id>', deduction_remove_view, name='remove-deduction'),
 | 
			
		||||
    path('<id>/deduction/<deduction_id>/remove', deduction_remove_view, name='remove-deduction'),
 | 
			
		||||
    path('<id>/deduction/<deduction_id>/edit', deduction_edit_view, name='edit-deduction'),
 | 
			
		||||
    path('<id>/deduct/new', new_deduction_view, name='new-deduction'),
 | 
			
		||||
 | 
			
		||||
]
 | 
			
		||||
@ -10,6 +10,7 @@ from compensation.views.payment import *
 | 
			
		||||
 | 
			
		||||
app_name = "pay"
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path('<intervention_id>/new', new_payment_view, name='new'),
 | 
			
		||||
    path('<id>/remove', payment_remove_view, name='remove'),
 | 
			
		||||
    path('<id>/new', new_payment_view, name='new'),
 | 
			
		||||
    path('<id>/remove/<payment_id>', payment_remove_view, name='remove'),
 | 
			
		||||
    path('<id>/edit/<payment_id>', payment_edit_view, name='edit'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -6,19 +6,23 @@ from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from compensation.forms.forms import NewCompensationForm, EditCompensationForm
 | 
			
		||||
from compensation.forms.modalForms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm, \
 | 
			
		||||
    NewCompensationDocumentForm
 | 
			
		||||
    NewCompensationDocumentModalForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm, \
 | 
			
		||||
    EditCompensationStateModalForm, EditCompensationActionModalForm, EditDeadlineModalForm
 | 
			
		||||
from compensation.models import Compensation, CompensationState, CompensationAction, CompensationDocument
 | 
			
		||||
from compensation.tables import CompensationTable
 | 
			
		||||
from intervention.models import Intervention
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
from konova.decorators import *
 | 
			
		||||
from konova.forms import RemoveModalForm, SimpleGeomForm
 | 
			
		||||
from konova.forms import RemoveModalForm, SimpleGeomForm, RemoveDeadlineModalForm, EditDocumentModalForm
 | 
			
		||||
from konova.models import Deadline
 | 
			
		||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 | 
			
		||||
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, \
 | 
			
		||||
    CHECKED_RECORDED_RESET, COMPENSATION_ADDED_TEMPLATE, COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED
 | 
			
		||||
    CHECKED_RECORDED_RESET, COMPENSATION_ADDED_TEMPLATE, COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, \
 | 
			
		||||
    COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, \
 | 
			
		||||
    DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, \
 | 
			
		||||
    DEADLINE_EDITED
 | 
			
		||||
from konova.utils.user_checks import in_group
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -274,7 +278,7 @@ def new_document_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    comp = get_object_or_404(Compensation, id=id)
 | 
			
		||||
    form = NewCompensationDocumentForm(request.POST or None, request.FILES or None, instance=comp, request=request)
 | 
			
		||||
    form = NewCompensationDocumentModalForm(request.POST or None, request.FILES or None, instance=comp, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=DOCUMENT_ADDED,
 | 
			
		||||
@ -284,45 +288,42 @@ def new_document_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
def get_document_view(request: HttpRequest, doc_id: str):
 | 
			
		||||
@shared_access_required(Compensation, "id")
 | 
			
		||||
def get_document_view(request: HttpRequest, id: str, doc_id: str):
 | 
			
		||||
    """ Returns the document as downloadable file
 | 
			
		||||
 | 
			
		||||
    Wraps the generic document fetcher function from konova.utils.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The compensation id
 | 
			
		||||
        doc_id (str): The document id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    comp = get_object_or_404(Compensation, id=id)
 | 
			
		||||
    doc = get_object_or_404(CompensationDocument, id=doc_id)
 | 
			
		||||
    user = request.user
 | 
			
		||||
    instance = doc.instance
 | 
			
		||||
    # File download only possible if related instance is shared with user
 | 
			
		||||
    if not instance.users.filter(id=user.id):
 | 
			
		||||
        messages.info(
 | 
			
		||||
            request,
 | 
			
		||||
            DATA_UNSHARED
 | 
			
		||||
        )
 | 
			
		||||
        return redirect("compensation:detail", id=instance.id)
 | 
			
		||||
    return get_document(doc)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
def remove_document_view(request: HttpRequest, doc_id: str):
 | 
			
		||||
@shared_access_required(Compensation, "id")
 | 
			
		||||
def remove_document_view(request: HttpRequest, id: str, doc_id: str):
 | 
			
		||||
    """ Removes the document from the database and file system
 | 
			
		||||
 | 
			
		||||
    Wraps the generic functionality from konova.utils.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The compensation id
 | 
			
		||||
        doc_id (str): The document id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    comp = get_object_or_404(Compensation, id=id)
 | 
			
		||||
    doc = get_object_or_404(CompensationDocument, id=doc_id)
 | 
			
		||||
    return remove_document(
 | 
			
		||||
        request,
 | 
			
		||||
@ -330,6 +331,32 @@ def remove_document_view(request: HttpRequest, doc_id: str):
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(Compensation, "id")
 | 
			
		||||
def edit_document_view(request: HttpRequest, id: str, doc_id: str):
 | 
			
		||||
    """ Removes the document from the database and file system
 | 
			
		||||
 | 
			
		||||
    Wraps the generic functionality from konova.utils.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The compensation id
 | 
			
		||||
        doc_id (str): The document id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    comp = get_object_or_404(Compensation, id=id)
 | 
			
		||||
    doc = get_object_or_404(CompensationDocument, id=doc_id)
 | 
			
		||||
    form = EditDocumentModalForm(request.POST or None, request.FILES or None, instance=comp, document=doc, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        DOCUMENT_EDITED,
 | 
			
		||||
        reverse("compensation:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(Compensation, "id")
 | 
			
		||||
@ -347,7 +374,7 @@ def state_new_view(request: HttpRequest, id: str):
 | 
			
		||||
    form = NewStateModalForm(request.POST or None, instance=comp, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("State added"),
 | 
			
		||||
        msg_success=COMPENSATION_STATE_ADDED,
 | 
			
		||||
        redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -369,7 +396,31 @@ def action_new_view(request: HttpRequest, id: str):
 | 
			
		||||
    form = NewActionModalForm(request.POST or None, instance=comp, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Action added"),
 | 
			
		||||
        msg_success=COMPENSATION_ACTION_ADDED,
 | 
			
		||||
        redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(Compensation, "id")
 | 
			
		||||
def action_edit_view(request: HttpRequest, id: str, action_id: str):
 | 
			
		||||
    """ Renders a form for editing actions for a compensation
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The compensation's id
 | 
			
		||||
        action_id (str): The action's id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    comp = get_object_or_404(Compensation, id=id)
 | 
			
		||||
    action = get_object_or_404(CompensationAction, id=action_id)
 | 
			
		||||
    form = EditCompensationActionModalForm(request.POST or None, instance=comp, action=action, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=COMPENSATION_ACTION_EDITED,
 | 
			
		||||
        redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -391,7 +442,31 @@ def deadline_new_view(request: HttpRequest, id: str):
 | 
			
		||||
    form = NewDeadlineModalForm(request.POST or None, instance=comp, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Deadline added"),
 | 
			
		||||
        msg_success=DEADLINE_ADDED,
 | 
			
		||||
        redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(Compensation, "id")
 | 
			
		||||
def deadline_edit_view(request: HttpRequest, id: str, deadline_id: str):
 | 
			
		||||
    """ Renders a form for editing deadlines from a compensation
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The compensation's id
 | 
			
		||||
        deadline_id (str): The deadline's id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    comp = get_object_or_404(Compensation, id=id)
 | 
			
		||||
    deadline = get_object_or_404(Deadline, id=deadline_id)
 | 
			
		||||
    form = EditDeadlineModalForm(request.POST or None, instance=comp, deadline=deadline, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=DEADLINE_EDITED,
 | 
			
		||||
        redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -410,11 +485,12 @@ def deadline_remove_view(request: HttpRequest, id: str, deadline_id: str):
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    comp = get_object_or_404(Compensation, id=id)
 | 
			
		||||
    deadline = get_object_or_404(Deadline, id=deadline_id)
 | 
			
		||||
    form = RemoveModalForm(request.POST or None, instance=deadline, request=request)
 | 
			
		||||
    form = RemoveDeadlineModalForm(request.POST or None, instance=comp, deadline=deadline, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Deadline removed"),
 | 
			
		||||
        msg_success=DEADLINE_REMOVED,
 | 
			
		||||
        redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -433,11 +509,36 @@ def state_remove_view(request: HttpRequest, id: str, state_id: str):
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    comp = get_object_or_404(Compensation, id=id)
 | 
			
		||||
    state = get_object_or_404(CompensationState, id=state_id)
 | 
			
		||||
    form = RemoveModalForm(request.POST or None, instance=state, request=request)
 | 
			
		||||
    form = RemoveCompensationStateModalForm(request.POST or None, instance=comp, state=state, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("State removed"),
 | 
			
		||||
        msg_success=COMPENSATION_STATE_REMOVED,
 | 
			
		||||
        redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(Compensation, "id")
 | 
			
		||||
def state_edit_view(request: HttpRequest, id: str, state_id: str):
 | 
			
		||||
    """ Renders a form for editing a compensation state
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The compensation's id
 | 
			
		||||
        state_id (str): The state's id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    comp = get_object_or_404(Compensation, id=id)
 | 
			
		||||
    state = get_object_or_404(CompensationState, id=state_id)
 | 
			
		||||
    form = EditCompensationStateModalForm(request.POST or None, instance=comp, state=state, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=COMPENSATION_STATE_EDITED,
 | 
			
		||||
        redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -456,11 +557,12 @@ def action_remove_view(request: HttpRequest, id: str, action_id: str):
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    comp = get_object_or_404(Compensation, id=id)
 | 
			
		||||
    action = get_object_or_404(CompensationAction, id=action_id)
 | 
			
		||||
    form = RemoveModalForm(request.POST or None, instance=action, request=request)
 | 
			
		||||
    form = RemoveCompensationActionModalForm(request.POST or None, instance=comp, action=action, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Action removed"),
 | 
			
		||||
        msg_success=COMPENSATION_ACTION_REMOVED,
 | 
			
		||||
        redirect_url=reverse("compensation:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -16,21 +16,26 @@ from django.shortcuts import render, get_object_or_404, redirect
 | 
			
		||||
 | 
			
		||||
from compensation.forms.forms import NewEcoAccountForm, EditEcoAccountForm
 | 
			
		||||
from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm, \
 | 
			
		||||
    NewEcoAccountDocumentForm
 | 
			
		||||
    NewEcoAccountDocumentModalForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm, \
 | 
			
		||||
    EditCompensationStateModalForm, EditCompensationActionModalForm, EditDeadlineModalForm
 | 
			
		||||
from compensation.models import EcoAccount, EcoAccountDocument, CompensationState, CompensationAction
 | 
			
		||||
from compensation.tables import EcoAccountTable
 | 
			
		||||
from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm
 | 
			
		||||
from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm, RemoveEcoAccountDeductionModalForm, \
 | 
			
		||||
    EditEcoAccountDeductionModalForm
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
from konova.decorators import any_group_check, default_group_required, conservation_office_group_required, \
 | 
			
		||||
    shared_access_required
 | 
			
		||||
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm, RecordModalForm
 | 
			
		||||
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentModalForm, RecordModalForm, \
 | 
			
		||||
    RemoveDeadlineModalForm, EditDocumentModalForm
 | 
			
		||||
from konova.models import Deadline
 | 
			
		||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 | 
			
		||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 | 
			
		||||
from konova.utils.documents import get_document, remove_document
 | 
			
		||||
from konova.utils.generators import generate_qr_code
 | 
			
		||||
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \
 | 
			
		||||
    CANCEL_ACC_RECORDED_OR_DEDUCTED, DEDUCTION_REMOVED, DEDUCTION_ADDED, DOCUMENT_ADDED
 | 
			
		||||
    CANCEL_ACC_RECORDED_OR_DEDUCTED, DEDUCTION_REMOVED, DEDUCTION_ADDED, DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, \
 | 
			
		||||
    COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, \
 | 
			
		||||
    DEDUCTION_EDITED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED
 | 
			
		||||
from konova.utils.user_checks import in_group
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -285,7 +290,7 @@ def deduction_remove_view(request: HttpRequest, id: str, deduction_id: str):
 | 
			
		||||
    except ObjectDoesNotExist:
 | 
			
		||||
        raise Http404("Unknown deduction")
 | 
			
		||||
 | 
			
		||||
    form = RemoveModalForm(request.POST or None, instance=eco_deduction, request=request)
 | 
			
		||||
    form = RemoveEcoAccountDeductionModalForm(request.POST or None, instance=acc, deduction=eco_deduction, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request=request,
 | 
			
		||||
        msg_success=DEDUCTION_REMOVED,
 | 
			
		||||
@ -293,6 +298,34 @@ def deduction_remove_view(request: HttpRequest, id: str, deduction_id: str):
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(EcoAccount, "id")
 | 
			
		||||
def deduction_edit_view(request: HttpRequest, id: str, deduction_id: str):
 | 
			
		||||
    """ Renders a modal view for editing deductions
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The eco account's id
 | 
			
		||||
        deduction_id (str): The deduction's id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    acc = get_object_or_404(EcoAccount, id=id)
 | 
			
		||||
    try:
 | 
			
		||||
        eco_deduction = acc.deductions.get(id=deduction_id)
 | 
			
		||||
    except ObjectDoesNotExist:
 | 
			
		||||
        raise Http404("Unknown deduction")
 | 
			
		||||
 | 
			
		||||
    form = EditEcoAccountDeductionModalForm(request.POST or None, instance=acc, deduction=eco_deduction, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request=request,
 | 
			
		||||
        msg_success=DEDUCTION_EDITED,
 | 
			
		||||
        redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(EcoAccount, "id")
 | 
			
		||||
@ -359,7 +392,7 @@ def state_new_view(request: HttpRequest, id: str):
 | 
			
		||||
    form = NewStateModalForm(request.POST or None, instance=acc, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("State added"),
 | 
			
		||||
        msg_success=COMPENSATION_STATE_ADDED,
 | 
			
		||||
        redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -381,7 +414,7 @@ def action_new_view(request: HttpRequest, id: str):
 | 
			
		||||
    form = NewActionModalForm(request.POST or None, instance=acc, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Action added"),
 | 
			
		||||
        msg_success=COMPENSATION_ACTION_ADDED,
 | 
			
		||||
        redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -400,11 +433,36 @@ def state_remove_view(request: HttpRequest, id: str, state_id: str):
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    acc = get_object_or_404(EcoAccount, id=id)
 | 
			
		||||
    state = get_object_or_404(CompensationState, id=state_id)
 | 
			
		||||
    form = RemoveModalForm(request.POST or None, instance=state, request=request)
 | 
			
		||||
    form = RemoveCompensationStateModalForm(request.POST or None, instance=acc, state=state, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("State removed"),
 | 
			
		||||
        msg_success=COMPENSATION_STATE_REMOVED,
 | 
			
		||||
        redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(EcoAccount, "id")
 | 
			
		||||
def state_edit_view(request: HttpRequest, id: str, state_id: str):
 | 
			
		||||
    """ Renders a form for editing a compensation state
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The compensation's id
 | 
			
		||||
        state_id (str): The state's id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    acc = get_object_or_404(EcoAccount, id=id)
 | 
			
		||||
    state = get_object_or_404(CompensationState, id=state_id)
 | 
			
		||||
    form = EditCompensationStateModalForm(request.POST or None, instance=acc, state=state, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=COMPENSATION_STATE_EDITED,
 | 
			
		||||
        redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -423,11 +481,60 @@ def action_remove_view(request: HttpRequest, id: str, action_id: str):
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    acc = get_object_or_404(EcoAccount, id=id)
 | 
			
		||||
    action = get_object_or_404(CompensationAction, id=action_id)
 | 
			
		||||
    form = RemoveModalForm(request.POST or None, instance=action, request=request)
 | 
			
		||||
    form = RemoveCompensationActionModalForm(request.POST or None, instance=acc, action=action, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Action removed"),
 | 
			
		||||
        msg_success=COMPENSATION_ACTION_REMOVED,
 | 
			
		||||
        redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(EcoAccount, "id")
 | 
			
		||||
def action_edit_view(request: HttpRequest, id: str, action_id: str):
 | 
			
		||||
    """ Renders a form for editing a compensation action
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The compensation's id
 | 
			
		||||
        id (str): The action's id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    acc = get_object_or_404(EcoAccount, id=id)
 | 
			
		||||
    action = get_object_or_404(CompensationAction, id=action_id)
 | 
			
		||||
    form = EditCompensationActionModalForm(request.POST or None, instance=acc, action=action, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=COMPENSATION_ACTION_EDITED,
 | 
			
		||||
        redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(EcoAccount, "id")
 | 
			
		||||
def deadline_edit_view(request: HttpRequest, id: str, deadline_id: str):
 | 
			
		||||
    """ Renders a form for editing deadlines from a compensation
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The compensation's id
 | 
			
		||||
        deadline_id (str): The deadline's id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    comp = get_object_or_404(EcoAccount, id=id)
 | 
			
		||||
    deadline = get_object_or_404(Deadline, id=deadline_id)
 | 
			
		||||
    form = EditDeadlineModalForm(request.POST or None, instance=comp, deadline=deadline, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=DEADLINE_EDITED,
 | 
			
		||||
        redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -446,11 +553,12 @@ def deadline_remove_view(request: HttpRequest, id: str, deadline_id: str):
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    comp = get_object_or_404(EcoAccount, id=id)
 | 
			
		||||
    deadline = get_object_or_404(Deadline, id=deadline_id)
 | 
			
		||||
    form = RemoveModalForm(request.POST or None, instance=deadline, request=request)
 | 
			
		||||
    form = RemoveDeadlineModalForm(request.POST or None, instance=comp, deadline=deadline, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Deadline removed"),
 | 
			
		||||
        msg_success=DEADLINE_REMOVED,
 | 
			
		||||
        redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -472,7 +580,7 @@ def deadline_new_view(request: HttpRequest, id: str):
 | 
			
		||||
    form = NewDeadlineModalForm(request.POST or None, instance=acc, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Deadline added"),
 | 
			
		||||
        msg_success=DEADLINE_ADDED,
 | 
			
		||||
        redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -490,7 +598,7 @@ def new_document_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    acc = get_object_or_404(EcoAccount, id=id)
 | 
			
		||||
    form = NewEcoAccountDocumentForm(request.POST or None, request.FILES or None, instance=acc, request=request)
 | 
			
		||||
    form = NewEcoAccountDocumentModalForm(request.POST or None, request.FILES or None, instance=acc, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=DOCUMENT_ADDED,
 | 
			
		||||
@ -500,46 +608,68 @@ def new_document_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
def get_document_view(request: HttpRequest, doc_id: str):
 | 
			
		||||
@shared_access_required(EcoAccount, "id")
 | 
			
		||||
def get_document_view(request: HttpRequest, id:str, doc_id: str):
 | 
			
		||||
    """ Returns the document as downloadable file
 | 
			
		||||
 | 
			
		||||
    Wraps the generic document fetcher function from konova.utils.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The account id
 | 
			
		||||
        doc_id (str): The document id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    acc = get_object_or_404(EcoAccount, id=id)
 | 
			
		||||
    doc = get_object_or_404(EcoAccountDocument, id=doc_id)
 | 
			
		||||
    user = request.user
 | 
			
		||||
    instance = doc.instance
 | 
			
		||||
    # File download only possible if related instance is shared with user
 | 
			
		||||
    if not instance.users.filter(id=user.id):
 | 
			
		||||
        messages.info(
 | 
			
		||||
            request,
 | 
			
		||||
            DATA_UNSHARED
 | 
			
		||||
        )
 | 
			
		||||
        return redirect("compensation:acc:detail", id=instance.id)
 | 
			
		||||
    return get_document(doc)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(EcoAccount, "id")
 | 
			
		||||
def remove_document_view(request: HttpRequest, doc_id: str):
 | 
			
		||||
def edit_document_view(request: HttpRequest, id: str, doc_id: str):
 | 
			
		||||
    """ Removes the document from the database and file system
 | 
			
		||||
 | 
			
		||||
    Wraps the generic functionality from konova.utils.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The account id
 | 
			
		||||
        doc_id (str): The document id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    acc = get_object_or_404(EcoAccount, id=id)
 | 
			
		||||
    doc = get_object_or_404(EcoAccountDocument, id=doc_id)
 | 
			
		||||
    form = EditDocumentModalForm(request.POST or None, request.FILES or None, instance=acc, document=doc, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        DOCUMENT_EDITED,
 | 
			
		||||
        reverse("compensation:acc:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(EcoAccount, "id")
 | 
			
		||||
def remove_document_view(request: HttpRequest, id: str, doc_id: str):
 | 
			
		||||
    """ Removes the document from the database and file system
 | 
			
		||||
 | 
			
		||||
    Wraps the generic functionality from konova.utils.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The account id
 | 
			
		||||
        doc_id (str): The document id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    acc = get_object_or_404(EcoAccount, id=id)
 | 
			
		||||
    doc = get_object_or_404(EcoAccountDocument, id=doc_id)
 | 
			
		||||
    return remove_document(
 | 
			
		||||
        request,
 | 
			
		||||
 | 
			
		||||
@ -11,52 +11,80 @@ from django.contrib.auth.decorators import login_required
 | 
			
		||||
from django.http import HttpRequest
 | 
			
		||||
from django.shortcuts import get_object_or_404
 | 
			
		||||
 | 
			
		||||
from compensation.forms.modalForms import NewPaymentForm
 | 
			
		||||
from compensation.forms.modalForms import NewPaymentForm, RemovePaymentModalForm, EditPaymentModalForm
 | 
			
		||||
from compensation.models import Payment
 | 
			
		||||
from intervention.models import Intervention
 | 
			
		||||
from konova.decorators import default_group_required
 | 
			
		||||
from konova.decorators import default_group_required, shared_access_required
 | 
			
		||||
from konova.forms import RemoveModalForm
 | 
			
		||||
from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED
 | 
			
		||||
from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED, PAYMENT_EDITED
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
def new_payment_view(request: HttpRequest, intervention_id: str):
 | 
			
		||||
@shared_access_required(Intervention, "id")
 | 
			
		||||
def new_payment_view(request: HttpRequest, id: str):
 | 
			
		||||
    """ Renders a modal view for adding new payments
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        intervention_id (str): The intervention's id for which a new payment shall be added
 | 
			
		||||
        id (str): The intervention's id for which a new payment shall be added
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    intervention = get_object_or_404(Intervention, id=intervention_id)
 | 
			
		||||
    intervention = get_object_or_404(Intervention, id=id)
 | 
			
		||||
    form = NewPaymentForm(request.POST or None, instance=intervention, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=PAYMENT_ADDED,
 | 
			
		||||
        redirect_url=reverse("intervention:detail", args=(intervention_id,)) + "#related_data"
 | 
			
		||||
        redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
def payment_remove_view(request: HttpRequest, id: str):
 | 
			
		||||
@shared_access_required(Intervention, "id")
 | 
			
		||||
def payment_remove_view(request: HttpRequest, id: str, payment_id: str):
 | 
			
		||||
    """ Renders a modal view for removing payments
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The payment's id
 | 
			
		||||
        id (str): The intervention's id
 | 
			
		||||
        payment_id (str): The payment's id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    payment = get_object_or_404(Payment, id=id)
 | 
			
		||||
    form = RemoveModalForm(request.POST or None, instance=payment, request=request)
 | 
			
		||||
    intervention = get_object_or_404(Intervention, id=id)
 | 
			
		||||
    payment = get_object_or_404(Payment, id=payment_id)
 | 
			
		||||
    form = RemovePaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request=request,
 | 
			
		||||
        msg_success=PAYMENT_REMOVED,
 | 
			
		||||
        redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(Intervention, "id")
 | 
			
		||||
def payment_edit_view(request: HttpRequest, id: str, payment_id: str):
 | 
			
		||||
    """ Renders a modal view for editing payments
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The intervention's id
 | 
			
		||||
        payment_id (str): The payment's id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    intervention = get_object_or_404(Intervention, id=id)
 | 
			
		||||
    payment = get_object_or_404(Payment, id=payment_id)
 | 
			
		||||
    form = EditPaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request=request,
 | 
			
		||||
        msg_success=PAYMENT_EDITED,
 | 
			
		||||
        redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@ from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from compensation.forms.forms import AbstractCompensationForm, CompensationResponsibleFormMixin
 | 
			
		||||
from ema.models import Ema, EmaDocument
 | 
			
		||||
from intervention.models import Responsibility
 | 
			
		||||
from konova.forms import SimpleGeomForm, NewDocumentForm
 | 
			
		||||
from konova.forms import SimpleGeomForm, NewDocumentModalForm
 | 
			
		||||
from user.models import UserActionLogEntry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -150,5 +150,5 @@ class EditEmaForm(NewEmaForm):
 | 
			
		||||
        return self.instance
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewEmaDocumentForm(NewDocumentForm):
 | 
			
		||||
class NewEmaDocumentModalForm(NewDocumentModalForm):
 | 
			
		||||
    document_model = EmaDocument
 | 
			
		||||
@ -51,28 +51,6 @@ class Ema(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin):
 | 
			
		||||
            self.identifier = new_id
 | 
			
		||||
        super().save(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_LANIS_link(self) -> str:
 | 
			
		||||
        """ Generates a link for LANIS depending on the geometry
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            geom = self.geometry.geom.transform(DEFAULT_SRID_RLP, clone=True)
 | 
			
		||||
            x = geom.centroid.x
 | 
			
		||||
            y = geom.centroid.y
 | 
			
		||||
            zoom_lvl = 16
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            # If no geometry has been added, yet.
 | 
			
		||||
            x = 1
 | 
			
		||||
            y = 1
 | 
			
		||||
            zoom_lvl = 6
 | 
			
		||||
        return LANIS_LINK_TEMPLATE.format(
 | 
			
		||||
            zoom_lvl,
 | 
			
		||||
            x,
 | 
			
		||||
            y,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def quality_check(self) -> EmaQualityChecker:
 | 
			
		||||
        """ Quality check
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ Created on: 19.08.21
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.http import HttpRequest
 | 
			
		||||
from django.template.loader import render_to_string
 | 
			
		||||
from django.utils.html import format_html
 | 
			
		||||
from django.utils.timezone import localtime
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
@ -34,6 +35,11 @@ class EmaTable(BaseTable, TableRenderMixin):
 | 
			
		||||
        orderable=True,
 | 
			
		||||
        accessor="title",
 | 
			
		||||
    )
 | 
			
		||||
    d = tables.Column(
 | 
			
		||||
        verbose_name=_("Parcel gmrkng"),
 | 
			
		||||
        orderable=True,
 | 
			
		||||
        accessor="geometry",
 | 
			
		||||
    )
 | 
			
		||||
    r = tables.Column(
 | 
			
		||||
        verbose_name=_("Recorded"),
 | 
			
		||||
        orderable=True,
 | 
			
		||||
@ -87,6 +93,29 @@ class EmaTable(BaseTable, TableRenderMixin):
 | 
			
		||||
        )
 | 
			
		||||
        return format_html(html)
 | 
			
		||||
 | 
			
		||||
    def render_d(self, value, record: Ema):
 | 
			
		||||
        """ Renders the parcel district column for a ema
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            value (str): The geometry
 | 
			
		||||
            record (Ema): The ema record
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        parcels = value.get_underlying_parcels().values_list(
 | 
			
		||||
            "gmrkng",
 | 
			
		||||
            flat=True
 | 
			
		||||
        ).distinct()
 | 
			
		||||
        html = render_to_string(
 | 
			
		||||
            "table/gmrkng_col.html",
 | 
			
		||||
            {
 | 
			
		||||
                "entries": parcels
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
        return html
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def render_r(self, value, record: Ema):
 | 
			
		||||
        """ Renders the registered column for a EMA
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                    {% trans 'Comment' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                <th class="w-10" scope="col">
 | 
			
		||||
                    <span class="float-right">
 | 
			
		||||
                        {% trans 'Action' %}
 | 
			
		||||
                    </span>
 | 
			
		||||
@ -58,9 +58,12 @@
 | 
			
		||||
                        {{ action.comment }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="">
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'ema:action-remove' obj.id action.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove action' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'ema:action-edit' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit action' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'ema:action-remove' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove action' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                    {% trans 'Comment' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                <th class="w-10" scope="col">
 | 
			
		||||
                    <span class="float-right">
 | 
			
		||||
                        {% trans 'Action' %}
 | 
			
		||||
                    </span>
 | 
			
		||||
@ -52,9 +52,12 @@
 | 
			
		||||
                        {{ deadline.comment }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'ema:deadline-remove' obj.id deadline.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove deadline' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'ema:deadline-edit' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit deadline' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'ema:deadline-remove' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove deadline' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -28,9 +28,12 @@
 | 
			
		||||
                    {% trans 'Title' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                    {% trans 'Comment' %}
 | 
			
		||||
                    {% trans 'Created on' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                    {% trans 'Comment' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                <th class="w-10" scope="col">
 | 
			
		||||
                    <span class="float-right">
 | 
			
		||||
                        {% trans 'Action' %}
 | 
			
		||||
                    </span>
 | 
			
		||||
@ -41,18 +44,24 @@
 | 
			
		||||
            {% for doc in obj.documents.all %}
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                    <a href="{% url 'ema:get-doc' doc.id %}">
 | 
			
		||||
                    <a href="{% url 'ema:get-doc' obj.id doc.id %}">
 | 
			
		||||
                        {{ doc.title }}
 | 
			
		||||
                    </a>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                    {{ doc.date_of_creation }}
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                    <div class="scroll-150">
 | 
			
		||||
                        {{ doc.comment }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'ema:remove-doc' doc.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove document' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'ema:edit-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit document' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'ema:remove-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove document' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,7 @@
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                    {% trans 'Surface' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                <th class="w-10" scope="col">
 | 
			
		||||
                    <span class="float-right">
 | 
			
		||||
                        {% trans 'Action' %}
 | 
			
		||||
                    </span>
 | 
			
		||||
@ -55,9 +55,12 @@
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </td>
 | 
			
		||||
                <td>{{ state.surface|floatformat:2 }} m²</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'ema:state-remove' obj.id state.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove state' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'ema:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'ema:state-remove' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove state' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,7 @@
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                    {% trans 'Surface' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                <th class="w-10" scope="col">
 | 
			
		||||
                    <span class="float-right">
 | 
			
		||||
                        {% trans 'Action' %}
 | 
			
		||||
                    </span>
 | 
			
		||||
@ -55,9 +55,12 @@
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </td>
 | 
			
		||||
                <td>{{ state.surface|floatformat:2 }} m²</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'ema:state-remove' obj.id state.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove state' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'ema:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'ema:state-remove' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove state' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -5,14 +5,15 @@ Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 26.10.21
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from django.db.models import Q
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.test.client import Client
 | 
			
		||||
 | 
			
		||||
from compensation.tests.test_views import CompensationViewTestCase
 | 
			
		||||
from compensation.tests.compensation.test_views import CompensationViewTestCase
 | 
			
		||||
from ema.models import Ema
 | 
			
		||||
from intervention.models import Responsibility
 | 
			
		||||
from konova.models import Geometry
 | 
			
		||||
from konova.models import Geometry, Deadline, DeadlineType
 | 
			
		||||
from konova.settings import DEFAULT_GROUP, ETS_GROUP
 | 
			
		||||
from user.models import UserActionLogEntry
 | 
			
		||||
 | 
			
		||||
@ -30,42 +31,57 @@ class EmaViewTestCase(CompensationViewTestCase):
 | 
			
		||||
    def setUpTestData(cls) -> None:
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        # Create dummy data and related objects, like states or actions
 | 
			
		||||
        cls.create_dummy_data()
 | 
			
		||||
        state = cls.create_dummy_states()
 | 
			
		||||
        action = cls.create_dummy_action()
 | 
			
		||||
        cls.ema.before_states.set([state])
 | 
			
		||||
        cls.ema.after_states.set([state])
 | 
			
		||||
        cls.ema.actions.set([action])
 | 
			
		||||
        self.create_dummy_data()
 | 
			
		||||
        state = self.create_dummy_states()
 | 
			
		||||
        action = self.create_dummy_action()
 | 
			
		||||
        self.ema.before_states.set([state])
 | 
			
		||||
        self.ema.after_states.set([state])
 | 
			
		||||
        self.ema.actions.set([action])
 | 
			
		||||
 | 
			
		||||
        # Prepare urls
 | 
			
		||||
        cls.index_url = reverse("ema:index", args=())
 | 
			
		||||
        cls.new_url = reverse("ema:new", args=())
 | 
			
		||||
        cls.new_id_url = reverse("ema:new-id", args=())
 | 
			
		||||
        cls.detail_url = reverse("ema:detail", args=(cls.ema.id,))
 | 
			
		||||
        cls.log_url = reverse("ema:log", args=(cls.ema.id,))
 | 
			
		||||
        cls.edit_url = reverse("ema:edit", args=(cls.ema.id,))
 | 
			
		||||
        cls.remove_url = reverse("ema:remove", args=(cls.ema.id,))
 | 
			
		||||
        cls.share_url = reverse("ema:share", args=(cls.ema.id, cls.ema.access_token,))
 | 
			
		||||
        cls.share_create_url = reverse("ema:share-create", args=(cls.ema.id,))
 | 
			
		||||
        cls.record_url = reverse("ema:record", args=(cls.ema.id,))
 | 
			
		||||
        cls.report_url = reverse("ema:report", args=(cls.ema.id,))
 | 
			
		||||
        cls.new_doc_url = reverse("ema:new-doc", args=(cls.ema.id,))
 | 
			
		||||
        cls.state_new_url = reverse("ema:new-state", args=(cls.ema.id,))
 | 
			
		||||
        cls.action_new_url = reverse("ema:new-action", args=(cls.ema.id,))
 | 
			
		||||
        cls.deadline_new_url = reverse("ema:new-deadline", args=(cls.ema.id,))
 | 
			
		||||
        cls.state_remove_url = reverse("ema:state-remove", args=(cls.ema.id, state.id,))
 | 
			
		||||
        cls.action_remove_url = reverse("ema:action-remove", args=(cls.ema.id, action.id,))
 | 
			
		||||
        self.index_url = reverse("ema:index", args=())
 | 
			
		||||
        self.new_url = reverse("ema:new", args=())
 | 
			
		||||
        self.new_id_url = reverse("ema:new-id", args=())
 | 
			
		||||
        self.detail_url = reverse("ema:detail", args=(self.ema.id,))
 | 
			
		||||
        self.log_url = reverse("ema:log", args=(self.ema.id,))
 | 
			
		||||
        self.edit_url = reverse("ema:edit", args=(self.ema.id,))
 | 
			
		||||
        self.remove_url = reverse("ema:remove", args=(self.ema.id,))
 | 
			
		||||
        self.share_url = reverse("ema:share", args=(self.ema.id, self.ema.access_token,))
 | 
			
		||||
        self.share_create_url = reverse("ema:share-create", args=(self.ema.id,))
 | 
			
		||||
        self.record_url = reverse("ema:record", args=(self.ema.id,))
 | 
			
		||||
        self.report_url = reverse("ema:report", args=(self.ema.id,))
 | 
			
		||||
        self.new_doc_url = reverse("ema:new-doc", args=(self.ema.id,))
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create_dummy_data(cls):
 | 
			
		||||
        self.state_new_url = reverse("ema:new-state", args=(self.ema.id,))
 | 
			
		||||
        self.state_edit_url = reverse("ema:state-edit", args=(self.ema.id, state.id))
 | 
			
		||||
        self.state_remove_url = reverse("ema:state-remove", args=(self.ema.id, state.id,))
 | 
			
		||||
 | 
			
		||||
        self.action_new_url = reverse("ema:new-action", args=(self.ema.id,))
 | 
			
		||||
        self.action_edit_url = reverse("ema:action-edit", args=(self.ema.id, action.id))
 | 
			
		||||
        self.action_remove_url = reverse("ema:action-remove", args=(self.ema.id, action.id,))
 | 
			
		||||
 | 
			
		||||
        self.deadline = Deadline.objects.get_or_create(
 | 
			
		||||
            type=DeadlineType.FINISHED,
 | 
			
		||||
            date="2020-01-01",
 | 
			
		||||
            comment="TESTCOMMENT",
 | 
			
		||||
        )[0]
 | 
			
		||||
        self.ema.deadlines.add(self.deadline)
 | 
			
		||||
 | 
			
		||||
        self.deadline_new_url = reverse("ema:new-deadline", args=(self.ema.id,))
 | 
			
		||||
        self.deadline_edit_url = reverse("ema:deadline-edit", args=(self.ema.id, self.deadline.id))
 | 
			
		||||
        self.deadline_remove_url = reverse("ema:deadline-remove", args=(self.ema.id, self.deadline.id))
 | 
			
		||||
 | 
			
		||||
    def create_dummy_data(self):
 | 
			
		||||
        # Create dummy data
 | 
			
		||||
        # Create log entry
 | 
			
		||||
        action = UserActionLogEntry.get_created_action(cls.superuser)
 | 
			
		||||
        action = UserActionLogEntry.get_created_action(self.superuser)
 | 
			
		||||
        # Create responsible data object
 | 
			
		||||
        responsibility_data = Responsibility.objects.create()
 | 
			
		||||
        geometry = Geometry.objects.create()
 | 
			
		||||
        cls.ema = Ema.objects.create(
 | 
			
		||||
        self.ema = Ema.objects.create(
 | 
			
		||||
            identifier="TEST",
 | 
			
		||||
            title="Test_title",
 | 
			
		||||
            created=action,
 | 
			
		||||
@ -106,10 +122,14 @@ class EmaViewTestCase(CompensationViewTestCase):
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.state_edit_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.deadline_edit_url,
 | 
			
		||||
            self.deadline_remove_url,
 | 
			
		||||
            self.action_edit_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
@ -152,10 +172,14 @@ class EmaViewTestCase(CompensationViewTestCase):
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.state_edit_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.action_edit_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.deadline_edit_url,
 | 
			
		||||
            self.deadline_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
@ -189,9 +213,13 @@ class EmaViewTestCase(CompensationViewTestCase):
 | 
			
		||||
            self.new_id_url,
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.state_edit_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.deadline_edit_url,
 | 
			
		||||
            self.deadline_remove_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.action_edit_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
@ -227,9 +255,13 @@ class EmaViewTestCase(CompensationViewTestCase):
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.state_new_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.state_edit_url,
 | 
			
		||||
            self.state_remove_url,
 | 
			
		||||
            self.deadline_new_url,
 | 
			
		||||
            self.deadline_edit_url,
 | 
			
		||||
            self.deadline_remove_url,
 | 
			
		||||
            self.action_new_url,
 | 
			
		||||
            self.action_edit_url,
 | 
			
		||||
            self.action_remove_url,
 | 
			
		||||
            self.new_doc_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										165
									
								
								ema/tests/test_workflow.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								ema/tests/test_workflow.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,165 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 08.02.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
import datetime
 | 
			
		||||
 | 
			
		||||
from django.contrib.gis.geos import MultiPolygon
 | 
			
		||||
from django.core.exceptions import ObjectDoesNotExist
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from ema.models import Ema
 | 
			
		||||
from konova.settings import ETS_GROUP
 | 
			
		||||
from konova.tests.test_views import BaseWorkflowTestCase
 | 
			
		||||
from user.models import UserAction
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EmaWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
    ema = None
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls) -> None:
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        # Create a fresh dummy (non-valid) compensation before each test
 | 
			
		||||
        self.ema = self.create_dummy_ema()
 | 
			
		||||
 | 
			
		||||
    def test_new(self):
 | 
			
		||||
        """ Test the creation of an Ema
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.superuser.groups.add(self.groups.get(name=ETS_GROUP))
 | 
			
		||||
        # Prepare url and form data to be posted
 | 
			
		||||
        new_url = reverse("ema:new")
 | 
			
		||||
        test_id = self.create_dummy_string()
 | 
			
		||||
        test_title = self.create_dummy_string()
 | 
			
		||||
        test_geom = self.create_dummy_geometry()
 | 
			
		||||
        test_conservation_office = self.get_conservation_office_code()
 | 
			
		||||
        post_data = {
 | 
			
		||||
            "identifier": test_id,
 | 
			
		||||
            "title": test_title,
 | 
			
		||||
            "geom": test_geom.geojson,
 | 
			
		||||
            "conservation_office": test_conservation_office.id
 | 
			
		||||
        }
 | 
			
		||||
        self.client_user.post(new_url, post_data)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            ema = Ema.objects.get(
 | 
			
		||||
                identifier=test_id
 | 
			
		||||
            )
 | 
			
		||||
        except ObjectDoesNotExist:
 | 
			
		||||
            self.fail(msg="Ema not created")
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(ema.identifier, test_id)
 | 
			
		||||
        self.assertEqual(ema.title, test_title)
 | 
			
		||||
        self.assert_equal_geometries(ema.geometry.geom, test_geom)
 | 
			
		||||
        self.assertEqual(ema.log.count(), 1)
 | 
			
		||||
 | 
			
		||||
        # Expect logs to be set
 | 
			
		||||
        self.assertEqual(ema.log.count(), 1)
 | 
			
		||||
        self.assertEqual(ema.log.first().action, UserAction.CREATED)
 | 
			
		||||
 | 
			
		||||
    def test_edit(self):
 | 
			
		||||
        """ Checks that the editing of an Ema works
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.superuser.groups.add(self.groups.get(name=ETS_GROUP))
 | 
			
		||||
        self.ema.users.add(self.superuser)
 | 
			
		||||
        url = reverse("ema:edit", args=(self.ema.id,))
 | 
			
		||||
        self.ema = self.fill_out_ema(self.ema)
 | 
			
		||||
        pre_edit_log_count = self.ema.log.count()
 | 
			
		||||
 | 
			
		||||
        new_title = self.create_dummy_string()
 | 
			
		||||
        new_identifier = self.create_dummy_string()
 | 
			
		||||
        new_comment = self.create_dummy_string()
 | 
			
		||||
        new_geometry = MultiPolygon(srid=4326)  # Create an empty geometry
 | 
			
		||||
        test_conservation_office = self.get_conservation_office_code()
 | 
			
		||||
 | 
			
		||||
        check_on_elements = {
 | 
			
		||||
            self.ema.title: new_title,
 | 
			
		||||
            self.ema.identifier: new_identifier,
 | 
			
		||||
            self.ema.comment: new_comment,
 | 
			
		||||
        }
 | 
			
		||||
        for k, v in check_on_elements.items():
 | 
			
		||||
            self.assertNotEqual(k, v)
 | 
			
		||||
 | 
			
		||||
        post_data = {
 | 
			
		||||
            "identifier": new_identifier,
 | 
			
		||||
            "title": new_title,
 | 
			
		||||
            "comment": new_comment,
 | 
			
		||||
            "geom": new_geometry.geojson,
 | 
			
		||||
            "conservation_office": test_conservation_office.id
 | 
			
		||||
        }
 | 
			
		||||
        self.client_user.post(url, post_data)
 | 
			
		||||
        self.ema.refresh_from_db()
 | 
			
		||||
 | 
			
		||||
        check_on_elements = {
 | 
			
		||||
            self.ema.title: new_title,
 | 
			
		||||
            self.ema.identifier: new_identifier,
 | 
			
		||||
            self.ema.comment: new_comment,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for k, v in check_on_elements.items():
 | 
			
		||||
            self.assertEqual(k, v)
 | 
			
		||||
 | 
			
		||||
        self.assert_equal_geometries(self.ema.geometry.geom, new_geometry)
 | 
			
		||||
 | 
			
		||||
        # Expect logs to be set
 | 
			
		||||
        self.assertEqual(pre_edit_log_count + 1, self.ema.log.count())
 | 
			
		||||
        self.assertEqual(self.ema.log.first().action, UserAction.EDITED)
 | 
			
		||||
 | 
			
		||||
    def test_recordability(self):
 | 
			
		||||
        """
 | 
			
		||||
        This tests if the recordability of the Ema is triggered by the quality of it's data (e.g. not all fields filled)
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        # Add proper privilege for the user
 | 
			
		||||
        self.superuser.groups.add(self.groups.get(name=ETS_GROUP))
 | 
			
		||||
        self.ema.users.add(self.superuser)
 | 
			
		||||
        pre_record_log_count = self.ema.log.count()
 | 
			
		||||
 | 
			
		||||
        # Prepare url and form data
 | 
			
		||||
        record_url = reverse("ema:record", args=(self.ema.id,))
 | 
			
		||||
        post_data = {
 | 
			
		||||
            "confirm": True,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        # Make sure the ema is not recorded
 | 
			
		||||
        self.assertIsNone(self.ema.recorded)
 | 
			
		||||
 | 
			
		||||
        # Run the request --> expect fail, since the Ema is not valid, yet
 | 
			
		||||
        self.client_user.post(record_url, post_data)
 | 
			
		||||
 | 
			
		||||
        # Check that the Ema is still not recorded
 | 
			
		||||
        self.assertIsNone(self.ema.recorded)
 | 
			
		||||
 | 
			
		||||
        # Now fill out the data for a compensation
 | 
			
		||||
        self.ema = self.fill_out_ema(self.ema)
 | 
			
		||||
 | 
			
		||||
        # Rerun the request
 | 
			
		||||
        self.client_user.post(record_url, post_data)
 | 
			
		||||
 | 
			
		||||
        # Expect the Ema now to be recorded
 | 
			
		||||
        # Attention: We can only test the date part of the timestamp,
 | 
			
		||||
        # since the delay in microseconds would lead to fail
 | 
			
		||||
        self.ema.refresh_from_db()
 | 
			
		||||
        recorded = self.ema.recorded
 | 
			
		||||
        self.assertIsNotNone(recorded)
 | 
			
		||||
        self.assertEqual(self.superuser, recorded.user)
 | 
			
		||||
        self.assertEqual(UserAction.RECORDED, recorded.action)
 | 
			
		||||
        self.assertEqual(datetime.date.today(), recorded.timestamp.date())
 | 
			
		||||
 | 
			
		||||
        # Expect the user action to be in the log
 | 
			
		||||
        self.assertIn(recorded, self.ema.log.all())
 | 
			
		||||
        self.assertEqual(pre_record_log_count + 1, self.ema.log.count())
 | 
			
		||||
							
								
								
									
										16
									
								
								ema/urls.py
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								ema/urls.py
									
									
									
									
									
								
							@ -19,18 +19,26 @@ urlpatterns = [
 | 
			
		||||
    path('<id>/remove', remove_view, name='remove'),
 | 
			
		||||
    path('<id>/record', record_view, name='record'),
 | 
			
		||||
    path('<id>/report', report_view, name='report'),
 | 
			
		||||
 | 
			
		||||
    path('<id>/state/new', state_new_view, name='new-state'),
 | 
			
		||||
    path('<id>/action/new', action_new_view, name='new-action'),
 | 
			
		||||
    path('<id>/state/<state_id>/remove', state_remove_view, name='state-remove'),
 | 
			
		||||
    path('<id>/state/<state_id>/edit', state_edit_view, name='state-edit'),
 | 
			
		||||
 | 
			
		||||
    path('<id>/action/new', action_new_view, name='new-action'),
 | 
			
		||||
    path('<id>/action/<action_id>/edit', action_edit_view, name='action-edit'),
 | 
			
		||||
    path('<id>/action/<action_id>/remove', action_remove_view, name='action-remove'),
 | 
			
		||||
    path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
 | 
			
		||||
 | 
			
		||||
    path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
 | 
			
		||||
    path('<id>/deadline/<deadline_id>/edit', deadline_edit_view, name='deadline-edit'),
 | 
			
		||||
    path('<id>/deadline/<deadline_id>/remove', deadline_remove_view, name='deadline-remove'),
 | 
			
		||||
 | 
			
		||||
    path('<id>/share/<token>', share_view, name='share'),
 | 
			
		||||
    path('<id>/share', create_share_view, name='share-create'),
 | 
			
		||||
 | 
			
		||||
    # Documents
 | 
			
		||||
    path('<id>/document/new/', document_new_view, name='new-doc'),
 | 
			
		||||
    path('document/<doc_id>', get_document_view, name='get-doc'),
 | 
			
		||||
    path('document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
 | 
			
		||||
    path('<id>/document/<doc_id>', get_document_view, name='get-doc'),
 | 
			
		||||
    path('<id>/document/<doc_id>/edit/', edit_document_view, name='edit-doc'),
 | 
			
		||||
    path('<id>/document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
 | 
			
		||||
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										153
									
								
								ema/views.py
									
									
									
									
									
								
							
							
						
						
									
										153
									
								
								ema/views.py
									
									
									
									
									
								
							@ -6,22 +6,27 @@ from django.shortcuts import render, get_object_or_404, redirect
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm
 | 
			
		||||
from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm, \
 | 
			
		||||
    RemoveCompensationActionModalForm, RemoveCompensationStateModalForm, EditCompensationStateModalForm, \
 | 
			
		||||
    EditCompensationActionModalForm, EditDeadlineModalForm
 | 
			
		||||
from compensation.models import CompensationAction, CompensationState
 | 
			
		||||
from ema.forms import NewEmaForm, EditEmaForm, NewEmaDocumentForm
 | 
			
		||||
from ema.forms import NewEmaForm, EditEmaForm, NewEmaDocumentModalForm
 | 
			
		||||
from ema.tables import EmaTable
 | 
			
		||||
from intervention.forms.modalForms import ShareModalForm
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
from konova.decorators import conservation_office_group_required, shared_access_required
 | 
			
		||||
from ema.models import Ema, EmaDocument
 | 
			
		||||
from konova.forms import RemoveModalForm, SimpleGeomForm, RecordModalForm
 | 
			
		||||
from konova.forms import RemoveModalForm, SimpleGeomForm, RecordModalForm, RemoveDeadlineModalForm, \
 | 
			
		||||
    EditDocumentModalForm
 | 
			
		||||
from konova.models import Deadline
 | 
			
		||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 | 
			
		||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 | 
			
		||||
from konova.utils.documents import get_document, remove_document
 | 
			
		||||
from konova.utils.generators import generate_qr_code
 | 
			
		||||
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \
 | 
			
		||||
    DOCUMENT_ADDED
 | 
			
		||||
    DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, \
 | 
			
		||||
    COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, \
 | 
			
		||||
    COMPENSATION_ACTION_EDITED, DEADLINE_EDITED
 | 
			
		||||
from konova.utils.user_checks import in_group
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -292,7 +297,7 @@ def state_new_view(request: HttpRequest, id: str):
 | 
			
		||||
    form = NewStateModalForm(request.POST or None, instance=ema, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("State added"),
 | 
			
		||||
        msg_success=COMPENSATION_STATE_ADDED,
 | 
			
		||||
        redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -314,7 +319,31 @@ def action_new_view(request: HttpRequest, id: str):
 | 
			
		||||
    form = NewActionModalForm(request.POST or None, instance=ema, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Action added"),
 | 
			
		||||
        msg_success=COMPENSATION_ACTION_ADDED,
 | 
			
		||||
        redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@conservation_office_group_required
 | 
			
		||||
@shared_access_required(Ema, "id")
 | 
			
		||||
def action_edit_view(request: HttpRequest, id: str, action_id: str):
 | 
			
		||||
    """ Renders a form for editing an actions for an EMA
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The EMA's id
 | 
			
		||||
        action_id (str): The action id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    ema = get_object_or_404(Ema, id=id)
 | 
			
		||||
    action = get_object_or_404(CompensationAction, id=action_id)
 | 
			
		||||
    form = EditCompensationActionModalForm(request.POST or None, instance=ema, action=action, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=COMPENSATION_ACTION_EDITED,
 | 
			
		||||
        redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -336,7 +365,7 @@ def deadline_new_view(request: HttpRequest, id: str):
 | 
			
		||||
    form = NewDeadlineModalForm(request.POST or None, instance=ema, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Deadline added"),
 | 
			
		||||
        msg_success=DEADLINE_ADDED,
 | 
			
		||||
        redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -354,7 +383,7 @@ def document_new_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    ema = get_object_or_404(Ema, id=id)
 | 
			
		||||
    form = NewEmaDocumentForm(request.POST or None, request.FILES or None, instance=ema, request=request)
 | 
			
		||||
    form = NewEmaDocumentModalForm(request.POST or None, request.FILES or None, instance=ema, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=DOCUMENT_ADDED,
 | 
			
		||||
@ -364,45 +393,68 @@ def document_new_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@conservation_office_group_required
 | 
			
		||||
def get_document_view(request: HttpRequest, doc_id: str):
 | 
			
		||||
@shared_access_required(Ema, "id")
 | 
			
		||||
def get_document_view(request: HttpRequest, id: str, doc_id: str):
 | 
			
		||||
    """ Returns the document as downloadable file
 | 
			
		||||
 | 
			
		||||
    Wraps the generic document fetcher function from konova.utils.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The EMA id
 | 
			
		||||
        doc_id (str): The document id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    ema = get_object_or_404(Ema, id=id)
 | 
			
		||||
    doc = get_object_or_404(EmaDocument, id=doc_id)
 | 
			
		||||
    user = request.user
 | 
			
		||||
    instance = doc.instance
 | 
			
		||||
    # File download only possible if related instance is shared with user
 | 
			
		||||
    if not instance.users.filter(id=user.id):
 | 
			
		||||
        messages.info(
 | 
			
		||||
            request,
 | 
			
		||||
            DATA_UNSHARED
 | 
			
		||||
        )
 | 
			
		||||
        return redirect("ema:detail", id=instance.id)
 | 
			
		||||
    return get_document(doc)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@conservation_office_group_required
 | 
			
		||||
def remove_document_view(request: HttpRequest, doc_id: str):
 | 
			
		||||
@shared_access_required(Ema, "id")
 | 
			
		||||
def edit_document_view(request: HttpRequest, id: str, doc_id: str):
 | 
			
		||||
    """ Removes the document from the database and file system
 | 
			
		||||
 | 
			
		||||
    Wraps the generic functionality from konova.utils.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The EMA id
 | 
			
		||||
        doc_id (str): The document id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    ema = get_object_or_404(Ema, id=id)
 | 
			
		||||
    doc = get_object_or_404(EmaDocument, id=doc_id)
 | 
			
		||||
    form = EditDocumentModalForm(request.POST or None, request.FILES or None, instance=ema, document=doc, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        DOCUMENT_EDITED,
 | 
			
		||||
        reverse("ema:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@conservation_office_group_required
 | 
			
		||||
@shared_access_required(Ema, "id")
 | 
			
		||||
def remove_document_view(request: HttpRequest, id:str, doc_id: str):
 | 
			
		||||
    """ Removes the document from the database and file system
 | 
			
		||||
 | 
			
		||||
    Wraps the generic functionality from konova.utils.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The EMA id
 | 
			
		||||
        doc_id (str): The document id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    ema = get_object_or_404(Ema, id=id)
 | 
			
		||||
    doc = get_object_or_404(EmaDocument, id=doc_id)
 | 
			
		||||
    return remove_document(
 | 
			
		||||
        request,
 | 
			
		||||
@ -424,11 +476,36 @@ def state_remove_view(request: HttpRequest, id: str, state_id: str):
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    ema = get_object_or_404(Ema, id=id)
 | 
			
		||||
    state = get_object_or_404(CompensationState, id=state_id)
 | 
			
		||||
    form = RemoveModalForm(request.POST or None, instance=state, request=request)
 | 
			
		||||
    form = RemoveCompensationStateModalForm(request.POST or None, instance=ema, state=state, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("State removed"),
 | 
			
		||||
        msg_success=COMPENSATION_STATE_REMOVED,
 | 
			
		||||
        redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@conservation_office_group_required
 | 
			
		||||
@shared_access_required(Ema, "id")
 | 
			
		||||
def state_edit_view(request: HttpRequest, id: str, state_id: str):
 | 
			
		||||
    """ Renders a form for editing an EMA state
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The ema id
 | 
			
		||||
        state_id (str): The state's id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    ema = get_object_or_404(Ema, id=id)
 | 
			
		||||
    state = get_object_or_404(CompensationState, id=state_id)
 | 
			
		||||
    form = EditCompensationStateModalForm(request.POST or None, instance=ema, state=state, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=COMPENSATION_STATE_EDITED,
 | 
			
		||||
        redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -447,11 +524,12 @@ def action_remove_view(request: HttpRequest, id: str, action_id: str):
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    ema = get_object_or_404(Ema, id=id)
 | 
			
		||||
    action = get_object_or_404(CompensationAction, id=action_id)
 | 
			
		||||
    form = RemoveModalForm(request.POST or None, instance=action, request=request)
 | 
			
		||||
    form = RemoveCompensationActionModalForm(request.POST or None, instance=ema, action=action, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Action removed"),
 | 
			
		||||
        msg_success=COMPENSATION_ACTION_REMOVED,
 | 
			
		||||
        redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -575,6 +653,30 @@ def create_share_view(request: HttpRequest, id: str):
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@conservation_office_group_required
 | 
			
		||||
@shared_access_required(Ema, "id")
 | 
			
		||||
def deadline_edit_view(request: HttpRequest, id: str, deadline_id: str):
 | 
			
		||||
    """ Renders a form for editing deadlines from a compensation
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The compensation's id
 | 
			
		||||
        deadline_id (str): The deadline's id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    ema = get_object_or_404(Ema, id=id)
 | 
			
		||||
    deadline = get_object_or_404(Deadline, id=deadline_id)
 | 
			
		||||
    form = EditDeadlineModalForm(request.POST or None, instance=ema, deadline=deadline, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=DEADLINE_EDITED,
 | 
			
		||||
        redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@conservation_office_group_required
 | 
			
		||||
@shared_access_required(Ema, "id")
 | 
			
		||||
@ -589,10 +691,11 @@ def deadline_remove_view(request: HttpRequest, id: str, deadline_id: str):
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    ema = get_object_or_404(Ema, id=id)
 | 
			
		||||
    deadline = get_object_or_404(Deadline, id=deadline_id)
 | 
			
		||||
    form = RemoveModalForm(request.POST or None, instance=deadline, request=request)
 | 
			
		||||
    form = RemoveDeadlineModalForm(request.POST or None, instance=ema, deadline=deadline, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=_("Deadline removed"),
 | 
			
		||||
        msg_success=DEADLINE_REMOVED,
 | 
			
		||||
        redirect_url=reverse("ema:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
@ -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
 | 
			
		||||
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,8 +18,8 @@ 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 konova.forms import BaseModalForm, NewDocumentForm
 | 
			
		||||
from intervention.models import Intervention, InterventionDocument, RevocationDocument
 | 
			
		||||
from konova.forms import BaseModalForm, NewDocumentModalForm, 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)
 | 
			
		||||
@ -172,6 +176,46 @@ class NewRevocationModalForm(BaseModalForm):
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
    Can be used for anything, where removing shall be confirmed by the user a second time.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    revocation = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        revocation = kwargs.pop("revocation", None)
 | 
			
		||||
        self.revocation = revocation
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        self.instance.remove_revocation(self)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CheckModalForm(BaseModalForm):
 | 
			
		||||
    """ The modal form for running a check on interventions and their compensations
 | 
			
		||||
 | 
			
		||||
@ -332,6 +376,21 @@ class NewDeductionModalForm(BaseModalForm):
 | 
			
		||||
        else:
 | 
			
		||||
            raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def _get_available_surface(self, acc):
 | 
			
		||||
        """ Calculates how much available surface is left on the account
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            acc (EcoAccount):
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        # Calculate valid surface
 | 
			
		||||
        deductable_surface = acc.deductable_surface
 | 
			
		||||
        sum_surface_deductions = acc.get_deductions_surface()
 | 
			
		||||
        rest_surface = deductable_surface - sum_surface_deductions
 | 
			
		||||
        return rest_surface
 | 
			
		||||
 | 
			
		||||
    def is_valid(self):
 | 
			
		||||
        """ Custom validity check
 | 
			
		||||
 | 
			
		||||
@ -350,10 +409,7 @@ class NewDeductionModalForm(BaseModalForm):
 | 
			
		||||
            )
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        # Calculate valid surface
 | 
			
		||||
        deductable_surface = acc.deductable_surface
 | 
			
		||||
        sum_surface_deductions = acc.get_deductions_surface()
 | 
			
		||||
        rest_surface = deductable_surface - sum_surface_deductions
 | 
			
		||||
        rest_surface = self._get_available_surface(acc)
 | 
			
		||||
        form_surface = float(self.cleaned_data["surface"])
 | 
			
		||||
        is_valid_surface = form_surface <= rest_surface
 | 
			
		||||
        if not is_valid_surface:
 | 
			
		||||
@ -386,9 +442,80 @@ class NewDeductionModalForm(BaseModalForm):
 | 
			
		||||
    def save(self):
 | 
			
		||||
        deduction = self.__create_deduction()
 | 
			
		||||
        self.cleaned_data["intervention"].mark_as_edited(self.user, edit_comment=DEDUCTION_ADDED)
 | 
			
		||||
        self.cleaned_data["account"].mark_as_edited(self.user, edit_comment=DEDUCTION_ADDED, reset_recorded=False)
 | 
			
		||||
        self.cleaned_data["account"].mark_as_edited(self.user, edit_comment=DEDUCTION_ADDED)
 | 
			
		||||
        return deduction
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewInterventionDocumentForm(NewDocumentForm):
 | 
			
		||||
class EditEcoAccountDeductionModalForm(NewDeductionModalForm):
 | 
			
		||||
    deduction = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.deduction = kwargs.pop("deduction", None)
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        form_data = {
 | 
			
		||||
            "account": self.deduction.account,
 | 
			
		||||
            "intervention": self.deduction.intervention,
 | 
			
		||||
            "surface": self.deduction.surface,
 | 
			
		||||
        }
 | 
			
		||||
        self.load_initial_data(form_data)
 | 
			
		||||
 | 
			
		||||
    def _get_available_surface(self, acc):
 | 
			
		||||
        rest_surface = super()._get_available_surface(acc)
 | 
			
		||||
        # Increase available surface by the currently deducted surface, so we can 'deduct' the same amount again or
 | 
			
		||||
        # increase the surface only a little, which will still be valid.
 | 
			
		||||
        # Example: 200 m² left, 500 m² deducted. Entering 700 m² would fail if we would not add the 500 m² to the available
 | 
			
		||||
        # surface again.
 | 
			
		||||
        rest_surface += self.deduction.surface
 | 
			
		||||
        return rest_surface
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        deduction = self.deduction
 | 
			
		||||
        form_account = self.cleaned_data.get("account", None)
 | 
			
		||||
        form_intervention = self.cleaned_data.get("intervention", None)
 | 
			
		||||
        current_account = deduction.account
 | 
			
		||||
        current_intervention = deduction.intervention
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        # If account or intervention has been changed, we put that change in the logs just as if the deduction has
 | 
			
		||||
        # been removed for this entry. Act as if the deduction is newly created for the new entries
 | 
			
		||||
        if current_account != form_account:
 | 
			
		||||
            current_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
 | 
			
		||||
            form_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
 | 
			
		||||
        else:
 | 
			
		||||
            current_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
 | 
			
		||||
 | 
			
		||||
        if current_intervention != form_intervention:
 | 
			
		||||
            current_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
 | 
			
		||||
            form_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
 | 
			
		||||
        else:
 | 
			
		||||
            current_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
 | 
			
		||||
 | 
			
		||||
        deduction.account = form_account
 | 
			
		||||
        deduction.intervention = self.cleaned_data.get("intervention", None)
 | 
			
		||||
        deduction.surface = self.cleaned_data.get("surface", None)
 | 
			
		||||
        deduction.save()
 | 
			
		||||
        return deduction
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RemoveEcoAccountDeductionModalForm(RemoveModalForm):
 | 
			
		||||
    """ Removing modal form for EcoAccountDeduction
 | 
			
		||||
 | 
			
		||||
    Can be used for anything, where removing shall be confirmed by the user a second time.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    deduction = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        deduction = kwargs.pop("deduction", None)
 | 
			
		||||
        self.deduction = deduction
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            self.deduction.intervention.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
 | 
			
		||||
            self.deduction.account.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
 | 
			
		||||
            self.deduction.delete()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewInterventionDocumentModalForm(NewDocumentModalForm):
 | 
			
		||||
    document_model = InterventionDocument
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -16,7 +18,6 @@ from django.db import models, transaction
 | 
			
		||||
from django.db.models import QuerySet
 | 
			
		||||
from django.http import HttpRequest
 | 
			
		||||
 | 
			
		||||
from compensation.models import EcoAccountDeduction
 | 
			
		||||
from intervention.managers import InterventionManager
 | 
			
		||||
from intervention.models.legal import Legal
 | 
			
		||||
from intervention.models.responsibility import Responsibility
 | 
			
		||||
@ -26,7 +27,8 @@ from konova.models import generate_document_file_upload_path, AbstractDocument,
 | 
			
		||||
    ShareableObjectMixin, \
 | 
			
		||||
    RecordableObjectMixin, CheckableObjectMixin, GeoReferencedMixin
 | 
			
		||||
from konova.settings import LANIS_LINK_TEMPLATE, LANIS_ZOOM_LUT, DEFAULT_SRID_RLP
 | 
			
		||||
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, DEDUCTION_ADDED, DOCUMENT_REMOVED_TEMPLATE
 | 
			
		||||
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, DOCUMENT_REMOVED_TEMPLATE, \
 | 
			
		||||
    PAYMENT_REMOVED, PAYMENT_ADDED, REVOCATION_REMOVED, INTERVENTION_HAS_REVOCATIONS_TEMPLATE
 | 
			
		||||
from user.models import UserActionLogEntry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -100,34 +102,6 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
 | 
			
		||||
        checker.run_check()
 | 
			
		||||
        return checker
 | 
			
		||||
 | 
			
		||||
    def get_LANIS_link(self) -> str:
 | 
			
		||||
        """ Generates a link for LANIS depending on the geometry
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            geom = self.geometry.geom.transform(DEFAULT_SRID_RLP, clone=True)
 | 
			
		||||
            x = geom.centroid.x
 | 
			
		||||
            y = geom.centroid.y
 | 
			
		||||
            area = int(geom.envelope.area)
 | 
			
		||||
            z_l = 16
 | 
			
		||||
            for k_area, v_zoom in LANIS_ZOOM_LUT.items():
 | 
			
		||||
                if k_area < area:
 | 
			
		||||
                    z_l = v_zoom
 | 
			
		||||
                    break
 | 
			
		||||
            zoom_lvl = z_l
 | 
			
		||||
        except (AttributeError, IndexError) as e:
 | 
			
		||||
            # If no geometry has been added, yet.
 | 
			
		||||
            x = 1
 | 
			
		||||
            y = 1
 | 
			
		||||
            zoom_lvl = 6
 | 
			
		||||
        return LANIS_LINK_TEMPLATE.format(
 | 
			
		||||
            zoom_lvl,
 | 
			
		||||
            x,
 | 
			
		||||
            y,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def get_documents(self) -> (QuerySet, QuerySet):
 | 
			
		||||
        """ Getter for all documents of an intervention
 | 
			
		||||
 | 
			
		||||
@ -196,6 +170,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
 | 
			
		||||
                comment=form_data.get("comment", None),
 | 
			
		||||
                intervention=self,
 | 
			
		||||
            )
 | 
			
		||||
            self.mark_as_edited(user, form.request, edit_comment=PAYMENT_ADDED)
 | 
			
		||||
        return pay
 | 
			
		||||
 | 
			
		||||
    def add_revocation(self, form):
 | 
			
		||||
@ -229,6 +204,56 @@ 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
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            form (RemoveRevocationModalForm): The form holding all relevant data
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        revocation = form.revocation
 | 
			
		||||
        user = form.user
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            revocation.delete()
 | 
			
		||||
            self.mark_as_edited(user, request=form.request, edit_comment=REVOCATION_REMOVED)
 | 
			
		||||
 | 
			
		||||
    def mark_as_edited(self, performing_user: User, request: HttpRequest = None, edit_comment: str = None, reset_recorded: bool = True):
 | 
			
		||||
        """ In case the object or a related object changed, internal processes need to be started, such as
 | 
			
		||||
        unrecord and uncheck
 | 
			
		||||
@ -242,7 +267,9 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        action = super().mark_as_edited(performing_user, request, edit_comment, reset_recorded)
 | 
			
		||||
        action = super().mark_as_edited(performing_user, edit_comment=edit_comment)
 | 
			
		||||
        if reset_recorded:
 | 
			
		||||
            self.unrecord(performing_user, request)
 | 
			
		||||
        if self.checked:
 | 
			
		||||
            self.set_unchecked()
 | 
			
		||||
        return action
 | 
			
		||||
@ -258,6 +285,13 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
 | 
			
		||||
        Returns:
 | 
			
		||||
            request (HttpRequest): The modified request
 | 
			
		||||
        """
 | 
			
		||||
        # Inform user about revocation
 | 
			
		||||
        if self.legal.revocations.exists():
 | 
			
		||||
            messages.error(
 | 
			
		||||
                request,
 | 
			
		||||
                INTERVENTION_HAS_REVOCATIONS_TEMPLATE.format(self.legal.revocations.count()),
 | 
			
		||||
                extra_tags="danger",
 | 
			
		||||
            )
 | 
			
		||||
        if not self.is_shared_with(request.user):
 | 
			
		||||
            messages.info(request, DATA_UNSHARED_EXPLANATION)
 | 
			
		||||
        request = self.set_geometry_conflict_message(request)
 | 
			
		||||
@ -287,6 +321,21 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
 | 
			
		||||
        """
 | 
			
		||||
        return reverse("intervention:share", args=(self.id, self.access_token))
 | 
			
		||||
 | 
			
		||||
    def remove_payment(self, form):
 | 
			
		||||
        """ Removes a Payment from the intervention
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            form (RemovePaymentModalForm): The form holding all relevant data
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        payment = form.payment
 | 
			
		||||
        user = form.user
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            payment.delete()
 | 
			
		||||
            self.mark_as_edited(user, request=form.request, edit_comment=PAYMENT_REMOVED)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InterventionDocument(AbstractDocument):
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,7 @@ class Revocation(BaseResource):
 | 
			
		||||
    legal = models.ForeignKey("Legal", null=False, blank=False, on_delete=models.CASCADE, help_text="Refers to 'Widerspruch am'", related_name="revocations")
 | 
			
		||||
    comment = models.TextField(null=True, blank=True)
 | 
			
		||||
 | 
			
		||||
    def delete(self, user=None, *args, **kwargs):
 | 
			
		||||
    def delete(self, *args, **kwargs):
 | 
			
		||||
        # Make sure related objects are being removed as well
 | 
			
		||||
        try:
 | 
			
		||||
            self.document.delete(*args, **kwargs)
 | 
			
		||||
@ -31,9 +31,6 @@ class Revocation(BaseResource):
 | 
			
		||||
            # No file to delete
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if user is not None:
 | 
			
		||||
            self.legal.intervention.mark_as_edited(user, edit_comment=REVOCATION_REMOVED)
 | 
			
		||||
 | 
			
		||||
        super().delete()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ Created on: 01.12.20
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.http import HttpRequest
 | 
			
		||||
from django.template.loader import render_to_string
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.html import format_html
 | 
			
		||||
from django.utils.timezone import localtime
 | 
			
		||||
@ -29,6 +30,11 @@ class InterventionTable(BaseTable, TableRenderMixin):
 | 
			
		||||
        orderable=True,
 | 
			
		||||
        accessor="title",
 | 
			
		||||
    )
 | 
			
		||||
    d = tables.Column(
 | 
			
		||||
        verbose_name=_("Parcel gmrkng"),
 | 
			
		||||
        orderable=True,
 | 
			
		||||
        accessor="geometry",
 | 
			
		||||
    )
 | 
			
		||||
    c = tables.Column(
 | 
			
		||||
        verbose_name=_("Checked"),
 | 
			
		||||
        orderable=True,
 | 
			
		||||
@ -41,12 +47,6 @@ class InterventionTable(BaseTable, TableRenderMixin):
 | 
			
		||||
        empty_values=[],
 | 
			
		||||
        accessor="recorded",
 | 
			
		||||
    )
 | 
			
		||||
    rev = tables.Column(
 | 
			
		||||
        verbose_name=_("Revocation"),
 | 
			
		||||
        orderable=True,
 | 
			
		||||
        empty_values=[],
 | 
			
		||||
        accessor="legal__revocation",
 | 
			
		||||
    )
 | 
			
		||||
    e = tables.Column(
 | 
			
		||||
        verbose_name=_("Editable"),
 | 
			
		||||
        orderable=True,
 | 
			
		||||
@ -84,14 +84,17 @@ class InterventionTable(BaseTable, TableRenderMixin):
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        html = ""
 | 
			
		||||
        html += self.render_link(
 | 
			
		||||
            tooltip=_("Open {}").format(_("Intervention")),
 | 
			
		||||
            href=reverse("intervention:detail", args=(record.id,)),
 | 
			
		||||
            txt=value,
 | 
			
		||||
            new_tab=False,
 | 
			
		||||
        context = {
 | 
			
		||||
            "tooltip": _("Open {}").format(_("Intervention")),
 | 
			
		||||
            "content": value,
 | 
			
		||||
            "url": reverse("intervention:detail", args=(record.id,)),
 | 
			
		||||
            "has_revocations": record.legal.revocations.exists()
 | 
			
		||||
        }
 | 
			
		||||
        html = render_to_string(
 | 
			
		||||
            "table/revocation_warning_col.html",
 | 
			
		||||
            context
 | 
			
		||||
        )
 | 
			
		||||
        return format_html(html)
 | 
			
		||||
        return html
 | 
			
		||||
 | 
			
		||||
    def render_c(self, value, record: Intervention):
 | 
			
		||||
        """ Renders the checked column for an intervention
 | 
			
		||||
@ -117,6 +120,28 @@ class InterventionTable(BaseTable, TableRenderMixin):
 | 
			
		||||
        )
 | 
			
		||||
        return format_html(html)
 | 
			
		||||
 | 
			
		||||
    def render_d(self, value, record: Intervention):
 | 
			
		||||
        """ Renders the parcel district column for an intervention
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            value (str): The intervention geometry
 | 
			
		||||
            record (Intervention): The intervention record
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        parcels = value.get_underlying_parcels().values_list(
 | 
			
		||||
            "gmrkng",
 | 
			
		||||
            flat=True
 | 
			
		||||
        ).distinct()
 | 
			
		||||
        html = render_to_string(
 | 
			
		||||
            "table/gmrkng_col.html",
 | 
			
		||||
            {
 | 
			
		||||
                "entries": parcels
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
        return html
 | 
			
		||||
 | 
			
		||||
    def render_r(self, value, record: Intervention):
 | 
			
		||||
        """ Renders the recorded column for an intervention
 | 
			
		||||
 | 
			
		||||
@ -162,28 +187,3 @@ class InterventionTable(BaseTable, TableRenderMixin):
 | 
			
		||||
        )
 | 
			
		||||
        return format_html(html)
 | 
			
		||||
 | 
			
		||||
    def render_rev(self, value, record: Intervention):
 | 
			
		||||
        """ Renders the revocation column for an intervention
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            value (str): The revocation value
 | 
			
		||||
            record (Intervention): The intervention record
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        html = ""
 | 
			
		||||
        exists = value is not None
 | 
			
		||||
        tooltip = _("No revocation")
 | 
			
		||||
        if exists:
 | 
			
		||||
            _date = value.date
 | 
			
		||||
            added_ts = localtime(value.created.timestamp)
 | 
			
		||||
            added_ts = added_ts.strftime(DEFAULT_DATE_TIME_FORMAT)
 | 
			
		||||
            on = _date.strftime(DEFAULT_DATE_FORMAT)
 | 
			
		||||
            tooltip = _("Revocation from {}, added on {} by {}").format(on, added_ts, value.created.user)
 | 
			
		||||
        html += self.render_stop(
 | 
			
		||||
            tooltip=tooltip,
 | 
			
		||||
            icn_filled=exists,
 | 
			
		||||
        )
 | 
			
		||||
        return format_html(html)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@
 | 
			
		||||
                    {% trans 'Title' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                {% if is_default_member and has_access %}
 | 
			
		||||
                    <th scope="col">
 | 
			
		||||
                    <th class="w-10" scope="col">
 | 
			
		||||
                        <span class="float-right">
 | 
			
		||||
                            {% trans 'Action' %}
 | 
			
		||||
                        </span>
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@
 | 
			
		||||
                    {% trans 'Created' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                {% if is_default_member and has_access %}
 | 
			
		||||
                    <th scope="col">
 | 
			
		||||
                    <th class="w-10" scope="col">
 | 
			
		||||
                        <span class="float-right">
 | 
			
		||||
                            {% trans 'Action' %}
 | 
			
		||||
                        </span>
 | 
			
		||||
@ -55,9 +55,12 @@
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="align-middle">{{ deduction.surface|floatformat:2|intcomma }} m²</td>
 | 
			
		||||
                <td class="align-middle">{{ deduction.created.timestamp|default_if_none:""|naturalday}}</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'intervention:remove-deduction' obj.id deduction.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove Deduction' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'intervention:edit-deduction' obj.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit Deduction' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'intervention:remove-deduction' obj.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove Deduction' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -27,11 +27,14 @@
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                    {% trans 'Title' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                <th class="w-50" scope="col">
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                    {% trans 'Created on' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                <th scope="col">
 | 
			
		||||
                    {% trans 'Comment' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                {% if is_default_member and has_access %}
 | 
			
		||||
                    <th scope="col">
 | 
			
		||||
                    <th class="w-10" scope="col">
 | 
			
		||||
                        <span class="float-right">
 | 
			
		||||
                            {% trans 'Action' %}
 | 
			
		||||
                        </span>
 | 
			
		||||
@ -43,18 +46,26 @@
 | 
			
		||||
            {% for doc in obj.documents.all %}
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td>
 | 
			
		||||
                    <a href="{% url 'intervention:get-doc' doc.id %}">
 | 
			
		||||
                    <a href="{% url 'intervention:get-doc' obj.id doc.id %}">
 | 
			
		||||
                        {{ doc.title }}
 | 
			
		||||
                    </a>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td>
 | 
			
		||||
                    <div class="scroll-150">
 | 
			
		||||
                        {{ doc.date_of_creation }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td>
 | 
			
		||||
                    <div class="scroll-150">
 | 
			
		||||
                        {{ doc.comment }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td>
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'intervention:remove-doc' doc.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove document' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'intervention:edit-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit document' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'intervention:remove-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove document' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@
 | 
			
		||||
                    {% trans 'Comment' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                {% if is_default_member and has_access %}
 | 
			
		||||
                    <th scope="col">
 | 
			
		||||
                    <th class="w-10" scope="col">
 | 
			
		||||
                        <span class="float-right">
 | 
			
		||||
                            {% trans 'Action' %}
 | 
			
		||||
                        </span>
 | 
			
		||||
@ -54,9 +54,12 @@
 | 
			
		||||
                        {{ pay.comment }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                <td class="align-middle float-right">
 | 
			
		||||
                    {% if is_default_member and has_access  %}
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:pay:remove' pay.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove payment' %}">
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:pay:edit' obj.id pay.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit payment' %}">
 | 
			
		||||
                        {% fa5_icon 'edit' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button data-form-url="{% url 'compensation:pay:remove' obj.id pay.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove payment' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -37,7 +37,7 @@
 | 
			
		||||
                    {% trans 'Comment' %}
 | 
			
		||||
                </th>
 | 
			
		||||
                {% if is_default_member and has_access %}
 | 
			
		||||
                    <th scope="col">
 | 
			
		||||
                    <th class="w-10" scope="col">
 | 
			
		||||
                        <span class="float-right">
 | 
			
		||||
                            {% trans 'Action' %}
 | 
			
		||||
                        </span>
 | 
			
		||||
@ -63,9 +63,12 @@
 | 
			
		||||
                            {{ rev.comment }}
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td class="align-middle">
 | 
			
		||||
                    <td class="align-middle float-right">
 | 
			
		||||
                        {% if is_default_member and has_access  %}
 | 
			
		||||
                        <button data-form-url="{% url 'intervention:remove-revocation' rev.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove revocation' %}">
 | 
			
		||||
                        <button data-form-url="{% url 'intervention:edit-revocation' obj.id rev.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit revocation' %}">
 | 
			
		||||
                            {% fa5_icon 'edit' %}
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <button data-form-url="{% url 'intervention:remove-revocation' obj.id rev.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove revocation' %}">
 | 
			
		||||
                            {% fa5_icon 'trash' %}
 | 
			
		||||
                        </button>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ from django.test import Client
 | 
			
		||||
from django.contrib.auth.models import Group
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from intervention.models import Revocation
 | 
			
		||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
 | 
			
		||||
from konova.tests.test_views import BaseViewTestCase
 | 
			
		||||
 | 
			
		||||
@ -20,19 +21,34 @@ class InterventionViewTestCase(BaseViewTestCase):
 | 
			
		||||
    def setUpTestData(cls) -> None:
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        # Prepare urls
 | 
			
		||||
        cls.index_url = reverse("intervention:index", args=())
 | 
			
		||||
        cls.new_url = reverse("intervention:new", args=())
 | 
			
		||||
        cls.new_id_url = reverse("intervention:new-id", args=())
 | 
			
		||||
        cls.detail_url = reverse("intervention:detail", args=(cls.intervention.id,))
 | 
			
		||||
        cls.log_url = reverse("intervention:log", args=(cls.intervention.id,))
 | 
			
		||||
        cls.edit_url = reverse("intervention:edit", args=(cls.intervention.id,))
 | 
			
		||||
        cls.remove_url = reverse("intervention:remove", args=(cls.intervention.id,))
 | 
			
		||||
        cls.share_url = reverse("intervention:share", args=(cls.intervention.id, cls.intervention.access_token,))
 | 
			
		||||
        cls.share_create_url = reverse("intervention:share-create", args=(cls.intervention.id,))
 | 
			
		||||
        cls.run_check_url = reverse("intervention:check", args=(cls.intervention.id,))
 | 
			
		||||
        cls.record_url = reverse("intervention:record", args=(cls.intervention.id,))
 | 
			
		||||
        cls.report_url = reverse("intervention:report", args=(cls.intervention.id,))
 | 
			
		||||
        self.index_url = reverse("intervention:index", args=())
 | 
			
		||||
        self.new_url = reverse("intervention:new", args=())
 | 
			
		||||
        self.new_id_url = reverse("intervention:new-id", args=())
 | 
			
		||||
        self.detail_url = reverse("intervention:detail", args=(self.intervention.id,))
 | 
			
		||||
        self.log_url = reverse("intervention:log", args=(self.intervention.id,))
 | 
			
		||||
        self.edit_url = reverse("intervention:edit", args=(self.intervention.id,))
 | 
			
		||||
        self.remove_url = reverse("intervention:remove", args=(self.intervention.id,))
 | 
			
		||||
        self.share_url = reverse("intervention:share", args=(self.intervention.id, self.intervention.access_token,))
 | 
			
		||||
        self.share_create_url = reverse("intervention:share-create", args=(self.intervention.id,))
 | 
			
		||||
        self.run_check_url = reverse("intervention:check", args=(self.intervention.id,))
 | 
			
		||||
        self.record_url = reverse("intervention:record", args=(self.intervention.id,))
 | 
			
		||||
        self.report_url = reverse("intervention:report", args=(self.intervention.id,))
 | 
			
		||||
 | 
			
		||||
        self.deduction.intervention = self.intervention
 | 
			
		||||
        self.deduction.save()
 | 
			
		||||
        self.deduction_new_url = reverse("intervention:new-deduction", args=(self.intervention.id,))
 | 
			
		||||
        self.deduction_edit_url = reverse("intervention:edit-deduction", args=(self.intervention.id, self.deduction.id,))
 | 
			
		||||
        self.deduction_remove_url = reverse("intervention:remove-deduction", args=(self.intervention.id, self.deduction.id))
 | 
			
		||||
 | 
			
		||||
        self.revocation = Revocation.objects.create(
 | 
			
		||||
            legal=self.intervention.legal
 | 
			
		||||
        )
 | 
			
		||||
        self.revocation_new_url = reverse("intervention:new-revocation", args=(self.intervention.id,))
 | 
			
		||||
        self.revocation_edit_url = reverse("intervention:edit-revocation", args=(self.intervention.id, self.revocation.id))
 | 
			
		||||
        self.revocation_remove_url = reverse("intervention:remove-revocation", args=(self.intervention.id, self.revocation.id))
 | 
			
		||||
 | 
			
		||||
    def test_views_anonymous_user(self):
 | 
			
		||||
        """ Check correct status code for all requests
 | 
			
		||||
@ -61,6 +77,12 @@ class InterventionViewTestCase(BaseViewTestCase):
 | 
			
		||||
            self.share_create_url: f"{login_redirect_base}{self.share_create_url}",
 | 
			
		||||
            self.run_check_url: f"{login_redirect_base}{self.run_check_url}",
 | 
			
		||||
            self.record_url: f"{login_redirect_base}{self.record_url}",
 | 
			
		||||
            self.deduction_new_url: f"{login_redirect_base}{self.deduction_new_url}",
 | 
			
		||||
            self.deduction_edit_url: f"{login_redirect_base}{self.deduction_edit_url}",
 | 
			
		||||
            self.deduction_remove_url: f"{login_redirect_base}{self.deduction_remove_url}",
 | 
			
		||||
            self.revocation_new_url: f"{login_redirect_base}{self.revocation_new_url}",
 | 
			
		||||
            self.revocation_edit_url: f"{login_redirect_base}{self.revocation_edit_url}",
 | 
			
		||||
            self.revocation_remove_url: f"{login_redirect_base}{self.revocation_remove_url}",
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
@ -96,6 +118,12 @@ class InterventionViewTestCase(BaseViewTestCase):
 | 
			
		||||
            self.share_create_url,
 | 
			
		||||
            self.run_check_url,
 | 
			
		||||
            self.record_url,
 | 
			
		||||
            self.revocation_new_url,
 | 
			
		||||
            self.revocation_edit_url,
 | 
			
		||||
            self.revocation_remove_url,
 | 
			
		||||
            self.deduction_new_url,
 | 
			
		||||
            self.deduction_edit_url,
 | 
			
		||||
            self.deduction_remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        self.assert_url_success(client, success_urls)
 | 
			
		||||
@ -128,6 +156,12 @@ class InterventionViewTestCase(BaseViewTestCase):
 | 
			
		||||
            self.edit_url,
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
            self.share_create_url,
 | 
			
		||||
            self.revocation_new_url,
 | 
			
		||||
            self.revocation_edit_url,
 | 
			
		||||
            self.revocation_remove_url,
 | 
			
		||||
            self.deduction_new_url,
 | 
			
		||||
            self.deduction_edit_url,
 | 
			
		||||
            self.deduction_remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
        fail_urls = [
 | 
			
		||||
            self.run_check_url,
 | 
			
		||||
@ -172,6 +206,12 @@ class InterventionViewTestCase(BaseViewTestCase):
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
            self.share_create_url,
 | 
			
		||||
            self.log_url,
 | 
			
		||||
            self.revocation_new_url,
 | 
			
		||||
            self.revocation_edit_url,
 | 
			
		||||
            self.revocation_remove_url,
 | 
			
		||||
            self.deduction_new_url,
 | 
			
		||||
            self.deduction_edit_url,
 | 
			
		||||
            self.deduction_remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
        success_urls_redirect = {
 | 
			
		||||
            self.share_url: self.detail_url
 | 
			
		||||
@ -212,6 +252,12 @@ class InterventionViewTestCase(BaseViewTestCase):
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
            self.share_create_url,
 | 
			
		||||
            self.record_url,
 | 
			
		||||
            self.revocation_new_url,
 | 
			
		||||
            self.revocation_edit_url,
 | 
			
		||||
            self.revocation_remove_url,
 | 
			
		||||
            self.deduction_new_url,
 | 
			
		||||
            self.deduction_edit_url,
 | 
			
		||||
            self.deduction_remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
        success_urls_redirect = {
 | 
			
		||||
            self.share_url: self.detail_url
 | 
			
		||||
@ -252,6 +298,12 @@ class InterventionViewTestCase(BaseViewTestCase):
 | 
			
		||||
            self.share_create_url,
 | 
			
		||||
            self.record_url,
 | 
			
		||||
            self.run_check_url,
 | 
			
		||||
            self.revocation_new_url,
 | 
			
		||||
            self.revocation_edit_url,
 | 
			
		||||
            self.revocation_remove_url,
 | 
			
		||||
            self.deduction_new_url,
 | 
			
		||||
            self.deduction_edit_url,
 | 
			
		||||
            self.deduction_remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
        success_urls_redirect = {
 | 
			
		||||
            self.share_url: self.detail_url
 | 
			
		||||
@ -292,6 +344,12 @@ class InterventionViewTestCase(BaseViewTestCase):
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
            self.share_create_url,
 | 
			
		||||
            self.run_check_url,
 | 
			
		||||
            self.revocation_new_url,
 | 
			
		||||
            self.revocation_edit_url,
 | 
			
		||||
            self.revocation_remove_url,
 | 
			
		||||
            self.deduction_new_url,
 | 
			
		||||
            self.deduction_edit_url,
 | 
			
		||||
            self.deduction_remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
        success_urls_redirect = {
 | 
			
		||||
            self.share_url: self.detail_url
 | 
			
		||||
@ -332,6 +390,12 @@ class InterventionViewTestCase(BaseViewTestCase):
 | 
			
		||||
            self.remove_url,
 | 
			
		||||
            self.share_create_url,
 | 
			
		||||
            self.run_check_url,
 | 
			
		||||
            self.revocation_new_url,
 | 
			
		||||
            self.revocation_edit_url,
 | 
			
		||||
            self.revocation_remove_url,
 | 
			
		||||
            self.deduction_new_url,
 | 
			
		||||
            self.deduction_edit_url,
 | 
			
		||||
            self.deduction_remove_url,
 | 
			
		||||
        ]
 | 
			
		||||
        # Define urls where a redirect to a specific location is the proper response
 | 
			
		||||
        success_urls_redirect = {
 | 
			
		||||
 | 
			
		||||
@ -74,6 +74,9 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
            self.assertEqual(obj.identifier, test_id)
 | 
			
		||||
            self.assertEqual(obj.title, test_title)
 | 
			
		||||
            self.assert_equal_geometries(obj.geometry.geom, test_geom)
 | 
			
		||||
            self.assertEqual(1, obj.log.count())
 | 
			
		||||
            self.assertEqual(obj.log.first().action, UserAction.CREATED)
 | 
			
		||||
            self.assertEqual(obj.log.first().user, self.superuser)
 | 
			
		||||
        except ObjectDoesNotExist:
 | 
			
		||||
            # Fail if there is no such object
 | 
			
		||||
            self.fail()
 | 
			
		||||
@ -215,6 +218,8 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
        # Make sure there are no payments on the intervention, yet
 | 
			
		||||
        self.assertEqual(0, self.intervention.payments.count())
 | 
			
		||||
 | 
			
		||||
        pre_payment_logs_count = self.intervention.log.count()
 | 
			
		||||
 | 
			
		||||
        # Create form data to be sent to the url
 | 
			
		||||
        test_amount = 10.00
 | 
			
		||||
        test_due = "2021-01-01"
 | 
			
		||||
@ -239,6 +244,10 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
        self.assertEqual(payment.amount, test_amount)
 | 
			
		||||
        self.assertEqual(payment.due_on, datetime.date.fromisoformat(test_due))
 | 
			
		||||
        self.assertEqual(payment.comment, test_comment)
 | 
			
		||||
 | 
			
		||||
        # Make sure a log entry has been created
 | 
			
		||||
        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
 | 
			
		||||
        self.assertEqual(pre_payment_logs_count + 1, self.intervention.log.count())
 | 
			
		||||
        return payment
 | 
			
		||||
 | 
			
		||||
    def subtest_delete_payment(self, payment: Payment):
 | 
			
		||||
@ -250,8 +259,10 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        pre_payment_logs_count = self.intervention.log.count()
 | 
			
		||||
 | 
			
		||||
        # Create removing url for the payment
 | 
			
		||||
        remove_url = reverse("compensation:pay:remove", args=(payment.id,))
 | 
			
		||||
        remove_url = reverse("compensation:pay:remove", args=(self.intervention.id, payment.id,))
 | 
			
		||||
        post_data = {
 | 
			
		||||
            "confirm": True,
 | 
			
		||||
        }
 | 
			
		||||
@ -266,6 +277,11 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
        # Now make sure the intervention has no payments anymore
 | 
			
		||||
        self.assertEqual(0, self.intervention.payments.count())
 | 
			
		||||
 | 
			
		||||
        # Make sure a log entry has been created
 | 
			
		||||
        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
 | 
			
		||||
        self.assertEqual(self.intervention.log.first().user, self.superuser)
 | 
			
		||||
        self.assertEqual(pre_payment_logs_count + 1, self.intervention.log.count())
 | 
			
		||||
 | 
			
		||||
    def test_payments(self):
 | 
			
		||||
        """
 | 
			
		||||
        Checks a 'normal' case of adding a payment.
 | 
			
		||||
@ -353,6 +369,8 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        pre_deduction_logs_count = self.intervention.log.count()
 | 
			
		||||
 | 
			
		||||
        # Prepare the account for a working situation (enough deductable surface, recorded and shared)
 | 
			
		||||
        self.eco_account.deductable_surface = 10000.00
 | 
			
		||||
        if self.eco_account.recorded is None:
 | 
			
		||||
@ -376,6 +394,11 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(deduction.surface, test_surface)
 | 
			
		||||
 | 
			
		||||
        # Make sure a log entry has been created
 | 
			
		||||
        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
 | 
			
		||||
        self.assertEqual(self.intervention.log.first().user, self.superuser)
 | 
			
		||||
        self.assertEqual(pre_deduction_logs_count + 1, self.intervention.log.count())
 | 
			
		||||
 | 
			
		||||
        # Return deduction for further usage in tests
 | 
			
		||||
        return deduction
 | 
			
		||||
 | 
			
		||||
@ -414,6 +437,8 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        pre_delete_logs_count = self.intervention.log.count()
 | 
			
		||||
 | 
			
		||||
        # Prepare url for deleting of this deduction
 | 
			
		||||
        delete_url = reverse("compensation:acc:remove-deduction", args=(self.eco_account.id, deduction.id,))
 | 
			
		||||
        post_data = {
 | 
			
		||||
@ -433,6 +458,11 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
        # Expect the deduction to be totally gone
 | 
			
		||||
        self.assert_object_is_deleted(deduction)
 | 
			
		||||
 | 
			
		||||
        # Make sure a log entry has been created
 | 
			
		||||
        self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
 | 
			
		||||
        self.assertEqual(self.intervention.log.first().user, self.superuser)
 | 
			
		||||
        self.assertEqual(pre_delete_logs_count + 1, self.intervention.log.count())
 | 
			
		||||
 | 
			
		||||
    def test_deduction(self):
 | 
			
		||||
        """
 | 
			
		||||
        Checks a 'normal case of adding a deduction.
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
    remove_deduction_view, remove_compensation_view, edit_deduction_view, edit_revocation_view, edit_document_view
 | 
			
		||||
 | 
			
		||||
app_name = "intervention"
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
@ -28,19 +28,22 @@ urlpatterns = [
 | 
			
		||||
    path('<id>/report', report_view, name='report'),
 | 
			
		||||
 | 
			
		||||
    # Compensations
 | 
			
		||||
    path('<id>/remove/<comp_id>', remove_compensation_view, name='remove-compensation'),
 | 
			
		||||
    path('<id>/compensation/<comp_id>/remove', remove_compensation_view, name='remove-compensation'),
 | 
			
		||||
 | 
			
		||||
    # Documents
 | 
			
		||||
    path('<id>/document/new/', new_document_view, name='new-doc'),
 | 
			
		||||
    path('document/<doc_id>', get_document_view, name='get-doc'),
 | 
			
		||||
    path('document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
 | 
			
		||||
    path('<id>/document/<doc_id>', get_document_view, name='get-doc'),
 | 
			
		||||
    path('<id>/document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
 | 
			
		||||
    path('<id>/document/<doc_id>/edit/', edit_document_view, name='edit-doc'),
 | 
			
		||||
 | 
			
		||||
    # Deductions
 | 
			
		||||
    path('<id>/deduction/new', new_deduction_view, name='new-deduction'),
 | 
			
		||||
    path('<id>/remove/<deduction_id>', remove_deduction_view, name='remove-deduction'),
 | 
			
		||||
    path('<id>/deduction/<deduction_id>/edit', edit_deduction_view, name='edit-deduction'),
 | 
			
		||||
    path('<id>/deduction/<deduction_id>/remove', remove_deduction_view, name='remove-deduction'),
 | 
			
		||||
 | 
			
		||||
    # Revocation routes
 | 
			
		||||
    path('<id>/revocation/new', new_revocation_view, name='new-revocation'),
 | 
			
		||||
    path('revocation/<id>/remove', remove_revocation_view, name='remove-revocation'),
 | 
			
		||||
    path('<id>/revocation/<revocation_id>/edit', edit_revocation_view, name='edit-revocation'),
 | 
			
		||||
    path('<id>/revocation/<revocation_id>/remove', remove_revocation_view, name='remove-revocation'),
 | 
			
		||||
    path('revocation/<doc_id>', get_revocation_view, name='get-doc-revocation'),
 | 
			
		||||
]
 | 
			
		||||
@ -89,7 +89,10 @@ class InterventionQualityChecker(AbstractQualityChecker):
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        c_comps = self.obj.compensations.count()
 | 
			
		||||
        comps = self.obj.compensations.filter(
 | 
			
		||||
            deleted=None
 | 
			
		||||
        )
 | 
			
		||||
        c_comps = comps.count()
 | 
			
		||||
        c_pays = self.obj.payments.count()
 | 
			
		||||
        c_deducs = self.obj.deductions.count()
 | 
			
		||||
        c_all = c_comps + c_pays + c_deducs
 | 
			
		||||
 | 
			
		||||
@ -6,18 +6,19 @@ from django.shortcuts import render
 | 
			
		||||
 | 
			
		||||
from intervention.forms.forms import NewInterventionForm, EditInterventionForm
 | 
			
		||||
from intervention.forms.modalForms import ShareModalForm, NewRevocationModalForm, \
 | 
			
		||||
    CheckModalForm, NewDeductionModalForm, NewInterventionDocumentForm
 | 
			
		||||
    CheckModalForm, NewDeductionModalForm, NewInterventionDocumentModalForm, RemoveEcoAccountDeductionModalForm, \
 | 
			
		||||
    RemoveRevocationModalForm, EditEcoAccountDeductionModalForm, EditRevocationModalForm
 | 
			
		||||
from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
 | 
			
		||||
from intervention.tables import InterventionTable
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
from konova.decorators import *
 | 
			
		||||
from konova.forms import SimpleGeomForm, RemoveModalForm, RecordModalForm
 | 
			
		||||
from konova.forms import SimpleGeomForm, RemoveModalForm, RecordModalForm, EditDocumentModalForm
 | 
			
		||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
 | 
			
		||||
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
 | 
			
		||||
    COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, DEDUCTION_EDITED, REVOCATION_EDITED, DOCUMENT_EDITED
 | 
			
		||||
from konova.utils.user_checks import in_group
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -128,7 +129,7 @@ def new_document_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    intervention = get_object_or_404(Intervention, id=id)
 | 
			
		||||
    form = NewInterventionDocumentForm(request.POST or None, request.FILES or None, instance=intervention, request=request)
 | 
			
		||||
    form = NewInterventionDocumentModalForm(request.POST or None, request.FILES or None, instance=intervention, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        msg_success=DOCUMENT_ADDED,
 | 
			
		||||
@ -160,48 +161,44 @@ def get_revocation_view(request: HttpRequest, doc_id: str):
 | 
			
		||||
        return redirect("intervention:detail", id=doc.instance.id)
 | 
			
		||||
    return get_document(doc)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
def get_document_view(request: HttpRequest, doc_id: str):
 | 
			
		||||
@shared_access_required(Intervention, "id")
 | 
			
		||||
def get_document_view(request: HttpRequest, id: str, doc_id: str):
 | 
			
		||||
    """ Returns the document as downloadable file
 | 
			
		||||
 | 
			
		||||
    Wraps the generic document fetcher function from konova.utils.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The intervention id
 | 
			
		||||
        doc_id (str): The document id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    intervention = get_object_or_404(Intervention, id=id)
 | 
			
		||||
    doc = get_object_or_404(InterventionDocument, id=doc_id)
 | 
			
		||||
    user = request.user
 | 
			
		||||
    instance = doc.instance
 | 
			
		||||
    # File download only possible if related instance is shared with user
 | 
			
		||||
    if not instance.users.filter(id=user.id):
 | 
			
		||||
        messages.info(
 | 
			
		||||
            request,
 | 
			
		||||
            DATA_UNSHARED
 | 
			
		||||
        )
 | 
			
		||||
        return redirect("intervention:detail", id=instance.id)
 | 
			
		||||
    return get_document(doc)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
def remove_document_view(request: HttpRequest, doc_id: str):
 | 
			
		||||
@shared_access_required(Intervention, "id")
 | 
			
		||||
def remove_document_view(request: HttpRequest, id: str, doc_id: str):
 | 
			
		||||
    """ Removes the document from the database and file system
 | 
			
		||||
 | 
			
		||||
    Wraps the generic functionality from konova.utils.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The intervention id
 | 
			
		||||
        doc_id (str): The document id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    intervention = get_object_or_404(Intervention, id=id)
 | 
			
		||||
    doc = get_object_or_404(InterventionDocument, id=doc_id)
 | 
			
		||||
    return remove_document(
 | 
			
		||||
        request,
 | 
			
		||||
@ -209,6 +206,32 @@ def remove_document_view(request: HttpRequest, doc_id: str):
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(Intervention, "id")
 | 
			
		||||
def edit_document_view(request: HttpRequest, id: str, doc_id: str):
 | 
			
		||||
    """ Removes the document from the database and file system
 | 
			
		||||
 | 
			
		||||
    Wraps the generic functionality from konova.utils.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The intervention id
 | 
			
		||||
        doc_id (str): The document id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    intervention = get_object_or_404(Intervention, id=id)
 | 
			
		||||
    doc = get_object_or_404(InterventionDocument, id=doc_id)
 | 
			
		||||
    form = EditDocumentModalForm(request.POST or None, request.FILES or None, instance=intervention, document=doc, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        DOCUMENT_EDITED,
 | 
			
		||||
        redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@any_group_check
 | 
			
		||||
def detail_view(request: HttpRequest, id: str):
 | 
			
		||||
@ -244,14 +267,6 @@ def detail_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
    parcels = intervention.get_underlying_parcels()
 | 
			
		||||
 | 
			
		||||
    # Inform user about revocation
 | 
			
		||||
    if intervention.legal.revocations.exists():
 | 
			
		||||
        messages.error(
 | 
			
		||||
            request,
 | 
			
		||||
            _("This intervention has {} revocations").format(intervention.legal.revocations.count()),
 | 
			
		||||
            extra_tags="danger",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    context = {
 | 
			
		||||
        "obj": intervention,
 | 
			
		||||
        "compensations": compensations,
 | 
			
		||||
@ -340,23 +355,51 @@ def remove_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
def remove_revocation_view(request: HttpRequest, id: str):
 | 
			
		||||
    """ Renders a remove view for a revocation
 | 
			
		||||
@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 revocation's id as string
 | 
			
		||||
        id (str): The intervention's id as string
 | 
			
		||||
        revocation_id (str): The revocation's id as string
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    obj = Revocation.objects.get(id=id)
 | 
			
		||||
    intervention = get_object_or_404(Intervention, id=id)
 | 
			
		||||
    revocation = get_object_or_404(Revocation, id=revocation_id)
 | 
			
		||||
 | 
			
		||||
    form = RemoveModalForm(request.POST or None, instance=obj, request=request)
 | 
			
		||||
    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")
 | 
			
		||||
def remove_revocation_view(request: HttpRequest, id: str, revocation_id: str):
 | 
			
		||||
    """ Renders a remove 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 = RemoveRevocationModalForm(request.POST or None, instance=intervention, revocation=revocation, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request,
 | 
			
		||||
        REVOCATION_REMOVED,
 | 
			
		||||
        redirect_url=reverse("intervention:detail", args=(obj.intervention.id,)) + "#related_data"
 | 
			
		||||
        redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -533,7 +576,7 @@ def remove_deduction_view(request: HttpRequest, id: str, deduction_id: str):
 | 
			
		||||
    except ObjectDoesNotExist:
 | 
			
		||||
        raise Http404("Unknown deduction")
 | 
			
		||||
 | 
			
		||||
    form = RemoveModalForm(request.POST or None, instance=eco_deduction, request=request)
 | 
			
		||||
    form = RemoveEcoAccountDeductionModalForm(request.POST or None, instance=intervention, deduction=eco_deduction, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request=request,
 | 
			
		||||
        msg_success=DEDUCTION_REMOVED,
 | 
			
		||||
@ -541,6 +584,34 @@ def remove_deduction_view(request: HttpRequest, id: str, deduction_id: str):
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@default_group_required
 | 
			
		||||
@shared_access_required(Intervention, "id")
 | 
			
		||||
def edit_deduction_view(request: HttpRequest, id: str, deduction_id: str):
 | 
			
		||||
    """ Renders a modal view for removing deductions
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
        id (str): The intervention's id
 | 
			
		||||
        deduction_id (str): The deduction's id
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    intervention = get_object_or_404(Intervention, id=id)
 | 
			
		||||
    try:
 | 
			
		||||
        eco_deduction = intervention.deductions.get(id=deduction_id)
 | 
			
		||||
    except ObjectDoesNotExist:
 | 
			
		||||
        raise Http404("Unknown deduction")
 | 
			
		||||
 | 
			
		||||
    form = EditEcoAccountDeductionModalForm(request.POST or None, instance=intervention, deduction=eco_deduction, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request=request,
 | 
			
		||||
        msg_success=DEDUCTION_EDITED,
 | 
			
		||||
        redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@conservation_office_group_required
 | 
			
		||||
@shared_access_required(Intervention, "id")
 | 
			
		||||
 | 
			
		||||
@ -35,8 +35,9 @@ class EcoAccountAutocomplete(Select2QuerySetView):
 | 
			
		||||
        )
 | 
			
		||||
        if self.q:
 | 
			
		||||
            qs = qs.filter(
 | 
			
		||||
                identifier__icontains=self.q
 | 
			
		||||
            )
 | 
			
		||||
                Q(identifier__icontains=self.q) |
 | 
			
		||||
                Q(title__icontains=self.q)
 | 
			
		||||
            ).distinct()
 | 
			
		||||
        return qs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -57,8 +58,9 @@ class InterventionAutocomplete(Select2QuerySetView):
 | 
			
		||||
        )
 | 
			
		||||
        if self.q:
 | 
			
		||||
            qs = qs.filter(
 | 
			
		||||
                identifier__icontains=self.q
 | 
			
		||||
            )
 | 
			
		||||
                Q(identifier__icontains=self.q) |
 | 
			
		||||
                Q(title__icontains=self.q)
 | 
			
		||||
            ).distinct()
 | 
			
		||||
        return qs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -81,8 +83,9 @@ class ShareUserAutocomplete(Select2QuerySetView):
 | 
			
		||||
        if self.q:
 | 
			
		||||
            # Due to privacy concerns only a full username match will return the proper user entry
 | 
			
		||||
            qs = qs.filter(
 | 
			
		||||
                username=self.q
 | 
			
		||||
            )
 | 
			
		||||
                Q(username=self.q) |
 | 
			
		||||
                Q(email=self.q)
 | 
			
		||||
            ).distinct()
 | 
			
		||||
        return qs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,8 @@ from bootstrap_modal_forms.forms import BSModalForm
 | 
			
		||||
from bootstrap_modal_forms.utils import is_ajax
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.db.models.fields.files import FieldFile
 | 
			
		||||
 | 
			
		||||
from user.models import User
 | 
			
		||||
from django.contrib.gis.forms import OSMWidget, MultiPolygonField
 | 
			
		||||
from django.contrib.gis.geos import MultiPolygon
 | 
			
		||||
@ -21,10 +23,10 @@ from django.shortcuts import render
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
from konova.models import BaseObject, Geometry, RecordableObjectMixin
 | 
			
		||||
from konova.models import BaseObject, Geometry, RecordableObjectMixin, AbstractDocument
 | 
			
		||||
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, DOCUMENT_EDITED
 | 
			
		||||
from user.models import UserActionLogEntry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -87,7 +89,7 @@ class BaseForm(forms.Form):
 | 
			
		||||
        """
 | 
			
		||||
        self.fields[field].widget.attrs["placeholder"] = val
 | 
			
		||||
 | 
			
		||||
    def load_initial_data(self, form_data: dict, disabled_fields: list):
 | 
			
		||||
    def load_initial_data(self, form_data: dict, disabled_fields: list = None):
 | 
			
		||||
        """ Initializes form data from instance
 | 
			
		||||
 | 
			
		||||
        Inserts instance data into form and disables form fields
 | 
			
		||||
@ -99,8 +101,9 @@ class BaseForm(forms.Form):
 | 
			
		||||
            return
 | 
			
		||||
        for k, v in form_data.items():
 | 
			
		||||
            self.initialize_form_field(k, v)
 | 
			
		||||
        for field in disabled_fields:
 | 
			
		||||
            self.disable_form_field(field)
 | 
			
		||||
        if disabled_fields:
 | 
			
		||||
            for field in disabled_fields:
 | 
			
		||||
                self.disable_form_field(field)
 | 
			
		||||
 | 
			
		||||
    def add_widget_html_class(self, field: str, cls: str):
 | 
			
		||||
        """ Adds a HTML class string to the widget of a field
 | 
			
		||||
@ -327,10 +330,27 @@ class RemoveModalForm(BaseModalForm):
 | 
			
		||||
            self.instance.mark_as_deleted(self.user)
 | 
			
		||||
        else:
 | 
			
		||||
            # If the class does not provide restorable delete functionality, we must delete the entry finally
 | 
			
		||||
            self.instance.delete(self.user)
 | 
			
		||||
            self.instance.delete()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewDocumentForm(BaseModalForm):
 | 
			
		||||
class RemoveDeadlineModalForm(RemoveModalForm):
 | 
			
		||||
    """ Removing modal form for deadlines
 | 
			
		||||
 | 
			
		||||
    Can be used for anything, where removing shall be confirmed by the user a second time.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    deadline = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        deadline = kwargs.pop("deadline", None)
 | 
			
		||||
        self.deadline = deadline
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        self.instance.remove_deadline(self)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewDocumentModalForm(BaseModalForm):
 | 
			
		||||
    """ Modal form for new documents
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
@ -402,18 +422,22 @@ class NewDocumentForm(BaseModalForm):
 | 
			
		||||
 | 
			
		||||
        _file = self.cleaned_data.get("file", None)
 | 
			
		||||
 | 
			
		||||
        if _file is None or 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",
 | 
			
		||||
                _("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
 | 
			
		||||
@ -440,6 +464,39 @@ class NewDocumentForm(BaseModalForm):
 | 
			
		||||
            return doc
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EditDocumentModalForm(NewDocumentModalForm):
 | 
			
		||||
    document = None
 | 
			
		||||
    document_model = AbstractDocument
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.document = kwargs.pop("document", None)
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        form_data = {
 | 
			
		||||
            "title": self.document.title,
 | 
			
		||||
            "comment": self.document.comment,
 | 
			
		||||
            "creation_date": str(self.document.date_of_creation),
 | 
			
		||||
            "file": self.document.file,
 | 
			
		||||
        }
 | 
			
		||||
        self.load_initial_data(form_data)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            document = self.document
 | 
			
		||||
            file = self.cleaned_data.get("file", None)
 | 
			
		||||
 | 
			
		||||
            document.title = self.cleaned_data.get("title", None)
 | 
			
		||||
            document.comment = self.cleaned_data.get("comment", None)
 | 
			
		||||
            document.date_of_creation = self.cleaned_data.get("creation_date", None)
 | 
			
		||||
            if not isinstance(file, FieldFile):
 | 
			
		||||
                document.replace_file(file)
 | 
			
		||||
            document.save()
 | 
			
		||||
 | 
			
		||||
            self.instance.mark_as_edited(self.user, self.request, edit_comment=DOCUMENT_EDITED)
 | 
			
		||||
 | 
			
		||||
            return document
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RecordModalForm(BaseModalForm):
 | 
			
		||||
    """ Modal form for recording data
 | 
			
		||||
 | 
			
		||||
@ -515,7 +572,9 @@ class RecordModalForm(BaseModalForm):
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        comps = self.instance.compensations.all()
 | 
			
		||||
        comps = self.instance.compensations.filter(
 | 
			
		||||
            deleted=None,
 | 
			
		||||
        )
 | 
			
		||||
        comps_valid = True
 | 
			
		||||
        for comp in comps:
 | 
			
		||||
            checker = comp.quality_check()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										54
									
								
								konova/migrations/0003_auto_20220208_1801.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								konova/migrations/0003_auto_20220208_1801.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
			
		||||
# Generated by Django 3.1.3 on 2022-02-08 17:01
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def migrate_parcels(apps, schema_editor):
 | 
			
		||||
    Geometry = apps.get_model('konova', 'Geometry')
 | 
			
		||||
    SpatialIntersection = apps.get_model('konova', 'SpatialIntersection')
 | 
			
		||||
 | 
			
		||||
    all_geoms = Geometry.objects.all()
 | 
			
		||||
    for geom in all_geoms:
 | 
			
		||||
        SpatialIntersection.objects.bulk_create([
 | 
			
		||||
            SpatialIntersection(geometry=geom, parcel=parcel)
 | 
			
		||||
            for parcel in geom.parcels.all()
 | 
			
		||||
        ])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('konova', '0002_auto_20220114_0936'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='SpatialIntersection',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
 | 
			
		||||
                ('calculated_on', models.DateTimeField(auto_now_add=True, null=True)),
 | 
			
		||||
                ('geometry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='konova.geometry')),
 | 
			
		||||
                ('parcel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='konova.parcel')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'abstract': False,
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RunPython(migrate_parcels),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='parcel',
 | 
			
		||||
            name='geometries_tmp',
 | 
			
		||||
            field=models.ManyToManyField(blank=True, related_name='parcels', through='konova.SpatialIntersection', to='konova.Geometry'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='parcel',
 | 
			
		||||
            name='geometries',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RenameField(
 | 
			
		||||
            model_name='parcel',
 | 
			
		||||
            old_name='geometries_tmp',
 | 
			
		||||
            new_name='geometries',
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
							
								
								
									
										17
									
								
								konova/migrations/0004_auto_20220209_0839.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								konova/migrations/0004_auto_20220209_0839.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
# Generated by Django 3.1.3 on 2022-02-09 07:39
 | 
			
		||||
 | 
			
		||||
from django.db import migrations
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('konova', '0003_auto_20220208_1801'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.RenameModel(
 | 
			
		||||
            old_name='SpatialIntersection',
 | 
			
		||||
            new_name='ParcelIntersection',
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@ -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()
 | 
			
		||||
@ -99,7 +99,7 @@ class Geometry(BaseResource):
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        from konova.models import Parcel, District
 | 
			
		||||
        from konova.models import Parcel, District, ParcelIntersection
 | 
			
		||||
        parcel_fetcher = ParcelWFSFetcher(
 | 
			
		||||
            geometry_id=self.id,
 | 
			
		||||
        )
 | 
			
		||||
@ -107,6 +107,7 @@ class Geometry(BaseResource):
 | 
			
		||||
        fetched_parcels = parcel_fetcher.get_features(
 | 
			
		||||
            typename
 | 
			
		||||
        )
 | 
			
		||||
        _now = timezone.now()
 | 
			
		||||
        underlying_parcels = []
 | 
			
		||||
        for result in fetched_parcels:
 | 
			
		||||
            fetched_parcel = result[typename]
 | 
			
		||||
@ -125,19 +126,35 @@ class Geometry(BaseResource):
 | 
			
		||||
                krs=fetched_parcel["ave:kreis"],
 | 
			
		||||
            )[0]
 | 
			
		||||
            parcel_obj.district = district
 | 
			
		||||
            parcel_obj.updated_on = timezone.now()
 | 
			
		||||
            parcel_obj.updated_on = _now
 | 
			
		||||
            parcel_obj.save()
 | 
			
		||||
            underlying_parcels.append(parcel_obj)
 | 
			
		||||
 | 
			
		||||
        # Update the linked parcels
 | 
			
		||||
        self.parcels.set(underlying_parcels)
 | 
			
		||||
 | 
			
		||||
        # Set the calculated_on intermediate field, so this related data will be found on lookups
 | 
			
		||||
        intersections_without_ts = self.parcelintersection_set.filter(
 | 
			
		||||
            parcel__in=self.parcels.all(),
 | 
			
		||||
            calculated_on__isnull=True,
 | 
			
		||||
        )
 | 
			
		||||
        for entry in intersections_without_ts:
 | 
			
		||||
            entry.calculated_on = _now
 | 
			
		||||
        ParcelIntersection.objects.bulk_update(
 | 
			
		||||
            intersections_without_ts,
 | 
			
		||||
            ["calculated_on"]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def get_underlying_parcels(self):
 | 
			
		||||
        """ Getter for related parcels and their districts
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            parcels (QuerySet): The related parcels as queryset
 | 
			
		||||
        """
 | 
			
		||||
        parcels = self.parcels.all().prefetch_related(
 | 
			
		||||
 | 
			
		||||
        parcels = self.parcels.filter(
 | 
			
		||||
            parcelintersection__calculated_on__isnull=False,
 | 
			
		||||
        ).prefetch_related(
 | 
			
		||||
            "district"
 | 
			
		||||
        ).order_by(
 | 
			
		||||
            "gmrkng",
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,7 @@ from abc import abstractmethod
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.db.models import QuerySet
 | 
			
		||||
 | 
			
		||||
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP, LANIS_ZOOM_LUT, LANIS_LINK_TEMPLATE
 | 
			
		||||
from konova.tasks import celery_send_mail_shared_access_removed, celery_send_mail_shared_access_given, \
 | 
			
		||||
    celery_send_mail_shared_data_recorded, celery_send_mail_shared_data_unrecorded, \
 | 
			
		||||
    celery_send_mail_shared_data_deleted, celery_send_mail_shared_data_checked
 | 
			
		||||
@ -128,10 +129,27 @@ class BaseObject(BaseResource):
 | 
			
		||||
                # Send mail
 | 
			
		||||
                shared_users = self.shared_users.values_list("id", flat=True)
 | 
			
		||||
                for user_id in shared_users:
 | 
			
		||||
                    celery_send_mail_shared_data_deleted.delay(self.identifier, user_id)
 | 
			
		||||
                    celery_send_mail_shared_data_deleted.delay(self.identifier, self.title, user_id)
 | 
			
		||||
 | 
			
		||||
            self.save()
 | 
			
		||||
 | 
			
		||||
    def mark_as_edited(self, performing_user: User, request: HttpRequest = None, edit_comment: str = None):
 | 
			
		||||
        """ In case the object or a related object changed the log history needs to be updated
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            performing_user (User): The user which performed the editing action
 | 
			
		||||
            request (HttpRequest): The used request for this action
 | 
			
		||||
            edit_comment (str): Additional comment for the log entry
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        edit_action = UserActionLogEntry.get_edited_action(performing_user, edit_comment)
 | 
			
		||||
        self.modified = edit_action
 | 
			
		||||
        self.log.add(edit_action)
 | 
			
		||||
        self.save()
 | 
			
		||||
        return edit_action
 | 
			
		||||
 | 
			
		||||
    def add_log_entry(self, action: UserAction, user: User, comment: str):
 | 
			
		||||
        """ Wraps adding of UserActionLogEntry to log
 | 
			
		||||
 | 
			
		||||
@ -200,6 +218,10 @@ class BaseObject(BaseResource):
 | 
			
		||||
        _str = "{}{}-{}".format(curr_month, curr_year, rand_str)
 | 
			
		||||
        return definitions[self.__class__]["template"].format(_str)
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def get_detail_url(self):
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RecordableObjectMixin(models.Model):
 | 
			
		||||
    """ Wraps record related fields and functionality
 | 
			
		||||
@ -236,7 +258,7 @@ class RecordableObjectMixin(models.Model):
 | 
			
		||||
 | 
			
		||||
        shared_users = self.users.all().values_list("id", flat=True)
 | 
			
		||||
        for user_id in shared_users:
 | 
			
		||||
            celery_send_mail_shared_data_unrecorded.delay(self.identifier, user_id)
 | 
			
		||||
            celery_send_mail_shared_data_unrecorded.delay(self.identifier, self.title, user_id)
 | 
			
		||||
 | 
			
		||||
        return action
 | 
			
		||||
 | 
			
		||||
@ -258,29 +280,22 @@ class RecordableObjectMixin(models.Model):
 | 
			
		||||
 | 
			
		||||
        shared_users = self.users.all().values_list("id", flat=True)
 | 
			
		||||
        for user_id in shared_users:
 | 
			
		||||
            celery_send_mail_shared_data_recorded.delay(self.identifier, user_id)
 | 
			
		||||
            celery_send_mail_shared_data_recorded.delay(self.identifier, self.title, user_id)
 | 
			
		||||
 | 
			
		||||
        return action
 | 
			
		||||
 | 
			
		||||
    def mark_as_edited(self, performing_user: User, request: HttpRequest = None, edit_comment: str = None, reset_recorded: bool = True):
 | 
			
		||||
        """ In case the object or a related object changed, internal processes need to be started, such as
 | 
			
		||||
        unrecord and uncheck
 | 
			
		||||
    def unrecord(self, performing_user: User, request: HttpRequest = None):
 | 
			
		||||
        """ Unrecords a dataset
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            performing_user (User): The user which performed the editing action
 | 
			
		||||
            request (HttpRequest): The used request for this action
 | 
			
		||||
            edit_comment (str): Additional comment for the log entry
 | 
			
		||||
            reset_recorded (bool): Whether the record-state of the object should be reset
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        edit_action = UserActionLogEntry.get_edited_action(performing_user, edit_comment)
 | 
			
		||||
        self.modified = edit_action
 | 
			
		||||
        self.log.add(edit_action)
 | 
			
		||||
        self.save()
 | 
			
		||||
 | 
			
		||||
        if self.recorded and reset_recorded:
 | 
			
		||||
        action = None
 | 
			
		||||
        if self.recorded:
 | 
			
		||||
            action = self.set_unrecorded(performing_user)
 | 
			
		||||
            self.log.add(action)
 | 
			
		||||
            if request:
 | 
			
		||||
@ -288,7 +303,7 @@ class RecordableObjectMixin(models.Model):
 | 
			
		||||
                    request,
 | 
			
		||||
                    CHECKED_RECORDED_RESET
 | 
			
		||||
                )
 | 
			
		||||
        return edit_action
 | 
			
		||||
        return action
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def is_ready_for_publish(self) -> bool:
 | 
			
		||||
@ -350,7 +365,7 @@ class CheckableObjectMixin(models.Model):
 | 
			
		||||
        # Send mail
 | 
			
		||||
        shared_users = self.users.all().values_list("id", flat=True)
 | 
			
		||||
        for user_id in shared_users:
 | 
			
		||||
            celery_send_mail_shared_data_checked.delay(self.identifier, user_id)
 | 
			
		||||
            celery_send_mail_shared_data_checked.delay(self.identifier, self.title, user_id)
 | 
			
		||||
 | 
			
		||||
        self.log.add(action)
 | 
			
		||||
        return action
 | 
			
		||||
@ -464,9 +479,9 @@ class ShareableObjectMixin(models.Model):
 | 
			
		||||
 | 
			
		||||
        # Send mails
 | 
			
		||||
        for user in removed_users:
 | 
			
		||||
            celery_send_mail_shared_access_removed.delay(self.identifier, user["id"])
 | 
			
		||||
            celery_send_mail_shared_access_removed.delay(self.identifier, self.title, user["id"])
 | 
			
		||||
        for user in new_accessing_users:
 | 
			
		||||
            celery_send_mail_shared_access_given.delay(self.identifier, user)
 | 
			
		||||
            celery_send_mail_shared_access_given.delay(self.identifier, self.title,  user)
 | 
			
		||||
 | 
			
		||||
        # Set new shared users
 | 
			
		||||
        self.share_with_list(users)
 | 
			
		||||
@ -530,3 +545,31 @@ class GeoReferencedMixin(models.Model):
 | 
			
		||||
            message_str = GEOMETRY_CONFLICT_WITH_TEMPLATE.format(instance_identifiers)
 | 
			
		||||
            messages.info(request, message_str)
 | 
			
		||||
        return request
 | 
			
		||||
 | 
			
		||||
    def get_LANIS_link(self) -> str:
 | 
			
		||||
        """ Generates a link for LANIS depending on the geometry
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            geom = self.geometry.geom.transform(DEFAULT_SRID_RLP, clone=True)
 | 
			
		||||
            x = geom.centroid.x
 | 
			
		||||
            y = geom.centroid.y
 | 
			
		||||
            area = int(geom.envelope.area)
 | 
			
		||||
            z_l = 16
 | 
			
		||||
            for k_area, v_zoom in LANIS_ZOOM_LUT.items():
 | 
			
		||||
                if k_area < area:
 | 
			
		||||
                    z_l = v_zoom
 | 
			
		||||
                    break
 | 
			
		||||
            zoom_lvl = z_l
 | 
			
		||||
        except (AttributeError, IndexError) as e:
 | 
			
		||||
            # If no geometry has been added, yet.
 | 
			
		||||
            x = 1
 | 
			
		||||
            y = 1
 | 
			
		||||
            zoom_lvl = 6
 | 
			
		||||
        return LANIS_LINK_TEMPLATE.format(
 | 
			
		||||
            zoom_lvl,
 | 
			
		||||
            x,
 | 
			
		||||
            y,
 | 
			
		||||
        )
 | 
			
		||||
@ -22,7 +22,7 @@ class Parcel(UuidModel):
 | 
			
		||||
    To avoid conflicts due to german Umlaute, the field names are shortened and vocals are dropped.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    geometries = models.ManyToManyField("konova.Geometry", related_name="parcels", blank=True)
 | 
			
		||||
    geometries = models.ManyToManyField("konova.Geometry", blank=True, related_name="parcels", through='ParcelIntersection')
 | 
			
		||||
    district = models.ForeignKey("konova.District", on_delete=models.SET_NULL, null=True, blank=True, related_name="parcels")
 | 
			
		||||
    gmrkng = models.CharField(
 | 
			
		||||
        max_length=1000,
 | 
			
		||||
@ -77,3 +77,22 @@ class District(UuidModel):
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"{self.gmnd} | {self.krs}"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ParcelIntersection(UuidModel):
 | 
			
		||||
    """ ParcelIntersection is an intermediary model, which is used to configure the
 | 
			
		||||
    M2M relation between Parcel and Geometry.
 | 
			
		||||
 | 
			
		||||
    Based on uuids, we will not have (practically) any problems on outrunning primary keys
 | 
			
		||||
    and extending the model with calculated_on timestamp, we can 'hide' entries while they
 | 
			
		||||
    are being recalculated and keep track on the last time they have been calculated this
 | 
			
		||||
    way.
 | 
			
		||||
 | 
			
		||||
    Please note: The calculated_on describes when the relation between the Parcel and the Geometry
 | 
			
		||||
    has been established. The updated_on field of Parcel describes when this Parcel has been
 | 
			
		||||
    changed the last time.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    parcel = models.ForeignKey(Parcel, on_delete=models.CASCADE)
 | 
			
		||||
    geometry = models.ForeignKey("konova.Geometry", on_delete=models.CASCADE)
 | 
			
		||||
    calculated_on = models.DateTimeField(auto_now_add=True, null=True, blank=True)
 | 
			
		||||
 | 
			
		||||
@ -219,6 +219,13 @@ Overwrites bootstrap .btn:focus box shadow color
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.w-20{
 | 
			
		||||
    width: 20%;
 | 
			
		||||
}
 | 
			
		||||
.w-10{
 | 
			
		||||
    width: 20%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Extends css for django autocomplete light (dal)
 | 
			
		||||
No other approach worked to get the autocomplete fields to full width of parent containers
 | 
			
		||||
 | 
			
		||||
@ -4,13 +4,19 @@ from celery import shared_task
 | 
			
		||||
from django.core.exceptions import ObjectDoesNotExist
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@shared_task
 | 
			
		||||
def celery_update_parcels(geometry_id: str, recheck: bool = True):
 | 
			
		||||
    from konova.models import Geometry
 | 
			
		||||
    from konova.models import Geometry, ParcelIntersection
 | 
			
		||||
    try:
 | 
			
		||||
        geom = Geometry.objects.get(id=geometry_id)
 | 
			
		||||
        geom.parcels.clear()
 | 
			
		||||
        objs = geom.parcelintersection_set.all()
 | 
			
		||||
        for obj in objs:
 | 
			
		||||
            obj.calculated_on = None
 | 
			
		||||
        ParcelIntersection.objects.bulk_update(
 | 
			
		||||
            objs,
 | 
			
		||||
            ["calculated_on"]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        geom.update_parcels()
 | 
			
		||||
    except ObjectDoesNotExist:
 | 
			
		||||
        if recheck:
 | 
			
		||||
@ -19,42 +25,42 @@ def celery_update_parcels(geometry_id: str, recheck: bool = True):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@shared_task
 | 
			
		||||
def celery_send_mail_shared_access_removed(obj_identifier, user_id):
 | 
			
		||||
def celery_send_mail_shared_access_removed(obj_identifier, obj_title=None, user_id=None):
 | 
			
		||||
    from user.models import User
 | 
			
		||||
    user = User.objects.get(id=user_id)
 | 
			
		||||
    user.send_mail_shared_access_removed(obj_identifier)
 | 
			
		||||
    user.send_mail_shared_access_removed(obj_identifier, obj_title)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@shared_task
 | 
			
		||||
def celery_send_mail_shared_access_given(obj_identifier, user_id):
 | 
			
		||||
def celery_send_mail_shared_access_given(obj_identifier, obj_title=None, user_id=None):
 | 
			
		||||
    from user.models import User
 | 
			
		||||
    user = User.objects.get(id=user_id)
 | 
			
		||||
    user.send_mail_shared_access_given(obj_identifier)
 | 
			
		||||
    user.send_mail_shared_access_given(obj_identifier, obj_title)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@shared_task
 | 
			
		||||
def celery_send_mail_shared_data_recorded(obj_identifier, user_id):
 | 
			
		||||
def celery_send_mail_shared_data_recorded(obj_identifier, obj_title=None, user_id=None):
 | 
			
		||||
    from user.models import User
 | 
			
		||||
    user = User.objects.get(id=user_id)
 | 
			
		||||
    user.send_mail_shared_data_recorded(obj_identifier)
 | 
			
		||||
    user.send_mail_shared_data_recorded(obj_identifier, obj_title)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@shared_task
 | 
			
		||||
def celery_send_mail_shared_data_unrecorded(obj_identifier, user_id):
 | 
			
		||||
def celery_send_mail_shared_data_unrecorded(obj_identifier, obj_title=None, user_id=None):
 | 
			
		||||
    from user.models import User
 | 
			
		||||
    user = User.objects.get(id=user_id)
 | 
			
		||||
    user.send_mail_shared_data_unrecorded(obj_identifier)
 | 
			
		||||
    user.send_mail_shared_data_unrecorded(obj_identifier, obj_title)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@shared_task
 | 
			
		||||
def celery_send_mail_shared_data_deleted(obj_identifier, user_id):
 | 
			
		||||
def celery_send_mail_shared_data_deleted(obj_identifier, obj_title=None, user_id=None):
 | 
			
		||||
    from user.models import User
 | 
			
		||||
    user = User.objects.get(id=user_id)
 | 
			
		||||
    user.send_mail_shared_data_deleted(obj_identifier)
 | 
			
		||||
    user.send_mail_shared_data_deleted(obj_identifier, obj_title)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@shared_task
 | 
			
		||||
def celery_send_mail_shared_data_checked(obj_identifier, user_id):
 | 
			
		||||
def celery_send_mail_shared_data_checked(obj_identifier, obj_title=None, user_id=None):
 | 
			
		||||
    from user.models import User
 | 
			
		||||
    user = User.objects.get(id=user_id)
 | 
			
		||||
    user.send_mail_shared_data_checked(obj_identifier)
 | 
			
		||||
    user.send_mail_shared_data_checked(obj_identifier, obj_title)
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ Created on: 26.10.21
 | 
			
		||||
"""
 | 
			
		||||
import datetime
 | 
			
		||||
 | 
			
		||||
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID
 | 
			
		||||
from ema.models import Ema
 | 
			
		||||
from user.models import User
 | 
			
		||||
from django.contrib.auth.models import Group
 | 
			
		||||
@ -15,7 +16,7 @@ from django.core.exceptions import ObjectDoesNotExist
 | 
			
		||||
from django.test import TestCase, Client
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from codelist.models import KonovaCode
 | 
			
		||||
from codelist.models import KonovaCode, KonovaCodeList
 | 
			
		||||
from compensation.models import Compensation, CompensationState, CompensationAction, EcoAccount, EcoAccountDeduction
 | 
			
		||||
from intervention.models import Legal, Responsibility, Intervention
 | 
			
		||||
from konova.management.commands.setup_data import GROUPS_DATA
 | 
			
		||||
@ -46,43 +47,58 @@ class BaseTestCase(TestCase):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        cls.create_users()
 | 
			
		||||
        cls.create_groups()
 | 
			
		||||
        cls.intervention = cls.create_dummy_intervention()
 | 
			
		||||
        cls.compensation = cls.create_dummy_compensation()
 | 
			
		||||
        cls.eco_account = cls.create_dummy_eco_account()
 | 
			
		||||
        cls.ema = cls.create_dummy_ema()
 | 
			
		||||
        cls.deduction = cls.create_dummy_deduction()
 | 
			
		||||
        cls.create_dummy_states()
 | 
			
		||||
        cls.create_dummy_action()
 | 
			
		||||
        cls.codes = cls.create_dummy_codes()
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        """ Setup data before each test run
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create_users(cls):
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        super().setUp()
 | 
			
		||||
 | 
			
		||||
        self.create_users()
 | 
			
		||||
        self.create_groups()
 | 
			
		||||
        self.intervention = self.create_dummy_intervention()
 | 
			
		||||
        self.compensation = self.create_dummy_compensation()
 | 
			
		||||
        self.eco_account = self.create_dummy_eco_account()
 | 
			
		||||
        self.ema = self.create_dummy_ema()
 | 
			
		||||
        self.deduction = self.create_dummy_deduction()
 | 
			
		||||
        self.create_dummy_states()
 | 
			
		||||
        self.create_dummy_action()
 | 
			
		||||
        self.codes = self.create_dummy_codes()
 | 
			
		||||
 | 
			
		||||
        # Set the default group as only group for the user
 | 
			
		||||
        default_group = self.groups.get(name=DEFAULT_GROUP)
 | 
			
		||||
        self.superuser.groups.set([default_group])
 | 
			
		||||
 | 
			
		||||
        # Create fresh logged in client and a non-logged in client (anon) for each test
 | 
			
		||||
        self.client_user = Client()
 | 
			
		||||
        self.client_user.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        self.client_anon = Client()
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    def create_users(self):
 | 
			
		||||
        # Create superuser and regular user
 | 
			
		||||
        cls.superuser = User.objects.create_superuser(
 | 
			
		||||
        self.superuser = User.objects.create_superuser(
 | 
			
		||||
            username="root",
 | 
			
		||||
            email="root@root.com",
 | 
			
		||||
            password=cls.superuser_pw,
 | 
			
		||||
            password=self.superuser_pw,
 | 
			
		||||
        )
 | 
			
		||||
        cls.user = User.objects.create_user(
 | 
			
		||||
        self.user = User.objects.create_user(
 | 
			
		||||
            username="user1",
 | 
			
		||||
            email="user@root.com",
 | 
			
		||||
            password=cls.user_pw
 | 
			
		||||
            password=self.user_pw
 | 
			
		||||
        )
 | 
			
		||||
        cls.users = User.objects.all()
 | 
			
		||||
        self.users = User.objects.all()
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create_groups(cls):
 | 
			
		||||
    
 | 
			
		||||
    def create_groups(self):
 | 
			
		||||
        # Create groups
 | 
			
		||||
        for group_data in GROUPS_DATA:
 | 
			
		||||
            name = group_data.get("name")
 | 
			
		||||
            Group.objects.get_or_create(
 | 
			
		||||
                name=name,
 | 
			
		||||
            )
 | 
			
		||||
        cls.groups = Group.objects.all()
 | 
			
		||||
        self.groups = Group.objects.all()
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def create_dummy_string(prefix: str = ""):
 | 
			
		||||
@ -93,8 +109,7 @@ class BaseTestCase(TestCase):
 | 
			
		||||
        """
 | 
			
		||||
        return f"{prefix}{generate_random_string(3, True)}"
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create_dummy_intervention(cls):
 | 
			
		||||
    def create_dummy_intervention(self):
 | 
			
		||||
        """ Creates an intervention which can be used for tests
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
@ -102,7 +117,7 @@ class BaseTestCase(TestCase):
 | 
			
		||||
        """
 | 
			
		||||
        # Create dummy data
 | 
			
		||||
        # Create log entry
 | 
			
		||||
        action = UserActionLogEntry.get_created_action(cls.superuser)
 | 
			
		||||
        action = UserActionLogEntry.get_created_action(self.superuser)
 | 
			
		||||
        # Create legal data object (without M2M laws first)
 | 
			
		||||
        legal_data = Legal.objects.create()
 | 
			
		||||
        # Create responsible data object
 | 
			
		||||
@ -121,32 +136,30 @@ class BaseTestCase(TestCase):
 | 
			
		||||
        intervention.generate_access_token(make_unique=True)
 | 
			
		||||
        return intervention
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create_dummy_compensation(cls):
 | 
			
		||||
    def create_dummy_compensation(self):
 | 
			
		||||
        """ Creates a compensation which can be used for tests
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if cls.intervention is None:
 | 
			
		||||
            cls.intervention = cls.create_dummy_intervention()
 | 
			
		||||
        if self.intervention is None:
 | 
			
		||||
            self.intervention = self.create_dummy_intervention()
 | 
			
		||||
        # Create dummy data
 | 
			
		||||
        # Create log entry
 | 
			
		||||
        action = UserActionLogEntry.get_created_action(cls.superuser)
 | 
			
		||||
        action = UserActionLogEntry.get_created_action(self.superuser)
 | 
			
		||||
        geometry = Geometry.objects.create()
 | 
			
		||||
        # Finally create main object, holding the other objects
 | 
			
		||||
        compensation = Compensation.objects.create(
 | 
			
		||||
            identifier="TEST",
 | 
			
		||||
            title="Test_title",
 | 
			
		||||
            intervention=cls.intervention,
 | 
			
		||||
            intervention=self.intervention,
 | 
			
		||||
            created=action,
 | 
			
		||||
            geometry=geometry,
 | 
			
		||||
            comment="Test",
 | 
			
		||||
        )
 | 
			
		||||
        return compensation
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create_dummy_eco_account(cls):
 | 
			
		||||
    def create_dummy_eco_account(self):
 | 
			
		||||
        """ Creates an eco account which can be used for tests
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
@ -154,7 +167,7 @@ class BaseTestCase(TestCase):
 | 
			
		||||
        """
 | 
			
		||||
        # Create dummy data
 | 
			
		||||
        # Create log entry
 | 
			
		||||
        action = UserActionLogEntry.get_created_action(cls.superuser)
 | 
			
		||||
        action = UserActionLogEntry.get_created_action(self.superuser)
 | 
			
		||||
        geometry = Geometry.objects.create()
 | 
			
		||||
        # Create responsible data object
 | 
			
		||||
        lega_data = Legal.objects.create()
 | 
			
		||||
@ -171,8 +184,7 @@ class BaseTestCase(TestCase):
 | 
			
		||||
        )
 | 
			
		||||
        return eco_account
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create_dummy_ema(cls):
 | 
			
		||||
    def create_dummy_ema(self):
 | 
			
		||||
        """ Creates an ema which can be used for tests
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
@ -180,7 +192,7 @@ class BaseTestCase(TestCase):
 | 
			
		||||
        """
 | 
			
		||||
        # Create dummy data
 | 
			
		||||
        # Create log entry
 | 
			
		||||
        action = UserActionLogEntry.get_created_action(cls.superuser)
 | 
			
		||||
        action = UserActionLogEntry.get_created_action(self.superuser)
 | 
			
		||||
        geometry = Geometry.objects.create()
 | 
			
		||||
        # Create responsible data object
 | 
			
		||||
        responsible_data = Responsibility.objects.create()
 | 
			
		||||
@ -195,51 +207,47 @@ class BaseTestCase(TestCase):
 | 
			
		||||
        )
 | 
			
		||||
        return ema
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create_dummy_deduction(cls):
 | 
			
		||||
    def create_dummy_deduction(self):
 | 
			
		||||
        return EcoAccountDeduction.objects.create(
 | 
			
		||||
            account=cls.create_dummy_eco_account(),
 | 
			
		||||
            intervention=cls.create_dummy_intervention(),
 | 
			
		||||
            account=self.create_dummy_eco_account(),
 | 
			
		||||
            intervention=self.create_dummy_intervention(),
 | 
			
		||||
            surface=100,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create_dummy_states(cls):
 | 
			
		||||
    def create_dummy_states(self):
 | 
			
		||||
        """ Creates an intervention which can be used for tests
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        cls.comp_state = CompensationState.objects.create(
 | 
			
		||||
        self.comp_state = CompensationState.objects.create(
 | 
			
		||||
            surface=10.00,
 | 
			
		||||
            biotope_type=None,
 | 
			
		||||
        )
 | 
			
		||||
        return cls.comp_state
 | 
			
		||||
        return self.comp_state
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create_dummy_action(cls):
 | 
			
		||||
    def create_dummy_action(self):
 | 
			
		||||
        """ Creates an intervention which can be used for tests
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        cls.comp_action = CompensationAction.objects.create(
 | 
			
		||||
        self.comp_action = CompensationAction.objects.create(
 | 
			
		||||
            amount=10
 | 
			
		||||
        )
 | 
			
		||||
        return cls.comp_action
 | 
			
		||||
        return self.comp_action
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create_dummy_codes(cls):
 | 
			
		||||
    def create_dummy_codes(self):
 | 
			
		||||
        """ Creates some dummy KonovaCodes which can be used for testing
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        codes = KonovaCode.objects.bulk_create([
 | 
			
		||||
            KonovaCode(id=1, is_selectable=True, long_name="Test1"),
 | 
			
		||||
            KonovaCode(id=2, is_selectable=True, long_name="Test2"),
 | 
			
		||||
            KonovaCode(id=3, is_selectable=True, long_name="Test3"),
 | 
			
		||||
            KonovaCode(id=4, is_selectable=True, long_name="Test4"),
 | 
			
		||||
            KonovaCode(id=1, is_selectable=True, is_archived=False, is_leaf=True, long_name="Test1"),
 | 
			
		||||
            KonovaCode(id=2, is_selectable=True, is_archived=False, is_leaf=True, long_name="Test2"),
 | 
			
		||||
            KonovaCode(id=3, is_selectable=True, is_archived=False, is_leaf=True, long_name="Test3"),
 | 
			
		||||
            KonovaCode(id=4, is_selectable=True, is_archived=False, is_leaf=True, long_name="Test4"),
 | 
			
		||||
        ])
 | 
			
		||||
        return codes
 | 
			
		||||
 | 
			
		||||
@ -255,8 +263,7 @@ class BaseTestCase(TestCase):
 | 
			
		||||
        polygon = polygon.transform(3857, clone=True)
 | 
			
		||||
        return MultiPolygon(polygon, srid=3857)  # 3857 is the default srid used for MultiPolygonField in the form
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def fill_out_intervention(cls, intervention: Intervention) -> Intervention:
 | 
			
		||||
    def fill_out_intervention(self, intervention: Intervention) -> Intervention:
 | 
			
		||||
        """ Adds all required (dummy) data to an intervention
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
@ -276,13 +283,12 @@ class BaseTestCase(TestCase):
 | 
			
		||||
        intervention.legal.process_type = KonovaCode.objects.get(id=3)
 | 
			
		||||
        intervention.legal.save()
 | 
			
		||||
        intervention.legal.laws.set([KonovaCode.objects.get(id=(4))])
 | 
			
		||||
        intervention.geometry.geom = cls.create_dummy_geometry()
 | 
			
		||||
        intervention.geometry.geom = self.create_dummy_geometry()
 | 
			
		||||
        intervention.geometry.save()
 | 
			
		||||
        intervention.save()
 | 
			
		||||
        return intervention
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def fill_out_compensation(cls, compensation: Compensation) -> Compensation:
 | 
			
		||||
    def fill_out_compensation(self, compensation: Compensation) -> Compensation:
 | 
			
		||||
        """ Adds all required (dummy) data to a compensation
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
@ -291,13 +297,62 @@ class BaseTestCase(TestCase):
 | 
			
		||||
        Returns:
 | 
			
		||||
            compensation (Compensation): The modified compensation
 | 
			
		||||
        """
 | 
			
		||||
        compensation.after_states.add(cls.comp_state)
 | 
			
		||||
        compensation.before_states.add(cls.comp_state)
 | 
			
		||||
        compensation.actions.add(cls.comp_action)
 | 
			
		||||
        compensation.geometry.geom = cls.create_dummy_geometry()
 | 
			
		||||
        compensation.after_states.add(self.comp_state)
 | 
			
		||||
        compensation.before_states.add(self.comp_state)
 | 
			
		||||
        compensation.actions.add(self.comp_action)
 | 
			
		||||
        compensation.geometry.geom = self.create_dummy_geometry()
 | 
			
		||||
        compensation.geometry.save()
 | 
			
		||||
        return compensation
 | 
			
		||||
 | 
			
		||||
    def get_conservation_office_code(self):
 | 
			
		||||
        """ Returns a dummy KonovaCode as conservation office code
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        codelist = KonovaCodeList.objects.get_or_create(
 | 
			
		||||
            id=CODELIST_CONSERVATION_OFFICE_ID
 | 
			
		||||
        )[0]
 | 
			
		||||
        code = KonovaCode.objects.get(id=2)
 | 
			
		||||
        codelist.codes.add(code)
 | 
			
		||||
        return code
 | 
			
		||||
 | 
			
		||||
    def fill_out_ema(self, ema):
 | 
			
		||||
        """ Adds all required (dummy) data to an Ema
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
        """
 | 
			
		||||
        ema.responsible.conservation_office = self.get_conservation_office_code()
 | 
			
		||||
        ema.responsible.conservation_file_number = "test"
 | 
			
		||||
        ema.responsible.handler = "handler"
 | 
			
		||||
        ema.responsible.save()
 | 
			
		||||
        ema.after_states.add(self.comp_state)
 | 
			
		||||
        ema.before_states.add(self.comp_state)
 | 
			
		||||
        ema.actions.add(self.comp_action)
 | 
			
		||||
        ema.geometry.geom = self.create_dummy_geometry()
 | 
			
		||||
        ema.geometry.save()
 | 
			
		||||
        return ema
 | 
			
		||||
 | 
			
		||||
    def fill_out_eco_account(self, eco_account):
 | 
			
		||||
        """ Adds all required (dummy) data to an EcoAccount
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
        """
 | 
			
		||||
        eco_account.legal.registration_date = "2022-01-01"
 | 
			
		||||
        eco_account.legal.save()
 | 
			
		||||
        eco_account.responsible.conservation_office = self.get_conservation_office_code()
 | 
			
		||||
        eco_account.responsible.conservation_file_number = "test"
 | 
			
		||||
        eco_account.responsible.handler = "handler"
 | 
			
		||||
        eco_account.responsible.save()
 | 
			
		||||
        eco_account.after_states.add(self.comp_state)
 | 
			
		||||
        eco_account.before_states.add(self.comp_state)
 | 
			
		||||
        eco_account.actions.add(self.comp_action)
 | 
			
		||||
        eco_account.geometry.geom = self.create_dummy_geometry()
 | 
			
		||||
        eco_account.geometry.save()
 | 
			
		||||
        eco_account.deductable_surface = eco_account.get_state_after_surface_sum()
 | 
			
		||||
        eco_account.save()
 | 
			
		||||
        return eco_account
 | 
			
		||||
 | 
			
		||||
    def assert_equal_geometries(self, geom1: MultiPolygon, geom2: MultiPolygon):
 | 
			
		||||
        """ Assert for geometries to be equal
 | 
			
		||||
 | 
			
		||||
@ -337,7 +392,10 @@ class BaseViewTestCase(BaseTestCase):
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls) -> None:
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
        cls.login_url = reverse("simple-sso-login")
 | 
			
		||||
        
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.login_url = reverse("simple-sso-login")
 | 
			
		||||
 | 
			
		||||
    def assert_url_success(self, client: Client, urls: list):
 | 
			
		||||
        """ Assert for all given urls a direct 200 response
 | 
			
		||||
@ -496,21 +554,6 @@ class BaseWorkflowTestCase(BaseTestCase):
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        """ Setup data before each test run
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        # Set the default group as only group for the user
 | 
			
		||||
        default_group = self.groups.get(name=DEFAULT_GROUP)
 | 
			
		||||
        self.superuser.groups.set([default_group])
 | 
			
		||||
 | 
			
		||||
        # Create fresh logged in client and a non-logged in client (anon) for each test
 | 
			
		||||
        self.client_user = Client()
 | 
			
		||||
        self.client_user.login(username=self.superuser.username, password=self.superuser_pw)
 | 
			
		||||
        self.client_anon = Client()
 | 
			
		||||
 | 
			
		||||
    def assert_object_is_deleted(self, obj):
 | 
			
		||||
        """ Provides a quick check whether an object has been removed from the database or not
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -50,5 +50,5 @@ def remove_document(request: HttpRequest, doc: AbstractDocument):
 | 
			
		||||
    form = RemoveModalForm(request.POST or None, instance=doc, request=request)
 | 
			
		||||
    return form.process_request(
 | 
			
		||||
        request=request,
 | 
			
		||||
        msg_success=DOCUMENT_REMOVED_TEMPLATE.format(title)
 | 
			
		||||
        msg_success=DOCUMENT_REMOVED_TEMPLATE.format(title),
 | 
			
		||||
    )
 | 
			
		||||
@ -45,11 +45,12 @@ class Mailer:
 | 
			
		||||
            auth_password=self.auth_password
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def send_mail_shared_access_removed(self, obj_identifier, user):
 | 
			
		||||
    def send_mail_shared_access_removed(self, obj_identifier, obj_title, user):
 | 
			
		||||
        """ Send a mail if user has no access to the object anymore
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            obj_identifier (str): The object identifier
 | 
			
		||||
            obj_title (str): The object title
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
@ -57,6 +58,7 @@ class Mailer:
 | 
			
		||||
        context = {
 | 
			
		||||
            "user": user,
 | 
			
		||||
            "obj_identifier": obj_identifier,
 | 
			
		||||
            "obj_title": obj_title,
 | 
			
		||||
            "EMAIL_REPLY_TO": EMAIL_REPLY_TO,
 | 
			
		||||
        }
 | 
			
		||||
        msg = render_to_string("email/sharing/shared_access_removed.html", context)
 | 
			
		||||
@ -67,7 +69,7 @@ class Mailer:
 | 
			
		||||
            msg
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def send_mail_shared_access_given(self, obj_identifier, user):
 | 
			
		||||
    def send_mail_shared_access_given(self, obj_identifier, obj_title, user):
 | 
			
		||||
        """ Send a mail if user just got access to the object
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
@ -79,6 +81,7 @@ class Mailer:
 | 
			
		||||
        context = {
 | 
			
		||||
            "user": user,
 | 
			
		||||
            "obj_identifier": obj_identifier,
 | 
			
		||||
            "obj_title": obj_title,
 | 
			
		||||
            "EMAIL_REPLY_TO": EMAIL_REPLY_TO,
 | 
			
		||||
        }
 | 
			
		||||
        msg = render_to_string("email/sharing/shared_access_given.html", context)
 | 
			
		||||
@ -89,7 +92,7 @@ class Mailer:
 | 
			
		||||
            msg
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def send_mail_shared_data_recorded(self, obj_identifier, user):
 | 
			
		||||
    def send_mail_shared_data_recorded(self, obj_identifier, obj_title, user):
 | 
			
		||||
        """ Send a mail if the user's shared data has just been unrecorded
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
@ -101,6 +104,7 @@ class Mailer:
 | 
			
		||||
        context = {
 | 
			
		||||
            "user": user,
 | 
			
		||||
            "obj_identifier": obj_identifier,
 | 
			
		||||
            "obj_title": obj_title,
 | 
			
		||||
            "EMAIL_REPLY_TO": EMAIL_REPLY_TO,
 | 
			
		||||
        }
 | 
			
		||||
        msg = render_to_string("email/recording/shared_data_recorded.html", context)
 | 
			
		||||
@ -111,7 +115,7 @@ class Mailer:
 | 
			
		||||
            msg
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def send_mail_shared_data_unrecorded(self, obj_identifier, user):
 | 
			
		||||
    def send_mail_shared_data_unrecorded(self, obj_identifier, obj_title, user):
 | 
			
		||||
        """ Send a mail if the user's shared data has just been unrecorded
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
@ -123,6 +127,7 @@ class Mailer:
 | 
			
		||||
        context = {
 | 
			
		||||
            "user": user,
 | 
			
		||||
            "obj_identifier": obj_identifier,
 | 
			
		||||
            "obj_title": obj_title,
 | 
			
		||||
            "EMAIL_REPLY_TO": EMAIL_REPLY_TO,
 | 
			
		||||
        }
 | 
			
		||||
        msg = render_to_string("email/recording/shared_data_unrecorded.html", context)
 | 
			
		||||
@ -133,7 +138,7 @@ class Mailer:
 | 
			
		||||
            msg
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def send_mail_shared_data_deleted(self, obj_identifier, user):
 | 
			
		||||
    def send_mail_shared_data_deleted(self, obj_identifier, obj_title, user):
 | 
			
		||||
        """ Send a mail if shared data has just been deleted
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
@ -145,6 +150,7 @@ class Mailer:
 | 
			
		||||
        context = {
 | 
			
		||||
            "user": user,
 | 
			
		||||
            "obj_identifier": obj_identifier,
 | 
			
		||||
            "obj_title": obj_title,
 | 
			
		||||
            "EMAIL_REPLY_TO": EMAIL_REPLY_TO,
 | 
			
		||||
        }
 | 
			
		||||
        msg = render_to_string("email/deleting/shared_data_deleted.html", context)
 | 
			
		||||
@ -155,7 +161,7 @@ class Mailer:
 | 
			
		||||
            msg
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def send_mail_shared_data_checked(self, obj_identifier, user):
 | 
			
		||||
    def send_mail_shared_data_checked(self, obj_identifier, obj_title, user):
 | 
			
		||||
        """ Send a mail if shared data just has been checked
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
@ -167,6 +173,7 @@ class Mailer:
 | 
			
		||||
        context = {
 | 
			
		||||
            "user": user,
 | 
			
		||||
            "obj_identifier": obj_identifier,
 | 
			
		||||
            "obj_title": obj_title,
 | 
			
		||||
            "EMAIL_REPLY_TO": EMAIL_REPLY_TO,
 | 
			
		||||
        }
 | 
			
		||||
        msg = render_to_string("email/checking/shared_data_checked.html", context)
 | 
			
		||||
 | 
			
		||||
@ -18,34 +18,61 @@ 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.")
 | 
			
		||||
 | 
			
		||||
# COMPENSATION
 | 
			
		||||
COMPENSATION_ADDED_TEMPLATE = _("Compensation {} added")
 | 
			
		||||
COMPENSATION_REMOVED_TEMPLATE = _("Compensation {} removed")
 | 
			
		||||
COMPENSATION_EDITED_TEMPLATE = _("Compensation {} edited")
 | 
			
		||||
ADDED_COMPENSATION_ACTION = _("Added compensation action")
 | 
			
		||||
ADDED_COMPENSATION_STATE = _("Added compensation state")
 | 
			
		||||
 | 
			
		||||
# COMPENSATION STATE
 | 
			
		||||
COMPENSATION_STATE_REMOVED = _("State removed")
 | 
			
		||||
COMPENSATION_STATE_EDITED = _("State edited")
 | 
			
		||||
COMPENSATION_STATE_ADDED = _("State added")
 | 
			
		||||
 | 
			
		||||
# COMPENSATION ACTION
 | 
			
		||||
COMPENSATION_ACTION_ADDED = _("Action added")
 | 
			
		||||
COMPENSATION_ACTION_EDITED = _("Action edited")
 | 
			
		||||
COMPENSATION_ACTION_REMOVED = _("Action removed")
 | 
			
		||||
 | 
			
		||||
# DEDUCTIONS
 | 
			
		||||
DEDUCTION_ADDED = _("Deduction added")
 | 
			
		||||
DEDUCTION_EDITED = _("Deduction edited")
 | 
			
		||||
DEDUCTION_REMOVED = _("Deduction removed")
 | 
			
		||||
 | 
			
		||||
# DEADLINE
 | 
			
		||||
DEADLINE_ADDED = _("Deadline added")
 | 
			
		||||
DEADLINE_EDITED = _("Deadline edited")
 | 
			
		||||
DEADLINE_REMOVED = _("Deadline removed")
 | 
			
		||||
 | 
			
		||||
# PAYMENTS
 | 
			
		||||
PAYMENT_ADDED = _("Payment added")
 | 
			
		||||
PAYMENT_EDITED = _("Payment edited")
 | 
			
		||||
PAYMENT_REMOVED = _("Payment removed")
 | 
			
		||||
 | 
			
		||||
# REVOCATIONS
 | 
			
		||||
REVOCATION_ADDED = _("Revocation added")
 | 
			
		||||
REVOCATION_EDITED = _("Revocation edited")
 | 
			
		||||
REVOCATION_REMOVED = _("Revocation removed")
 | 
			
		||||
 | 
			
		||||
# DOCUMENTS
 | 
			
		||||
DOCUMENT_REMOVED_TEMPLATE = _("Document '{}' deleted")
 | 
			
		||||
DOCUMENT_ADDED = _("Document added")
 | 
			
		||||
DOCUMENT_EDITED = _("Document edited")
 | 
			
		||||
 | 
			
		||||
# Edited
 | 
			
		||||
EDITED_GENERAL_DATA = _("Edited general data")
 | 
			
		||||
ADDED_COMPENSATION_STATE = _("Added compensation state")
 | 
			
		||||
ADDED_DEADLINE = _("Added deadline")
 | 
			
		||||
ADDED_COMPENSATION_ACTION = _("Added compensation action")
 | 
			
		||||
 | 
			
		||||
# Geometry conflicts
 | 
			
		||||
GEOMETRY_CONFLICT_WITH_TEMPLATE = _("Geometry conflict detected with {}")
 | 
			
		||||
 | 
			
		||||
# INTERVENTION
 | 
			
		||||
INTERVENTION_HAS_REVOCATIONS_TEMPLATE = _("This intervention has {} revocations")
 | 
			
		||||
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -11,6 +11,8 @@
 | 
			
		||||
        <br>
 | 
			
		||||
        <strong>{{obj_identifier}}</strong>
 | 
			
		||||
        <br>
 | 
			
		||||
        <strong>{{obj_title}}</strong>
 | 
			
		||||
        <br>
 | 
			
		||||
        {% trans 'This means, the responsible registration office just confirmed the correctness of this dataset.' %}
 | 
			
		||||
        <br>
 | 
			
		||||
        <br>
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,8 @@
 | 
			
		||||
        <br>
 | 
			
		||||
        <strong>{{obj_identifier}}</strong>
 | 
			
		||||
        <br>
 | 
			
		||||
        <strong>"{{obj_title}}"</strong>
 | 
			
		||||
        <br>
 | 
			
		||||
        {% trans 'If this should not have been happened, please contact us. See the signature for details.' %}
 | 
			
		||||
        <br>
 | 
			
		||||
        <br>
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,8 @@
 | 
			
		||||
        <br>
 | 
			
		||||
        <strong>{{obj_identifier}}</strong>
 | 
			
		||||
        <br>
 | 
			
		||||
        <strong>"{{obj_title}}"</strong>
 | 
			
		||||
        <br>
 | 
			
		||||
        {% trans 'This means the data is now publicly available, e.g. in LANIS' %}
 | 
			
		||||
        <br>
 | 
			
		||||
        <br>
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,8 @@
 | 
			
		||||
        <br>
 | 
			
		||||
        <strong>{{obj_identifier}}</strong>
 | 
			
		||||
        <br>
 | 
			
		||||
        <strong>"{{obj_title}}"</strong>
 | 
			
		||||
        <br>
 | 
			
		||||
        {% trans 'This means the data is no longer publicly available.' %}
 | 
			
		||||
        <br>
 | 
			
		||||
        <br>
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,8 @@
 | 
			
		||||
        <br>
 | 
			
		||||
        <strong>{{obj_identifier}}</strong>
 | 
			
		||||
        <br>
 | 
			
		||||
        <strong>"{{obj_title}}"</strong>
 | 
			
		||||
        <br>
 | 
			
		||||
        {% trans 'This means you can now edit this dataset.' %}
 | 
			
		||||
        {% trans 'The shared dataset appears now by default on your overview for this dataset type.' %}
 | 
			
		||||
        <br>
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,8 @@
 | 
			
		||||
        <br>
 | 
			
		||||
        <strong>{{obj_identifier}}</strong>
 | 
			
		||||
        <br>
 | 
			
		||||
        <strong>"{{obj_title}}"</strong>
 | 
			
		||||
        <br>
 | 
			
		||||
        {% trans 'However, you are still able to view the dataset content.' %}
 | 
			
		||||
        {% trans 'Please use the provided search filter on the dataset`s overview pages to find them.' %}
 | 
			
		||||
        <br>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								templates/table/gmrkng_col.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								templates/table/gmrkng_col.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
{% load i18n fontawesome_5 %}
 | 
			
		||||
 | 
			
		||||
{% for entry in entries %}
 | 
			
		||||
    <span class="badge pill-badge rlp-r">{{entry}}</span>
 | 
			
		||||
{% empty %}
 | 
			
		||||
    <span class="text-info" title="{% trans 'If the geometry is not empty, the parcels are currently recalculated. Please refresh this page in a few moments.' %}">
 | 
			
		||||
        {% fa5_icon 'hourglass-half' %}
 | 
			
		||||
    </span>
 | 
			
		||||
{% endfor %}
 | 
			
		||||
							
								
								
									
										14
									
								
								templates/table/revocation_warning_col.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								templates/table/revocation_warning_col.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
{% load i18n fontawesome_5 %}
 | 
			
		||||
 | 
			
		||||
{% if has_revocations %}
 | 
			
		||||
    <strong>
 | 
			
		||||
        <a href="{{url}}" title="{% trans 'Revocations exists' %}">
 | 
			
		||||
            {% fa5_icon 'ban' %}
 | 
			
		||||
            {{content}}
 | 
			
		||||
        </a>
 | 
			
		||||
    </strong>
 | 
			
		||||
{% else %}
 | 
			
		||||
    <a href="{{url}}" title="{{tooltip}}">
 | 
			
		||||
        {{content}}
 | 
			
		||||
    </a>
 | 
			
		||||
{% endif %}
 | 
			
		||||
@ -60,11 +60,12 @@ class User(AbstractUser):
 | 
			
		||||
            name=ETS_GROUP
 | 
			
		||||
        ).exists()
 | 
			
		||||
 | 
			
		||||
    def send_mail_shared_access_removed(self, obj_identifier):
 | 
			
		||||
    def send_mail_shared_access_removed(self, obj_identifier, obj_title):
 | 
			
		||||
        """ Sends a mail to the user in case of removed shared access
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            obj_identifier ():
 | 
			
		||||
            obj_title ():
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
@ -72,9 +73,9 @@ class User(AbstractUser):
 | 
			
		||||
        notification_set = self.is_notification_setting_set(UserNotificationEnum.NOTIFY_ON_SHARED_ACCESS_REMOVED)
 | 
			
		||||
        if notification_set:
 | 
			
		||||
            mailer = Mailer()
 | 
			
		||||
            mailer.send_mail_shared_access_removed(obj_identifier, self)
 | 
			
		||||
            mailer.send_mail_shared_access_removed(obj_identifier, obj_title, self)
 | 
			
		||||
 | 
			
		||||
    def send_mail_shared_access_given(self, obj_identifier):
 | 
			
		||||
    def send_mail_shared_access_given(self, obj_identifier, obj_title):
 | 
			
		||||
        """ Sends a mail to the user in case of given shared access
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
@ -86,9 +87,9 @@ class User(AbstractUser):
 | 
			
		||||
        notification_set = self.is_notification_setting_set(UserNotificationEnum.NOTIFY_ON_SHARED_ACCESS_GAINED)
 | 
			
		||||
        if notification_set:
 | 
			
		||||
            mailer = Mailer()
 | 
			
		||||
            mailer.send_mail_shared_access_given(obj_identifier, self)
 | 
			
		||||
            mailer.send_mail_shared_access_given(obj_identifier, obj_title, self)
 | 
			
		||||
 | 
			
		||||
    def send_mail_shared_data_recorded(self, obj_identifier):
 | 
			
		||||
    def send_mail_shared_data_recorded(self, obj_identifier, obj_title):
 | 
			
		||||
        """ Sends a mail to the user in case of shared data has been recorded
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
@ -100,9 +101,9 @@ class User(AbstractUser):
 | 
			
		||||
        notification_set = self.is_notification_setting_set(UserNotificationEnum.NOTIFY_ON_SHARED_DATA_RECORDED)
 | 
			
		||||
        if notification_set:
 | 
			
		||||
            mailer = Mailer()
 | 
			
		||||
            mailer.send_mail_shared_data_recorded(obj_identifier, self)
 | 
			
		||||
            mailer.send_mail_shared_data_recorded(obj_identifier, obj_title, self)
 | 
			
		||||
 | 
			
		||||
    def send_mail_shared_data_unrecorded(self, obj_identifier):
 | 
			
		||||
    def send_mail_shared_data_unrecorded(self, obj_identifier, obj_title):
 | 
			
		||||
        """ Sends a mail to the user in case of shared data has been unrecorded
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
@ -114,9 +115,9 @@ class User(AbstractUser):
 | 
			
		||||
        notification_set = self.is_notification_setting_set(UserNotificationEnum.NOTIFY_ON_SHARED_DATA_RECORDED)
 | 
			
		||||
        if notification_set:
 | 
			
		||||
            mailer = Mailer()
 | 
			
		||||
            mailer.send_mail_shared_data_unrecorded(obj_identifier, self)
 | 
			
		||||
            mailer.send_mail_shared_data_unrecorded(obj_identifier, obj_title, self)
 | 
			
		||||
 | 
			
		||||
    def send_mail_shared_data_deleted(self, obj_identifier):
 | 
			
		||||
    def send_mail_shared_data_deleted(self, obj_identifier, obj_title):
 | 
			
		||||
        """ Sends a mail to the user in case of shared data has been deleted
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
@ -128,9 +129,9 @@ class User(AbstractUser):
 | 
			
		||||
        notification_set = self.is_notification_setting_set(UserNotificationEnum.NOTIFY_ON_SHARED_DATA_DELETED)
 | 
			
		||||
        if notification_set:
 | 
			
		||||
            mailer = Mailer()
 | 
			
		||||
            mailer.send_mail_shared_data_deleted(obj_identifier, self)
 | 
			
		||||
            mailer.send_mail_shared_data_deleted(obj_identifier, obj_title, self)
 | 
			
		||||
 | 
			
		||||
    def send_mail_shared_data_checked(self, obj_identifier):
 | 
			
		||||
    def send_mail_shared_data_checked(self, obj_identifier, obj_title):
 | 
			
		||||
        """ Sends a mail to the user in case of shared data has been deleted
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
@ -142,7 +143,7 @@ class User(AbstractUser):
 | 
			
		||||
        notification_set = self.is_notification_setting_set(UserNotificationEnum.NOTIFY_ON_SHARED_DATA_CHECKED)
 | 
			
		||||
        if notification_set:
 | 
			
		||||
            mailer = Mailer()
 | 
			
		||||
            mailer.send_mail_shared_data_checked(obj_identifier, self)
 | 
			
		||||
            mailer.send_mail_shared_data_checked(obj_identifier, obj_title, self)
 | 
			
		||||
 | 
			
		||||
    def get_API_token(self):
 | 
			
		||||
        """ Getter for an API token
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user