Merge pull request 'test' (#347) from test into master

Reviewed-on: SGD-Nord/konova#347
pull/348/head
mpeltriaux 1 year ago
commit ffae18ebc4

4
.gitignore vendored

@ -1,3 +1,5 @@
# Project exclude paths
/venv/
/.idea/
/.idea/
/.coverage
/htmlcov/

@ -9,4 +9,8 @@ Created on: 19.10.21
# Defines the date of the legal publishing of the LKompVzVo
from django.utils import timezone
LKOMPVZVO_PUBLISH_DATE = timezone.make_aware(timezone.datetime.fromisoformat("2018-06-16")).date()
LKOMPVZVO_PUBLISH_DATE = timezone.make_aware(
timezone.datetime.fromisoformat(
"2018-06-16"
)
).date()

@ -31,6 +31,6 @@
{% include 'analysis/reports/includes/intervention/card_intervention.html' %}
{% include 'analysis/reports/includes/compensation/card_compensation.html' %}
{% include 'analysis/reports/includes/eco_account/card_eco_account.html' %}
{% include 'analysis/reports/includes/old_data/card_old_interventions.html' %}
{% include 'analysis/reports/includes/old_data/card_old_data.html' %}
</div>
{% endblock %}

@ -10,6 +10,7 @@
{% fa5_icon 'leaf' %}
{% trans 'Compensations' %}
</h5>
<span>{% trans 'Binding date after' %} 16.06.2018</span>
</div>
</div>
</div>

@ -10,6 +10,7 @@
{% fa5_icon 'tree' %}
{% trans 'Eco-Accounts' %}
</h5>
<span>{% trans 'Binding date after' %} 16.06.2018</span>
</div>
</div>
</div>

@ -9,6 +9,7 @@
{% fa5_icon 'pencil-ruler' %}
{% trans 'Interventions' %}
</h5>
<span>{% trans 'Binding date after' %} 16.06.2018</span>
</div>
</div>
</div>

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,7 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 15.08.23
"""

@ -0,0 +1,7 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 15.08.23
"""

@ -0,0 +1,47 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 15.08.23
"""
from datetime import timedelta
from django.urls import reverse
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from analysis.forms import TimespanReportForm
from konova.tests.test_views import BaseTestCase
class TimeSpanReportFormTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
eiv = self.create_dummy_intervention()
def test_init(self):
form = TimespanReportForm()
self.assertEqual(form.form_title, str(_("Generate report")))
self.assertEqual(form.form_caption, str(_("Select a timespan and the desired conservation office") ))
self.assertEqual(form.action_url, reverse("analysis:reports"))
self.assertFalse(form.show_cancel_btn)
self.assertEqual(form.action_btn_label, str(_("Continue")))
def test_save(self):
date_from = now().date() - timedelta(days=365)
date_to = now().date()
office = self.get_conservation_office_code()
data = {
"date_from": date_from,
"date_to": date_to,
"conservation_office": office,
}
form = TimespanReportForm(data)
self.assertTrue(form.is_valid(), msg=f"{form.errors}")
detail_report_url = form.save()
self.assertEqual(
detail_report_url,
reverse("analysis:report-detail", args=(office.id,)) + f"?df={date_from}&dt={date_to}"
)

@ -0,0 +1,98 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 17.08.23
"""
from datetime import timedelta
from django.utils.timezone import now
from analysis.settings import LKOMPVZVO_PUBLISH_DATE
from analysis.utils.report import TimespanReport
from konova.sub_settings.django_settings import DEFAULT_DATE_FORMAT
from konova.tests.test_views import BaseTestCase
class TimeSpanReportTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
today = now().date()
old_date = LKOMPVZVO_PUBLISH_DATE - timedelta(days=1)
self.conservation_office = self.get_conservation_office_code()
self.eiv_old = self.create_dummy_intervention()
self.kom_old = self.create_dummy_compensation(interv=self.eiv_old)
self.assertNotEqual(self.compensation.intervention, self.kom_old.intervention)
self.eiv = self.compensation.intervention
self.oek_old = self.create_dummy_eco_account()
self.eiv_old.responsible.conservation_office = self.conservation_office
self.eiv_old.legal.binding_date = old_date
self.eiv_old.legal.registration_date = old_date
self.eiv.responsible.conservation_office = self.conservation_office
self.eiv.legal.binding_date = today
self.eiv.legal.registration_date = today
self.eco_account.responsible.conservation_office = self.conservation_office
self.eco_account.legal.registration_date = today
self.eco_account.legal.binding_date = today
self.oek_old.responsible.conservation_office = self.conservation_office
self.oek_old.legal.registration_date = old_date
self.oek_old.legal.binding_date = old_date
self.eiv.legal.save()
self.eiv.responsible.save()
self.eiv_old.legal.save()
self.eiv_old.responsible.save()
self.eco_account.legal.save()
self.eco_account.responsible.save()
self.oek_old.legal.save()
self.oek_old.responsible.save()
self.deduction.account = self.eco_account
self.deduction.intervention = self.eiv
self.deduction.save()
def test_init(self):
date_from = now().date() - timedelta(days=365)
date_to = now().date()
report = TimespanReport(self.conservation_office.id, date_from, date_to)
self.assertEqual(report.office_id, self.conservation_office.id)
self.assertEqual(report.date_from, date_from)
self.assertEqual(report.date_to, date_to)
self.assertIsNotNone(report.intervention_report)
self.assertIsNotNone(report.compensation_report)
self.assertIsNotNone(report.eco_account_report)
self.assertIsNotNone(report.old_data_report)
self.assertEqual(report.excel_map["date_from"], date_from.strftime(DEFAULT_DATE_FORMAT))
self.assertEqual(report.excel_map["date_to"], date_to.strftime(DEFAULT_DATE_FORMAT))
self.assertEqual(report.old_data_report.queryset_intervention_count, 1)
self.assertEqual(report.old_data_report.queryset_intervention_recorded_count, 0)
self.assertEqual(report.old_data_report.queryset_comps_count, 1)
self.assertEqual(report.old_data_report.queryset_acc_count, 1)
self.assertEqual(report.old_data_report.queryset_acc_recorded_count, 0)
self.assertEqual(report.intervention_report.queryset_count, 1)
self.assertEqual(report.intervention_report.queryset_checked_count, 0)
self.assertEqual(report.intervention_report.queryset_recorded_count, 0)
self.assertEqual(report.compensation_report.queryset_count, 1)
self.assertEqual(report.compensation_report.queryset_checked_count, 0)
self.assertEqual(report.compensation_report.queryset_recorded_count, 0)
self.assertEqual(report.eco_account_report.queryset_count, 1)
self.assertEqual(report.eco_account_report.queryset_recorded_count, 0)
self.assertEqual(report.eco_account_report.queryset_deductions_count, 1)
self.assertEqual(report.eco_account_report.queryset_deductions_recorded_count, 0)

@ -413,6 +413,7 @@ class TimespanReport:
def __init__(self, id: str, date_from: str, date_to: str):
# First fetch all eco account for this office
self.queryset = EcoAccount.objects.filter(
legal__registration_date__gt=LKOMPVZVO_PUBLISH_DATE,
responsible__conservation_office__id=id,
deleted=None,
created__timestamp__date__gte=date_from,
@ -516,8 +517,8 @@ class TimespanReport:
legal__registration_date__lte=LKOMPVZVO_PUBLISH_DATE,
responsible__conservation_office__id=id,
deleted=None,
created__timestamp__gte=date_from,
created__timestamp__lte=date_to,
created__timestamp__date__gte=date_from,
created__timestamp__date__lte=date_to,
)
self.queryset_acc_recorded = self.queryset_acc.filter(
recorded__isnull=False,

@ -14,7 +14,7 @@ class APIUserToken(models.Model):
valid_until = models.DateField(
blank=True,
null=True,
help_text="Token is only valid until this date",
help_text="Token is only valid until this date. Forever if null/blank.",
)
is_active = models.BooleanField(
default=False,
@ -25,12 +25,11 @@ class APIUserToken(models.Model):
return self.token
@staticmethod
def get_user_from_token(token: str, username: str):
def get_user_from_token(token: str):
""" Getter for the related user object
Args:
token (str): The used token
username (str): The username
Returns:
user (User): Otherwise None
@ -39,7 +38,6 @@ class APIUserToken(models.Model):
try:
token_obj = APIUserToken.objects.get(
token=token,
user__username=username
)
if not token_obj.is_active:
raise PermissionError("Token unverified")

@ -0,0 +1,7 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 17.08.23
"""

@ -0,0 +1,71 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 17.08.23
"""
from datetime import timedelta
from django.utils.timezone import now
from api.models import APIUserToken
from konova.tests.test_views import BaseTestCase
class APIUserTokenTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
self.token = APIUserToken.objects.create()
self.superuser.api_token = self.token
self.superuser.save()
def test_str(self):
self.assertEqual(str(self.token), self.token.token)
def test_get_user_from_token(self):
a_day = timedelta(days=1)
today = now().date()
self.assertFalse(self.token.is_active)
self.assertIsNone(self.token.valid_until)
try:
#Token not existing --> fail
token_user = APIUserToken.get_user_from_token(self.token.token[::-1])
self.fail("There should not have been any token")
except PermissionError:
pass
try:
# Token not active --> fail
token_user = APIUserToken.get_user_from_token(self.token.token)
self.fail("Token is unverified but token user has been fetchable.")
except PermissionError:
pass
self.token.is_active = True
self.token.valid_until = today - a_day
self.token.save()
try:
# Token valid until yesterday --> fail
token_user = APIUserToken.get_user_from_token(self.token.token)
self.fail("Token reached end of lifetime but token user has been fetchable.")
except PermissionError:
pass
# Token valid until tomorrow --> success
self.token.valid_until = today + a_day
self.token.save()
token_user = APIUserToken.get_user_from_token(self.token.token)
self.assertEqual(token_user, self.superuser)
del token_user
# Token valid forever --> success
self.token.valid_until = None
self.token.save()
token_user = APIUserToken.get_user_from_token(self.token.token)
self.assertEqual(token_user, self.superuser)

@ -4,7 +4,6 @@ from django.urls import reverse
from konova.settings import DEFAULT_GROUP
from konova.tests.test_views import BaseTestCase
from konova.utils.user_checks import is_default_group_only
class BaseAPIV1TestCase(BaseTestCase):
@ -138,7 +137,7 @@ class APIV1SharingTestCase(BaseAPIV1TestCase):
# Give the user only default group rights
default_group = self.groups.get(name=DEFAULT_GROUP)
self.superuser.groups.set([default_group])
self.assertTrue(is_default_group_only(self.superuser))
self.assertTrue(self.superuser.is_default_group_only())
# Add only him as shared_users an object
self.intervention.users.set([self.superuser])

@ -18,7 +18,6 @@ from compensation.models import EcoAccount
from ema.models import Ema
from intervention.models import Intervention
from konova.utils.message_templates import DATA_UNSHARED
from konova.utils.user_checks import is_default_group_only
from user.models import User, Team
@ -53,7 +52,13 @@ class AbstractAPIView(View):
# Fetch the proper user from the given request header token
ksp_token = request.headers.get(KSP_TOKEN_HEADER_IDENTIFIER, None)
ksp_user = request.headers.get(KSP_USER_HEADER_IDENTIFIER, None)
self.user = APIUserToken.get_user_from_token(ksp_token, ksp_user)
token_user = APIUserToken.get_user_from_token(ksp_token)
if ksp_user != token_user.username:
raise PermissionError(f"Invalid token for {ksp_user}")
else:
self.user = token_user
request.user = self.user
if not self.user.is_default_user():
raise PermissionError("Default permissions required")
@ -315,7 +320,7 @@ class AbstractModelShareAPIView(AbstractAPIView):
for team_name in new_teams:
new_teams_objs.append(Team.objects.get(name=team_name))
if is_default_group_only(self.user):
if self.user.is_default_group_only():
# Default only users are not allowed to remove other users from having access. They can only add new ones!
new_users_to_be_added = User.objects.filter(
username__in=new_users

@ -172,6 +172,23 @@ class EditEcoAccountForm(NewEcoAccountForm):
disabled_fields
)
def is_valid(self):
valid = super().is_valid()
deductable_surface = self.cleaned_data.get("surface")
deduction_surface_sum = self.instance.get_deductions_surface()
if deductable_surface < deduction_surface_sum:
self.add_error(
"surface",
_("{}m² have been deducted from this eco account so far. The given value of {} would be too low.").format(
deduction_surface_sum,
deductable_surface
)
)
valid &= False
return valid
def save(self, user: User, geom_form: SimpleGeomForm):
with transaction.atomic():
# Fetch data from cleaned POST values

@ -93,7 +93,7 @@ class NewCompensationActionModalForm(BaseModalForm):
super().__init__(*args, **kwargs)
self.form_title = _("New action")
self.form_caption = _("Insert data for the new action")
choices =KonovaCode.objects.filter(
choices = KonovaCode.objects.filter(
code_lists__in=[CODELIST_COMPENSATION_ACTION_ID],
is_archived=False,
is_leaf=True,

@ -77,8 +77,11 @@ class NewPaymentForm(BaseModalForm):
is_valid (bool): True if valid, False otherwise
"""
super_valid = super().is_valid()
date = self.cleaned_data["due"]
comment = self.cleaned_data["comment"] or None
if not super_valid:
return super_valid
date = self.cleaned_data.get("due", None)
comment = self.cleaned_data.get("comment", None)
if not date and not comment:
# At least one needs to be set!
self.add_error(

@ -10,6 +10,7 @@ import shutil
from django.contrib import messages
from codelist.models import KonovaCode
from compensation.settings import COMPENSATION_IDENTIFIER_TEMPLATE, COMPENSATION_IDENTIFIER_LENGTH
from user.models import User, Team
from django.db import models, transaction
from django.db.models import QuerySet, Sum
@ -298,6 +299,9 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin, PikMixin):
objects = CompensationManager()
identifier_length = COMPENSATION_IDENTIFIER_LENGTH
identifier_template = COMPENSATION_IDENTIFIER_TEMPLATE
def __str__(self):
return "{}".format(self.identifier)
@ -395,7 +399,7 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin, PikMixin):
Returns:
users (QuerySet)
"""
return self.intervention.users.all()
return self.intervention.shared_users
@property
def shared_teams(self) -> QuerySet:
@ -404,7 +408,7 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin, PikMixin):
Returns:
users (QuerySet)
"""
return self.intervention.teams.all()
return self.intervention.shared_teams
def get_documents(self) -> QuerySet:
""" Getter for all documents of a compensation
@ -417,19 +421,18 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin, PikMixin):
)
return docs
def mark_as_edited(self, user: User, request: HttpRequest = None, edit_comment: str = None, reset_recorded: bool = True):
""" Performs internal logic for setting the recordedd/checked state of the related intervention
def mark_as_edited(self, user: User, request: HttpRequest = None, edit_comment: str = None):
""" Performs internal logic for setting the checked state of the related intervention
Args:
user (User): The performing user
request (HttpRequest): The performing request
edit_comment (str): Additional comment for the log entry
reset_recorded (bool): Whether the record-state of the object should be reset
Returns:
"""
self.intervention.unrecord(user, request)
self.intervention.set_unchecked()
action = super().mark_as_edited(user, edit_comment=edit_comment)
return action
@ -509,8 +512,11 @@ class CompensationDocument(AbstractDocument):
# The only file left for this compensation is the one which is currently processed and will be deleted
# Make sure that the compensation folder itself is deleted as well, not only the file
# Therefore take the folder path from the file path
folder_path = self.file.path.split("/")[:-1]
folder_path = "/".join(folder_path)
try:
folder_path = self.file.path.split("/")[:-1]
folder_path = "/".join(folder_path)
except ValueError:
folder_path = None
if user:
self.instance.mark_as_edited(user, edit_comment=DOCUMENT_REMOVED_TEMPLATE.format(self.title))

@ -9,6 +9,7 @@ import shutil
from django.urls import reverse
from compensation.settings import ECO_ACCOUNT_IDENTIFIER_TEMPLATE, ECO_ACCOUNT_IDENTIFIER_LENGTH
from konova.utils.message_templates import DEDUCTION_REMOVED, DOCUMENT_REMOVED_TEMPLATE
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
@ -52,23 +53,12 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
objects = EcoAccountManager()
identifier_length = ECO_ACCOUNT_IDENTIFIER_LENGTH
identifier_template = ECO_ACCOUNT_IDENTIFIER_TEMPLATE
def __str__(self):
return f"{self.identifier} ({self.title})"
def clean(self):
# Deductable surface can not be larger than added states after surface
after_state_sum = self.get_surface_after_states()
if self.deductable_surface > after_state_sum:
raise ValidationError(_("Deductable surface can not be larger than existing surfaces in after states"))
# Deductable surface can not be lower than amount of already deducted surfaces
# User needs to contact deducting user in case of further problems
deducted_sum = self.get_deductions_surface()
if self.deductable_surface < deducted_sum:
raise ValidationError(
_("Deductable surface can not be smaller than the sum of already existing deductions. Please contact the responsible users for the deductions!")
)
def save(self, *args, **kwargs):
if self.identifier is None or len(self.identifier) == 0:
# Create new identifier if none was given
@ -236,8 +226,11 @@ class EcoAccountDocument(AbstractDocument):
# The only file left for this eco account is the one which is currently processed and will be deleted
# Make sure that the compensation folder itself is deleted as well, not only the file
# Therefore take the folder path from the file path
folder_path = self.file.path.split("/")[:-1]
folder_path = "/".join(folder_path)
try:
folder_path = self.file.path.split("/")[:-1]
folder_path = "/".join(folder_path)
except ValueError:
folder_path = None
if user:
self.instance.mark_as_edited(user, edit_comment=DOCUMENT_REMOVED_TEMPLATE.format(self.title))

@ -10,8 +10,6 @@ from django.db import models
from intervention.models import Intervention
from konova.models import BaseResource
from konova.utils.message_templates import PAYMENT_REMOVED
from user.models import UserActionLogEntry
class Payment(BaseResource):

@ -244,6 +244,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
self.client_user.post(record_url, post_data)
# Check that the intervention is still not recorded
self.intervention.refresh_from_db()
self.assertIsNone(self.intervention.recorded)
# Now fill out the data for a compensation

@ -0,0 +1,7 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 21.08.23
"""

@ -0,0 +1,318 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 21.08.23
"""
from django.core.exceptions import ObjectDoesNotExist
from django.test import RequestFactory
from django.utils.translation import gettext_lazy as _
from codelist.models import KonovaCodeList
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID
from compensation.forms.modals.compensation_action import NewCompensationActionModalForm, \
EditCompensationActionModalForm, RemoveCompensationActionModalForm
from compensation.forms.modals.state import NewCompensationStateModalForm, EditCompensationStateModalForm, \
RemoveCompensationStateModalForm
from compensation.models import UnitChoices
from konova.tests.test_views import BaseTestCase
from konova.utils.generators import generate_random_string
from konova.utils.message_templates import COMPENSATION_ACTION_EDITED, ADDED_COMPENSATION_ACTION, \
COMPENSATION_ACTION_REMOVED, ADDED_COMPENSATION_STATE, COMPENSATION_STATE_EDITED, \
COMPENSATION_STATE_REMOVED
from user.models import UserAction
class NewCompensationActionModalFormTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
self.request = RequestFactory().request()
self.request.user = self.superuser
self.action_dummy_code = self.create_dummy_codes().first()
action_list = KonovaCodeList.objects.get_or_create(
id=CODELIST_COMPENSATION_ACTION_ID,
)[0]
action_list.codes.add(self.action_dummy_code)
def test_init(self):
form = NewCompensationActionModalForm()
self.assertEqual(form.form_title, str(_("New action")))
self.assertEqual(form.form_caption, str(_("Insert data for the new action")))
self.assertTrue(len(form.fields["action_type"].choices) == 1)
def test_save(self):
comment = "TEST_comment"
unit = UnitChoices.km
amount = 2.5
data = {
"action_type": [self.action_dummy_code.id],
"action_type_details": [],
"unit": unit,
"amount": amount,
"comment": comment,
}
form = NewCompensationActionModalForm(data, request=self.request, instance=self.compensation)
self.assertTrue(form.is_valid(), msg=form.errors)
comp_action = form.save()
last_log = self.compensation.log.first()
self.assertIn(comp_action, self.compensation.actions.all())
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.user, self.superuser)
self.assertEqual(last_log.comment, ADDED_COMPENSATION_ACTION)
self.assertEqual(comp_action.amount, amount)
self.assertEqual(comp_action.unit, unit)
self.assertEqual(comp_action.comment, comment)
comp_action_types = comp_action.action_type.all()
self.assertEqual(comp_action_types.count(), 1)
self.assertEqual(comp_action_types.first(), self.action_dummy_code)
class EditCompensationActionModalFormTestCase(NewCompensationActionModalFormTestCase):
def setUp(self) -> None:
super().setUp()
self.comp_action = self.create_dummy_action()
self.compensation.actions.add(self.comp_action)
def test_init(self):
form = EditCompensationActionModalForm(request=self.request, instance=self.compensation, action=self.comp_action)
self.assertEqual(form.form_title, str(_("Edit action")))
self.assertEqual(len(form.fields["action_type"].initial), self.comp_action.action_type.count())
self.assertEqual(len(form.fields["action_type_details"].initial), self.comp_action.action_type_details.count())
self.assertEqual(form.fields["amount"].initial, self.comp_action.amount)
self.assertEqual(form.fields["unit"].initial, self.comp_action.unit)
self.assertEqual(form.fields["comment"].initial, self.comp_action.comment)
def test_save(self):
amount = 25.4
unit = UnitChoices.cm
comment = generate_random_string(length=20, use_numbers=True, use_letters_lc=True, use_letters_uc=True)
data = {
"action_type": [self.action_dummy_code.id],
"action_type_details": [],
"amount": amount,
"unit": unit,
"comment": comment,
}
form = EditCompensationActionModalForm(data, request=self.request, instance=self.compensation, action=self.comp_action)
self.assertTrue(form.is_valid())
action = form.save()
self.assertEqual(action.action_type.count(), len(data["action_type"]))
self.assertEqual(action.action_type_details.count(), 0)
self.assertEqual(float(action.amount), amount)
self.assertEqual(action.unit, unit)
self.assertEqual(action.comment, comment)
last_log = self.compensation.log.first()
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.user, self.superuser)
self.assertEqual(last_log.comment, COMPENSATION_ACTION_EDITED)
self.assertIn(action, self.compensation.actions.all())
self.assertEqual(self.compensation.actions.count(), 1)
class RemoveCompensationActionModalFormTestCase(EditCompensationActionModalFormTestCase):
def setUp(self) -> None:
super().setUp()
def test_init(self):
self.assertIn(self.comp_action, self.compensation.actions.all())
form = RemoveCompensationActionModalForm(request=self.request, instance=self.compensation, action=self.comp_action)
self.assertEqual(form.action, self.comp_action)
def test_save(self):
data = {
"confirm": True,
}
form = RemoveCompensationActionModalForm(
data,
request=self.request,
instance=self.compensation,
action=self.comp_action
)
self.assertTrue(form.is_valid())
self.assertIn(self.comp_action, self.compensation.actions.all())
form.save()
last_log = self.compensation.log.first()
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.user, self.superuser)
self.assertEqual(last_log.comment, COMPENSATION_ACTION_REMOVED)
self.assertNotIn(self.comp_action, self.compensation.actions.all())
try:
self.comp_action.refresh_from_db()
self.fail(msg="This action should not be fetchable anymore")
except ObjectDoesNotExist:
pass
class NewCompensationStateModalFormTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
self.request = RequestFactory().request()
self.request.user = self.superuser
self.comp_biotope_code = self.create_dummy_codes().first()
self.biotope_codelist = KonovaCodeList.objects.get_or_create(
id=CODELIST_BIOTOPES_ID
)[0]
self.biotope_codelist.codes.add(self.comp_biotope_code)
def test_init(self):
form = NewCompensationStateModalForm(request=self.request, instance=self.compensation)
self.assertEqual(form.form_title, str(_("New state")))
self.assertEqual(form.form_caption, str(_("Insert data for the new state")))
self.assertEqual(len(form.fields["biotope_type"].choices), 1)
def test_save(self):
test_surface = 123.45
data = {
"biotope_type": self.comp_biotope_code.id,
"biotope_extra": [],
"surface": test_surface,
}
self.assertEqual(self.compensation.before_states.count(), 0)
self.assertEqual(self.compensation.after_states.count(), 0)
form = NewCompensationStateModalForm(data, request=self.request, instance=self.compensation)
self.assertTrue(form.is_valid(), msg=form.errors)
is_before_state = True
state = form.save(is_before_state)
self.assertEqual(self.compensation.before_states.count(), 1)
self.assertEqual(self.compensation.after_states.count(), 0)
self.assertIn(state, self.compensation.before_states.all())
self.assertEqual(state.biotope_type, self.comp_biotope_code)
self.assertEqual(state.biotope_type_details.count(), 0)
self.assertEqual(float(state.surface), test_surface)
last_log = self.compensation.log.first()
self.assertEqual(last_log.user, self.superuser)
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.comment, ADDED_COMPENSATION_STATE)
is_before_state = False
state = form.save(is_before_state)
self.assertEqual(self.compensation.before_states.count(), 1)
self.assertEqual(self.compensation.after_states.count(), 1)
self.assertIn(state, self.compensation.after_states.all())
self.assertEqual(state.biotope_type, self.comp_biotope_code)
self.assertEqual(state.biotope_type_details.count(), 0)
self.assertEqual(float(state.surface), test_surface)
last_log = self.compensation.log.first()
self.assertEqual(last_log.user, self.superuser)
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.comment, ADDED_COMPENSATION_STATE)
class EditCompensationStateModalFormTestCase(NewCompensationStateModalFormTestCase):
def setUp(self) -> None:
super().setUp()
self.comp_state.biotope_type = self.comp_biotope_code
self.comp_state.save()
self.compensation.after_states.add(self.comp_state)
def test_init(self):
form = EditCompensationStateModalForm(request=self.request, instance=self.compensation, state=self.comp_state)
self.assertEqual(form.state, self.comp_state)
self.assertEqual(form.form_title, str(_("Edit state")))
self.assertEqual(form.fields["biotope_type"].initial, self.comp_state.biotope_type.id)
self.assertTrue(
form.fields["biotope_extra"].initial.difference(
self.comp_state.biotope_type_details.all()
).count() == 0
)
self.assertEqual(form.fields["surface"].initial, self.comp_state.surface)
def test_save(self):
test_surface = 987.65
test_code = self.create_dummy_codes().exclude(
id=self.comp_biotope_code.id
).first()
self.biotope_codelist.codes.add(test_code)
self.assertEqual(self.compensation.after_states.count(), 1)
self.assertEqual(self.compensation.before_states.count(), 0)
data = {
"biotope_type": test_code.id,
"biotope_extra": [],
"surface": test_surface,
}
form = EditCompensationStateModalForm(
data,
request=self.request,
instance=self.compensation,
state=self.comp_state
)
self.assertTrue(form.is_valid(), msg=form.errors)
is_before_state = False
state = form.save(is_before_state=is_before_state)
self.assertEqual(state.biotope_type, test_code)
self.assertEqual(state.biotope_type_details.count(), 0)
self.assertEqual(float(state.surface), test_surface)
last_log = self.compensation.log.first()
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.user, self.superuser)
self.assertEqual(last_log.comment, COMPENSATION_STATE_EDITED)
class RemoveCompensationStateModalFormTestCase(EditCompensationStateModalFormTestCase):
def setUp(self) -> None:
super().setUp()
def test_init(self):
form = RemoveCompensationStateModalForm(request=self.request, instance=self.compensation, state=self.comp_state)
self.assertEqual(form.state, self.comp_state)
def test_save(self):
data = {
"confirm": True
}
form = RemoveCompensationStateModalForm(
data,
request=self.request,
instance=self.compensation,
state=self.comp_state
)
self.assertTrue(form.is_valid(), msg=form.errors)
self.assertIn(self.comp_state, self.compensation.after_states.all())
self.assertNotIn(self.comp_state, self.compensation.before_states.all())
form.save()
self.assertEqual(self.compensation.after_states.count(), 0)
self.assertEqual(self.compensation.before_states.count(), 0)
try:
self.comp_state.refresh_from_db()
self.fail("Entry should not existing anymore")
except ObjectDoesNotExist:
pass
last_log = self.compensation.log.first()
self.assertEqual(last_log.user, self.superuser)
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.comment, COMPENSATION_STATE_REMOVED)

