Merge pull request 'test' (#347) from test into master
Reviewed-on: SGD-Nord/konova#347
This commit is contained in:
commit
ffae18ebc4
4
.gitignore
vendored
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.
|
7
analysis/tests/__init__.py
Normal file
7
analysis/tests/__init__.py
Normal file
@ -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
|
||||
|
||||
"""
|
7
analysis/tests/unit/__init__.py
Normal file
7
analysis/tests/unit/__init__.py
Normal file
@ -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
|
||||
|
||||
"""
|
47
analysis/tests/unit/test_forms.py
Normal file
47
analysis/tests/unit/test_forms.py
Normal file
@ -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}"
|
||||
)
|
98
analysis/tests/unit/test_report.py
Normal file
98
analysis/tests/unit/test_report.py
Normal file
@ -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")
|
||||
|
7
api/tests/unit/__init__.py
Normal file
7
api/tests/unit/__init__.py
Normal file
@ -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
|
||||
|
||||
"""
|
71
api/tests/unit/test_token.py
Normal file
71
api/tests/unit/test_token.py
Normal file
@ -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
|
||||
|
7
compensation/tests/compensation/unit/__init__.py
Normal file
7
compensation/tests/compensation/unit/__init__.py
Normal file
@ -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
|
||||
|
||||
"""
|
318
compensation/tests/compensation/unit/test_forms.py
Normal file
318
compensation/tests/compensation/unit/test_forms.py
Normal file
@ -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)
|
||||
|
201
compensation/tests/compensation/unit/test_models.py
Normal file
201
compensation/tests/compensation/unit/test_models.py
Normal file
@ -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)
|
||||
|
7
compensation/tests/ecoaccount/unit/__init__.py
Normal file
7
compensation/tests/ecoaccount/unit/__init__.py
Normal file
@ -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
|
||||
|
||||
"""
|
128
compensation/tests/ecoaccount/unit/test_models.py
Normal file
128
compensation/tests/ecoaccount/unit/test_models.py
Normal file
@ -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)
|
||||
|
||||
|
12
ema/forms.py
12
ema/forms.py
@ -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-{}"
|
7
ema/tests/unit/__init__.py
Normal file
7
ema/tests/unit/__init__.py
Normal file
@ -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
|
||||
|
||||
"""
|
141
ema/tests/unit/test_forms.py
Normal file
141
ema/tests/unit/test_forms.py
Normal file
@ -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))
|
||||
|
90
ema/tests/unit/test_models.py
Normal file
90
ema/tests/unit/test_models.py
Normal file
@ -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
|
||||
|
7
intervention/tests/unit/__init__.py
Normal file
7
intervention/tests/unit/__init__.py
Normal file
@ -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
|
||||
|
||||
"""
|
333
intervention/tests/unit/test_forms.py
Normal file
333
intervention/tests/unit/test_forms.py
Normal file
@ -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)
|
50
intervention/tests/unit/test_models.py
Normal file
50
intervention/tests/unit/test_models.py
Normal file
@ -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
|
||||
|
||||
|
365
konova/tests/unit/test_forms.py
Normal file
365
konova/tests/unit/test_forms.py
Normal file
@ -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)
|
201
konova/tests/unit/test_models.py
Normal file
201
konova/tests/unit/test_models.py
Normal file
@ -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
|
||||
class GeomParcelsView(LoginRequiredMixin, View):
|
||||
|
||||
Returns all parcels of the requested geometry rendered into a simple HTML table
|
||||
def get(self, request: HttpRequest, id: str):
|
||||
""" Getter for HTMX
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
id (str): The geometry's id
|
||||
Returns all parcels of the requested geometry rendered into a simple HTML table
|
||||
|
||||
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)
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
id (str): The geometry's id
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
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
|
||||
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_available or not geometry_exists:
|
||||
municipals = geom.get_underlying_municipals(parcels)
|
||||
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
|
||||
|
||||
|
7
user/tests/unit/__init__.py
Normal file
7
user/tests/unit/__init__.py
Normal file
@ -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
|
||||
|
||||
"""
|
286
user/tests/unit/test_forms.py
Normal file
286
user/tests/unit/test_forms.py
Normal file
@ -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
|
61
user/tests/unit/test_models.py
Normal file
61
user/tests/unit/test_models.py
Normal file
@ -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…
Reference in New Issue
Block a user