Compare commits
13 Commits
7535f008b7
...
a096b2a413
Author | SHA1 | Date | |
---|---|---|---|
a096b2a413 | |||
6ea66180d1 | |||
402bc2d6f3 | |||
5e79f16e1e | |||
23c04c8883 | |||
d5a3c70788 | |||
02ccb78080 | |||
07c6f19d5c | |||
5ebb3f833a | |||
43bc3517ff | |||
5eebd42c3c | |||
98df0f93c3 | |||
7d3c3f030b |
@ -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, NewDocumentForm, 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
|
||||
|
||||
|
||||
class NewPaymentForm(BaseModalForm):
|
||||
@ -100,10 +100,26 @@ 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 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 +235,40 @@ class NewStateModalForm(BaseModalForm):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
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 +321,6 @@ 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
|
||||
|
||||
|
||||
|
@ -76,13 +76,3 @@ class CompensationAction(BaseResource):
|
||||
if choice[0] == self.unit:
|
||||
return choice[1]
|
||||
return None
|
||||
|
||||
def delete(self, user=None, *args, **kwargs):
|
||||
from compensation.models import Compensation
|
||||
if user:
|
||||
comps = Compensation.objects.filter(
|
||||
actions__id__in=[self.id]
|
||||
).distinct()
|
||||
for comp in comps:
|
||||
comp.mark_as_edited(user, edit_comment=COMPENSATION_ACTION_REMOVED)
|
||||
super().delete(*args, **kwargs)
|
@ -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, COMPENSATION_EDITED_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
|
||||
|
||||
|
||||
@ -71,8 +72,24 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
|
||||
|
||||
self.save()
|
||||
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
|
||||
|
||||
@ -98,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
|
||||
|
||||
@ -122,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
|
||||
|
||||
@ -284,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
|
||||
|
||||
@ -330,7 +355,7 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
|
||||
|
||||
"""
|
||||
self.intervention.unrecord(user, request)
|
||||
action = super().mark_as_edited(user, edit_comment)
|
||||
action = super().mark_as_edited(user, edit_comment=edit_comment)
|
||||
return action
|
||||
|
||||
def is_ready_for_publish(self) -> bool:
|
||||
@ -343,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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -47,14 +47,3 @@ class CompensationState(UuidModel):
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.biotope_type} | {self.surface} m²"
|
||||
|
||||
def delete(self, user=None, *args, **kwargs):
|
||||
from compensation.models import Compensation
|
||||
if user:
|
||||
comps = Compensation.objects.filter(
|
||||
Q(before_states__id__in=[self.id]) |
|
||||
Q(after_states__id__in=[self.id])
|
||||
).distinct()
|
||||
for comp in comps:
|
||||
comp.mark_as_edited(user, edit_comment=COMPENSATION_STATE_REMOVED)
|
||||
super().delete(*args, **kwargs)
|
||||
|
@ -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.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.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
|
||||
|
||||
|
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
|
||||
|
||||
"""
|
@ -2,11 +2,11 @@
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 27.10.21
|
||||
Created on: 07.02.22
|
||||
|
||||
"""
|
||||
from django.test.client import Client
|
||||
from django.urls import reverse
|
||||
from django.test import Client
|
||||
|
||||
from konova.settings import DEFAULT_GROUP
|
||||
from konova.tests.test_views import BaseViewTestCase
|
||||
@ -223,184 +223,3 @@ class CompensationViewTestCase(BaseViewTestCase):
|
||||
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)
|
||||
|
@ -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
|
||||
|
||||
@ -55,6 +55,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 +67,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 +91,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 +104,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 +118,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 +154,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 +172,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 +208,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 +223,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 +258,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
|
||||
|
||||
"""
|
194
compensation/tests/ecoaccount/test_views.py
Normal file
194
compensation/tests/ecoaccount/test_views.py
Normal file
@ -0,0 +1,194 @@
|
||||
"""
|
||||
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.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()
|
||||
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)
|
||||
|
231
compensation/tests/ecoaccount/test_workflow.py
Normal file
231
compensation/tests/ecoaccount/test_workflow.py
Normal file
@ -0,0 +1,231 @@
|
||||
"""
|
||||
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
|
||||
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_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)
|
||||
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.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)
|
||||
|
||||
|
@ -10,6 +10,6 @@ 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'),
|
||||
]
|
||||
|
@ -6,20 +6,21 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from compensation.forms.forms import NewCompensationForm, EditCompensationForm
|
||||
from compensation.forms.modalForms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm, \
|
||||
NewCompensationDocumentForm
|
||||
NewCompensationDocumentForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm
|
||||
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
|
||||
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, \
|
||||
COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED
|
||||
COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, \
|
||||
DEADLINE_ADDED, DEADLINE_REMOVED
|
||||
from konova.utils.user_checks import in_group
|
||||
|
||||
|
||||
@ -392,7 +393,7 @@ 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"
|
||||
)
|
||||
|
||||
@ -411,11 +412,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"
|
||||
)
|
||||
|
||||
@ -434,8 +436,9 @@ 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=COMPENSATION_STATE_REMOVED,
|
||||
@ -457,8 +460,9 @@ 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=COMPENSATION_ACTION_REMOVED,
|
||||
|
@ -16,14 +16,14 @@ 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
|
||||
NewEcoAccountDocumentForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm
|
||||
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
|
||||
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, NewDocumentForm, RecordModalForm, RemoveDeadlineModalForm
|
||||
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
|
||||
@ -31,7 +31,7 @@ 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, COMPENSATION_STATE_REMOVED, \
|
||||
COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED
|
||||
COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED
|
||||
from konova.utils.user_checks import in_group
|
||||
|
||||
|
||||
@ -286,7 +286,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,
|
||||
@ -401,8 +401,9 @@ 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=COMPENSATION_STATE_REMOVED,
|
||||
@ -424,8 +425,9 @@ 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=COMPENSATION_ACTION_REMOVED,
|
||||
@ -447,11 +449,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"
|
||||
)
|
||||
|
||||
@ -473,7 +476,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"
|
||||
)
|
||||
|
||||
|
@ -11,7 +11,7 @@ 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
|
||||
from compensation.models import Payment
|
||||
from intervention.models import Intervention
|
||||
from konova.decorators import default_group_required
|
||||
@ -21,39 +21,41 @@ from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED
|
||||
|
||||
@login_required
|
||||
@default_group_required
|
||||
def new_payment_view(request: HttpRequest, intervention_id: str):
|
||||
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):
|
||||
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,
|
||||
|
@ -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.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
|
||||
|
||||
|
@ -5,11 +5,12 @@ 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
|
||||
|
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())
|
20
ema/views.py
20
ema/views.py
@ -6,7 +6,8 @@ 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
|
||||
from compensation.models import CompensationAction, CompensationState
|
||||
from ema.forms import NewEmaForm, EditEmaForm, NewEmaDocumentForm
|
||||
from ema.tables import EmaTable
|
||||
@ -14,7 +15,7 @@ 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
|
||||
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
|
||||
@ -22,7 +23,7 @@ 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, COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, \
|
||||
COMPENSATION_ACTION_ADDED
|
||||
COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED
|
||||
from konova.utils.user_checks import in_group
|
||||
|
||||
|
||||
@ -337,7 +338,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"
|
||||
)
|
||||
|
||||
@ -425,8 +426,9 @@ 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=COMPENSATION_STATE_REMOVED,
|
||||
@ -448,8 +450,9 @@ 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=COMPENSATION_ACTION_REMOVED,
|
||||
@ -590,10 +593,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"
|
||||
)
|
@ -7,7 +7,7 @@ Created on: 27.09.21
|
||||
"""
|
||||
from dal import autocomplete
|
||||
|
||||
from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED
|
||||
from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED
|
||||
from user.models import User, UserActionLogEntry
|
||||
from django.db import transaction
|
||||
from django import forms
|
||||
@ -16,7 +16,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from compensation.models import EcoAccount, EcoAccountDeduction
|
||||
from intervention.inputs import TextToClipboardInput
|
||||
from intervention.models import Intervention, InterventionDocument
|
||||
from konova.forms import BaseModalForm, NewDocumentForm
|
||||
from konova.forms import BaseModalForm, NewDocumentForm, RemoveModalForm
|
||||
from konova.utils.general import format_german_float
|
||||
from konova.utils.user_checks import is_default_group_only
|
||||
|
||||
@ -172,6 +172,23 @@ class NewRevocationModalForm(BaseModalForm):
|
||||
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
|
||||
|
||||
@ -390,5 +407,25 @@ class NewDeductionModalForm(BaseModalForm):
|
||||
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 NewInterventionDocumentForm(NewDocumentForm):
|
||||
document_model = InterventionDocument
|
||||
|
@ -16,7 +16,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 +25,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 +100,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 +168,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 +202,21 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
|
||||
)
|
||||
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 +230,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
|
||||
Returns:
|
||||
|
||||
"""
|
||||
action = super().mark_as_edited(performing_user, edit_comment)
|
||||
action = super().mark_as_edited(performing_user, edit_comment=edit_comment)
|
||||
if reset_recorded:
|
||||
self.unrecord(performing_user, request)
|
||||
if self.checked:
|
||||
@ -260,6 +248,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)
|
||||
@ -289,6 +284,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.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)
|
||||
|
||||
|
@ -56,7 +56,7 @@
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
{% 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:remove' obj.id pay.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove payment' %}">
|
||||
{% fa5_icon 'trash' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
@ -65,7 +65,7 @@
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
{% 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:remove-revocation' obj.id rev.id %}" class="btn btn-default btn-modal float-right" 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
|
||||
|
||||
@ -34,6 +35,17 @@ class InterventionViewTestCase(BaseViewTestCase):
|
||||
cls.record_url = reverse("intervention:record", args=(cls.intervention.id,))
|
||||
cls.report_url = reverse("intervention:report", args=(cls.intervention.id,))
|
||||
|
||||
cls.deduction.intervention = cls.intervention
|
||||
cls.deduction.save()
|
||||
cls.deduction_new_url = reverse("intervention:new-deduction", args=(cls.intervention.id,))
|
||||
cls.deduction_remove_url = reverse("intervention:remove-deduction", args=(cls.intervention.id, cls.deduction.id))
|
||||
|
||||
cls.revocation = Revocation.objects.create(
|
||||
legal=cls.intervention.legal
|
||||
)
|
||||
cls.revocation_new_url = reverse("intervention:new-revocation", args=(cls.intervention.id,))
|
||||
cls.revocation_remove_url = reverse("intervention:remove-revocation", args=(cls.intervention.id, cls.revocation.id))
|
||||
|
||||
def test_views_anonymous_user(self):
|
||||
""" Check correct status code for all requests
|
||||
|
||||
@ -61,6 +73,10 @@ 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_remove_url: f"{login_redirect_base}{self.deduction_remove_url}",
|
||||
self.revocation_new_url: f"{login_redirect_base}{self.revocation_new_url}",
|
||||
self.revocation_remove_url: f"{login_redirect_base}{self.revocation_remove_url}",
|
||||
}
|
||||
|
||||
self.assert_url_success(client, success_urls)
|
||||
@ -96,6 +112,10 @@ class InterventionViewTestCase(BaseViewTestCase):
|
||||
self.share_create_url,
|
||||
self.run_check_url,
|
||||
self.record_url,
|
||||
self.revocation_new_url,
|
||||
self.revocation_remove_url,
|
||||
self.deduction_new_url,
|
||||
self.deduction_remove_url,
|
||||
]
|
||||
|
||||
self.assert_url_success(client, success_urls)
|
||||
@ -128,6 +148,10 @@ class InterventionViewTestCase(BaseViewTestCase):
|
||||
self.edit_url,
|
||||
self.remove_url,
|
||||
self.share_create_url,
|
||||
self.revocation_new_url,
|
||||
self.revocation_remove_url,
|
||||
self.deduction_new_url,
|
||||
self.deduction_remove_url,
|
||||
]
|
||||
fail_urls = [
|
||||
self.run_check_url,
|
||||
@ -172,6 +196,10 @@ class InterventionViewTestCase(BaseViewTestCase):
|
||||
self.remove_url,
|
||||
self.share_create_url,
|
||||
self.log_url,
|
||||
self.revocation_new_url,
|
||||
self.revocation_remove_url,
|
||||
self.deduction_new_url,
|
||||
self.deduction_remove_url,
|
||||
]
|
||||
success_urls_redirect = {
|
||||
self.share_url: self.detail_url
|
||||
@ -212,6 +240,10 @@ class InterventionViewTestCase(BaseViewTestCase):
|
||||
self.remove_url,
|
||||
self.share_create_url,
|
||||
self.record_url,
|
||||
self.revocation_new_url,
|
||||
self.revocation_remove_url,
|
||||
self.deduction_new_url,
|
||||
self.deduction_remove_url,
|
||||
]
|
||||
success_urls_redirect = {
|
||||
self.share_url: self.detail_url
|
||||
@ -252,6 +284,10 @@ class InterventionViewTestCase(BaseViewTestCase):
|
||||
self.share_create_url,
|
||||
self.record_url,
|
||||
self.run_check_url,
|
||||
self.revocation_new_url,
|
||||
self.revocation_remove_url,
|
||||
self.deduction_new_url,
|
||||
self.deduction_remove_url,
|
||||
]
|
||||
success_urls_redirect = {
|
||||
self.share_url: self.detail_url
|
||||
@ -292,6 +328,10 @@ class InterventionViewTestCase(BaseViewTestCase):
|
||||
self.remove_url,
|
||||
self.share_create_url,
|
||||
self.run_check_url,
|
||||
self.revocation_new_url,
|
||||
self.revocation_remove_url,
|
||||
self.deduction_new_url,
|
||||
self.deduction_remove_url,
|
||||
]
|
||||
success_urls_redirect = {
|
||||
self.share_url: self.detail_url
|
||||
@ -332,6 +372,10 @@ class InterventionViewTestCase(BaseViewTestCase):
|
||||
self.remove_url,
|
||||
self.share_create_url,
|
||||
self.run_check_url,
|
||||
self.revocation_new_url,
|
||||
self.revocation_remove_url,
|
||||
self.deduction_new_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.
|
||||
|
@ -28,7 +28,7 @@ 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'),
|
||||
@ -37,10 +37,10 @@ urlpatterns = [
|
||||
|
||||
# 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>/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>/remove', remove_revocation_view, name='remove-revocation'),
|
||||
path('revocation/<doc_id>', get_revocation_view, name='get-doc-revocation'),
|
||||
]
|
@ -6,7 +6,8 @@ from django.shortcuts import render
|
||||
|
||||
from intervention.forms.forms import NewInterventionForm, EditInterventionForm
|
||||
from intervention.forms.modalForms import ShareModalForm, NewRevocationModalForm, \
|
||||
CheckModalForm, NewDeductionModalForm, NewInterventionDocumentForm
|
||||
CheckModalForm, NewDeductionModalForm, NewInterventionDocumentForm, RemoveEcoAccountDeductionModalForm, \
|
||||
RemoveRevocationModalForm
|
||||
from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
|
||||
from intervention.tables import InterventionTable
|
||||
from konova.contexts import BaseContext
|
||||
@ -244,14 +245,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,7 +333,8 @@ def remove_view(request: HttpRequest, id: str):
|
||||
|
||||
@login_required
|
||||
@default_group_required
|
||||
def remove_revocation_view(request: HttpRequest, id: str):
|
||||
@shared_access_required(Intervention, "id")
|
||||
def remove_revocation_view(request: HttpRequest, id: str, revocation_id: str):
|
||||
""" Renders a remove view for a revocation
|
||||
|
||||
Args:
|
||||
@ -350,13 +344,14 @@ def remove_revocation_view(request: HttpRequest, id: str):
|
||||
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 = 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 +528,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,
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -327,7 +327,24 @@ 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 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 NewDocumentForm(BaseModalForm):
|
||||
|
@ -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,11 +129,11 @@ 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, edit_comment: str = None):
|
||||
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:
|
||||
@ -217,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
|
||||
@ -253,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
|
||||
|
||||
@ -275,7 +280,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_recorded.delay(self.identifier, user_id)
|
||||
celery_send_mail_shared_data_recorded.delay(self.identifier, self.title, user_id)
|
||||
|
||||
return action
|
||||
|
||||
@ -360,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
|
||||
@ -474,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)
|
||||
@ -540,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,
|
||||
)
|
@ -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
|
||||
|
@ -19,42 +19,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
|
||||
@ -236,10 +237,10 @@ class BaseTestCase(TestCase):
|
||||
|
||||
"""
|
||||
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
|
||||
|
||||
@ -298,6 +299,58 @@ class BaseTestCase(TestCase):
|
||||
compensation.geometry.save()
|
||||
return compensation
|
||||
|
||||
@classmethod
|
||||
def get_conservation_office_code(cls):
|
||||
""" 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
|
||||
|
||||
@classmethod
|
||||
def fill_out_ema(cls, ema):
|
||||
""" Adds all required (dummy) data to an Ema
|
||||
|
||||
Returns:
|
||||
"""
|
||||
ema.responsible.conservation_office = cls.get_conservation_office_code()
|
||||
ema.responsible.conservation_file_number = "test"
|
||||
ema.responsible.handler = "handler"
|
||||
ema.responsible.save()
|
||||
ema.after_states.add(cls.comp_state)
|
||||
ema.before_states.add(cls.comp_state)
|
||||
ema.actions.add(cls.comp_action)
|
||||
ema.geometry.geom = cls.create_dummy_geometry()
|
||||
ema.geometry.save()
|
||||
return ema
|
||||
|
||||
@classmethod
|
||||
def fill_out_eco_account(cls, 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 = cls.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(cls.comp_state)
|
||||
eco_account.before_states.add(cls.comp_state)
|
||||
eco_account.actions.add(cls.comp_action)
|
||||
eco_account.geometry.geom = cls.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
|
||||
|
||||
@ -502,6 +555,7 @@ class BaseWorkflowTestCase(BaseTestCase):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
super().setUp()
|
||||
# Set the default group as only group for the user
|
||||
default_group = self.groups.get(name=DEFAULT_GROUP)
|
||||
self.superuser.groups.set([default_group])
|
||||
|
@ -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)
|
||||
|
@ -40,6 +40,10 @@ COMPENSATION_ACTION_REMOVED = _("Action removed")
|
||||
DEDUCTION_ADDED = _("Deduction added")
|
||||
DEDUCTION_REMOVED = _("Deduction removed")
|
||||
|
||||
# DEADLINE
|
||||
DEADLINE_ADDED = _("Deadline added")
|
||||
DEADLINE_REMOVED = _("Deadline removed")
|
||||
|
||||
# PAYMENTS
|
||||
PAYMENT_ADDED = _("Payment added")
|
||||
PAYMENT_REMOVED = _("Payment removed")
|
||||
@ -58,3 +62,6 @@ ADDED_DEADLINE = _("Added deadline")
|
||||
|
||||
# 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…
Reference in New Issue
Block a user