@ -0,0 +1,201 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 30.08.23
"""
from django.core.exceptions import ObjectDoesNotExist
from django.test import RequestFactory
from django.utils.timezone import now
from compensation.forms.modals.deadline import NewDeadlineModalForm
from compensation.models import CompensationDocument
from konova.forms.modals import RemoveDeadlineModalForm
from konova.models import DeadlineType
from konova.tests.test_views import BaseTestCase
from konova.utils.message_templates import DEADLINE_REMOVED, DOCUMENT_REMOVED_TEMPLATE, COMPENSATION_REMOVED_TEMPLATE, \
DEADLINE_ADDED
from user.models import UserAction, Team
class AbstractCompensationModelTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
self.request = RequestFactory().request()
self.request.user = self.superuser
def test_remove_deadline(self):
self.compensation.deadlines.add(self.finished_deadline)
data = {
"confirm": True
}
form = RemoveDeadlineModalForm(
data,
request=self.request,
instance=self.compensation,
deadline=self.finished_deadline,
)
self.assertTrue(form.is_valid(), msg=form.errors)
self.assertIn(self.finished_deadline, self.compensation.deadlines.all())
form.save()
last_log = self.compensation.log.first()
self.assertEqual(last_log.user, self.request.user)
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.comment, DEADLINE_REMOVED)
self.assertNotIn(self.finished_deadline, self.compensation.deadlines.all())
try:
self.finished_deadline.refresh_from_db()
self.fail("Deadline should not exist anymore after removing from abstract compensation")
except ObjectDoesNotExist:
pass
def test_add_deadline(self):
request = RequestFactory().request()
request.user = self.superuser
data = {
"type": DeadlineType.MAINTAIN,
"date": now().date(),
"comment": "TestDeadline"
}
form = NewDeadlineModalForm(
data,
request=self.request,
instance=self.compensation,
)
self.assertTrue(form.is_valid(), msg=form.errors)
deadline = self.compensation.add_deadline(form)
self.assertEqual(deadline.date, data["date"])
self.assertEqual(deadline.type, data["type"])
self.assertEqual(deadline.comment, data["comment"])
self.assertEqual(deadline.created.action, UserAction.CREATED)
self.assertEqual(deadline.created.user, self.superuser)
self.assertEqual(deadline.created.comment, None)
self.assertIn(deadline, self.compensation.deadlines.all())
last_log = self.compensation.log.first()
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.user, self.superuser)
self.assertEqual(last_log.comment, DEADLINE_ADDED)
class CompensationTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
def test_str(self):
self.assertEqual(str(self.compensation), self.compensation.identifier)
def test_save(self):
old_identifier = self.compensation.identifier
self.compensation.identifier = None
self.compensation.save()
self.assertIsNotNone(self.compensation.identifier)
self.assertNotEqual(old_identifier, self.compensation.identifier)
def test_share_with_user(self):
self.assertNotIn(self.user, self.compensation.shared_users)
self.compensation.share_with_user(self.user)
self.assertIn(self.user, self.compensation.shared_users)
def test_share_with_user_list(self):
user_list = [
self.user
]
self.assertNotIn(self.user, self.compensation.shared_users)
self.compensation.share_with_user_list(user_list)
self.assertIn(self.user, self.compensation.shared_users)
user_list = [
self.superuser
]
self.assertNotIn(self.superuser, self.compensation.shared_users)
self.compensation.share_with_user_list(user_list)
self.assertIn(self.superuser, self.compensation.shared_users)
self.assertNotIn(self.user, self.compensation.shared_users)
def test_share_with_team(self):
self.assertNotIn(self.team, self.compensation.shared_teams)
self.compensation.share_with_team(self.team)
self.assertIn(self.team, self.compensation.shared_teams)
def test_share_with_team_list(self):
self.compensation.share_with_team(self.team)
self.assertIn(self.team, self.compensation.shared_teams)
other_team = Team.objects.create(
name="NewTeam"
)
team_list = [
other_team
]
self.compensation.share_with_team_list(team_list)
self.assertIn(other_team, self.compensation.shared_teams)
self.assertNotIn(self.team, self.compensation.shared_teams)
def test_shared_users(self):
intervention = self.compensation.intervention
diff = self.compensation.shared_users.difference(intervention.shared_users)
self.assertEqual(diff.count(), 0)
self.compensation.share_with_user(self.superuser)
diff = self.compensation.shared_users.difference(intervention.shared_users)
self.assertEqual(diff.count(), 0)
def test_shared_teams(self):
intervention = self.compensation.intervention
diff = self.compensation.shared_users.difference(intervention.shared_users)
self.assertEqual(diff.count(), 0)
self.compensation.share_with_user(self.superuser)
diff = self.compensation.shared_users.difference(intervention.shared_users)
self.assertEqual(diff.count(), 0)
def test_get_documents(self):
doc = self.create_dummy_document(CompensationDocument, self.compensation)
docs = self.compensation.get_documents()
self.assertIn(doc, docs)
def test_mark_as_deleted(self):
self.assertIsNone(self.compensation.deleted)
self.compensation.mark_as_deleted(self.superuser, send_mail=False)
comp_deleted = self.compensation.deleted
self.assertIsNotNone(comp_deleted)
self.assertEqual(comp_deleted.action, UserAction.DELETED)
self.assertEqual(comp_deleted.user, self.superuser)
self.assertEqual(comp_deleted.comment, None)
intervention_last_log = self.compensation.intervention.log.first()
self.assertEqual(intervention_last_log.action, UserAction.EDITED)
self.assertEqual(intervention_last_log.user, self.superuser)
self.assertEqual(
intervention_last_log.comment,
COMPENSATION_REMOVED_TEMPLATE.format(
self.compensation.identifier
)
)
class CompensationDocumentTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
self.doc = self.create_dummy_document(CompensationDocument, self.compensation)
def test_delete(self):
doc_title = self.doc.title
self.assertIn(self.doc, self.compensation.get_documents())
self.doc.delete(self.superuser)
self.assertNotIn(self.doc, self.compensation.get_documents())
try:
self.doc.refresh_from_db()
self.fail("Document should not be fetchable anymore")
except ObjectDoesNotExist:
pass
last_log = self.compensation.log.first()
self.assertEqual(last_log.user, self.superuser)
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.comment, DOCUMENT_REMOVED_TEMPLATE.format(doc_title))

@ -106,14 +106,17 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
"surface": test_deductable_surface,
"conservation_office": test_conservation_office.id
}
self.client_user.post(url, post_data)
response = self.client_user.post(url, post_data)
self.assertEqual(response.status_code, 302, msg=f"{response.content.decode('utf-8')}")
self.eco_account.refresh_from_db()
deductions_surface = self.eco_account.get_deductions_surface()
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.deductable_rest: test_deductable_surface,
self.eco_account.deductable_rest: test_deductable_surface - deductions_surface,
self.eco_account.comment: new_comment,
}
@ -223,7 +226,9 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
self.eco_account.refresh_from_db()
self.assertEqual(1, self.eco_account.deductions.count())
self.assertEqual(1, self.intervention.deductions.count())
deduction = self.eco_account.deductions.first()
deduction = self.eco_account.deductions.get(
surface=test_surface
)
self.assertEqual(deduction.surface, test_surface)
self.assertEqual(self.eco_account.deductable_rest, self.eco_account.deductable_surface - deduction.surface)
self.assertEqual(deduction.account, self.eco_account)

@ -0,0 +1,7 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 30.08.23
"""

@ -0,0 +1,128 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 30.08.23
"""
from django.core.exceptions import ObjectDoesNotExist
from django.urls import reverse
from compensation.models import EcoAccountDocument
from konova.tests.test_views import BaseTestCase
from konova.utils.message_templates import DOCUMENT_REMOVED_TEMPLATE, DEDUCTION_REMOVED
from user.models import UserAction
class EcoAccountTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
def test_str(self):
self.assertEqual(str(self.eco_account), f"{self.eco_account.identifier} ({self.eco_account.title})")
def test_save(self):
old_id = self.eco_account.identifier
self.assertIsNotNone(self.eco_account.identifier)
self.eco_account.identifier = None
self.eco_account.save()
self.assertIsNotNone(self.eco_account.identifier)
self.assertNotEqual(old_id, self.eco_account.identifier)
def test_property_deductions_surface_sum(self):
self.assertEqual(
self.eco_account.deductions_surface_sum,
self.eco_account.get_deductions_surface()
)
def test_get_documents(self):
docs = self.eco_account.get_documents()
self.assertEqual(docs.count(), 0)
doc = self.create_dummy_document(EcoAccountDocument, self.eco_account)
self.assertIn(doc, self.eco_account.get_documents())
def test_get_share_link(self):
self.assertEqual(
self.eco_account.get_share_link(),
reverse(
"compensation:acc:share-token",
args=(self.eco_account.id, self.eco_account.access_token)
)
)
def test_get_deductable_rest_relative(self):
self.assertEqual(self.eco_account.deductions.count(), 0)
self.eco_account.deductable_surface = 5.0
self.eco_account.save()
self.eco_account.update_deductable_rest()
self.assertEqual(self.eco_account.get_deductable_rest_relative(), 100)
self.eco_account.deductable_surface = None
self.eco_account.save()
self.assertEqual(self.eco_account.get_deductable_rest_relative(), 0)
class EcoAccountDocumentTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
def test_delete(self):
doc = self.create_dummy_document(
EcoAccountDocument,
self.eco_account
)
doc_title = doc.title
docs = self.eco_account.get_documents()
self.assertIn(doc, docs)
doc.delete(user=self.superuser)
last_log = self.eco_account.log.first()
self.assertEqual(last_log.user, self.superuser)
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.comment, DOCUMENT_REMOVED_TEMPLATE.format(
doc_title
))
try:
doc.refresh_from_db()
self.fail("Document should not have been fetchable")
except ObjectDoesNotExist:
pass
class EcoAccountDeductionTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
def test_str(self):
self.assertEqual(str(self.deduction), f"{self.deduction.surface} of {self.deduction.account}")
def test_delete(self):
self.deduction.account = self.eco_account
self.deduction.intervention = self.intervention
self.deduction.save()
self.eco_account.update_deductable_rest()
old_deductable_rest = self.eco_account.deductable_rest
deduction_surface = self.deduction.surface
self.deduction.delete(self.superuser)
last_log_intervention = self.intervention.log.first()
last_log_account = self.eco_account.log.first()
logs = [
last_log_intervention,
last_log_account,
]
for log in logs:
self.assertEqual(log.action, UserAction.EDITED)
self.assertEqual(log.user, self.superuser)
self.assertEqual(log.comment, DEDUCTION_REMOVED)
self.assertLess(old_deductable_rest, self.eco_account.deductable_rest)
self.assertEqual(old_deductable_rest + deduction_surface, self.eco_account.deductable_rest)
try:
self.deduction.refresh_from_db()
self.fail("Deduction still fetchable after deleting")
except ObjectDoesNotExist:
pass

@ -25,9 +25,8 @@ from konova.forms.modals import RemoveModalForm
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE, DATA_CHECKED_PREVIOUSLY_TEMPLATE, \
RECORDED_BLOCKS_EDIT, CHECKED_RECORDED_RESET, FORM_INVALID, PARAMS_INVALID, IDENTIFIER_REPLACED, \
RECORDED_BLOCKS_EDIT, CHECK_STATE_RESET, FORM_INVALID, PARAMS_INVALID, IDENTIFIER_REPLACED, \
COMPENSATION_ADDED_TEMPLATE, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED
from konova.utils.user_checks import in_group
@login_required
@ -170,15 +169,14 @@ def edit_view(request: HttpRequest, id: str):
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
# Preserve state of intervention recorded/checked to determine whether the user must be informed or not
# about a change of the recorded/checked state
intervention_recorded = comp.intervention.recorded is not None
intervention_checked = comp.intervention.checked is not None
# Preserve state of intervention checked to determine whether the user must be informed or not
# about a change of the check state
intervention_is_checked = comp.intervention.checked is not None
# The data form takes the geom form for processing, as well as the performing user
comp = data_form.save(request.user, geom_form)
if intervention_recorded or intervention_checked:
messages.info(request, CHECKED_RECORDED_RESET)
if intervention_is_checked:
messages.info(request, CHECK_STATE_RESET)
messages.success(request, _("Compensation {} edited").format(comp.identifier))
if geom_form.geometry_simplified:
messages.info(
@ -266,9 +264,9 @@ def detail_view(request: HttpRequest, id: str):
"sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"is_default_member": in_group(_user, DEFAULT_GROUP),
"is_zb_member": in_group(_user, ZB_GROUP),
"is_ets_member": in_group(_user, ETS_GROUP),
"is_default_member": _user.in_group(DEFAULT_GROUP),
"is_zb_member": _user.in_group(ZB_GROUP),
"is_ets_member": _user.in_group(ETS_GROUP),
"LANIS_LINK": comp.get_LANIS_link(),
TAB_TITLE_IDENTIFIER: f"{comp.identifier} - {comp.title}",
"has_finished_deadlines": comp.get_finished_deadlines().exists(),

@ -23,7 +23,6 @@ from konova.settings import ETS_GROUP, DEFAULT_GROUP, ZB_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import CANCEL_ACC_RECORDED_OR_DEDUCTED, RECORDED_BLOCKS_EDIT, FORM_INVALID, \
IDENTIFIER_REPLACED, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED
from konova.utils.user_checks import in_group
@login_required
@ -150,7 +149,9 @@ def edit_view(request: HttpRequest, id: str):
data_form = EditEcoAccountForm(request.POST or None, instance=acc)
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc)
if request.method == "POST":
if data_form.is_valid() and geom_form.is_valid():
data_form_valid = data_form.is_valid()
geom_form_valid = geom_form.is_valid()
if data_form_valid and geom_form_valid:
# The data form takes the geom form for processing, as well as the performing user
acc = data_form.save(request.user, geom_form)
messages.success(request, _("Eco-Account {} edited").format(acc.identifier))
@ -242,9 +243,9 @@ def detail_view(request: HttpRequest, id: str):
"diff_states": diff_states,
"available": available_relative,
"available_total": available_total,
"is_default_member": in_group(_user, DEFAULT_GROUP),
"is_zb_member": in_group(_user, ZB_GROUP),
"is_ets_member": in_group(_user, ETS_GROUP),
"is_default_member": _user.in_group(DEFAULT_GROUP),
"is_zb_member": _user.in_group(ZB_GROUP),
"is_ets_member": _user.in_group(ETS_GROUP),
"LANIS_LINK": acc.get_LANIS_link(),
"deductions": deductions,
"actions": actions,
@ -275,7 +276,7 @@ def remove_view(request: HttpRequest, id: str):
# default group user
if acc.recorded is not None or acc.deductions.exists():
user = request.user
if not in_group(user, ETS_GROUP):
if not user.in_group(ETS_GROUP):
messages.info(request, CANCEL_ACC_RECORDED_OR_DEDUCTED)
return redirect("compensation:acc:detail", id=id)

@ -76,7 +76,7 @@ class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin, Pik
)
# Finally create main object
acc = Ema.objects.create(
ema = Ema.objects.create(
identifier=identifier,
title=title,
responsible=responsible,
@ -87,16 +87,16 @@ class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin, Pik
)
# Add the creating user to the list of shared users
acc.share_with_user(user)
ema.share_with_user(user)
# Add the log entry to the main objects log list
acc.log.add(action)
ema.log.add(action)
# Process the geometry form (NOT ATOMIC TRANSACTION DUE TO CELERY!)
geometry = geom_form.save(action)
acc.geometry = geometry
acc.save()
return acc
ema.geometry = geometry
ema.save()
return ema
class EditEmaForm(NewEmaForm):

@ -15,6 +15,7 @@ from django.urls import reverse
from compensation.models import AbstractCompensation, PikMixin
from ema.managers import EmaManager
from ema.settings import EMA_IDENTIFIER_LENGTH, EMA_IDENTIFIER_TEMPLATE
from ema.utils.quality import EmaQualityChecker
from konova.models import AbstractDocument, generate_document_file_upload_path, RecordableObjectMixin, ShareableObjectMixin
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, DOCUMENT_REMOVED_TEMPLATE
@ -38,6 +39,9 @@ class Ema(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin, Pik
"""
objects = EmaManager()
identifier_length = EMA_IDENTIFIER_LENGTH
identifier_template = EMA_IDENTIFIER_TEMPLATE
def __str__(self):
return "{}".format(self.identifier)
@ -122,7 +126,7 @@ class EmaDocument(AbstractDocument):
def delete(self, user=None, *args, **kwargs):
"""
Custom delete functionality for EcoAccountDocuments.
Custom delete functionality for EmaDocuments.
Removes the folder from the file system if there are no further documents for this entry.
Args:
@ -139,8 +143,11 @@ class EmaDocument(AbstractDocument):
# The only file left for this EMA is the one which is currently processed and will be deleted
# Make sure that the compensation folder itself is deleted as well, not only the file
# Therefore take the folder path from the file path
folder_path = self.file.path.split("/")[:-1]
folder_path = "/".join(folder_path)
try:
folder_path = self.file.path.split("/")[:-1]
folder_path = "/".join(folder_path)
except ValueError:
folder_path = None
if user:
self.instance.mark_as_edited(user, edit_comment=DOCUMENT_REMOVED_TEMPLATE.format(self.title))

@ -6,5 +6,5 @@ Created on: 19.08.21
"""
EMA_ACCOUNT_IDENTIFIER_LENGTH = 6
EMA_ACCOUNT_IDENTIFIER_TEMPLATE = "EMA-{}"
EMA_IDENTIFIER_LENGTH = 6
EMA_IDENTIFIER_TEMPLATE = "EMA-{}"

@ -0,0 +1,7 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 24.08.23
"""

@ -0,0 +1,141 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 01.09.23
"""
import json
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from ema.forms import NewEmaForm, EditEmaForm
from konova.forms import SimpleGeomForm
from konova.tests.test_views import BaseTestCase
from konova.utils.generators import generate_random_string
from user.models import UserAction
class NewEmaFormTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
def test_init(self):
form = NewEmaForm()
self.assertEqual(form.form_title, str(_("New EMA")))
self.assertEqual(form.action_url, reverse("ema:new"))
self.assertEqual(form.cancel_redirect, reverse("ema:index"))
self.assertIsNotNone(form.fields["identifier"].initial)
self.assertEqual(form.fields["title"].widget.attrs["placeholder"], str(_("Compensation XY; Location ABC")))
def test_save(self):
cons_office_code = self.get_conservation_office_code()
data = {
"identifier": generate_random_string(length=20, use_numbers=True),
"title": generate_random_string(length=20, use_letters_lc=True),
"conservation_office": cons_office_code,
"conservation_file_number": generate_random_string(length=10, use_numbers=True),
"is_pik": True,
"comment": generate_random_string(length=20, use_numbers=True, use_letters_uc=True),
}
form = NewEmaForm(data)
test_geom = self.create_dummy_geometry()
geom_form_data = self.create_geojson(
test_geom
)
geom_form_data = json.loads(geom_form_data)
geom_form_data = {
"geom": json.dumps(geom_form_data)
}
geom_form = SimpleGeomForm(geom_form_data)
self.assertTrue(form.is_valid(), msg=form.errors)
self.assertTrue(geom_form.is_valid(), msg=form.errors)
obj = form.save(user=self.superuser, geom_form=geom_form)
self.assertEqual(obj.title, data["title"])
self.assertEqual(obj.is_pik, data["is_pik"])
self.assertIsNotNone(obj.responsible)
self.assertIsNotNone(obj.responsible.handler)
self.assertEqual(obj.responsible.conservation_office, data["conservation_office"])
self.assertEqual(obj.responsible.conservation_file_number, data["conservation_file_number"])
self.assertEqual(obj.identifier, data["identifier"])
self.assertEqual(obj.comment, data["comment"])
self.assertIn(self.superuser, obj.shared_users)
last_log = obj.log.first()
self.assertEqual(obj.created, obj.modified)
self.assertEqual(obj.created, last_log)
self.assertEqual(last_log.action, UserAction.CREATED)
self.assertEqual(last_log.user, self.superuser)
self.assertTrue(test_geom.equals_exact(obj.geometry.geom, 0.000001))
class EditEmaFormTestCase(BaseTestCase):
def test_init(self):
form = EditEmaForm(instance=self.ema)
self.assertEqual(form.form_title, str(_("Edit EMA")))
self.assertEqual(form.action_url, reverse("ema:edit", args=(self.ema.id,)))
self.assertEqual(form.cancel_redirect, reverse("ema:detail", args=(self.ema.id,)))
self.assertEqual(form.fields["identifier"].widget.attrs["url"], reverse("ema:new-id"))
self.assertEqual(form.fields["title"].widget.attrs["placeholder"], str(_("Compensation XY; Location ABC")))
values = {
"identifier": self.ema.identifier,
"title": self.ema.title,
"comment": self.ema.comment,
"conservation_office": self.ema.responsible.conservation_office,
"conservation_file_number": self.ema.responsible.conservation_file_number,
"is_pik": self.ema.is_pik,
"handler_type": self.ema.responsible.handler.type,
"handler_detail": self.ema.responsible.handler.detail,
}
for k, v in values.items():
self.assertEqual(form.fields[k].initial, v)
def test_save(self):
cons_office_code = self.get_conservation_office_code()
data = {
"identifier": generate_random_string(length=20, use_numbers=True),
"title": generate_random_string(length=20, use_letters_lc=True),
"conservation_office": cons_office_code,
"conservation_file_number": generate_random_string(length=10, use_numbers=True),
"is_pik": not self.ema.is_pik,
"comment": generate_random_string(length=20, use_numbers=True, use_letters_uc=True),
}
form = EditEmaForm(data, instance=self.ema)
self.assertTrue(form.is_valid(), msg=form.errors)
test_geom = self.create_dummy_geometry()
geom_form_data = self.create_geojson(
test_geom
)
geom_form_data = json.loads(geom_form_data)
geom_form_data = {
"geom": json.dumps(geom_form_data)
}
geom_form = SimpleGeomForm(geom_form_data)
self.assertTrue(geom_form.is_valid())
obj = form.save(self.superuser, geom_form)
self.assertEqual(obj.id, self.ema.id)
self.assertEqual(obj.title, data["title"])
self.assertEqual(obj.is_pik, data["is_pik"])
self.assertIsNotNone(obj.responsible)
self.assertIsNotNone(obj.responsible.handler)
self.assertEqual(obj.responsible.conservation_office, data["conservation_office"])
self.assertEqual(obj.responsible.conservation_file_number, data["conservation_file_number"])
self.assertEqual(obj.identifier, data["identifier"])
self.assertEqual(obj.comment, data["comment"])
last_log = obj.log.first()
self.assertEqual(obj.modified, last_log)
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.user, self.superuser)
self.assertTrue(test_geom.equals_exact(obj.geometry.geom, 0.000001))

@ -0,0 +1,90 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 24.08.23
"""
from django.urls import reverse
from django.utils.timezone import now
from ema.models import Ema, EmaDocument
from ema.settings import EMA_IDENTIFIER_TEMPLATE
from konova.tests.test_views import BaseTestCase
from konova.utils.message_templates import DOCUMENT_REMOVED_TEMPLATE
from user.models import UserAction
class EmaModelTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
def test_str(self):
self.assertEqual(str(self.ema), f"{self.ema.identifier}")
def test_save(self):
new_ema = Ema(
title="Test"
)
self.assertIsNone(new_ema.identifier)
new_ema.save()
new_ema.refresh_from_db()
self.assertIsNotNone(new_ema.identifier)
self.assertIn("EMA-", new_ema.identifier)
def test_is_ready_for_publish(self):
self.assertIsNone(self.ema.recorded)
self.assertFalse(self.ema.is_ready_for_publish())
self.ema.set_recorded(self.superuser)
self.ema.refresh_from_db()
self.assertIsNotNone(self.ema.recorded)
self.assertTrue(self.ema.is_ready_for_publish())
def test_get_share_link(self):
self.assertEqual(
self.ema.get_share_link(),
reverse("ema:share-token", args=(self.ema.id, self.ema.access_token))
)
def test_get_documents(self):
self.assertEqual(self.ema.get_documents().count(), 0)
doc = EmaDocument(
instance=self.ema,
date_of_creation=now().date(),
comment="Test",
)
doc.save()
docs = self.ema.get_documents()
self.assertEqual(docs.count(), 1)
self.assertEqual(docs.first(), doc)
class EmaDocumentModelTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
def test_delete(self):
doc = EmaDocument.objects.create(
date_of_creation=now().date(),
instance=self.ema,
comment="TEST"
)
self.ema.refresh_from_db()
docs = self.ema.get_documents()
self.assertEqual(docs.count(), 1)
self.assertEqual(docs.first(), doc)
doc_title = doc.title
doc.delete(user=self.superuser)
last_log = self.ema.log.first()
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.user, self.superuser)
self.assertEqual(last_log.comment, DOCUMENT_REMOVED_TEMPLATE.format(doc_title))
docs = self.ema.get_documents()
self.assertEqual(docs.count(), 0)

@ -24,7 +24,6 @@ from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, IDENTIFIER_REPLACED, FORM_INVALID, \
DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED
from konova.utils.user_checks import in_group
@login_required
@ -172,9 +171,9 @@ def detail_view(request: HttpRequest, id: str):
"sum_before_states": sum_before_states,
"sum_after_states": sum_after_states,
"diff_states": diff_states,
"is_default_member": in_group(_user, DEFAULT_GROUP),
"is_zb_member": in_group(_user, ZB_GROUP),
"is_ets_member": in_group(_user, ETS_GROUP),
"is_default_member": _user.in_group(DEFAULT_GROUP),
"is_zb_member": _user.in_group(ZB_GROUP),
"is_ets_member": _user.in_group(ETS_GROUP),
"LANIS_LINK": ema.get_LANIS_link(),
TAB_TITLE_IDENTIFIER: f"{ema.identifier} - {ema.title}",
"has_finished_deadlines": ema.get_finished_deadlines().exists(),

@ -43,6 +43,7 @@ class CheckModalForm(BaseModalForm):
"""
deductions = self.instance.deductions.all()
valid = True
for deduction in deductions:
checker = deduction.account.quality_check()
for msg in checker.messages:
@ -50,8 +51,8 @@ class CheckModalForm(BaseModalForm):
"checked_comps",
f"{deduction.account.identifier}: {msg}"
)
return checker.valid
return True
valid &= checker.valid
return valid
def _are_comps_valid(self):
""" Performs validity checks on all types of compensations

@ -12,7 +12,6 @@ from django.utils.translation import gettext_lazy as _
from intervention.inputs import TextToClipboardInput
from konova.forms.modals import BaseModalForm
from konova.utils.message_templates import ENTRY_REMOVE_MISSING_PERMISSION
from konova.utils.user_checks import is_default_group_only
from user.models import Team, User
@ -80,7 +79,7 @@ class ShareModalForm(BaseModalForm):
teams = self.cleaned_data.get("teams", Team.objects.none())
_is_valid = True
if is_default_group_only(self.user):
if self.user.is_default_group_only():
shared_users = self.instance.shared_users
shared_teams = self.instance.shared_teams

@ -14,7 +14,7 @@ from django.urls import reverse
from django.utils import timezone
from analysis.settings import LKOMPVZVO_PUBLISH_DATE
from compensation.models import EcoAccountDeduction
from intervention.settings import INTERVENTION_IDENTIFIER_LENGTH, INTERVENTION_IDENTIFIER_TEMPLATE
from intervention.tasks import celery_export_to_egon
from user.models import User
from django.db import models, transaction
@ -61,6 +61,9 @@ class Intervention(BaseObject,
objects = InterventionManager()
identifier_length = INTERVENTION_IDENTIFIER_LENGTH
identifier_template = INTERVENTION_IDENTIFIER_TEMPLATE
def __str__(self):
return f"{self.identifier} ({self.title})"
@ -276,22 +279,20 @@ class Intervention(BaseObject,
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
def mark_as_edited(self, performing_user: User, request: HttpRequest = None, edit_comment: str = None):
""" Log the edit action
If the object is checked, set it to unchecked due to the editing. Another check is needed then.
Args:
performing_user (User): The user which performed the editing action
request (HttpRequest): The used request for this action
edit_comment (str): Additional comment for the log entry
reset_recorded (bool): Whether the record-state of the object should be reset
Returns:
"""
action = super().mark_as_edited(performing_user, edit_comment=edit_comment)
if reset_recorded:
self.unrecord(performing_user, request)
if self.checked:
self.set_unchecked()
return action
@ -416,8 +417,11 @@ class InterventionDocument(AbstractDocument):
# The only file left for this intervention is the one which is currently processed and will be deleted
# Make sure that the intervention folder itself is deleted as well, not only the file
# Therefore take the folder path from the file path
folder_path = self.file.path.split("/")[:-1]
folder_path = "/".join(folder_path)
try:
folder_path = self.file.path.split("/")[:-1]
folder_path = "/".join(folder_path)
except ValueError:
folder_path = None
if user:
self.instance.mark_as_edited(user, edit_comment=DOCUMENT_REMOVED_TEMPLATE.format(self.title))

@ -400,12 +400,13 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
self.eco_account.share_with_user_list([self.superuser])
self.eco_account.save()
num_all_deducs = EcoAccountDeduction.objects.count()
num_acc_deducs = self.eco_account.deductions.count()
# Run the request
self.client_user.post(new_url, post_data)
# Expect the deduction to be created, since all constraints are fulfilled
self.assertEqual(1, self.eco_account.deductions.count())
self.assertEqual(num_acc_deducs + 1, self.eco_account.deductions.count())
self.assertEqual(num_all_deducs + 1, EcoAccountDeduction.objects.count())
# Make sure the deduction contains the expected data

@ -0,0 +1,7 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 24.08.23
"""

@ -0,0 +1,333 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 24.08.23
"""
import json
from datetime import timedelta
from django.core.exceptions import ObjectDoesNotExist
from django.urls import reverse
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.test import RequestFactory
from intervention.forms.intervention import NewInterventionForm, EditInterventionForm
from intervention.forms.modals.revocation import NewRevocationModalForm, EditRevocationModalForm, \
RemoveRevocationModalForm
from intervention.forms.modals.share import ShareModalForm
from intervention.models import Revocation
from konova.forms import SimpleGeomForm
from konova.settings import DEFAULT_GROUP, ZB_GROUP
from konova.tests.test_views import BaseTestCase
from konova.utils.generators import generate_random_string
from konova.utils.message_templates import REVOCATION_ADDED, REVOCATION_EDITED, REVOCATION_REMOVED
from user.models import UserAction
class NewInterventionFormTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
def test_init(self):
form = NewInterventionForm()
self.assertEqual(form.form_title, str(_("New intervention")))
self.assertEqual(form.action_url, reverse("intervention:new"))
self.assertEqual(form.cancel_redirect, reverse("intervention:index"))
initial_identifier = form.fields["identifier"].initial
self.assertIsNotNone(initial_identifier)
self.assertIn("EIV-", initial_identifier)
def test_is_valid(self):
data = {
"identifier": generate_random_string(length=15, use_letters_uc=True),
"title": generate_random_string(length=15, use_letters_uc=True),
}
form = NewInterventionForm({})
self.assertFalse(form.is_valid())
form = NewInterventionForm(data)
self.assertTrue(form.is_valid(), msg=form.errors)
def test_save(self):
data = {
"identifier": generate_random_string(length=15, use_letters_uc=True),
"title": generate_random_string(length=15, use_letters_uc=True),
}
test_geom = self.create_dummy_geometry()
geom_form_data = self.create_geojson(
test_geom
)
geom_form_data = json.loads(geom_form_data)
geom_form_data = {
"geom": json.dumps(geom_form_data)
}
geom_form = SimpleGeomForm(geom_form_data)
form = NewInterventionForm(data)
self.assertTrue(form.is_valid())
self.assertTrue(geom_form.is_valid())
obj = form.save(self.superuser, geom_form)
self.assertEqual(obj.identifier, data["identifier"])
self.assertEqual(obj.title, data["title"])
self.assertIsNotNone(obj.legal)
self.assertIsNotNone(obj.responsible)
self.assertIsNotNone(obj.responsible.handler)
self.assertEqual(obj.created.action, UserAction.CREATED)
self.assertEqual(obj.created.user, self.superuser)
self.assertEqual(obj.created, obj.log.first())
self.assertEqual(obj.created, obj.modified)
self.assertIn(self.superuser, obj.shared_users)
self.assertTrue(test_geom.equals_exact(obj.geometry.geom, 0.000001))
class EditInterventionFormTestCase(NewInterventionFormTestCase):
def test_init(self):
today = now().date()
data = {
"identifier": self.intervention.identifier,
"title": generate_random_string(length=5, use_letters_lc=True),
"comment": generate_random_string(length=5, use_letters_lc=True),
"registration_date": today,
"binding_date": today,
"registration_file_number": generate_random_string(length=5, use_numbers=True),
"conservation_file_number": generate_random_string(length=5, use_numbers=True),
}
test_geom = self.create_dummy_geometry()
geom_form_data = self.create_geojson(
test_geom
)
geom_form_data = json.loads(geom_form_data)
geom_form_data = {
"geom": json.dumps(geom_form_data)
}
geom_form = SimpleGeomForm(geom_form_data)
form = EditInterventionForm(data, instance=self.intervention)
self.assertTrue(geom_form.is_valid())
self.assertTrue(form.is_valid())
obj = form.save(self.superuser, geom_form)
last_log = obj.log.first()
self.assertEqual(last_log.user, self.superuser)
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log, obj.modified)
self.assertEqual(obj.identifier, self.intervention.identifier)
self.assertIsNotNone(obj.legal)
self.assertIsNotNone(obj.responsible)
self.assertIsNotNone(obj.responsible.handler)
self.assertEqual(obj.title, data["title"])
self.assertEqual(obj.comment, data["comment"])
self.assertTrue(test_geom.equals_exact(obj.geometry.geom, 0.000001))
self.assertEqual(obj.legal.binding_date, today)
self.assertEqual(obj.legal.registration_date, today)
self.assertEqual(obj.responsible.registration_file_number, data["registration_file_number"])
self.assertEqual(obj.responsible.conservation_file_number, data["conservation_file_number"])
class ShareModalFormTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
self.request = RequestFactory().request()
self.request.user = self.superuser
def test_init(self):
self.intervention.access_token = None
self.intervention.save()
form = ShareModalForm(
request=self.request,
instance=self.intervention
)
self.assertIsNotNone(self.intervention.access_token)
self.assertEqual(form.form_title, str(_("Share")))
self.assertEqual(form.form_caption, str(_("Share settings for {}").format(
self.intervention.identifier
)))
self.assertEqual(form.template, "modal/modal_form.html")
self.assertEqual(form.instance, self.intervention)
self.assertEqual(form.user, self.superuser)
def test_is_valid_and_save(self):
# make user default-group-only (special treatment)
self.superuser.groups.set(
self.groups.filter(
name=DEFAULT_GROUP
)
)
self.assertNotIn(self.superuser, self.intervention.shared_users)
self.assertNotIn(self.team, self.intervention.shared_teams)
# Add new sharing data
## Default-only is able to add new sharing but can not remove existing ones
data = {
"users": [self.superuser.id,],
"teams": [self.team.id,],
}
form = ShareModalForm(
data,
request=self.request,
instance=self.intervention,
)
self.assertTrue(form.is_valid(), msg=form.errors)
form.save()
self.assertIn(self.superuser, self.intervention.shared_users)
self.assertIn(self.team, self.intervention.shared_teams)
# Try to remove sharing data das default-only user
data = {
"users": [],
"teams": [],
}
form = ShareModalForm(
data,
request=self.request,
instance=self.intervention,
)
self.assertFalse(form.is_valid(), msg=form.errors)
self.assertTrue(form.has_error("teams"))
self.assertTrue(form.has_error("users"))
# Add another permission group for user
self.superuser.groups.add(
self.groups.get(
name=ZB_GROUP
)
)
form = ShareModalForm(
data,
request=self.request,
instance=self.intervention,
)
self.assertTrue(form.is_valid(), msg=form.errors)
self.assertIn(self.superuser, self.intervention.shared_users)
self.assertIn(self.team, self.intervention.shared_teams)
form.save()
self.assertNotIn(self.superuser, self.intervention.shared_users)
self.assertNotIn(self.team, self.intervention.shared_teams)
class NewRevocationModalFormTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
self.request = RequestFactory().request()
self.request.user = self.superuser
def test_init(self):
form = NewRevocationModalForm(
request=self.request,
instance=self.intervention
)
self.assertEqual(form.instance, self.intervention)
self.assertEqual(form.user, self.request.user)
self.assertEqual(form.request, self.request)
self.assertEqual(form.form_title, str(_("Add revocation")))
self.assertEqual(form.form_caption, "")
self.assertEqual(form.form_attrs, {
"enctype": "multipart/form-data"
})
def test_save(self):
data = {
"date": now().date(),
"file": None,
"comment": generate_random_string(20, use_letters_uc=True)
}
form = NewRevocationModalForm(
data,
request=self.request,
instance=self.intervention
)
self.assertTrue(form.is_valid(), msg=form.errors)
obj = form.save()
self.assertEqual(obj.intervention, self.intervention)
self.assertEqual(obj.date, data["date"])
self.assertEqual(obj.legal, self.intervention.legal)
self.assertEqual(obj.comment, data["comment"])
last_log = self.intervention.log.first()
self.assertEqual(last_log.user, self.superuser)
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.comment, REVOCATION_ADDED)
class EditRevocationModalFormTestCase(NewRevocationModalFormTestCase):
def setUp(self) -> None:
super().setUp()
self.revoc = Revocation.objects.get_or_create(
date=now().date(),
comment="TEST",
legal=self.intervention.legal,
)[0]
def test_init(self):
new_date = now().date() - timedelta(days=10)
data = {
"date": new_date,
"comment": generate_random_string(20, use_letters_lc=True)
}
form = EditRevocationModalForm(
data,
request=self.request,
instance=self.intervention,
revocation=self.revoc
)
self.assertTrue(form.is_valid(), msg=form.errors)
obj = form.save()
self.assertEqual(obj.date, new_date)
self.assertEqual(obj.comment, data["comment"])
self.assertEqual(obj.legal, self.intervention.legal)
self.assertEqual(obj.intervention, self.intervention)
last_log = self.intervention.log.first()
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.user, self.superuser)
self.assertEqual(last_log.comment, REVOCATION_EDITED)
class RemoveRevocationModalFormTestCase(EditRevocationModalFormTestCase):
def setUp(self) -> None:
super().setUp()
def test_init(self):
form = RemoveRevocationModalForm(
request=self.request,
instance=self.intervention,
revocation=self.revoc,
)
self.assertEqual(form.instance, self.intervention)
self.assertEqual(form.revocation, self.revoc)
self.assertEqual(form.request, self.request)
self.assertEqual(form.user, self.request.user)
def test_save(self):
data = {
"confirm": True,
}
form = RemoveRevocationModalForm(
data,
request=self.request,
instance=self.intervention,
revocation=self.revoc
)
self.assertTrue(form.is_valid(), msg=form.errors)
form.save()
try:
self.revoc.refresh_from_db()
self.fail("Revocation should not exist anymore")
except ObjectDoesNotExist:
pass
last_log = self.intervention.log.first()
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.user, self.superuser)
self.assertEqual(last_log.comment, REVOCATION_REMOVED)

@ -0,0 +1,50 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 07.09.23
"""
from django.core.exceptions import ObjectDoesNotExist
from django.utils.timezone import now
from intervention.models import RevocationDocument, Revocation
from konova.tests.test_views import BaseTestCase
class RevocationDocumentTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
self.revocation = Revocation.objects.get_or_create(
date=now().date(),
comment="Test",
legal=self.intervention.legal
)[0]
self.doc = self.create_dummy_document(
RevocationDocument,
instance=self.revocation
)
def test_intervention_property(self):
self.assertEqual(
self.doc.intervention,
self.doc.instance.legal.intervention
)
self.assertEqual(
self.doc.intervention,
self.intervention
)
def test_delete(self):
revoc_docs, other_intervention_docs = self.intervention.get_documents()
self.assertIn(self.doc, revoc_docs)
try:
self.doc.delete()
self.doc.refresh_from_db()
self.fail("Should not be fetchable anymore!")
except ObjectDoesNotExist:
pass
revoc_docs, other_intervention_docs = self.intervention.get_documents()
self.assertEqual(revoc_docs.count(), 0)

@ -22,8 +22,7 @@ from konova.forms.modals import RemoveModalForm
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from konova.utils.message_templates import DATA_CHECKED_PREVIOUSLY_TEMPLATE, RECORDED_BLOCKS_EDIT, \
CHECKED_RECORDED_RESET, FORM_INVALID, IDENTIFIER_REPLACED, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED
from konova.utils.user_checks import in_group
CHECK_STATE_RESET, FORM_INVALID, IDENTIFIER_REPLACED, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED
@login_required
@ -186,9 +185,9 @@ def detail_view(request: HttpRequest, id: str):
"compensations": compensations,
"has_access": is_data_shared,
"geom_form": geom_form,
"is_default_member": in_group(_user, DEFAULT_GROUP),
"is_zb_member": in_group(_user, ZB_GROUP),
"is_ets_member": in_group(_user, ETS_GROUP),
"is_default_member": _user.in_group(DEFAULT_GROUP),
"is_zb_member": _user.in_group(ZB_GROUP),
"is_ets_member": _user.in_group(ETS_GROUP),
"LANIS_LINK": intervention.get_LANIS_link(),
"has_payment_without_document": has_payment_without_document,
TAB_TITLE_IDENTIFIER: f"{intervention.identifier} - {intervention.title}",
@ -230,12 +229,11 @@ def edit_view(request: HttpRequest, id: str):
if data_form.is_valid() and geom_form.is_valid():
# The data form takes the geom form for processing, as well as the performing user
# Save the current state of recorded|checked to inform the user in case of a status reset due to editing
i_rec = intervention.recorded is not None
i_check = intervention.checked is not None
intervention_is_checked = intervention.checked is not None
intervention = data_form.save(request.user, geom_form)
messages.success(request, _("Intervention {} edited").format(intervention.identifier))
if i_check or i_rec:
messages.info(request, CHECKED_RECORDED_RESET)
if intervention_is_checked:
messages.info(request, CHECK_STATE_RESET)
if geom_form.geometry_simplified:
messages.info(
request,

@ -8,10 +8,10 @@ Created on: 15.08.22
import json
from django.contrib.gis import gdal
from django.contrib.gis.forms import MultiPolygonField
from django.contrib.gis.geos import MultiPolygon, Polygon
from django.contrib.gis.geos.prototypes.io import WKTWriter
from django.utils.translation import gettext_lazy as _
from django.forms import JSONField
from konova.forms.base_form import BaseForm
from konova.models import Geometry
@ -27,8 +27,7 @@ class SimpleGeomForm(BaseForm):
"""
read_only = True
geometry_simplified = False
geom = MultiPolygonField(
srid=DEFAULT_SRID_RLP,
geom = JSONField(
label=_("Geometry"),
help_text=_(""),
label_suffix="",

@ -14,6 +14,12 @@ from user.models import UserActionLogEntry, User
class RemoveForm(BaseForm):
""" DEPRECATED
NOT USED IN ANY PLACE.
CAN BE DELETED AT SOME POINT.
"""
check = forms.BooleanField(
label=_("Confirm"),
label_suffix=_(""),

@ -317,7 +317,7 @@ class Geometry(BaseResource):
"""
geom = self.geom
if geom.srid != srid:
geom.transform(ct=srid)
geom.transform(srid)
polygons = [p for p in geom]
geojson = {
"type": "FeatureCollection",

@ -23,13 +23,9 @@ from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpRequest
from django.utils.timezone import now
from django.db import models, transaction
from compensation.settings import COMPENSATION_IDENTIFIER_TEMPLATE, COMPENSATION_IDENTIFIER_LENGTH, \
ECO_ACCOUNT_IDENTIFIER_TEMPLATE, ECO_ACCOUNT_IDENTIFIER_LENGTH
from ema.settings import EMA_ACCOUNT_IDENTIFIER_LENGTH, EMA_ACCOUNT_IDENTIFIER_TEMPLATE
from intervention.settings import INTERVENTION_IDENTIFIER_LENGTH, INTERVENTION_IDENTIFIER_TEMPLATE
from konova.utils import generators
from konova.utils.generators import generate_random_string
from konova.utils.message_templates import CHECKED_RECORDED_RESET, GEOMETRY_CONFLICT_WITH_TEMPLATE
from konova.utils.message_templates import GEOMETRY_CONFLICT_WITH_TEMPLATE
class UuidModel(models.Model):
@ -143,6 +139,9 @@ class BaseObject(BaseResource, DeletableObjectMixin):
comment = models.TextField(null=True, blank=True)
log = models.ManyToManyField("user.UserActionLogEntry", blank=True, help_text="Keeps all user actions of an object", editable=False)
identifier_length = 6 # Fallback - specified in inheriting classes
identifier_template = "UNBEKANNT-{}" # Fallback - specified in inheriting classes
class Meta:
abstract = True
@ -193,32 +192,8 @@ class BaseObject(BaseResource, DeletableObjectMixin):
Returns:
str
"""
from compensation.models import Compensation, EcoAccount
from intervention.models import Intervention
from ema.models import Ema
definitions = {
Intervention: {
"length": INTERVENTION_IDENTIFIER_LENGTH,
"template": INTERVENTION_IDENTIFIER_TEMPLATE,
},
Compensation: {
"length": COMPENSATION_IDENTIFIER_LENGTH,
"template": COMPENSATION_IDENTIFIER_TEMPLATE,
},
EcoAccount: {
"length": ECO_ACCOUNT_IDENTIFIER_LENGTH,
"template": ECO_ACCOUNT_IDENTIFIER_TEMPLATE,
},
Ema: {
"length": EMA_ACCOUNT_IDENTIFIER_LENGTH,
"template": EMA_ACCOUNT_IDENTIFIER_TEMPLATE,
},
}
if self.__class__ not in definitions:
# Not defined, yet. Create fallback identifier for this case
return generate_random_string(10)
id_len = self.identifier_length
id_template = self.identifier_template
_now = now()
curr_month = _now.month
@ -229,13 +204,13 @@ class BaseObject(BaseResource, DeletableObjectMixin):
curr_month = str(curr_month)
curr_year = str(_now.year)
rand_str = generate_random_string(
length=definitions[self.__class__]["length"],
length=id_len,
use_numbers=True,
use_letters_lc=False,
use_letters_uc=True,
)
_str = "{}{}-{}".format(curr_month, curr_year, rand_str)
return definitions[self.__class__]["template"].format(_str)
return id_template.format(_str)
@abstractmethod
def get_detail_url(self):
@ -319,27 +294,6 @@ class RecordableObjectMixin(models.Model):
return action
def unrecord(self, performing_user, request: HttpRequest = None):
""" Unrecords a dataset
Args:
performing_user (User): The user which performed the editing action
request (HttpRequest): The used request for this action
Returns:
"""
action = None
if self.recorded:
action = self.set_unrecorded(performing_user)
self.log.add(action)
if request:
messages.info(
request,
CHECKED_RECORDED_RESET
)
return action
@abstractmethod
def is_ready_for_publish(self) -> bool:
""" Check for all needed publishing-constraints on the data
@ -374,7 +328,7 @@ class CheckableObjectMixin(models.Model):
abstract = True
def set_unchecked(self) -> None:
""" Perform unrecording
""" Perform unchecking
Args:
@ -384,7 +338,7 @@ class CheckableObjectMixin(models.Model):
if not self.checked:
# Nothing to do
return
# Do not .delete() the checked attribute! Just set it to None, since a delete() would kill it out of the
# Do not .delete() the checked attribute! Just set it to None, since a delete() would remove it from the
# log history, which is not what we want!
self.checked = None
self.save()
@ -686,12 +640,11 @@ class ShareableObjectMixin(models.Model):
Returns:
"""
from konova.utils.user_checks import is_default_group_only
users = self.shared_users
cleaned_users = []
default_users = []
for user in users:
if not is_default_group_only(user):
if not user.is_default_group_only():
cleaned_users.append(user)
else:
default_users.append(user)

@ -15,7 +15,7 @@ DEFAULT_SRID_RLP = 25832
# Needed to redirect to LANIS
## Values to be inserted are [zoom_level, x_coord, y_coord]
LANIS_LINK_TEMPLATE = "https://geodaten.naturschutz.rlp.de/kartendienste_naturschutz/index.php?lang=de&zl={}&x={}&y={}&bl=tk_rlp_tms_grau&bo=1&lo=0.8,0.8,0.8,0.6,0.8,0.8,0.8,0.8,0.8&layers=eiv_recorded,eiv_unrecorded,kom_recorded,kom_unrecorded,oek_recorded,oek_unrecorded,ema_recorded,ema_unrecorded,mae&service=kartendienste_naturschutz"
LANIS_LINK_TEMPLATE = "https://geodaten.naturschutz.rlp.de/kartendienste_naturschutz/index.php?lang=de&zl={}&x={}&y={}&bl=tk_rlp_tms_grau&bo=1&lo=0.8,0.8,0.8,0.6,0.8,0.8,0.8,0.8,0.8&layers=eiv_recorded,eiv_unrecorded,eiv_unrecorded_old_entries,kom_recorded,kom_unrecorded,kom_unrecorded_old_entries,oek_recorded,oek_unrecorded,ema_recorded,ema_unrecorded,mae&service=kartendienste_naturschutz"
## This look up table (LUT) defines different zoom levels on the size of the calculate area of a geometry.
LANIS_ZOOM_LUT = {
1000000000: 6,

@ -5,9 +5,12 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 15.12.21
"""
import json
from django.contrib.gis.db.models.functions import Translate
from konova.models import Geometry, GeometryConflict
from konova.sub_settings.lanis_settings import DEFAULT_SRID
from konova.tests.test_views import BaseTestCase
from konova.utils.schneider.fetcher import ParcelFetcher
@ -74,3 +77,66 @@ class GeometryTestCase(BaseTestCase):
fetcher = ParcelFetcher(geometry=self.geom_1)
features = fetcher.get_parcels()
self.assertNotEqual(0, len(features), msg="Spatial fetcher get feature did not work!")
def test_str(self):
self.assertEqual(
str(self.geom_1),
str(self.geom_1.id)
)
def test_get_data_objects(self):
num_objs_with_geom = 0
self.assertEqual(
len(self.geom_1.get_data_objects()),
num_objs_with_geom
)
objs = [
self.intervention,
self.compensation,
self.eco_account,
self.ema,
]
for obj in objs:
obj.geometry = self.geom_1
obj.save()
num_objs_with_geom += 1
geom_objs = self.geom_1.get_data_objects()
self.assertEqual(
len(geom_objs),
num_objs_with_geom
)
self.assertIn(obj, geom_objs)
def test_as_feature_collection(self):
geometry = self.geom_1.geom
polygons = [p for p in geometry]
expected_result = {
"type": "FeatureCollection",
"crs": {
"type": "name",
"properties": {
"name": f"urn:ogc:def:crs:EPSG::{geometry.srid}"
}
},
"features": [
{
"type": "Feature",
"geometry": json.loads(p.json),
}
for p in polygons
]
}
result = self.geom_1.as_feature_collection()
result = json.dumps(result)
expected_result = json.dumps(expected_result)
self.assertEqual(expected_result, result)
# Transform geometry into non-default-rlp srid to trigger retransforming in later steps
geometry.transform(DEFAULT_SRID)
different_result = self.geom_1.as_feature_collection()
different_result = json.dumps(result)
self.assertNotEqual(different_result, result)

@ -8,7 +8,7 @@ Created on: 26.10.21
import datetime
import json
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_REGISTRATION_OFFICE_ID
from ema.models import Ema
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
from user.models import User, Team
@ -157,14 +157,17 @@ class BaseTestCase(TestCase):
intervention.generate_access_token(make_unique=True)
return intervention
def create_dummy_compensation(self):
def create_dummy_compensation(self, interv: Intervention=None):
""" Creates a compensation which can be used for tests
Returns:
"""
if self.intervention is None:
self.intervention = self.create_dummy_intervention()
if not interv:
if self.intervention is None:
interv = self.create_dummy_intervention()
else:
interv = self.intervention
# Create dummy data
# Create log entry
action = UserActionLogEntry.get_created_action(self.superuser)
@ -173,7 +176,7 @@ class BaseTestCase(TestCase):
compensation = Compensation.objects.create(
identifier="TEST",
title="Test_title",
intervention=self.intervention,
intervention=interv,
created=action,
geometry=geometry,
comment="Test",
@ -196,9 +199,11 @@ class BaseTestCase(TestCase):
handler = self.handler
responsible_data.handler = handler
responsible_data.save()
identifier = EcoAccount().generate_new_identifier()
# Finally create main object, holding the other objects
eco_account = EcoAccount.objects.create(
identifier="TEST",
identifier=identifier,
title="Test_title",
deductable_surface=500,
legal=lega_data,
@ -234,10 +239,15 @@ class BaseTestCase(TestCase):
)
return ema
def create_dummy_deduction(self):
def create_dummy_deduction(self, acc: EcoAccount = None, interv: Intervention = None):
if not acc:
acc = self.create_dummy_eco_account()
if not interv:
interv = self.create_dummy_intervention()
return EcoAccountDeduction.objects.create(
account=self.create_dummy_eco_account(),
intervention=self.create_dummy_intervention(),
account=acc,
intervention=interv,
surface=100,
)
@ -270,15 +280,17 @@ class BaseTestCase(TestCase):
Returns:
"""
codes = KonovaCode.objects.bulk_create([
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"),
])
codes = KonovaCode.objects.all()
if codes.count() == 0:
codes = KonovaCode.objects.bulk_create([
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
def create_dummy_team(self):
def create_dummy_team(self, name: str = None):
""" Creates a dummy team
Returns:
@ -287,8 +299,11 @@ class BaseTestCase(TestCase):
if self.superuser is None:
self.create_users()
if not name:
name = "Testteam"
team = Team.objects.get_or_create(
name="Testteam",
name=name,
description="Testdescription",
)[0]
team.users.add(self.superuser)
@ -318,7 +333,7 @@ class BaseTestCase(TestCase):
"""
polygon = Polygon.from_bbox((7.592449, 50.359385, 7.593382, 50.359874))
polygon.srid = 4326
polygon = polygon.transform(DEFAULT_SRID_RLP, clone=True)
polygon.transform(DEFAULT_SRID_RLP)
return MultiPolygon(polygon, srid=DEFAULT_SRID_RLP)
def create_geojson(self, geometry):
@ -408,6 +423,19 @@ class BaseTestCase(TestCase):
codelist.codes.add(code)
return code
def get_registration_office_code(self):
""" Returns a dummy KonovaCode as conservation office code
Returns:
"""
codelist = KonovaCodeList.objects.get_or_create(
id=CODELIST_REGISTRATION_OFFICE_ID
)[0]
code = KonovaCode.objects.get(id=3)
codelist.codes.add(code)
return code
def fill_out_ema(self, ema):
""" Adds all required (dummy) data to an Ema

@ -0,0 +1,365 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 07.09.23
"""
import mimetypes
from datetime import timedelta
from django.core.exceptions import ObjectDoesNotExist
from django.test import RequestFactory
from django.utils.translation import gettext_lazy as _
from django.core.files.uploadedfile import SimpleUploadedFile
from django.utils.timezone import now
from compensation.forms.modals.document import NewEcoAccountDocumentModalForm, NewCompensationDocumentModalForm
from compensation.models import Payment
from ema.forms import NewEmaDocumentModalForm
from intervention.forms.modals.document import NewInterventionDocumentModalForm
from intervention.models import InterventionDocument
from konova.forms.modals import EditDocumentModalForm, NewDocumentModalForm, RecordModalForm, RemoveModalForm, \
RemoveDeadlineModalForm, ResubmissionModalForm
from konova.models import Resubmission
from konova.tests.test_views import BaseTestCase
from konova.utils.generators import generate_random_string
from konova.utils.message_templates import DOCUMENT_EDITED, DEADLINE_REMOVED
from user.models import UserAction
class NewDocumentModalFormTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
self.request = RequestFactory().request()
self.request.user = self.superuser
self.dummy_file = SimpleUploadedFile(
"some_file.pdf",
b"Some conent in this file",
mimetypes.types_map[".pdf"]
)
dummy_file_dict = {
"file": self.dummy_file
}
self.data = {
"title": generate_random_string(length=5, use_letters_lc=True),
"creation_date": now().date(),
"comment": generate_random_string(length=50, use_letters_uc=True),
}
self.forms = [
NewInterventionDocumentModalForm(self.data, dummy_file_dict, request=self.request, instance=self.intervention),
NewCompensationDocumentModalForm(self.data, dummy_file_dict, request=self.request, instance=self.compensation),
NewEmaDocumentModalForm(self.data, dummy_file_dict, request=self.request, instance=self.ema),
NewEcoAccountDocumentModalForm(self.data, dummy_file_dict, request=self.request, instance=self.eco_account),
]
def test_init(self):
for form in self.forms:
self.assertEqual(form.form_title, str(_("Add new document")))
self.assertEqual(form.form_caption, str(_("")))
self.assertEqual(
form.form_attrs,
{
"enctype": "multipart/form-data"
}
)
self.assertEqual(form.request, self.request)
self.assertEqual(form.user, self.superuser)
try:
NewDocumentModalForm(request=self.request, instance=self.intervention)
self.fail("Base form NewDocumentModalForm should not be creatable")
except NotImplementedError:
pass
def test_is_valid(self):
for form in self.forms:
self.assertTrue(
form.is_valid(), msg=form.errors
)
def test_save(self):
for form in self.forms:
form.is_valid()
obj = form.save()
self.assertEqual(obj.created.action, UserAction.CREATED)
self.assertEqual(obj.created.user, self.superuser)
self.assertEqual(obj.title, self.data["title"])
self.assertEqual(obj.date_of_creation, self.data["creation_date"])
self.assertEqual(obj.comment, self.data["comment"])
self.assertIsNotNone(obj.file)
last_log = obj.instance.log.first()
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.user, self.request.user)
self.assertEqual(last_log.comment, str(_("Added document")))
self.assertEqual(obj.instance.modified, last_log)
class EditDocumentModalFormTestCase(NewDocumentModalFormTestCase):
def setUp(self) -> None:
super().setUp()
dummy_file_dict = {
"file": self.dummy_file
}
self.doc = self.create_dummy_document(
InterventionDocument,
instance=self.intervention
)
self.form = EditDocumentModalForm(
self.data,
dummy_file_dict,
request=self.request,
instance=self.intervention,
document=self.doc
)
def test_init(self):
self.assertEqual(self.form.form_title, str(_("Edit document")))
self.assertEqual(self.form.document, self.doc)
self.assertEqual(self.form.request, self.request)
self.assertEqual(self.form.user, self.request.user)
self.assertEqual(self.form.fields["title"].initial, self.doc.title)
self.assertEqual(self.form.fields["comment"].initial, self.doc.comment)
self.assertEqual(self.form.fields["creation_date"].initial, self.doc.date_of_creation)
self.assertEqual(self.form.fields["file"].initial, self.doc.file)
def test_save(self):
self.assertTrue(self.form.is_valid(), msg=self.form.errors)
obj = self.form.save()
self.assertEqual(obj.title, self.data["title"])
self.assertEqual(obj.comment, self.data["comment"])
self.assertEqual(obj.date_of_creation, self.data["creation_date"])
last_log = obj.instance.log.first()
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.user, self.request.user)
self.assertEqual(last_log.comment, DOCUMENT_EDITED)
class RecordModalFormTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
self.request = RequestFactory().request()
self.request.user = self.user
self.fill_out_compensation(self.compensation)
def test_init(self):
form = RecordModalForm(
request=self.request,
instance=self.intervention
)
self.assertEqual(form.form_title, str(_("Record data")))
self.assertEqual(form.form_caption, str(
_("I, {} {}, confirm that all necessary control steps have been performed by myself.").format(
self.user.first_name,
self.user.last_name
)
))
self.intervention.set_recorded(self.user)
self.intervention.refresh_from_db()
form = RecordModalForm(
request=self.request,
instance=self.intervention
)
self.assertEqual(form.form_title, str(_("Unrecord data")))
self.assertEqual(form.fields["confirm"].label, str(_("Confirm unrecord")))
self.assertEqual(form.form_caption, str(
_("I, {} {}, confirm that this data must be unrecorded.").format(
self.user.first_name,
self.user.last_name
)))
def test_is_valid(self):
data = {
"confirm": True
}
form = RecordModalForm(data, request=self.request, instance=self.intervention)
self.assertFalse(self.intervention.is_recorded)
self.assertFalse(form.is_valid(), msg=form.errors) # intervention not complete
self.intervention = self.fill_out_intervention(self.intervention)
form = RecordModalForm(data, request=self.request, instance=self.intervention)
self.assertTrue(form.is_valid(), msg=form.errors)
def test_save(self):
data = {
"confirm": True
}
self.intervention = self.fill_out_intervention(self.intervention)
form = RecordModalForm(data, request=self.request, instance=self.intervention)
self.assertTrue(form.is_valid(), msg=form.errors)
form.save()
self.assertEqual(self.intervention.recorded.action, UserAction.RECORDED)
self.assertEqual(self.intervention.recorded.user, self.request.user)
self.assertEqual(self.intervention.recorded, self.intervention.log.first())
class RemoveModalFormTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
self.request = RequestFactory().request()
self.request.user = self.user
def test_init(self):
form = RemoveModalForm(request=self.request, instance=self.intervention)
self.assertEqual(form.form_title, str(_("Remove")))
self.assertEqual(form.form_caption, str(_("Are you sure?")))
self.assertEqual(form.template, "modal/modal_form.html")
self.assertEqual(form.request, self.request)
self.assertEqual(form.user, self.request.user)
def test_save(self):
data = {
"confirm": True,
}
form = RemoveModalForm(
data,
request=self.request,
instance=self.intervention
)
self.assertTrue(form.is_valid(), msg=form.errors)
form.save()
self.assertEqual(self.intervention.deleted.action, UserAction.DELETED)
self.assertEqual(self.intervention.deleted.user, self.request.user)
payment = Payment.objects.create(
amount=1.0,
intervention=self.intervention
)
form = RemoveModalForm(
data,
request=self.request,
instance=payment
)
self.assertTrue(form.is_valid(), msg=form.errors)
form.save()
try:
payment.refresh_from_db()
self.fail("Payment still exists")
except ObjectDoesNotExist:
pass
class RemoveDeadlineTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
request = RequestFactory().request()
request.user = self.user
self.request = request
def test_init(self):
form = RemoveDeadlineModalForm(
request=self.request,
instance=self.compensation,
deadline=self.finished_deadline
)
self.assertEqual(form.form_title, str(_("Remove")))
self.assertEqual(form.form_caption, str(_("Are you sure?")))
self.assertEqual(form.template, "modal/modal_form.html")
self.assertEqual(form.request, self.request)
self.assertEqual(form.user, self.request.user)
def test_save(self):
self.compensation.deadlines.add(self.finished_deadline)
data = {
"confirm": True
}
form = RemoveDeadlineModalForm(
data,
request=self.request,
instance=self.compensation,
deadline=self.finished_deadline
)
self.assertTrue(form.is_valid(), msg=form.errors)
form.save()
try:
self.finished_deadline.refresh_from_db()
self.fail("Deadline still exists")
except ObjectDoesNotExist:
pass
last_log = self.compensation.log.first()
self.assertEqual(last_log.action, UserAction.EDITED)
self.assertEqual(last_log.user, self.request.user)
self.assertEqual(last_log.comment, DEADLINE_REMOVED)
class ResubmissionModalFormTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
self.request = RequestFactory().request()
self.request.user = self.user
def test_init(self):
# Resubmission nonexistent
form = ResubmissionModalForm(request=self.request, instance=self.intervention)
self.assertEqual(form.form_title, str(_("Resubmission")))
self.assertEqual(form.form_caption, str(_("Set your resubmission for this entry.")))
self.assertEqual(form.action_url, None)
self.assertIsNotNone(form.resubmission)
resubmission = Resubmission.objects.create(
user=self.request.user,
resubmit_on=now().date(),
comment=generate_random_string(length=10, use_letters_lc=True)
)
self.intervention.resubmissions.add(resubmission)
# Resubmission exists
form = ResubmissionModalForm(request=self.request, instance=self.intervention)
self.assertEqual(form.form_title, str(_("Resubmission")))
self.assertEqual(form.form_caption, str(_("Set your resubmission for this entry.")))
self.assertEqual(form.action_url, None)
self.assertEqual(form.fields["date"].initial, str(resubmission.resubmit_on))
self.assertEqual(form.fields["comment"].initial, resubmission.comment)
self.assertEqual(form.resubmission, resubmission)
def test_is_valid(self):
yesterday = now().date() - timedelta(days=1)
data = {
"date": yesterday,
"comment": "Edited comment"
}
form = ResubmissionModalForm(
data,
request=self.request,
instance=self.intervention
)
self.assertFalse(form.is_valid(), msg=form.errors)
self.assertTrue(form.has_error("date"))
tomorrow = yesterday + timedelta(days=2)
data = {
"date": tomorrow,
"comment": "Edited comment"
}
form = ResubmissionModalForm(
data,
request=self.request,
instance=self.intervention
)
self.assertTrue(form.is_valid(), msg=form.errors)
def test_save(self):
data = {
"date": now().date() + timedelta(days=1),
"comment": "New comment for new resubmission"
}
form = ResubmissionModalForm(
data,
request=self.request,
instance=self.intervention
)
self.assertTrue(form.is_valid(), msg=form.errors)
obj = form.save()
self.assertIn(obj, self.intervention.resubmissions.all())
self.assertEqual(obj.resubmit_on, data["date"])
self.assertEqual(obj.comment, data["comment"])
self.assertEqual(obj.resubmission_sent, False)
self.assertEqual(obj.user, self.request.user)

@ -0,0 +1,201 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 08.09.23
"""
from django.test import RequestFactory
from django.utils.timezone import now
from intervention.forms.modals.share import ShareModalForm
from konova.models import DeadlineType, Resubmission
from konova.settings import ZB_GROUP
from konova.tests.test_views import BaseTestCase
from user.models import UserAction
class DeadlineTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
def test_str(self):
self.assertEqual(str(self.finished_deadline), self.finished_deadline.type)
def test_type_humanized_property(self):
self.assertEqual(self.finished_deadline.type_humanized, DeadlineType.FINISHED.label)
class BaseObjectTestCase(BaseTestCase):
def test_add_log_entry(self):
self.assertEqual(self.intervention.log.count(), 0)
self.intervention.add_log_entry(UserAction.EDITED, self.user, "TEST")
self.assertEqual(self.intervention.log.count(), 1)
last_log = self.intervention.log.first()
self.assertEqual(last_log.user, self.user)
self.assertEqual(last_log.comment, "TEST")
self.assertEqual(last_log.action, UserAction.EDITED)
def test_generate_new_identifier(self):
old_identifier = self.intervention.identifier
new_identifier = self.intervention.generate_new_identifier()
self.assertNotEqual(old_identifier, new_identifier)
class RecordableObjectMixinTestCase(BaseTestCase):
def test_set_recorded_and_set_unrecorded(self):
""" Tests set_unrecorded() as well
Returns:
"""
self.intervention.set_recorded(self.user)
self.assertIsNotNone(self.intervention.recorded)
self.assertEqual(self.intervention.recorded.user, self.user)
self.assertEqual(self.intervention.recorded.action, UserAction.RECORDED)
self.intervention.set_unrecorded(self.user)
self.assertIsNone(self.intervention.recorded)
last_log = self.intervention.log.first()
self.assertEqual(last_log.action, UserAction.UNRECORDED)
self.assertEqual(last_log.user, self.user)
class CheckableObjectMixinTestCase(BaseTestCase):
def test_set_unchecked_and_set_checked(self):
self.intervention.set_checked(self.user)
self.assertIsNotNone(self.intervention.checked)
self.assertEqual(self.intervention.checked.action, UserAction.CHECKED)
self.assertEqual(self.intervention.checked.user, self.user)
checked_action = self.intervention.checked
self.intervention.set_unchecked()
self.assertIsNone(self.intervention.checked)
# There is no explicit UNCHECKED UserAction since unchecking does never happen manually but only as an
# automatic consequence of editing an already checked entry. Therefore the last log entry in this case would
# be the checking of the entry
last_log = self.intervention.log.first()
self.assertEqual(last_log.action, UserAction.CHECKED)
self.assertEqual(last_log.user, self.user)
self.assertEqual(last_log, checked_action)
def test_get_last_checked_action(self):
self.intervention.set_checked(self.user)
action = self.intervention.checked
self.intervention.mark_as_edited(self.user)
last_log = self.intervention.log.first()
self.assertNotEqual(last_log, action)
last_check_action = self.intervention.get_last_checked_action()
self.assertEqual(action, last_check_action)
class ShareableObjectMixinTestCase(BaseTestCase):
def test_share_with_and_is_shared_with(self):
self.assertFalse(self.intervention.is_shared_with(self.user))
self.assertNotIn(self.user, self.intervention.shared_users)
self.intervention.share_with_user(self.user)
self.assertTrue(self.intervention.is_shared_with(self.user))
self.assertIn(self.user, self.intervention.shared_users)
self.assertTrue(self.intervention.is_only_shared_with(self.user))
self.assertFalse(self.intervention.is_only_shared_with(self.superuser))
self.assertNotIn(self.superuser, self.intervention.shared_users)
self.intervention.share_with_user(self.superuser)
self.assertFalse(self.intervention.is_only_shared_with(self.user))
self.assertIn(self.superuser, self.intervention.shared_users)
self.intervention.share_with_user_list([])
self.assertNotIn(self.superuser, self.intervention.shared_users)
self.assertNotIn(self.user, self.intervention.shared_users)
self.intervention.share_with_user_list([
self.superuser,
self.user
])
self.assertIn(self.superuser, self.intervention.shared_users)
self.assertIn(self.user, self.intervention.shared_users)
def test_share_with_team_and_team_list(self):
self.assertNotIn(self.team, self.intervention.shared_teams)
self.intervention.share_with_team(self.team)
self.assertIn(self.team, self.intervention.shared_teams)
another_team = self.create_dummy_team(name="Another team")
team_list = [
self.team,
another_team
]
self.assertNotIn(another_team, self.intervention.shared_teams)
self.intervention.share_with_team_list(team_list)
self.assertIn(another_team, self.intervention.shared_teams)
def test_update_shared_access(self):
another_team = self.create_dummy_team(name="Another team")
request = RequestFactory().request()
request.user = self.superuser
self.superuser.groups.add(
self.groups.get(name=ZB_GROUP)
)
self.intervention.share_with_team(another_team)
self.intervention.share_with_user(self.user)
self.assertTrue(self.intervention.is_shared_with(self.user))
self.assertIn(another_team, self.intervention.shared_teams)
data = {
"users": [
self.superuser.id,
],
"teams": [
self.team.id,
]
}
form = ShareModalForm(data, request=request, instance=self.intervention)
self.assertTrue(form.is_valid(), msg=form.errors)
form.save()
self.assertNotIn(self.user, self.intervention.shared_users)
self.assertNotIn(another_team, self.intervention.shared_teams)
self.assertIn(self.superuser, self.intervention.shared_users)
self.assertIn(self.team, self.intervention.shared_teams)
def test_unshare_with_default_users(self):
self.superuser.groups.add(
self.groups.get(
name=ZB_GROUP
)
)
self.intervention.share_with_user(self.user)
self.intervention.share_with_user(self.superuser)
self.assertTrue(self.user.is_default_group_only())
self.assertFalse(self.superuser.is_default_group_only())
self.assertTrue(self.intervention.is_shared_with(self.user))
self.assertTrue(self.intervention.is_shared_with(self.superuser))
self.intervention.unshare_with_default_users()
self.assertFalse(self.intervention.is_shared_with(self.user))
self.assertTrue(self.intervention.is_shared_with(self.superuser))
class ResubmissionTestCase(BaseTestCase):
def test_send_resubmission_mail(self):
resubmission = Resubmission.objects.create(
user=self.user,
resubmit_on=now().date(),
comment="Test",
)
self.intervention.resubmissions.add(resubmission)
self.assertFalse(resubmission.resubmission_sent)
resubmission.send_resubmission_mail(
self.intervention.identifier,
[
"Test_municipal_1"
],
)
self.assertTrue(resubmission.resubmission_sent)

@ -19,17 +19,17 @@ from django.urls import path, include
from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
from konova.sso.sso import KonovaSSOClient
from konova.views.logout import logout_view
from konova.views.geometry import get_geom_parcels, get_geom_parcels_content
from konova.views.home import home_view
from konova.views.logout import LogoutView
from konova.views.geometry import GeomParcelsView, GeomParcelsContentView
from konova.views.home import HomeView
from konova.views.map_proxy import ClientProxyParcelSearch, ClientProxyParcelWFS
sso_client = KonovaSSOClient(SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY)
urlpatterns = [
path('admin/', admin.site.urls),
path('login/', include(sso_client.get_urls())),
path('logout/', logout_view, name="logout"),
path('', home_view, name="home"),
path('logout/', LogoutView.as_view(), name="logout"),
path('', HomeView.as_view(), name="home"),
path('intervention/', include("intervention.urls")),
path('compensation/', include("compensation.urls")),
path('ema/', include("ema.urls")),
@ -38,8 +38,8 @@ urlpatterns = [
path('cl/', include("codelist.urls")),
path('analysis/', include("analysis.urls")),
path('api/', include("api.urls")),
path('geom/<id>/parcels/', get_geom_parcels, name="geometry-parcels"),
path('geom/<id>/parcels/<int:page>', get_geom_parcels_content, name="geometry-parcels-content"),
path('geom/<id>/parcels/', GeomParcelsView.as_view(), name="geometry-parcels"),
path('geom/<id>/parcels/<int:page>', GeomParcelsContentView.as_view(), name="geometry-parcels-content"),
path('client/proxy', ClientProxyParcelSearch.as_view(), name="client-proxy-search"),
path('client/proxy/wfs', ClientProxyParcelWFS.as_view(), name="client-proxy-wfs"),
]
@ -50,4 +50,4 @@ if DEBUG:
]
handler404 = "konova.views.error.get_404_view"
handler500 = "konova.views.error.get_500_view"
handler500 = "konova.views.error.get_500_view"

@ -18,7 +18,7 @@ INTERVENTION_INVALID = _("There are errors in this intervention.")
IDENTIFIER_REPLACED = _("The identifier '{}' had to be changed to '{}' since another entry has been added in the meanwhile, which uses this identifier")
ENTRY_REMOVE_MISSING_PERMISSION = _("Only conservation or registration office users are allowed to remove entries.")
MISSING_GROUP_PERMISSION = _("You need to be part of another user group.")
CHECKED_RECORDED_RESET = _("Status of Checked and Recorded reseted")
CHECK_STATE_RESET = _("Status of Checked reset")
RECORDED_BLOCKS_EDIT = _("Entry is recorded. To edit data, the entry first needs to be unrecorded.")
# SHARE

@ -1,37 +0,0 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 02.07.21
"""
from user.models import User
from konova.settings import ETS_GROUP, ZB_GROUP
def in_group(user: User, group: str) -> bool:
""" Checks if the user is part of a group
Args:
user (User): The user object
group (str): The group's name
Returns:
bool
"""
return user.groups.filter(
name=group
)
def is_default_group_only(user: User) -> bool:
""" Checks if the user is only part of the default group
Args:
user (User): The user object
Returns:
bool
"""
return not in_group(user, ZB_GROUP) and not in_group(user, ETS_GROUP)

@ -5,104 +5,110 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.gis.geos import MultiPolygon
from django.http import HttpResponse, HttpRequest
from django.shortcuts import get_object_or_404
from django.template.loader import render_to_string
from django.views import View
from konova.models import Geometry, Municipal
from konova.models import Geometry
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
def get_geom_parcels(request: HttpRequest, id: str):
""" Getter for HTMX
Returns all parcels of the requested geometry rendered into a simple HTML table
Args:
request (HttpRequest): The incoming request
id (str): The geometry's id
Returns:
A rendered piece of HTML
"""
# HTTP code 286 states that the HTMX should stop polling for updates
# https://htmx.org/docs/#polling
status_code = 286
template = "konova/includes/parcels/parcel_table_frame.html"
geom = get_object_or_404(Geometry, id=id)
parcels = geom.get_underlying_parcels()
geos_geom = geom.geom or MultiPolygon(srid=DEFAULT_SRID_RLP)
geometry_exists = not geos_geom.empty
parcels_are_currently_calculated = geometry_exists and geos_geom.area > 0 and len(parcels) == 0
parcels_available = len(parcels) > 0
if parcels_are_currently_calculated:
# Parcels are being calculated right now. Change the status code, so polling stays active for fetching
# resutls after the calculation
status_code = 200
if parcels_available or not geometry_exists:
municipals = geom.get_underlying_municipals(parcels)
class GeomParcelsView(LoginRequiredMixin, View):
def get(self, request: HttpRequest, id: str):
""" Getter for HTMX
Returns all parcels of the requested geometry rendered into a simple HTML table
Args:
request (HttpRequest): The incoming request
id (str): The geometry's id
Returns:
A rendered piece of HTML
"""
# HTTP code 286 states that the HTMX should stop polling for updates
# https://htmx.org/docs/#polling
status_code = 286
template = "konova/includes/parcels/parcel_table_frame.html"
geom = get_object_or_404(Geometry, id=id)
parcels = geom.get_underlying_parcels()
geos_geom = geom.geom or MultiPolygon(srid=DEFAULT_SRID_RLP)
geometry_exists = not geos_geom.empty
parcels_are_currently_calculated = geometry_exists and geos_geom.area > 0 and len(parcels) == 0
parcels_available = len(parcels) > 0
if parcels_are_currently_calculated:
# Parcels are being calculated right now. Change the status code, so polling stays active for fetching
# resutls after the calculation
status_code = 200
if parcels_available or not geometry_exists:
municipals = geom.get_underlying_municipals(parcels)
rpp = 100
num_all_parcels = parcels.count()
parcels = parcels[:rpp]
next_page = 1
if len(parcels) < rpp:
next_page = None
context = {
"num_parcels": num_all_parcels,
"parcels": parcels,
"municipals": municipals,
"geom_id": str(id),
"next_page": next_page,
}
html = render_to_string(template, context, request)
return HttpResponse(html, status=status_code)
else:
return HttpResponse(None, status=404)
class GeomParcelsContentView(LoginRequiredMixin, View):
def get(self, request: HttpRequest, id: str, page: int):
""" Getter for infinite scroll of HTMX
Returns parcels of a specific page/slice of the found parcel set.
Implementation of infinite scroll htmx example: https://htmx.org/examples/infinite-scroll/
Args:
request (HttpRequest): The incoming request
id (str): The geometry's id
page (int): The requested page number
Returns:
A rendered piece of HTML
"""
if page < 0:
raise AssertionError("Parcel page can not be negative")
# HTTP code 286 states that the HTMX should stop polling for updates
# https://htmx.org/docs/#polling
status_code = 286
template = "konova/includes/parcels/parcel_table_content.html"
geom = get_object_or_404(Geometry, id=id)
parcels = geom.get_underlying_parcels()
parcels = parcels.order_by("-municipal", "flr", "flrstck_zhlr", "flrstck_nnr")
rpp = 100
num_all_parcels = parcels.count()
parcels = parcels[:rpp]
next_page = 1
from_p = rpp * (page-1)
to_p = rpp * (page)
next_page = page + 1
parcels = parcels[from_p:to_p]
if len(parcels) < rpp:
next_page = None
context = {
"num_parcels": num_all_parcels,
"parcels": parcels,
"municipals": municipals,
"geom_id": str(id),
"next_page": next_page,
}
html = render_to_string(template, context, request)
return HttpResponse(html, status=status_code)
else:
return HttpResponse(None, status=404)
def get_geom_parcels_content(request: HttpRequest, id: str, page: int):
""" Getter for infinite scroll of HTMX
Returns parcels of a specific page/slice of the found parcel set.
Implementation of infinite scroll htmx example: https://htmx.org/examples/infinite-scroll/
Args:
request (HttpRequest): The incoming request
id (str): The geometry's id
page (int): The requested page number
Returns:
A rendered piece of HTML
"""
if page < 0:
raise AssertionError("Parcel page can not be negative")
# HTTP code 286 states that the HTMX should stop polling for updates
# https://htmx.org/docs/#polling
status_code = 286
template = "konova/includes/parcels/parcel_table_content.html"
geom = get_object_or_404(Geometry, id=id)
parcels = geom.get_underlying_parcels()
parcels = parcels.order_by("-municipal", "flr", "flrstck_zhlr", "flrstck_nnr")
rpp = 100
from_p = rpp * (page-1)
to_p = rpp * (page)
next_page = page + 1
parcels = parcels[from_p:to_p]
if len(parcels) < rpp:
next_page = None
context = {
"parcels": parcels,
"geom_id": str(id),
"next_page": next_page,
}
html = render_to_string(template, context, request)
return HttpResponse(html, status=status_code)

@ -5,12 +5,13 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 19.08.22
"""
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q
from django.http import HttpRequest
from django.shortcuts import render
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.views import View
from compensation.models import EcoAccount, Compensation
from intervention.models import Intervention
@ -20,59 +21,59 @@ from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
from news.models import ServerMessage
@login_required
@any_group_check
def home_view(request: HttpRequest):
"""
Renders the landing page
class HomeView(LoginRequiredMixin, View):
Args:
request (HttpRequest): The used request object
@method_decorator(any_group_check)
def get(self, request: HttpRequest):
"""
Renders the landing page
Returns:
A redirect
"""
template = "konova/home.html"
now = timezone.now()
user = request.user
user_teams = user.shared_teams
Args:
request (HttpRequest): The used request object
# Fetch the four newest active and published ServerMessages
msgs = ServerMessage.get_current_news()[:3]
Returns:
A redirect
"""
template = "konova/home.html"
user = request.user
user_teams = user.shared_teams
# First fetch all valid objects (undeleted, only newest versions)
interventions = Intervention.objects.filter(
deleted=None,
)
# Then fetch only user related ones
user_interventions = interventions.filter(
Q(users__in=[user]) | Q(teams__in=user_teams)
).distinct()
# Fetch the four newest active and published ServerMessages
msgs = ServerMessage.get_current_news()[:3]
# Repeat for other objects
comps = Compensation.objects.filter(
deleted=None,
)
user_comps = comps.filter(
Q(intervention__users__in=[user]) | Q(intervention__teams__in=user_teams)
).distinct()
eco_accs = EcoAccount.objects.filter(
deleted=None,
)
user_ecco_accs = eco_accs.filter(
Q(users__in=[user]) | Q(teams__in=user_teams)
).distinct()
# First fetch all valid objects (undeleted, only newest versions)
interventions = Intervention.objects.filter(
deleted=None,
)
# Then fetch only user related ones
user_interventions = interventions.filter(
Q(users__in=[user]) | Q(teams__in=user_teams)
).distinct()
additional_context = {
"msgs": msgs,
"total_intervention_count": interventions.count(),
"user_intervention_count": user_interventions.count(),
"total_compensation_count": comps.count(),
"user_compensation_count": user_comps.count(),
"total_eco_count": eco_accs.count(),
"user_eco_count": user_ecco_accs.count(),
TAB_TITLE_IDENTIFIER: _("Home"),
}
context = BaseContext(request, additional_context).context
return render(request, template, context)
# Repeat for other objects
comps = Compensation.objects.filter(
deleted=None,
)
user_comps = comps.filter(
Q(intervention__users__in=[user]) | Q(intervention__teams__in=user_teams)
).distinct()
eco_accs = EcoAccount.objects.filter(
deleted=None,
)
user_ecco_accs = eco_accs.filter(
Q(users__in=[user]) | Q(teams__in=user_teams)
).distinct()
additional_context = {
"msgs": msgs,
"total_intervention_count": interventions.count(),
"user_intervention_count": user_interventions.count(),
"total_compensation_count": comps.count(),
"user_compensation_count": user_comps.count(),
"total_eco_count": eco_accs.count(),
"user_eco_count": user_ecco_accs.count(),
TAB_TITLE_IDENTIFIER: _("Home"),
}
context = BaseContext(request, additional_context).context
return render(request, template, context)

@ -8,19 +8,21 @@ Created on: 19.08.22
from django.contrib.auth import logout
from django.http import HttpRequest
from django.shortcuts import redirect
from django.views import View
from konova.sub_settings.sso_settings import SSO_SERVER_BASE
def logout_view(request: HttpRequest):
"""
Logout route for ending the session manually.
class LogoutView(View):
def get(self, request: HttpRequest):
"""
Logout route for ending the session manually.
Args:
request (HttpRequest): The used request object
Args:
request (HttpRequest): The used request object
Returns:
A redirect
"""
logout(request)
return redirect(SSO_SERVER_BASE)
Returns:
A redirect
"""
logout(request)
return redirect(SSO_SERVER_BASE)

Binary file not shown.

@ -29,21 +29,21 @@
#: konova/filters/mixins/office.py:25 konova/filters/mixins/office.py:56
#: konova/filters/mixins/office.py:57 konova/filters/mixins/record.py:23
#: konova/filters/mixins/self_created.py:24 konova/filters/mixins/share.py:23
#: konova/forms/geometry_form.py:33 konova/forms/modals/document_form.py:26
#: konova/forms/geometry_form.py:32 konova/forms/modals/document_form.py:26
#: konova/forms/modals/document_form.py:36
#: konova/forms/modals/document_form.py:50
#: konova/forms/modals/document_form.py:62
#: konova/forms/modals/document_form.py:80
#: konova/forms/modals/remove_form.py:23
#: konova/forms/modals/resubmission_form.py:22
#: konova/forms/modals/resubmission_form.py:38 konova/forms/remove_form.py:19
#: user/forms/user.py:39
#: konova/forms/modals/resubmission_form.py:38 konova/forms/remove_form.py:25
#: konova/tests/unit/test_forms.py:59 user/forms/user.py:39
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-08-29 10:29+0200\n"
"POT-Creation-Date: 2023-09-08 11:30+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -96,15 +96,16 @@ msgstr "Verantwortliche Stelle"
msgid "Click for selection"
msgstr "Auswählen..."
#: analysis/forms.py:70
#: analysis/forms.py:70 analysis/tests/unit/test_forms.py:25
msgid "Generate report"
msgstr "Bericht generieren"
#: analysis/forms.py:71
#: analysis/forms.py:71 analysis/tests/unit/test_forms.py:26
msgid "Select a timespan and the desired conservation office"
msgstr "Wählen Sie die Zeitspanne und die gewünschte Eintragungsstelle"
#: analysis/forms.py:74 konova/forms/modals/base_form.py:30
#: analysis/forms.py:74 analysis/tests/unit/test_forms.py:29
#: konova/forms/modals/base_form.py:30
msgid "Continue"
msgstr "Weiter"
@ -231,6 +232,12 @@ msgstr "Andere Zulassungsbehörden"
msgid "Compensations"
msgstr "Kompensationen"
#: analysis/templates/analysis/reports/includes/compensation/card_compensation.html:13
#: analysis/templates/analysis/reports/includes/eco_account/card_eco_account.html:13
#: analysis/templates/analysis/reports/includes/intervention/card_intervention.html:12
msgid "Binding date after"
msgstr "Bestandskraft- bzw. Rechtskraftdatum nach"
#: analysis/templates/analysis/reports/includes/eco_account/card_eco_account.html:11
msgid "Eco-Accounts"
msgstr "Ökokonten"
@ -345,11 +352,11 @@ msgstr "Eingriff"
msgid "Eco-account"
msgstr "Ökokonto"
#: analysis/templates/analysis/reports/includes/old_data/card_old_interventions.html:11
#: analysis/templates/analysis/reports/includes/old_data/card_old_data.html:11
msgid "Old interventions"
msgstr "Altfälle"
#: analysis/templates/analysis/reports/includes/old_data/card_old_interventions.html:13
#: analysis/templates/analysis/reports/includes/old_data/card_old_data.html:13
msgid "Binding date before"
msgstr "Bestandskraft- bzw. Rechtskraftdatum vor"
@ -394,6 +401,7 @@ msgid "An explanatory name"
msgstr "Aussagekräftiger Titel"
#: compensation/forms/compensation.py:49 ema/forms.py:51 ema/forms.py:114
#: ema/tests/unit/test_forms.py:31 ema/tests/unit/test_forms.py:85
msgid "Compensation XY; Location ABC"
msgstr "Kompensation XY; Flur ABC"
@ -478,7 +486,15 @@ msgstr "Ökokonto XY; Flur ABC"
msgid "Edit Eco-Account"
msgstr "Ökokonto bearbeiten"
#: compensation/forms/eco_account.py:232
#: compensation/forms/eco_account.py:183
msgid ""
"{}m² have been deducted from this eco account so far. The given value of {} "
"would be too low."
msgstr ""
"{}n² wurden bereits von diesem Ökokonto abgebucht. Der eingegebene Wert von "
"{} wäre daher zu klein."
#: compensation/forms/eco_account.py:249
msgid "The account can not be removed, since there are still deductions."
msgstr ""
"Das Ökokonto kann nicht entfernt werden, da hierzu noch Abbuchungen "
@ -591,16 +607,19 @@ msgid "Insert the amount"
msgstr "Menge eingeben"
#: compensation/forms/modals/compensation_action.py:94
#: compensation/tests/compensation/unit/test_forms.py:42
msgid "New action"
msgstr "Neue Maßnahme"
#: compensation/forms/modals/compensation_action.py:95
#: compensation/tests/compensation/unit/test_forms.py:43
msgid "Insert data for the new action"
msgstr "Geben Sie die Daten der neuen Maßnahme ein"
#: compensation/forms/modals/compensation_action.py:119
#: compensation/templates/compensation/detail/compensation/includes/actions.html:68
#: compensation/templates/compensation/detail/eco_account/includes/actions.html:67
#: compensation/tests/compensation/unit/test_forms.py:84
#: ema/templates/ema/detail/includes/actions.html:65
msgid "Edit action"
msgstr "Maßnahme bearbeiten"
@ -634,18 +653,21 @@ msgid "Additional comment, maximum {} letters"
msgstr "Zusätzlicher Kommentar, maximal {} Zeichen"
#: compensation/forms/modals/deadline.py:65
#: konova/tests/unit/test_deadline.py:29
msgid "New deadline"
msgstr "Neue Frist"
#: compensation/forms/modals/deadline.py:66
#: konova/tests/unit/test_deadline.py:30
msgid "Insert data for the new deadline"
msgstr "Geben Sie die Daten der neuen Frist ein"
#: compensation/forms/modals/deadline.py:75
#: compensation/forms/modals/deadline.py:78
#: konova/tests/unit/test_deadline.py:57
msgid "Please explain this 'other' type of deadline."
msgstr "Bitte erklären Sie um welchen 'sonstigen' Termin es sich handelt."
#: compensation/forms/modals/deadline.py:92
#: compensation/forms/modals/deadline.py:95
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:64
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:62
#: ema/templates/ema/detail/includes/deadlines.html:62
@ -700,10 +722,12 @@ msgid "in m²"
msgstr ""
#: compensation/forms/modals/state.py:72
#: compensation/tests/compensation/unit/test_forms.py:175
msgid "New state"
msgstr "Neuer Zustand"
#: compensation/forms/modals/state.py:73
#: compensation/tests/compensation/unit/test_forms.py:176
msgid "Insert data for the new state"
msgstr "Geben Sie die Daten des neuen Zustandes ein"
@ -716,6 +740,7 @@ msgstr "Objekt entfernt"
#: compensation/templates/compensation/detail/compensation/includes/states-before.html:62
#: compensation/templates/compensation/detail/eco_account/includes/states-after.html:62
#: compensation/templates/compensation/detail/eco_account/includes/states-before.html:62
#: compensation/tests/compensation/unit/test_forms.py:236
#: ema/templates/ema/detail/includes/states-after.html:60
#: ema/templates/ema/detail/includes/states-before.html:60
msgid "Edit state"
@ -749,21 +774,6 @@ msgstr ""
msgid "Pieces"
msgstr "Stück"
#: compensation/models/eco_account.py:62
msgid ""
"Deductable surface can not be larger than existing surfaces in after states"
msgstr ""
"Die abbuchbare Fläche darf die Gesamtfläche der Zielzustände nicht "
"überschreiten"
#: compensation/models/eco_account.py:69
msgid ""
"Deductable surface can not be smaller than the sum of already existing "
"deductions. Please contact the responsible users for the deductions!"
msgstr ""
"Es wurde bereits mehr Fläche abgebucht, als Sie nun als abbuchbar einstellen "
"wollen. Kontaktieren Sie die für die Abbuchungen verantwortlichen Nutzer!"
#: compensation/tables/compensation.py:33 compensation/tables/eco_account.py:34
#: ema/tables.py:36 intervention/tables.py:33
#: konova/filters/mixins/geo_reference.py:42
@ -926,6 +936,7 @@ msgstr "Öffentlicher Bericht"
#: ema/templates/ema/detail/includes/controls.html:15
#: intervention/templates/intervention/detail/includes/controls.html:15
#: konova/forms/modals/resubmission_form.py:51
#: konova/tests/unit/test_forms.py:302 konova/tests/unit/test_forms.py:316
#: templates/email/resubmission/resubmission.html:4
msgid "Resubmission"
msgstr "Wiedervorlage"
@ -988,7 +999,7 @@ msgstr "Dokumente"
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:14
#: ema/templates/ema/detail/includes/documents.html:14
#: intervention/templates/intervention/detail/includes/documents.html:14
#: konova/forms/modals/document_form.py:79
#: konova/forms/modals/document_form.py:79 konova/tests/unit/test_forms.py:58
msgid "Add new document"
msgstr "Neues Dokument hinzufügen"
@ -1004,7 +1015,7 @@ msgstr "Erstellt"
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:61
#: ema/templates/ema/detail/includes/documents.html:61
#: intervention/templates/intervention/detail/includes/documents.html:70
#: konova/forms/modals/document_form.py:141
#: konova/forms/modals/document_form.py:141 konova/tests/unit/test_forms.py:118
msgid "Edit document"
msgstr "Dokument bearbeiten"
@ -1178,6 +1189,7 @@ msgstr "weitere Nutzer"
#: ema/templates/ema/detail/includes/controls.html:18
#: intervention/forms/modals/share.py:63
#: intervention/templates/intervention/detail/includes/controls.html:18
#: intervention/tests/unit/test_forms.py:150
msgid "Share"
msgstr "Freigabe"
@ -1282,14 +1294,14 @@ msgstr "Daten zu den verantwortlichen Stellen"
msgid "Compensations - Overview"
msgstr "Kompensationen - Übersicht"
#: compensation/views/compensation/compensation.py:182
#: compensation/views/compensation/compensation.py:181
#: konova/utils/message_templates.py:40
msgid "Compensation {} edited"
msgstr "Kompensation {} bearbeitet"
#: compensation/views/compensation/compensation.py:197
#: compensation/views/eco_account/eco_account.py:171 ema/views/ema.py:231
#: intervention/views/intervention.py:253
#: compensation/views/compensation/compensation.py:196
#: compensation/views/eco_account/eco_account.py:173 ema/views/ema.py:231
#: intervention/views/intervention.py:252
msgid "Edit {}"
msgstr "Bearbeite {}"
@ -1307,19 +1319,19 @@ msgstr "Ökokonten - Übersicht"
msgid "Eco-Account {} added"
msgstr "Ökokonto {} hinzugefügt"
#: compensation/views/eco_account/eco_account.py:156
#: compensation/views/eco_account/eco_account.py:158
msgid "Eco-Account {} edited"
msgstr "Ökokonto {} bearbeitet"
#: compensation/views/eco_account/eco_account.py:285
#: compensation/views/eco_account/eco_account.py:287
msgid "Eco-account removed"
msgstr "Ökokonto entfernt"
#: ema/forms.py:42 ema/views/ema.py:102
#: ema/forms.py:42 ema/tests/unit/test_forms.py:27 ema/views/ema.py:102
msgid "New EMA"
msgstr "Neue EMA hinzufügen"
#: ema/forms.py:108
#: ema/forms.py:108 ema/tests/unit/test_forms.py:81
msgid "Edit EMA"
msgstr "Bearbeite EMA"
@ -1418,6 +1430,7 @@ msgid "Binding on"
msgstr "Datum Bestandskraft bzw. Rechtskraft"
#: intervention/forms/intervention.py:216
#: intervention/tests/unit/test_forms.py:36
#: intervention/views/intervention.py:105
msgid "New intervention"
msgstr "Neuer Eingriff"
@ -1440,6 +1453,7 @@ msgid "Run check"
msgstr "Prüfung vornehmen"
#: intervention/forms/modals/check.py:36 konova/forms/modals/record_form.py:30
#: konova/tests/unit/test_forms.py:155
msgid ""
"I, {} {}, confirm that all necessary control steps have been performed by "
"myself."
@ -1502,6 +1516,7 @@ msgstr "Muss kleiner als 15 Mb sein"
#: intervention/forms/modals/revocation.py:62
#: intervention/templates/intervention/detail/includes/revocation.html:18
#: intervention/tests/unit/test_forms.py:234
msgid "Add revocation"
msgstr "Widerspruch hinzufügen"
@ -1543,6 +1558,7 @@ msgstr ""
"noch nicht freigegeben wurde. Geben Sie den ganzen Nutzernamen an."
#: intervention/forms/modals/share.py:64
#: intervention/tests/unit/test_forms.py:151
msgid "Share settings for {}"
msgstr "Freigabe Einstellungen für {}"
@ -1658,11 +1674,11 @@ msgstr "Eingriffe - Übersicht"
msgid "Intervention {} added"
msgstr "Eingriff {} hinzugefügt"
#: intervention/views/intervention.py:236
#: intervention/views/intervention.py:235
msgid "Intervention {} edited"
msgstr "Eingriff {} bearbeitet"
#: intervention/views/intervention.py:278
#: intervention/views/intervention.py:277
msgid "{} removed"
msgstr "{} entfernt"
@ -1780,12 +1796,12 @@ msgstr "Speichern"
msgid "Not editable"
msgstr "Nicht editierbar"
#: konova/forms/geometry_form.py:32 konova/utils/quality.py:44
#: konova/forms/geometry_form.py:31 konova/utils/quality.py:44
#: konova/utils/quality.py:46 templates/form/collapsable/form.html:45
msgid "Geometry"
msgstr "Geometrie"
#: konova/forms/geometry_form.py:101
#: konova/forms/geometry_form.py:100
msgid "Only surfaces allowed. Points or lines must be buffered."
msgstr ""
"Nur Flächen erlaubt. Punkte oder Linien müssen zu Flächen gepuffert werden."
@ -1803,7 +1819,7 @@ msgstr "Datei"
msgid "Allowed formats: pdf, jpg, png. Max size 15 MB."
msgstr "Formate: pdf, jpg, png. Maximal 15 MB."
#: konova/forms/modals/document_form.py:116
#: konova/forms/modals/document_form.py:116 konova/tests/unit/test_forms.py:95
msgid "Added document"
msgstr "Dokument hinzugefügt"
@ -1811,32 +1827,34 @@ msgstr "Dokument hinzugefügt"
msgid "Confirm record"
msgstr "Verzeichnen bestätigen"
#: konova/forms/modals/record_form.py:29
#: konova/forms/modals/record_form.py:29 konova/tests/unit/test_forms.py:153
msgid "Record data"
msgstr "Daten verzeichnen"
#: konova/forms/modals/record_form.py:36
#: konova/forms/modals/record_form.py:36 konova/tests/unit/test_forms.py:168
msgid "Confirm unrecord"
msgstr "Entzeichnen bestätigen"
#: konova/forms/modals/record_form.py:37
#: konova/forms/modals/record_form.py:37 konova/tests/unit/test_forms.py:167
msgid "Unrecord data"
msgstr "Daten entzeichnen"
#: konova/forms/modals/record_form.py:38
#: konova/forms/modals/record_form.py:38 konova/tests/unit/test_forms.py:170
msgid "I, {} {}, confirm that this data must be unrecorded."
msgstr ""
"Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen."
#: konova/forms/modals/remove_form.py:22 konova/forms/remove_form.py:18
#: konova/forms/modals/remove_form.py:22 konova/forms/remove_form.py:24
msgid "Confirm"
msgstr "Bestätige"
#: konova/forms/modals/remove_form.py:32 konova/forms/remove_form.py:30
#: konova/forms/modals/remove_form.py:32 konova/forms/remove_form.py:36
#: konova/tests/unit/test_forms.py:209 konova/tests/unit/test_forms.py:261
msgid "Remove"
msgstr "Löschen"
#: konova/forms/modals/remove_form.py:33
#: konova/forms/modals/remove_form.py:33 konova/tests/unit/test_forms.py:210
#: konova/tests/unit/test_forms.py:262
msgid "Are you sure?"
msgstr "Sind Sie sicher?"
@ -1845,6 +1863,7 @@ msgid "When do you want to be reminded?"
msgstr "Wann wollen Sie erinnert werden?"
#: konova/forms/modals/resubmission_form.py:52
#: konova/tests/unit/test_forms.py:303 konova/tests/unit/test_forms.py:317
msgid "Set your resubmission for this entry."
msgstr "Setzen Sie eine Wiedervorlage für diesen Eintrag."
@ -1852,7 +1871,7 @@ msgstr "Setzen Sie eine Wiedervorlage für diesen Eintrag."
msgid "The date should be in the future"
msgstr "Das Datum sollte in der Zukunft liegen"
#: konova/forms/remove_form.py:32
#: konova/forms/remove_form.py:38
msgid "You are about to remove {} {}"
msgstr "Sie sind dabei {} {} zu löschen"
@ -2067,8 +2086,8 @@ msgid "You need to be part of another user group."
msgstr "Hierfür müssen Sie einer anderen Nutzergruppe angehören!"
#: konova/utils/message_templates.py:21
msgid "Status of Checked and Recorded reseted"
msgstr "'Geprüft'/'Verzeichnet' wurde zurückgesetzt"
msgid "Status of Checked reset"
msgstr "Status 'Geprüft' wurde zurückgesetzt"
#: konova/utils/message_templates.py:22
msgid ""
@ -2227,15 +2246,11 @@ msgstr "Dokument bearbeitet"
msgid "Edited general data"
msgstr "Allgemeine Daten bearbeitet"
#: konova/utils/message_templates.py:82
msgid "Added deadline"
msgstr "Frist/Termin hinzugefügt"
#: konova/utils/message_templates.py:85
#: konova/utils/message_templates.py:84
msgid "Geometry conflict detected with {}"
msgstr "Geometriekonflikt mit folgenden Einträgen erkannt: {}"
#: konova/utils/message_templates.py:86
#: konova/utils/message_templates.py:85
msgid ""
"The geometry contained more than {} vertices. It had to be simplified to "
"match the allowed limit of {} vertices."
@ -2243,20 +2258,20 @@ msgstr ""
"Die Geometrie enthielt mehr als {} Eckpunkte. Sie musste vereinfacht werden "
"um die Obergrenze von {} erlaubten Eckpunkten einzuhalten."
#: konova/utils/message_templates.py:89
#: konova/utils/message_templates.py:88
msgid "This intervention has {} revocations"
msgstr "Dem Eingriff liegen {} Widersprüche vor"
#: konova/utils/message_templates.py:92
#: konova/utils/message_templates.py:91
msgid "Checked on {} by {}"
msgstr "Am {} von {} geprüft worden"
#: konova/utils/message_templates.py:93
#: konova/utils/message_templates.py:92
msgid "Data has changed since last check on {} by {}"
msgstr ""
"Daten wurden nach der letzten Prüfung geändert. Letzte Prüfung am {} durch {}"
#: konova/utils/message_templates.py:94
#: konova/utils/message_templates.py:93
msgid "Current data not checked yet"
msgstr "Momentane Daten noch nicht geprüft"
@ -2290,7 +2305,7 @@ msgstr ""
"Dieses Datum ist unrealistisch. Geben Sie bitte das korrekte Datum ein "
"(>1950)."
#: konova/views/home.py:74 templates/navbars/navbar.html:16
#: konova/views/home.py:75 templates/navbars/navbar.html:16
msgid "Home"
msgstr "Home"
@ -4607,6 +4622,24 @@ msgstr ""
msgid "Unable to connect to qpid with SASL mechanism %s"
msgstr ""
#~ msgid ""
#~ "Deductable surface can not be larger than existing surfaces in after "
#~ "states"
#~ msgstr ""
#~ "Die abbuchbare Fläche darf die Gesamtfläche der Zielzustände nicht "
#~ "überschreiten"
#~ msgid ""
#~ "Deductable surface can not be smaller than the sum of already existing "
#~ "deductions. Please contact the responsible users for the deductions!"
#~ msgstr ""
#~ "Es wurde bereits mehr Fläche abgebucht, als Sie nun als abbuchbar "
#~ "einstellen wollen. Kontaktieren Sie die für die Abbuchungen "
#~ "verantwortlichen Nutzer!"
#~ msgid "Added deadline"
#~ msgstr "Frist/Termin hinzugefügt"
#~ msgid "Change default configuration for your KSP map"
#~ msgstr "Karteneinstellungen ändern"

@ -60,6 +60,29 @@ class User(AbstractUser):
name=ETS_GROUP
).exists()
def is_default_group_only(self) -> bool:
""" Checks if the user is only part of the default group
Args:
Returns:
bool
"""
return not self.in_group(ZB_GROUP) and not self.in_group(ETS_GROUP)
def in_group(self, group: str) -> bool:
""" Checks if the user is part of a group
Args:
group (str): The group's name
Returns:
bool
"""
return self.groups.filter(
name=group
)
def send_mail_shared_access_removed(self, obj_identifier, obj_title, municipals_names):
""" Sends a mail to the user in case of removed shared access

@ -0,0 +1,7 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 12.09.23
"""

@ -0,0 +1,286 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 12.09.23
"""
from django.core.exceptions import ObjectDoesNotExist
from django.test import RequestFactory
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from api.models import APIUserToken
from konova.tests.test_views import BaseTestCase
from user.forms.modals.team import NewTeamModalForm, EditTeamModalForm, RemoveTeamModalForm, LeaveTeamModalForm
from user.forms.user import UserNotificationForm, UserAPITokenForm
from user.models import Team, UserAction, UserNotification
class NewTeamModalFormTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
self.request = RequestFactory().request()
self.request.user = self.user
def test_init(self):
form = NewTeamModalForm(
request=self.request
)
self.assertEqual(form.form_title, str(_("Create new team")))
self.assertEqual(form.form_caption, str(_("You will become the administrator for this group by default. You do not need to add yourself to the list of members.")))
self.assertEqual(form.action_url, reverse("user:team-new"))
self.assertEqual(form.cancel_redirect, reverse("user:team-index"))
self.assertEqual(form.request, self.request)
self.assertEqual(form.user, self.request.user)
def test_is_valid(self):
invalid_data = {
"name": self.team.name,
"description": "Test description",
"members": [self.superuser.id,],
}
form = NewTeamModalForm(
invalid_data,
request=self.request
)
self.assertFalse(form.is_valid())
self.assertTrue(form.has_error("name"))
valid_data = invalid_data
valid_data["name"] = self.team.name + "_OTHER"
form = NewTeamModalForm(
invalid_data,
request=self.request
)
self.assertTrue(form.is_valid())
def test_save(self):
valid_data = {
"name": self.team.name + "_OTHER",
"description": "Test description",
"members": [self.superuser.id,],
}
form = NewTeamModalForm(
valid_data,
request=self.request
)
self.assertTrue(form.is_valid())
obj = form.save()
self.assertEqual(obj.name, valid_data["name"])
self.assertEqual(obj.description, valid_data["description"])
users = obj.users.all()
admins = obj.admins.all()
self.assertIn(self.request.user, users)
self.assertIn(self.request.user, admins)
self.assertIn(self.superuser, users)
self.assertNotIn(self.superuser, admins)
class EditTeamModalFormTestCase(NewTeamModalFormTestCase):
def test_init(self):
self.team.admins.add(self.superuser)
form = EditTeamModalForm(request=self.request, instance=self.team)
self.assertEqual(form.form_title, str(_("Edit team")))
self.assertEqual(form.action_url, reverse("user:team-edit", args=(self.team.id,)))
self.assertEqual(form.cancel_redirect, reverse("user:team-index"))
self.assertEqual(form.fields["name"].initial, self.team.name)
self.assertEqual(form.fields["description"].initial, self.team.description)
self.assertEqual(form.fields["members"].initial.count(), 1)
self.assertIn(self.superuser, form.fields["members"].initial)
self.assertEqual(form.fields["admins"].initial.count(), 1)
self.assertIn(self.superuser, form.fields["admins"].initial)
def test_is_valid(self):
data = {
"name": self.team.name,
"description": self.team.description,
"members": self.team.users.values_list("id", flat=True),
"admins": self.team.admins.values_list("id", flat=True),
}
form = EditTeamModalForm(
data,
request=self.request,
instance=self.team
)
# Error 1: Admin not in user list
self.team.users.set([self.superuser])
self.team.admins.set([self.user])
self.assertFalse(form.is_valid())
self.assertTrue(form.has_error("admins"))
# Error 2: Admin list empty
self.team.admins.set([])
self.assertFalse(form.is_valid())
self.assertTrue(form.has_error("admins"))
# Error 3: Name taken
other_team = Team.objects.create(
name=self.team.name
)
self.team.admins.set([self.superuser])
self.assertFalse(form.is_valid())
self.assertTrue(form.has_error("name"))
def test_save(self):
data = {
"name": self.team.name + "_EDITED",
"description": self.team.description + "_EDITED",
"members": [self.user.id, self.superuser.id,],
"admins": [self.user.id,],
}
form = EditTeamModalForm(
data,
request=self.request,
instance=self.team
)
self.assertTrue(form.is_valid(), msg=form.errors)
obj = form.save()
self.assertEqual(obj.name, data["name"])
self.assertEqual(obj.description, data["description"])
self.assertIn(self.user, obj.users.all())
self.assertIn(self.superuser, obj.users.all())
self.assertIn(self.user, obj.admins.all())
self.assertEqual(obj.admins.count(), 1)
self.assertEqual(obj.users.count(), 2)
class RemoveTeamModalFormTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
self.request = RequestFactory().request()
self.request.user = self.user
def test_init(self):
form = RemoveTeamModalForm(
request=self.request,
instance=self.team
)
self.assertEqual(form.form_caption, str(_("ATTENTION!\n\nRemoving the team means all members will lose their access to data, based on this team! \n\nAre you sure to remove this team?")))
self.assertEqual(form.user, self.request.user)
self.assertEqual(form.request, self.request)
def test_save(self):
data = {
"confirm": True
}
form = RemoveTeamModalForm(
data,
request=self.request,
instance=self.team
)
self.assertTrue(form.is_valid(), msg=form.errors)
form.save()
self.team.refresh_from_db()
self.assertIsNotNone(self.team.deleted)
self.assertEqual(self.team.deleted.user, self.request.user)
self.assertEqual(self.team.deleted.action, UserAction.DELETED)
class LeaveTeamModalFormTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
self.request = RequestFactory().request()
self.request.user = self.user
def test_init(self):
form = LeaveTeamModalForm(
request=self.request,
instance=self.team
)
self.assertEqual(form.form_title, str(_("Leave team")))
def test_save(self):
self.team.users.add(self.user)
data = {
"confirm": True,
}
form = LeaveTeamModalForm(
data,
request=self.request,
instance=self.team
)
self.assertTrue(form.is_valid(), msg=form.errors)
self.assertIn(self.request.user, self.team.users.all())
form.save()
self.assertNotIn(self.request.user, self.team.users.all())
class UserNotificationFormTestCase(BaseTestCase):
def setUp(self) -> None:
super().setUp()
if not UserNotification.objects.all().exists():
self.notifications = UserNotification.objects.bulk_create(
[
UserNotification(id="notification_1", name="notification_1", is_active=True),
UserNotification(id="notification_2", name="notification_2", is_active=True),
UserNotification(id="notification_3", name="notification_3", is_active=True),
UserNotification(id="notification_4", name="notification_4", is_active=True),
]
)
def test_init(self):
form = UserNotificationForm(
user=self.user
)
self.assertEqual(form.form_title, str(_("Edit notifications")))
self.assertEqual(form.form_caption, "")
self.assertEqual(form.action_url, reverse("user:notifications"))
self.assertEqual(form.cancel_redirect, reverse("user:index"))
def test_save(self):
selected_notification = UserNotification.objects.first()
data = {
"notifications": [selected_notification.id,]
}
form = UserNotificationForm(
data=data,
user=self.user
)
self.assertTrue(form.is_valid(), msg=form.errors)
self.assertEqual(self.user.notifications.count(), 0)
form.save()
self.assertEqual(self.user.notifications.count(), 1)
self.assertIn(selected_notification, self.user.notifications.all())
class UserAPITokenFormTestCase(BaseTestCase):
def test_init(self):
form = UserAPITokenForm(
instance=self.user
)
self.assertEqual(form.form_title, str(_("Create new token")))
self.assertEqual(form.form_caption, str(_("A new token needs to be validated by an administrator!")))
self.assertEqual(form.action_url, reverse("user:api-token"))
self.assertEqual(form.cancel_redirect, reverse("user:index"))
self.assertIsNone(form.fields["token"].initial)
self.assertTrue(form.fields["token"].widget.attrs["readonly"])
def test_save(self):
data = {
"token": APIUserToken().token
}
form = UserAPITokenForm(
data,
instance=self.user
)
self.assertTrue(form.is_valid(), msg=form.errors)
self.assertIsNone(self.user.api_token)
token = form.save()
self.assertEqual(self.user.api_token, token)
new_token = form.save()
self.assertEqual(self.user.api_token, new_token)
try:
token.refresh_from_db()
self.fail("Token should be deleted and not be fetchable anymore")
except ObjectDoesNotExist:
pass

@ -0,0 +1,61 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 13.09.23
"""
from konova.settings import ZB_GROUP, DEFAULT_GROUP, ETS_GROUP
from konova.tests.test_views import BaseTestCase
from user.enums import UserNotificationEnum
from user.models import UserNotification
class UserTestCase(BaseTestCase):
def test_is_notification_setting_set(self):
notification = UserNotification.objects.create(
id=UserNotificationEnum.NOTIFY_ON_DEDUCTION_CHANGES.name,
name=UserNotificationEnum.NOTIFY_ON_DEDUCTION_CHANGES.value,
)
self.assertFalse(self.user.is_notification_setting_set(UserNotificationEnum.NOTIFY_ON_DEDUCTION_CHANGES))
self.user.notifications.add(notification)
self.assertTrue(self.user.is_notification_setting_set(UserNotificationEnum.NOTIFY_ON_DEDUCTION_CHANGES))
def test_is_group_member(self):
zb_group = self.groups.get(name=ZB_GROUP)
ets_group = self.groups.get(name=ETS_GROUP)
default_group = self.groups.get(name=DEFAULT_GROUP)
self.user.groups.set([])
self.assertFalse(self.user.is_zb_user())
self.assertFalse(self.user.is_ets_user())
self.assertFalse(self.user.is_default_user())
self.user.groups.add(zb_group)
self.assertTrue(self.user.is_zb_user())
self.user.groups.add(ets_group)
self.assertTrue(self.user.is_ets_user())
self.user.groups.add(default_group)
self.assertTrue(self.user.is_default_user())
def test_get_API_token(self):
self.assertIsNone(self.user.api_token)
token = self.user.get_API_token()
self.assertIsNotNone(self.user.api_token)
self.assertEqual(self.user.api_token, token)
# Make sure the same token is returned if command is called twice
token = self.user.get_API_token()
self.assertEqual(self.user.api_token, token)
def test_shared_teams_property(self):
shared_teams = self.user.shared_teams
self.assertEqual(shared_teams.count(), 0)
self.team.users.add(self.user)
shared_teams = self.user.shared_teams
self.assertEqual(shared_teams.count(), 1)
self.assertIn(self.team, shared_teams)
Loading…
Cancel
Save