Compare commits

..

5 Commits

Author SHA1 Message Date
1b5cda648e #86 Revocation edit
* adds support for revocation edit
    * revocation document files will be replaced on an edit
2022-02-09 16:02:28 +01:00
d106977c34 #86 Edit deductions
* adds support for editing deductions
* adds tests
* improves major base test logic
2022-02-09 14:49:56 +01:00
ce9143e4b2 #86 Edit payment
* adds button for payment editing
* adds new edit form payment editing
* adds tests for views and workflow
2022-02-09 10:29:34 +01:00
c5e3800c34 #86 District column simplification
* simplifies the fetching of districts for district column
2022-02-09 09:30:37 +01:00
aa338e5519 #86 Parcel-Geometry improvement
* improves the way parcel-geometry relations are stored on the DB
    * instead of a numerical sequence we switched to UUID, so no sequence will run out at anytime (new model: ParcelIntersection)
    * instead of dropping all M2M relations between parcel and geometry on each calculation, we keep the ones that still exist, drop the ones that do not exist and add new ones (if new ones exist)
2022-02-09 09:18:35 +01:00
37 changed files with 1205 additions and 370 deletions

View File

@ -12,15 +12,17 @@ class BaseAPIV1TestCase(BaseTestCase):
def setUpTestData(cls): def setUpTestData(cls):
super().setUpTestData() super().setUpTestData()
cls.superuser.get_API_token() def setUp(self) -> None:
cls.superuser.api_token.is_active = True super().setUp()
cls.superuser.api_token.save() self.superuser.get_API_token()
default_group = cls.groups.get(name=DEFAULT_GROUP) self.superuser.api_token.is_active = True
cls.superuser.groups.add(default_group) self.superuser.api_token.save()
default_group = self.groups.get(name=DEFAULT_GROUP)
self.superuser.groups.add(default_group)
cls.header_data = { self.header_data = {
"HTTP_ksptoken": cls.superuser.api_token.token, "HTTP_ksptoken": self.superuser.api_token.token,
"HTTP_kspuser": cls.superuser.username, "HTTP_kspuser": self.superuser.username,
} }

View File

@ -21,7 +21,7 @@ from konova.contexts import BaseContext
from konova.forms import BaseModalForm, NewDocumentForm, RemoveModalForm from konova.forms import BaseModalForm, NewDocumentForm, RemoveModalForm
from konova.models import DeadlineType from konova.models import DeadlineType
from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, ADDED_DEADLINE, \ from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, ADDED_DEADLINE, \
ADDED_COMPENSATION_ACTION ADDED_COMPENSATION_ACTION, PAYMENT_EDITED
class NewPaymentForm(BaseModalForm): class NewPaymentForm(BaseModalForm):
@ -103,6 +103,32 @@ class NewPaymentForm(BaseModalForm):
return pay return pay
class EditPaymentModalForm(NewPaymentForm):
""" Form handling edit for Payment
"""
payment = None
def __init__(self, *args, **kwargs):
self.payment = kwargs.pop("payment", None)
super().__init__(*args, **kwargs)
form_date = {
"amount": self.payment.amount,
"due": str(self.payment.due_on),
"comment": self.payment.comment,
}
self.load_initial_data(form_date, disabled_fields=[])
def save(self):
payment = self.payment
payment.amount = self.cleaned_data.get("amount", None)
payment.due_on = self.cleaned_data.get("due", None)
payment.comment = self.cleaned_data.get("comment", None)
payment.save()
self.instance.mark_as_edited(self.user, self.request, edit_comment=PAYMENT_EDITED)
return payment
class RemovePaymentModalForm(RemoveModalForm): class RemovePaymentModalForm(RemoveModalForm):
""" Removing modal form for Payment """ Removing modal form for Payment

View File

@ -133,7 +133,7 @@ class CompensationTable(BaseTable, TableRenderMixin):
Returns: Returns:
""" """
parcels = value.parcels.values_list( parcels = value.get_underlying_parcels().values_list(
"gmrkng", "gmrkng",
flat=True flat=True
).distinct() ).distinct()
@ -294,7 +294,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
Returns: Returns:
""" """
parcels = value.parcels.values_list( parcels = value.get_underlying_parcels().values_list(
"gmrkng", "gmrkng",
flat=True flat=True
).distinct() ).distinct()

View File

@ -60,9 +60,12 @@
</td> </td>
<td class="align-middle">{{ deduction.surface|floatformat:2|intcomma }} m²</td> <td class="align-middle">{{ deduction.surface|floatformat:2|intcomma }} m²</td>
<td class="align-middle">{{ deduction.created.timestamp|default_if_none:""|naturalday}}</td> <td class="align-middle">{{ deduction.created.timestamp|default_if_none:""|naturalday}}</td>
<td> <td class="align-middle float-right">
{% if is_default_member and has_access %} {% if is_default_member and has_access %}
<button data-form-url="{% url 'compensation:acc:remove-deduction' deduction.account.id deduction.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove Deduction' %}"> <button data-form-url="{% url 'compensation:acc:edit-deduction' deduction.account.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit Deduction' %}">
{% fa5_icon 'edit' %}
</button>
<button data-form-url="{% url 'compensation:acc:remove-deduction' deduction.account.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove Deduction' %}">
{% fa5_icon 'trash' %} {% fa5_icon 'trash' %}
</button> </button>
{% endif %} {% endif %}

View File

@ -21,29 +21,32 @@ class CompensationViewTestCase(BaseViewTestCase):
@classmethod @classmethod
def setUpTestData(cls) -> None: def setUpTestData(cls) -> None:
super().setUpTestData() super().setUpTestData()
state = cls.create_dummy_states()
cls.compensation.before_states.set([state])
cls.compensation.after_states.set([state])
action = cls.create_dummy_action() def setUp(self) -> None:
cls.compensation.actions.set([action]) super().setUp()
state = self.create_dummy_states()
self.compensation.before_states.set([state])
self.compensation.after_states.set([state])
action = self.create_dummy_action()
self.compensation.actions.set([action])
# Prepare urls # Prepare urls
cls.index_url = reverse("compensation:index", args=()) self.index_url = reverse("compensation:index", args=())
cls.new_url = reverse("compensation:new", args=(cls.intervention.id,)) self.new_url = reverse("compensation:new", args=(self.intervention.id,))
cls.new_id_url = reverse("compensation:new-id", args=()) self.new_id_url = reverse("compensation:new-id", args=())
cls.detail_url = reverse("compensation:detail", args=(cls.compensation.id,)) self.detail_url = reverse("compensation:detail", args=(self.compensation.id,))
cls.log_url = reverse("compensation:log", args=(cls.compensation.id,)) self.log_url = reverse("compensation:log", args=(self.compensation.id,))
cls.edit_url = reverse("compensation:edit", args=(cls.compensation.id,)) self.edit_url = reverse("compensation:edit", args=(self.compensation.id,))
cls.remove_url = reverse("compensation:remove", args=(cls.compensation.id,)) self.remove_url = reverse("compensation:remove", args=(self.compensation.id,))
cls.report_url = reverse("compensation:report", args=(cls.compensation.id,)) self.report_url = reverse("compensation:report", args=(self.compensation.id,))
cls.state_new_url = reverse("compensation:new-state", args=(cls.compensation.id,)) self.state_new_url = reverse("compensation:new-state", args=(self.compensation.id,))
cls.action_new_url = reverse("compensation:new-action", args=(cls.compensation.id,)) self.action_new_url = reverse("compensation:new-action", args=(self.compensation.id,))
cls.deadline_new_url = reverse("compensation:new-deadline", args=(cls.compensation.id,)) self.deadline_new_url = reverse("compensation:new-deadline", args=(self.compensation.id,))
cls.new_doc_url = reverse("compensation:new-doc", args=(cls.compensation.id,)) self.new_doc_url = reverse("compensation:new-doc", args=(self.compensation.id,))
cls.state_remove_url = reverse("compensation:state-remove", args=(cls.compensation.id, cls.comp_state.id,)) self.state_remove_url = reverse("compensation:state-remove", args=(self.compensation.id, self.comp_state.id,))
cls.action_remove_url = reverse("compensation:action-remove", args=(cls.compensation.id, cls.comp_action.id,)) self.action_remove_url = reverse("compensation:action-remove", args=(self.compensation.id, self.comp_action.id,))
def test_anonymous_user(self): def test_anonymous_user(self):
""" Check correct status code for all requests """ Check correct status code for all requests
@ -117,7 +120,7 @@ class CompensationViewTestCase(BaseViewTestCase):
def test_logged_in_no_groups_unshared(self): def test_logged_in_no_groups_unshared(self):
""" Check correct status code for all requests """ Check correct status code for all requests
Assumption: User logged in and has no groups and data is shared Assumption: User logged in and has no groups and data is not shared
Returns: Returns:

View File

@ -21,17 +21,18 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
def setUpTestData(cls): def setUpTestData(cls):
super().setUpTestData() super().setUpTestData()
# Give the user shared access to the dummy intervention -> inherits the access to the compensation
cls.intervention.share_with(cls.superuser)
# Make sure the intervention itself would be fine with valid data
cls.intervention = cls.fill_out_intervention(cls.intervention)
# Make sure the compensation is linked to the intervention
cls.intervention.compensations.set([cls.compensation])
def setUp(self) -> None: def setUp(self) -> None:
super().setUp() super().setUp()
# Give the user shared access to the dummy intervention -> inherits the access to the compensation
self.intervention.share_with(self.superuser)
# Make sure the intervention itself would be fine with valid data
self.intervention = self.fill_out_intervention(self.intervention)
# Make sure the compensation is linked to the intervention
self.intervention.compensations.set([self.compensation])
# Delete all existing compensations, which might be created by tests # Delete all existing compensations, which might be created by tests
Compensation.objects.all().delete() Compensation.objects.all().delete()

View File

@ -25,28 +25,31 @@ class EcoAccountViewTestCase(CompensationViewTestCase):
@classmethod @classmethod
def setUpTestData(cls) -> None: def setUpTestData(cls) -> None:
super().setUpTestData() super().setUpTestData()
state = cls.create_dummy_states()
cls.eco_account.before_states.set([state])
cls.eco_account.after_states.set([state])
action = cls.create_dummy_action() def setUp(self) -> None:
cls.eco_account.actions.set([action]) super().setUp()
state = self.create_dummy_states()
self.eco_account.before_states.set([state])
self.eco_account.after_states.set([state])
action = self.create_dummy_action()
self.eco_account.actions.set([action])
# Prepare urls # Prepare urls
cls.index_url = reverse("compensation:acc:index", args=()) self.index_url = reverse("compensation:acc:index", args=())
cls.new_url = reverse("compensation:acc:new", args=()) self.new_url = reverse("compensation:acc:new", args=())
cls.new_id_url = reverse("compensation:acc:new-id", args=()) self.new_id_url = reverse("compensation:acc:new-id", args=())
cls.detail_url = reverse("compensation:acc:detail", args=(cls.eco_account.id,)) self.detail_url = reverse("compensation:acc:detail", args=(self.eco_account.id,))
cls.log_url = reverse("compensation:acc:log", args=(cls.eco_account.id,)) self.log_url = reverse("compensation:acc:log", args=(self.eco_account.id,))
cls.edit_url = reverse("compensation:acc:edit", args=(cls.eco_account.id,)) self.edit_url = reverse("compensation:acc:edit", args=(self.eco_account.id,))
cls.remove_url = reverse("compensation:acc:remove", args=(cls.eco_account.id,)) self.remove_url = reverse("compensation:acc:remove", args=(self.eco_account.id,))
cls.report_url = reverse("compensation:acc:report", args=(cls.eco_account.id,)) self.report_url = reverse("compensation:acc:report", args=(self.eco_account.id,))
cls.state_new_url = reverse("compensation:acc:new-state", args=(cls.eco_account.id,)) self.state_new_url = reverse("compensation:acc:new-state", args=(self.eco_account.id,))
cls.action_new_url = reverse("compensation:acc:new-action", args=(cls.eco_account.id,)) self.action_new_url = reverse("compensation:acc:new-action", args=(self.eco_account.id,))
cls.deadline_new_url = reverse("compensation:acc:new-deadline", args=(cls.eco_account.id,)) self.deadline_new_url = reverse("compensation:acc:new-deadline", args=(self.eco_account.id,))
cls.new_doc_url = reverse("compensation:acc:new-doc", args=(cls.eco_account.id,)) self.new_doc_url = reverse("compensation:acc:new-doc", args=(self.eco_account.id,))
cls.state_remove_url = reverse("compensation:acc:state-remove", args=(cls.eco_account.id, cls.comp_state.id,)) self.state_remove_url = reverse("compensation:acc:state-remove", args=(self.eco_account.id, self.comp_state.id,))
cls.action_remove_url = reverse("compensation:acc:action-remove", args=(cls.eco_account.id, cls.comp_action.id,)) self.action_remove_url = reverse("compensation:acc:action-remove", args=(self.eco_account.id, self.comp_action.id,))
def test_logged_in_no_groups_shared(self): def test_logged_in_no_groups_shared(self):
""" Check correct status code for all requests """ Check correct status code for all requests

View File

@ -11,7 +11,7 @@ from django.contrib.gis.geos import MultiPolygon
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.urls import reverse from django.urls import reverse
from compensation.models import EcoAccount from compensation.models import EcoAccount, EcoAccountDeduction
from konova.settings import ETS_GROUP, DEFAULT_GROUP from konova.settings import ETS_GROUP, DEFAULT_GROUP
from konova.tests.test_views import BaseWorkflowTestCase from konova.tests.test_views import BaseWorkflowTestCase
from user.models import UserAction from user.models import UserAction
@ -168,7 +168,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
self.assertIn(recorded, self.eco_account.log.all()) self.assertIn(recorded, self.eco_account.log.all())
self.assertEqual(pre_record_log_count + 1, self.eco_account.log.count()) self.assertEqual(pre_record_log_count + 1, self.eco_account.log.count())
def test_deductability(self): def test_new_deduction(self):
""" """
This tests the deductability of an eco account. This tests the deductability of an eco account.
@ -187,7 +187,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
test_surface = 10.00 test_surface = 10.00
post_data = { post_data = {
"surface": test_surface, "surface": test_surface,
"account": self.id, "account": self.eco_account.id,
"intervention": self.intervention.id, "intervention": self.intervention.id,
} }
# Perform request --> expect to fail # Perform request --> expect to fail
@ -228,4 +228,75 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
self.assertEqual(pre_deduction_int_log_count + 1, self.intervention.log.count()) self.assertEqual(pre_deduction_int_log_count + 1, self.intervention.log.count())
self.assertTrue(self.intervention.log.first().action == UserAction.EDITED) self.assertTrue(self.intervention.log.first().action == UserAction.EDITED)
def test_edit_deduction(self):
test_surface = self.eco_account.get_available_rest()[0]
self.eco_account.set_recorded(self.superuser)
self.eco_account.refresh_from_db()
deduction = EcoAccountDeduction.objects.create(
intervention=self.intervention,
account=self.eco_account,
surface=0
)
self.assertEqual(1, self.intervention.deductions.count())
self.assertEqual(1, self.eco_account.deductions.count())
# Prepare url and form data to be posted
new_url = reverse("compensation:acc:edit-deduction", args=(self.eco_account.id, deduction.id))
post_data = {
"intervention": deduction.intervention.id,
"account": deduction.account.id,
"surface": test_surface,
}
pre_edit_intervention_log_count = self.intervention.log.count()
pre_edit_account_log_count = self.eco_account.log.count()
num_deductions_intervention = self.intervention.deductions.count()
num_deductions_account = self.eco_account.deductions.count()
self.client_user.post(new_url, post_data)
self.intervention.refresh_from_db()
self.eco_account.refresh_from_db()
deduction.refresh_from_db()
self.assertEqual(num_deductions_intervention, self.intervention.deductions.count())
self.assertEqual(num_deductions_account, self.eco_account.deductions.count())
self.assertEqual(deduction.surface, test_surface)
# Expect logs to be set
self.assertEqual(pre_edit_intervention_log_count + 1, self.intervention.log.count())
self.assertEqual(pre_edit_account_log_count + 1, self.eco_account.log.count())
self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
self.assertEqual(self.eco_account.log.first().action, UserAction.EDITED)
def test_remove_deduction(self):
intervention = self.deduction.intervention
account = self.deduction.account
# Prepare url and form data to be posted
new_url = reverse("compensation:acc:remove-deduction", args=(account.id, self.deduction.id))
post_data = {
"confirm": True,
}
intervention.share_with(self.superuser)
account.share_with(self.superuser)
pre_edit_intervention_log_count = intervention.log.count()
pre_edit_account_log_count = account.log.count()
num_deductions_intervention = intervention.deductions.count()
num_deductions_account = account.deductions.count()
self.client_user.post(new_url, post_data)
intervention.refresh_from_db()
account.refresh_from_db()
self.assertEqual(num_deductions_intervention - 1, intervention.deductions.count())
self.assertEqual(num_deductions_account - 1, account.deductions.count())
# Expect logs to be set
self.assertEqual(pre_edit_intervention_log_count + 1, intervention.log.count())
self.assertEqual(pre_edit_account_log_count + 1, account.log.count())
self.assertEqual(intervention.log.first().action, UserAction.EDITED)
self.assertEqual(account.log.first().action, UserAction.EDITED)

View File

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

View File

@ -0,0 +1,156 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 09.02.22
"""
from django.urls import reverse
from django.test.client import Client
from compensation.models import Payment
from konova.settings import DEFAULT_GROUP
from konova.tests.test_views import BaseViewTestCase
class PaymentViewTestCase(BaseViewTestCase):
@classmethod
def setUpTestData(cls) -> None:
super().setUpTestData()
def setUp(self) -> None:
super().setUp()
self.payment = Payment.objects.get_or_create(
intervention=self.intervention,
amount=1,
due_on="2020-01-01",
comment="Testcomment"
)[0]
self.new_url = reverse("compensation:pay:new", args=(self.intervention.id,))
self.edit_url = reverse("compensation:pay:edit", args=(self.intervention.id, self.payment.id))
self.remove_url = reverse("compensation:pay:remove", args=(self.intervention.id, self.payment.id))
def test_anonymous_user(self):
""" Check correct status code for all requests
Assumption: User not logged in
Returns:
"""
client = Client()
success_urls = [
]
fail_urls = [
self.new_url,
self.edit_url,
self.remove_url,
]
self.assert_url_success(client, success_urls)
self.assert_url_fail(client, fail_urls)
def test_logged_in_no_groups_shared(self):
""" Check correct status code for all requests
Assumption: User logged in and has no groups and data is shared
Returns:
"""
client = Client()
client.login(username=self.superuser.username, password=self.superuser_pw)
self.superuser.groups.set([])
self.intervention.share_with_list([self.superuser])
# Since the user has no groups, it does not matter that data has been shared. There SHOULD not be any difference
# to a user without access, since the important permissions are missing
success_urls = [
]
fail_urls = [
self.new_url,
self.edit_url,
self.remove_url,
]
self.assert_url_success(client, success_urls)
self.assert_url_fail(client, fail_urls)
def test_logged_in_no_groups_unshared(self):
""" Check correct status code for all requests
Assumption: User logged in and has no groups and data is not shared
Returns:
"""
client = Client()
client.login(username=self.superuser.username, password=self.superuser_pw)
self.superuser.groups.set([])
# Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
self.intervention.share_with_list([])
# Since the user has no groups, it does not matter that data is unshared. There SHOULD not be any difference
# to a user having shared access, since all important permissions are missing
success_urls = [
]
fail_urls = [
self.new_url,
self.edit_url,
self.remove_url,
]
self.assert_url_success(client, success_urls)
self.assert_url_fail(client, fail_urls)
def test_logged_in_default_group_shared(self):
""" Check correct status code for all requests
Assumption: User logged in, is default group member and data is shared
--> Default group necessary since all base functionalities depend on this group membership
Returns:
"""
client = Client()
client.login(username=self.superuser.username, password=self.superuser_pw)
group = self.groups.get(name=DEFAULT_GROUP)
self.superuser.groups.set([group])
# Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
self.intervention.share_with_list([self.superuser])
success_urls = [
self.new_url,
self.edit_url,
self.remove_url,
]
self.assert_url_success(client, success_urls)
def test_logged_in_default_group_unshared(self):
""" Check correct status code for all requests
Assumption: User logged in, is default group member and data is NOT shared
--> Default group necessary since all base functionalities depend on this group membership
Returns:
"""
client = Client()
client.login(username=self.superuser.username, password=self.superuser_pw)
group = self.groups.get(name=DEFAULT_GROUP)
self.superuser.groups.set([group])
# Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
self.intervention.share_with_list([])
success_urls = [
]
fail_urls = [
self.new_url,
self.edit_url,
self.remove_url,
]
self.assert_url_fail(client, fail_urls)
self.assert_url_success(client, success_urls)

View File

@ -0,0 +1,127 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 09.02.22
"""
from django.core.exceptions import ObjectDoesNotExist
from django.urls import reverse
from compensation.models import Payment
from konova.tests.test_views import BaseWorkflowTestCase
from user.models import UserAction
class PaymentWorkflowTestCase(BaseWorkflowTestCase):
@classmethod
def setUpTestData(cls):
super().setUpTestData()
def setUp(self) -> None:
super().setUp()
# Give the user shared access to the dummy intervention
self.intervention.share_with(self.superuser)
self.payment = Payment.objects.get_or_create(
intervention=self.intervention,
amount=1,
due_on="2020-01-01",
comment="Testcomment"
)[0]
def test_new(self):
""" Test the creation of a payment
Returns:
"""
# Prepare url and form data to be posted
new_url = reverse("compensation:pay:new", args=(self.intervention.id,))
test_amount = 12345
test_due_on = "1970-01-01"
test_comment = self.create_dummy_string()
post_data = {
"amount": test_amount,
"due": test_due_on,
"comment": test_comment,
}
pre_creation_intervention_log_count = self.intervention.log.count()
num_payments = self.intervention.payments.count()
self.client_user.post(new_url, post_data)
self.intervention.refresh_from_db()
self.assertEqual(num_payments + 1, self.intervention.payments.count())
new_payment = self.intervention.payments.get(amount=test_amount)
self.assertEqual(new_payment.amount, test_amount)
self.assertEqual(str(new_payment.due_on), test_due_on)
self.assertEqual(new_payment.comment, test_comment)
# Expect logs to be set
self.assertEqual(pre_creation_intervention_log_count + 1, self.intervention.log.count())
self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
def test_edit(self):
""" Test edit of a payment
Returns:
"""
# Prepare url and form data to be posted
new_url = reverse("compensation:pay:edit", args=(self.intervention.id, self.payment.id))
test_amount = self.payment.amount * 2
test_due_on = "1970-01-01"
test_comment = self.create_dummy_string()
post_data = {
"amount": test_amount,
"due": test_due_on,
"comment": test_comment,
}
pre_edit_intervention_log_count = self.intervention.log.count()
num_payments = self.intervention.payments.count()
self.client_user.post(new_url, post_data)
self.intervention.refresh_from_db()
self.payment.refresh_from_db()
self.assertEqual(num_payments, self.intervention.payments.count())
self.assertEqual(self.payment.amount, test_amount)
self.assertEqual(str(self.payment.due_on), test_due_on)
self.assertEqual(self.payment.comment, test_comment)
# Expect logs to be set
self.assertEqual(pre_edit_intervention_log_count + 1, self.intervention.log.count())
self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)
def test_remove(self):
""" Test remove of a payment
Returns:
"""
# Prepare url and form data to be posted
new_url = reverse("compensation:pay:remove", args=(self.intervention.id, self.payment.id))
post_data = {
"confirm": True,
}
pre_remove_intervention_log_count = self.intervention.log.count()
num_payments = self.intervention.payments.count()
self.client_user.post(new_url, post_data)
self.intervention.refresh_from_db()
try:
self.payment.refresh_from_db()
self.fail(msg="Payment still exists after delete")
except ObjectDoesNotExist:
pass
self.assertEqual(num_payments - 1, self.intervention.payments.count())
# Expect logs to be set
self.assertEqual(pre_remove_intervention_log_count + 1, self.intervention.log.count())
self.assertEqual(self.intervention.log.first().action, UserAction.EDITED)

View File

@ -34,7 +34,8 @@ urlpatterns = [
path('document/<doc_id>/remove/', remove_document_view, name='remove-doc'), path('document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
# Eco-account deductions # Eco-account deductions
path('<id>/remove/<deduction_id>', deduction_remove_view, name='remove-deduction'), path('<id>/deduction/<deduction_id>/remove', deduction_remove_view, name='remove-deduction'),
path('<id>/deduction/<deduction_id>/edit', deduction_edit_view, name='edit-deduction'),
path('<id>/deduct/new', new_deduction_view, name='new-deduction'), path('<id>/deduct/new', new_deduction_view, name='new-deduction'),
] ]

View File

@ -12,4 +12,5 @@ app_name = "pay"
urlpatterns = [ urlpatterns = [
path('<id>/new', new_payment_view, name='new'), path('<id>/new', new_payment_view, name='new'),
path('<id>/remove/<payment_id>', payment_remove_view, name='remove'), path('<id>/remove/<payment_id>', payment_remove_view, name='remove'),
path('<id>/edit/<payment_id>', payment_edit_view, name='edit'),
] ]

View File

@ -19,7 +19,8 @@ from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm,
NewEcoAccountDocumentForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm NewEcoAccountDocumentForm, RemoveCompensationActionModalForm, RemoveCompensationStateModalForm
from compensation.models import EcoAccount, EcoAccountDocument, CompensationState, CompensationAction from compensation.models import EcoAccount, EcoAccountDocument, CompensationState, CompensationAction
from compensation.tables import EcoAccountTable from compensation.tables import EcoAccountTable
from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm, RemoveEcoAccountDeductionModalForm from intervention.forms.modalForms import NewDeductionModalForm, ShareModalForm, RemoveEcoAccountDeductionModalForm, \
EditEcoAccountDeductionModalForm
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.decorators import any_group_check, default_group_required, conservation_office_group_required, \ from konova.decorators import any_group_check, default_group_required, conservation_office_group_required, \
shared_access_required shared_access_required
@ -31,7 +32,8 @@ from konova.utils.documents import get_document, remove_document
from konova.utils.generators import generate_qr_code from konova.utils.generators import generate_qr_code
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \ from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \
CANCEL_ACC_RECORDED_OR_DEDUCTED, DEDUCTION_REMOVED, DEDUCTION_ADDED, DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, \ CANCEL_ACC_RECORDED_OR_DEDUCTED, DEDUCTION_REMOVED, DEDUCTION_ADDED, DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, \
COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, \
DEDUCTION_EDITED
from konova.utils.user_checks import in_group from konova.utils.user_checks import in_group
@ -294,6 +296,34 @@ def deduction_remove_view(request: HttpRequest, id: str, deduction_id: str):
) )
@login_required
@default_group_required
@shared_access_required(EcoAccount, "id")
def deduction_edit_view(request: HttpRequest, id: str, deduction_id: str):
""" Renders a modal view for editing deductions
Args:
request (HttpRequest): The incoming request
id (str): The eco account's id
deduction_id (str): The deduction's id
Returns:
"""
acc = get_object_or_404(EcoAccount, id=id)
try:
eco_deduction = acc.deductions.get(id=deduction_id)
except ObjectDoesNotExist:
raise Http404("Unknown deduction")
form = EditEcoAccountDeductionModalForm(request.POST or None, instance=acc, deduction=eco_deduction, request=request)
return form.process_request(
request=request,
msg_success=DEDUCTION_EDITED,
redirect_url=reverse("compensation:acc:detail", args=(id,)) + "#related_data"
)
@login_required @login_required
@default_group_required @default_group_required
@shared_access_required(EcoAccount, "id") @shared_access_required(EcoAccount, "id")

View File

@ -11,16 +11,17 @@ from django.contrib.auth.decorators import login_required
from django.http import HttpRequest from django.http import HttpRequest
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from compensation.forms.modalForms import NewPaymentForm, RemovePaymentModalForm from compensation.forms.modalForms import NewPaymentForm, RemovePaymentModalForm, EditPaymentModalForm
from compensation.models import Payment from compensation.models import Payment
from intervention.models import Intervention from intervention.models import Intervention
from konova.decorators import default_group_required from konova.decorators import default_group_required, shared_access_required
from konova.forms import RemoveModalForm from konova.forms import RemoveModalForm
from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED, PAYMENT_EDITED
@login_required @login_required
@default_group_required @default_group_required
@shared_access_required(Intervention, "id")
def new_payment_view(request: HttpRequest, id: str): def new_payment_view(request: HttpRequest, id: str):
""" Renders a modal view for adding new payments """ Renders a modal view for adding new payments
@ -42,6 +43,7 @@ def new_payment_view(request: HttpRequest, id: str):
@login_required @login_required
@default_group_required @default_group_required
@shared_access_required(Intervention, "id")
def payment_remove_view(request: HttpRequest, id: str, payment_id: str): def payment_remove_view(request: HttpRequest, id: str, payment_id: str):
""" Renders a modal view for removing payments """ Renders a modal view for removing payments
@ -62,3 +64,27 @@ def payment_remove_view(request: HttpRequest, id: str, payment_id: str):
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data" redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
) )
@login_required
@default_group_required
@shared_access_required(Intervention, "id")
def payment_edit_view(request: HttpRequest, id: str, payment_id: str):
""" Renders a modal view for editing payments
Args:
request (HttpRequest): The incoming request
id (str): The intervention's id
payment_id (str): The payment's id
Returns:
"""
intervention = get_object_or_404(Intervention, id=id)
payment = get_object_or_404(Payment, id=payment_id)
form = EditPaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
return form.process_request(
request=request,
msg_success=PAYMENT_EDITED,
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
)

View File

@ -103,7 +103,7 @@ class EmaTable(BaseTable, TableRenderMixin):
Returns: Returns:
""" """
parcels = value.parcels.values_list( parcels = value.get_underlying_parcels().values_list(
"gmrkng", "gmrkng",
flat=True flat=True
).distinct() ).distinct()

View File

@ -31,42 +31,43 @@ class EmaViewTestCase(CompensationViewTestCase):
def setUpTestData(cls) -> None: def setUpTestData(cls) -> None:
super().setUpTestData() super().setUpTestData()
def setUp(self) -> None:
super().setUp()
# Create dummy data and related objects, like states or actions # Create dummy data and related objects, like states or actions
cls.create_dummy_data() self.create_dummy_data()
state = cls.create_dummy_states() state = self.create_dummy_states()
action = cls.create_dummy_action() action = self.create_dummy_action()
cls.ema.before_states.set([state]) self.ema.before_states.set([state])
cls.ema.after_states.set([state]) self.ema.after_states.set([state])
cls.ema.actions.set([action]) self.ema.actions.set([action])
# Prepare urls # Prepare urls
cls.index_url = reverse("ema:index", args=()) self.index_url = reverse("ema:index", args=())
cls.new_url = reverse("ema:new", args=()) self.new_url = reverse("ema:new", args=())
cls.new_id_url = reverse("ema:new-id", args=()) self.new_id_url = reverse("ema:new-id", args=())
cls.detail_url = reverse("ema:detail", args=(cls.ema.id,)) self.detail_url = reverse("ema:detail", args=(self.ema.id,))
cls.log_url = reverse("ema:log", args=(cls.ema.id,)) self.log_url = reverse("ema:log", args=(self.ema.id,))
cls.edit_url = reverse("ema:edit", args=(cls.ema.id,)) self.edit_url = reverse("ema:edit", args=(self.ema.id,))
cls.remove_url = reverse("ema:remove", args=(cls.ema.id,)) self.remove_url = reverse("ema:remove", args=(self.ema.id,))
cls.share_url = reverse("ema:share", args=(cls.ema.id, cls.ema.access_token,)) self.share_url = reverse("ema:share", args=(self.ema.id, self.ema.access_token,))
cls.share_create_url = reverse("ema:share-create", args=(cls.ema.id,)) self.share_create_url = reverse("ema:share-create", args=(self.ema.id,))
cls.record_url = reverse("ema:record", args=(cls.ema.id,)) self.record_url = reverse("ema:record", args=(self.ema.id,))
cls.report_url = reverse("ema:report", args=(cls.ema.id,)) self.report_url = reverse("ema:report", args=(self.ema.id,))
cls.new_doc_url = reverse("ema:new-doc", args=(cls.ema.id,)) self.new_doc_url = reverse("ema:new-doc", args=(self.ema.id,))
cls.state_new_url = reverse("ema:new-state", args=(cls.ema.id,)) self.state_new_url = reverse("ema:new-state", args=(self.ema.id,))
cls.action_new_url = reverse("ema:new-action", args=(cls.ema.id,)) self.action_new_url = reverse("ema:new-action", args=(self.ema.id,))
cls.deadline_new_url = reverse("ema:new-deadline", args=(cls.ema.id,)) self.deadline_new_url = reverse("ema:new-deadline", args=(self.ema.id,))
cls.state_remove_url = reverse("ema:state-remove", args=(cls.ema.id, state.id,)) self.state_remove_url = reverse("ema:state-remove", args=(self.ema.id, state.id,))
cls.action_remove_url = reverse("ema:action-remove", args=(cls.ema.id, action.id,)) self.action_remove_url = reverse("ema:action-remove", args=(self.ema.id, action.id,))
@classmethod def create_dummy_data(self):
def create_dummy_data(cls):
# Create dummy data # Create dummy data
# Create log entry # Create log entry
action = UserActionLogEntry.get_created_action(cls.superuser) action = UserActionLogEntry.get_created_action(self.superuser)
# Create responsible data object # Create responsible data object
responsibility_data = Responsibility.objects.create() responsibility_data = Responsibility.objects.create()
geometry = Geometry.objects.create() geometry = Geometry.objects.create()
cls.ema = Ema.objects.create( self.ema = Ema.objects.create(
identifier="TEST", identifier="TEST",
title="Test_title", title="Test_title",
created=action, created=action,

View File

@ -6,8 +6,11 @@ Created on: 27.09.21
""" """
from dal import autocomplete from dal import autocomplete
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.fields.files import FieldFile
from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED, \
REVOCATION_EDITED, FILE_TYPE_UNSUPPORTED, FILE_SIZE_TOO_LARGE
from user.models import User, UserActionLogEntry from user.models import User, UserActionLogEntry
from django.db import transaction from django.db import transaction
from django import forms from django import forms
@ -15,7 +18,7 @@ from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccount, EcoAccountDeduction from compensation.models import EcoAccount, EcoAccountDeduction
from intervention.inputs import TextToClipboardInput from intervention.inputs import TextToClipboardInput
from intervention.models import Intervention, InterventionDocument from intervention.models import Intervention, InterventionDocument, RevocationDocument
from konova.forms import BaseModalForm, NewDocumentForm, RemoveModalForm from konova.forms import BaseModalForm, NewDocumentForm, RemoveModalForm
from konova.utils.general import format_german_float from konova.utils.general import format_german_float
from konova.utils.user_checks import is_default_group_only from konova.utils.user_checks import is_default_group_only
@ -157,6 +160,7 @@ class NewRevocationModalForm(BaseModalForm):
} }
) )
) )
document_model = RevocationDocument
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -166,12 +170,61 @@ class NewRevocationModalForm(BaseModalForm):
"enctype": "multipart/form-data", # important for file upload "enctype": "multipart/form-data", # important for file upload
} }
def is_valid(self):
super_valid = super().is_valid()
_file = self.cleaned_data.get("file", None)
if isinstance(_file, FieldFile):
# FieldFile declares that no new file has been uploaded and we do not need to check on the file again
return super_valid
mime_type_valid = self.document_model.is_mime_type_valid(_file)
if not mime_type_valid:
self.add_error(
"file",
FILE_TYPE_UNSUPPORTED
)
file_size_valid = self.document_model.is_file_size_valid(_file)
if not file_size_valid:
self.add_error(
"file",
FILE_SIZE_TOO_LARGE
)
file_valid = mime_type_valid and file_size_valid
return super_valid and file_valid
def save(self): def save(self):
revocation = self.instance.add_revocation(self) revocation = self.instance.add_revocation(self)
self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_ADDED) self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_ADDED)
return revocation return revocation
class EditRevocationModalForm(NewRevocationModalForm):
revocation = None
def __init__(self, *args, **kwargs):
self.revocation = kwargs.pop("revocation", None)
super().__init__(*args, **kwargs)
try:
doc = self.revocation.document.file
except ObjectDoesNotExist:
doc = None
form_data = {
"date": str(self.revocation.date),
"file": doc,
"comment": self.revocation.comment,
}
self.load_initial_data(form_data)
def save(self):
revocation = self.instance.edit_revocation(self)
self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_EDITED)
return revocation
class RemoveRevocationModalForm(RemoveModalForm): class RemoveRevocationModalForm(RemoveModalForm):
""" Removing modal form for Revocation """ Removing modal form for Revocation
@ -349,6 +402,21 @@ class NewDeductionModalForm(BaseModalForm):
else: else:
raise NotImplementedError raise NotImplementedError
def _get_available_surface(self, acc):
""" Calculates how much available surface is left on the account
Args:
acc (EcoAccount):
Returns:
"""
# Calculate valid surface
deductable_surface = acc.deductable_surface
sum_surface_deductions = acc.get_deductions_surface()
rest_surface = deductable_surface - sum_surface_deductions
return rest_surface
def is_valid(self): def is_valid(self):
""" Custom validity check """ Custom validity check
@ -367,10 +435,7 @@ class NewDeductionModalForm(BaseModalForm):
) )
return False return False
# Calculate valid surface rest_surface = self._get_available_surface(acc)
deductable_surface = acc.deductable_surface
sum_surface_deductions = acc.get_deductions_surface()
rest_surface = deductable_surface - sum_surface_deductions
form_surface = float(self.cleaned_data["surface"]) form_surface = float(self.cleaned_data["surface"])
is_valid_surface = form_surface <= rest_surface is_valid_surface = form_surface <= rest_surface
if not is_valid_surface: if not is_valid_surface:
@ -407,6 +472,57 @@ class NewDeductionModalForm(BaseModalForm):
return deduction return deduction
class EditEcoAccountDeductionModalForm(NewDeductionModalForm):
deduction = None
def __init__(self, *args, **kwargs):
self.deduction = kwargs.pop("deduction", None)
super().__init__(*args, **kwargs)
form_data = {
"account": self.deduction.account,
"intervention": self.deduction.intervention,
"surface": self.deduction.surface,
}
self.load_initial_data(form_data)
def _get_available_surface(self, acc):
rest_surface = super()._get_available_surface(acc)
# Increase available surface by the currently deducted surface, so we can 'deduct' the same amount again or
# increase the surface only a little, which will still be valid.
# Example: 200 m² left, 500 m² deducted. Entering 700 m² would fail if we would not add the 500 m² to the available
# surface again.
rest_surface += self.deduction.surface
return rest_surface
def save(self):
deduction = self.deduction
form_account = self.cleaned_data.get("account", None)
form_intervention = self.cleaned_data.get("intervention", None)
current_account = deduction.account
current_intervention = deduction.intervention
# If account or intervention has been changed, we put that change in the logs just as if the deduction has
# been removed for this entry. Act as if the deduction is newly created for the new entries
if current_account != form_account:
current_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
form_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
else:
current_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
if current_intervention != form_intervention:
current_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
form_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
else:
current_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)
deduction.account = form_account
deduction.intervention = self.cleaned_data.get("intervention", None)
deduction.surface = self.cleaned_data.get("surface", None)
deduction.save()
return deduction
class RemoveEcoAccountDeductionModalForm(RemoveModalForm): class RemoveEcoAccountDeductionModalForm(RemoveModalForm):
""" Removing modal form for EcoAccountDeduction """ Removing modal form for EcoAccountDeduction

View File

@ -8,6 +8,8 @@ Created on: 15.11.21
import shutil import shutil
from django.contrib import messages from django.contrib import messages
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.fields.files import FieldFile
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
@ -202,6 +204,41 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
) )
return revocation return revocation
def edit_revocation(self, form):
""" Updates a revocation of the intervention
Args:
form (EditRevocationModalForm): The form holding the data
Returns:
"""
form_data = form.cleaned_data
file = form_data.get("file", None)
revocation = form.revocation
revocation.date = form_data.get("date", None)
revocation.comment = form_data.get("comment", None)
with transaction.atomic():
try:
revocation.document.date_of_creation = revocation.date
revocation.document.comment = revocation.comment
if not isinstance(file, FieldFile):
revocation.document.replace_file(file)
revocation.document.save()
except ObjectDoesNotExist:
revocation.document = RevocationDocument.objects.create(
title="revocation_of_{}".format(self.identifier),
date_of_creation=revocation.date,
comment=revocation.comment,
file=file,
instance=revocation
)
revocation.save()
return revocation
def remove_revocation(self, form): def remove_revocation(self, form):
""" Removes a revocation from the intervention """ Removes a revocation from the intervention

View File

@ -130,7 +130,7 @@ class InterventionTable(BaseTable, TableRenderMixin):
Returns: Returns:
""" """
parcels = value.parcels.values_list( parcels = value.get_underlying_parcels().values_list(
"gmrkng", "gmrkng",
flat=True flat=True
).distinct() ).distinct()

View File

@ -55,9 +55,12 @@
</td> </td>
<td class="align-middle">{{ deduction.surface|floatformat:2|intcomma }} m²</td> <td class="align-middle">{{ deduction.surface|floatformat:2|intcomma }} m²</td>
<td class="align-middle">{{ deduction.created.timestamp|default_if_none:""|naturalday}}</td> <td class="align-middle">{{ deduction.created.timestamp|default_if_none:""|naturalday}}</td>
<td> <td class="align-middle float-right">
{% if is_default_member and has_access %} {% if is_default_member and has_access %}
<button data-form-url="{% url 'intervention:remove-deduction' obj.id deduction.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove Deduction' %}"> <button data-form-url="{% url 'intervention:edit-deduction' obj.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit Deduction' %}">
{% fa5_icon 'edit' %}
</button>
<button data-form-url="{% url 'intervention:remove-deduction' obj.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove Deduction' %}">
{% fa5_icon 'trash' %} {% fa5_icon 'trash' %}
</button> </button>
{% endif %} {% endif %}

View File

@ -54,9 +54,12 @@
{{ pay.comment }} {{ pay.comment }}
</div> </div>
</td> </td>
<td class="align-middle"> <td class="align-middle float-right">
{% if is_default_member and has_access %} {% if is_default_member and has_access %}
<button data-form-url="{% url 'compensation:pay:remove' obj.id pay.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove payment' %}"> <button data-form-url="{% url 'compensation:pay:edit' obj.id pay.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit payment' %}">
{% fa5_icon 'edit' %}
</button>
<button data-form-url="{% url 'compensation:pay:remove' obj.id pay.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove payment' %}">
{% fa5_icon 'trash' %} {% fa5_icon 'trash' %}
</button> </button>
{% endif %} {% endif %}

View File

@ -63,9 +63,12 @@
{{ rev.comment }} {{ rev.comment }}
</div> </div>
</td> </td>
<td class="align-middle"> <td class="align-middle float-right">
{% if is_default_member and has_access %} {% if is_default_member and has_access %}
<button data-form-url="{% url 'intervention:remove-revocation' obj.id rev.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove revocation' %}"> <button data-form-url="{% url 'intervention:edit-revocation' obj.id rev.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit revocation' %}">
{% fa5_icon 'edit' %}
</button>
<button data-form-url="{% url 'intervention:remove-revocation' obj.id rev.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove revocation' %}">
{% fa5_icon 'trash' %} {% fa5_icon 'trash' %}
</button> </button>
{% endif %} {% endif %}

View File

@ -21,30 +21,32 @@ class InterventionViewTestCase(BaseViewTestCase):
def setUpTestData(cls) -> None: def setUpTestData(cls) -> None:
super().setUpTestData() super().setUpTestData()
def setUp(self) -> None:
super().setUp()
# Prepare urls # Prepare urls
cls.index_url = reverse("intervention:index", args=()) self.index_url = reverse("intervention:index", args=())
cls.new_url = reverse("intervention:new", args=()) self.new_url = reverse("intervention:new", args=())
cls.new_id_url = reverse("intervention:new-id", args=()) self.new_id_url = reverse("intervention:new-id", args=())
cls.detail_url = reverse("intervention:detail", args=(cls.intervention.id,)) self.detail_url = reverse("intervention:detail", args=(self.intervention.id,))
cls.log_url = reverse("intervention:log", args=(cls.intervention.id,)) self.log_url = reverse("intervention:log", args=(self.intervention.id,))
cls.edit_url = reverse("intervention:edit", args=(cls.intervention.id,)) self.edit_url = reverse("intervention:edit", args=(self.intervention.id,))
cls.remove_url = reverse("intervention:remove", args=(cls.intervention.id,)) self.remove_url = reverse("intervention:remove", args=(self.intervention.id,))
cls.share_url = reverse("intervention:share", args=(cls.intervention.id, cls.intervention.access_token,)) self.share_url = reverse("intervention:share", args=(self.intervention.id, self.intervention.access_token,))
cls.share_create_url = reverse("intervention:share-create", args=(cls.intervention.id,)) self.share_create_url = reverse("intervention:share-create", args=(self.intervention.id,))
cls.run_check_url = reverse("intervention:check", args=(cls.intervention.id,)) self.run_check_url = reverse("intervention:check", args=(self.intervention.id,))
cls.record_url = reverse("intervention:record", args=(cls.intervention.id,)) self.record_url = reverse("intervention:record", args=(self.intervention.id,))
cls.report_url = reverse("intervention:report", args=(cls.intervention.id,)) self.report_url = reverse("intervention:report", args=(self.intervention.id,))
cls.deduction.intervention = cls.intervention self.deduction.intervention = self.intervention
cls.deduction.save() self.deduction.save()
cls.deduction_new_url = reverse("intervention:new-deduction", args=(cls.intervention.id,)) self.deduction_new_url = reverse("intervention:new-deduction", args=(self.intervention.id,))
cls.deduction_remove_url = reverse("intervention:remove-deduction", args=(cls.intervention.id, cls.deduction.id)) self.deduction_remove_url = reverse("intervention:remove-deduction", args=(self.intervention.id, self.deduction.id))
cls.revocation = Revocation.objects.create( self.revocation = Revocation.objects.create(
legal=cls.intervention.legal legal=self.intervention.legal
) )
cls.revocation_new_url = reverse("intervention:new-revocation", args=(cls.intervention.id,)) self.revocation_new_url = reverse("intervention:new-revocation", args=(self.intervention.id,))
cls.revocation_remove_url = reverse("intervention:remove-revocation", args=(cls.intervention.id, cls.revocation.id)) self.revocation_remove_url = reverse("intervention:remove-revocation", args=(self.intervention.id, self.revocation.id))
def test_views_anonymous_user(self): def test_views_anonymous_user(self):
""" Check correct status code for all requests """ Check correct status code for all requests

View File

@ -10,7 +10,7 @@ from django.urls import path
from intervention.views import index_view, new_view, detail_view, edit_view, remove_view, new_document_view, share_view, \ from intervention.views import index_view, new_view, detail_view, edit_view, remove_view, new_document_view, share_view, \
create_share_view, remove_revocation_view, new_revocation_view, check_view, log_view, new_deduction_view, \ create_share_view, remove_revocation_view, new_revocation_view, check_view, log_view, new_deduction_view, \
record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view, report_view, \ record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view, report_view, \
remove_deduction_view, remove_compensation_view remove_deduction_view, remove_compensation_view, edit_deduction_view, edit_revocation_view
app_name = "intervention" app_name = "intervention"
urlpatterns = [ urlpatterns = [
@ -37,10 +37,12 @@ urlpatterns = [
# Deductions # Deductions
path('<id>/deduction/new', new_deduction_view, name='new-deduction'), path('<id>/deduction/new', new_deduction_view, name='new-deduction'),
path('<id>/deduction/<deduction_id>/edit', edit_deduction_view, name='edit-deduction'),
path('<id>/deduction/<deduction_id>/remove', remove_deduction_view, name='remove-deduction'), path('<id>/deduction/<deduction_id>/remove', remove_deduction_view, name='remove-deduction'),
# Revocation routes # Revocation routes
path('<id>/revocation/new', new_revocation_view, name='new-revocation'), path('<id>/revocation/new', new_revocation_view, name='new-revocation'),
path('<id>/revocation/<revocation_id>/edit', edit_revocation_view, name='edit-revocation'),
path('<id>/revocation/<revocation_id>/remove', remove_revocation_view, name='remove-revocation'), path('<id>/revocation/<revocation_id>/remove', remove_revocation_view, name='remove-revocation'),
path('revocation/<doc_id>', get_revocation_view, name='get-doc-revocation'), path('revocation/<doc_id>', get_revocation_view, name='get-doc-revocation'),
] ]

View File

@ -7,7 +7,7 @@ from django.shortcuts import render
from intervention.forms.forms import NewInterventionForm, EditInterventionForm from intervention.forms.forms import NewInterventionForm, EditInterventionForm
from intervention.forms.modalForms import ShareModalForm, NewRevocationModalForm, \ from intervention.forms.modalForms import ShareModalForm, NewRevocationModalForm, \
CheckModalForm, NewDeductionModalForm, NewInterventionDocumentForm, RemoveEcoAccountDeductionModalForm, \ CheckModalForm, NewDeductionModalForm, NewInterventionDocumentForm, RemoveEcoAccountDeductionModalForm, \
RemoveRevocationModalForm RemoveRevocationModalForm, EditEcoAccountDeductionModalForm, EditRevocationModalForm
from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
from intervention.tables import InterventionTable from intervention.tables import InterventionTable
from konova.contexts import BaseContext from konova.contexts import BaseContext
@ -18,7 +18,7 @@ from konova.utils.documents import remove_document, get_document
from konova.utils.generators import generate_qr_code from konova.utils.generators import generate_qr_code
from konova.utils.message_templates import INTERVENTION_INVALID, FORM_INVALID, IDENTIFIER_REPLACED, \ from konova.utils.message_templates import INTERVENTION_INVALID, FORM_INVALID, IDENTIFIER_REPLACED, \
CHECKED_RECORDED_RESET, DEDUCTION_REMOVED, DEDUCTION_ADDED, REVOCATION_ADDED, REVOCATION_REMOVED, \ CHECKED_RECORDED_RESET, DEDUCTION_REMOVED, DEDUCTION_ADDED, REVOCATION_ADDED, REVOCATION_REMOVED, \
COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, DEDUCTION_EDITED, REVOCATION_EDITED
from konova.utils.user_checks import in_group from konova.utils.user_checks import in_group
@ -331,6 +331,31 @@ def remove_view(request: HttpRequest, id: str):
) )
@login_required
@default_group_required
@shared_access_required(Intervention, "id")
def edit_revocation_view(request: HttpRequest, id: str, revocation_id: str):
""" Renders a edit view for a revocation
Args:
request (HttpRequest): The incoming request
id (str): The intervention's id as string
revocation_id (str): The revocation's id as string
Returns:
"""
intervention = get_object_or_404(Intervention, id=id)
revocation = get_object_or_404(Revocation, id=revocation_id)
form = EditRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention, revocation=revocation, request=request)
return form.process_request(
request,
REVOCATION_EDITED,
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
)
@login_required @login_required
@default_group_required @default_group_required
@shared_access_required(Intervention, "id") @shared_access_required(Intervention, "id")
@ -339,7 +364,8 @@ def remove_revocation_view(request: HttpRequest, id: str, revocation_id: str):
Args: Args:
request (HttpRequest): The incoming request request (HttpRequest): The incoming request
id (str): The revocation's id as string id (str): The intervention's id as string
revocation_id (str): The revocation's id as string
Returns: Returns:
@ -536,6 +562,34 @@ def remove_deduction_view(request: HttpRequest, id: str, deduction_id: str):
) )
@login_required
@default_group_required
@shared_access_required(Intervention, "id")
def edit_deduction_view(request: HttpRequest, id: str, deduction_id: str):
""" Renders a modal view for removing deductions
Args:
request (HttpRequest): The incoming request
id (str): The intervention's id
deduction_id (str): The deduction's id
Returns:
"""
intervention = get_object_or_404(Intervention, id=id)
try:
eco_deduction = intervention.deductions.get(id=deduction_id)
except ObjectDoesNotExist:
raise Http404("Unknown deduction")
form = EditEcoAccountDeductionModalForm(request.POST or None, instance=intervention, deduction=eco_deduction, request=request)
return form.process_request(
request=request,
msg_success=DEDUCTION_EDITED,
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
)
@login_required @login_required
@conservation_office_group_required @conservation_office_group_required
@shared_access_required(Intervention, "id") @shared_access_required(Intervention, "id")

View File

@ -24,7 +24,7 @@ from konova.contexts import BaseContext
from konova.models import BaseObject, Geometry, RecordableObjectMixin from konova.models import BaseObject, Geometry, RecordableObjectMixin
from konova.settings import DEFAULT_SRID from konova.settings import DEFAULT_SRID
from konova.tasks import celery_update_parcels from konova.tasks import celery_update_parcels
from konova.utils.message_templates import FORM_INVALID from konova.utils.message_templates import FORM_INVALID, FILE_TYPE_UNSUPPORTED, FILE_SIZE_TOO_LARGE
from user.models import UserActionLogEntry from user.models import UserActionLogEntry
@ -87,7 +87,7 @@ class BaseForm(forms.Form):
""" """
self.fields[field].widget.attrs["placeholder"] = val self.fields[field].widget.attrs["placeholder"] = val
def load_initial_data(self, form_data: dict, disabled_fields: list): def load_initial_data(self, form_data: dict, disabled_fields: list = None):
""" Initializes form data from instance """ Initializes form data from instance
Inserts instance data into form and disables form fields Inserts instance data into form and disables form fields
@ -99,8 +99,9 @@ class BaseForm(forms.Form):
return return
for k, v in form_data.items(): for k, v in form_data.items():
self.initialize_form_field(k, v) self.initialize_form_field(k, v)
for field in disabled_fields: if disabled_fields:
self.disable_form_field(field) for field in disabled_fields:
self.disable_form_field(field)
def add_widget_html_class(self, field: str, cls: str): def add_widget_html_class(self, field: str, cls: str):
""" Adds a HTML class string to the widget of a field """ Adds a HTML class string to the widget of a field
@ -423,14 +424,14 @@ class NewDocumentForm(BaseModalForm):
if not mime_type_valid: if not mime_type_valid:
self.add_error( self.add_error(
"file", "file",
_("Unsupported file type") FILE_TYPE_UNSUPPORTED
) )
file_size_valid = self.document_model.is_file_size_valid(_file) file_size_valid = self.document_model.is_file_size_valid(_file)
if not file_size_valid: if not file_size_valid:
self.add_error( self.add_error(
"file", "file",
_("File too large") FILE_SIZE_TOO_LARGE
) )
file_valid = mime_type_valid and file_size_valid file_valid = mime_type_valid and file_size_valid

View File

@ -0,0 +1,54 @@
# Generated by Django 3.1.3 on 2022-02-08 17:01
from django.db import migrations, models
import django.db.models.deletion
import uuid
def migrate_parcels(apps, schema_editor):
Geometry = apps.get_model('konova', 'Geometry')
SpatialIntersection = apps.get_model('konova', 'SpatialIntersection')
all_geoms = Geometry.objects.all()
for geom in all_geoms:
SpatialIntersection.objects.bulk_create([
SpatialIntersection(geometry=geom, parcel=parcel)
for parcel in geom.parcels.all()
])
class Migration(migrations.Migration):
dependencies = [
('konova', '0002_auto_20220114_0936'),
]
operations = [
migrations.CreateModel(
name='SpatialIntersection',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('calculated_on', models.DateTimeField(auto_now_add=True, null=True)),
('geometry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='konova.geometry')),
('parcel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='konova.parcel')),
],
options={
'abstract': False,
},
),
migrations.RunPython(migrate_parcels),
migrations.AddField(
model_name='parcel',
name='geometries_tmp',
field=models.ManyToManyField(blank=True, related_name='parcels', through='konova.SpatialIntersection', to='konova.Geometry'),
),
migrations.RemoveField(
model_name='parcel',
name='geometries',
),
migrations.RenameField(
model_name='parcel',
old_name='geometries_tmp',
new_name='geometries',
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.1.3 on 2022-02-09 07:39
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('konova', '0003_auto_20220208_1801'),
]
operations = [
migrations.RenameModel(
old_name='SpatialIntersection',
new_name='ParcelIntersection',
),
]

View File

@ -101,3 +101,19 @@ class AbstractDocument(BaseResource):
def is_file_size_valid(cls, _file): def is_file_size_valid(cls, _file):
max_size = cls._maximum_file_size * pow(1000, 2) max_size = cls._maximum_file_size * pow(1000, 2)
return _file.size <= max_size return _file.size <= max_size
def replace_file(self, new_file):
""" Replaces the old file on the hard drive with the new one
Args:
new_file (File): The new file
Returns:
"""
try:
os.remove(self.file.file.name)
except FileNotFoundError:
pass
self.file = new_file
self.save()

View File

@ -99,7 +99,7 @@ class Geometry(BaseResource):
Returns: Returns:
""" """
from konova.models import Parcel, District from konova.models import Parcel, District, ParcelIntersection
parcel_fetcher = ParcelWFSFetcher( parcel_fetcher = ParcelWFSFetcher(
geometry_id=self.id, geometry_id=self.id,
) )
@ -107,6 +107,7 @@ class Geometry(BaseResource):
fetched_parcels = parcel_fetcher.get_features( fetched_parcels = parcel_fetcher.get_features(
typename typename
) )
_now = timezone.now()
underlying_parcels = [] underlying_parcels = []
for result in fetched_parcels: for result in fetched_parcels:
fetched_parcel = result[typename] fetched_parcel = result[typename]
@ -125,19 +126,35 @@ class Geometry(BaseResource):
krs=fetched_parcel["ave:kreis"], krs=fetched_parcel["ave:kreis"],
)[0] )[0]
parcel_obj.district = district parcel_obj.district = district
parcel_obj.updated_on = timezone.now() parcel_obj.updated_on = _now
parcel_obj.save() parcel_obj.save()
underlying_parcels.append(parcel_obj) underlying_parcels.append(parcel_obj)
# Update the linked parcels
self.parcels.set(underlying_parcels) self.parcels.set(underlying_parcels)
# Set the calculated_on intermediate field, so this related data will be found on lookups
intersections_without_ts = self.parcelintersection_set.filter(
parcel__in=self.parcels.all(),
calculated_on__isnull=True,
)
for entry in intersections_without_ts:
entry.calculated_on = _now
ParcelIntersection.objects.bulk_update(
intersections_without_ts,
["calculated_on"]
)
def get_underlying_parcels(self): def get_underlying_parcels(self):
""" Getter for related parcels and their districts """ Getter for related parcels and their districts
Returns: Returns:
parcels (QuerySet): The related parcels as queryset parcels (QuerySet): The related parcels as queryset
""" """
parcels = self.parcels.all().prefetch_related(
parcels = self.parcels.filter(
parcelintersection__calculated_on__isnull=False,
).prefetch_related(
"district" "district"
).order_by( ).order_by(
"gmrkng", "gmrkng",

View File

@ -22,7 +22,7 @@ class Parcel(UuidModel):
To avoid conflicts due to german Umlaute, the field names are shortened and vocals are dropped. To avoid conflicts due to german Umlaute, the field names are shortened and vocals are dropped.
""" """
geometries = models.ManyToManyField("konova.Geometry", related_name="parcels", blank=True) geometries = models.ManyToManyField("konova.Geometry", blank=True, related_name="parcels", through='ParcelIntersection')
district = models.ForeignKey("konova.District", on_delete=models.SET_NULL, null=True, blank=True, related_name="parcels") district = models.ForeignKey("konova.District", on_delete=models.SET_NULL, null=True, blank=True, related_name="parcels")
gmrkng = models.CharField( gmrkng = models.CharField(
max_length=1000, max_length=1000,
@ -77,3 +77,22 @@ class District(UuidModel):
def __str__(self): def __str__(self):
return f"{self.gmnd} | {self.krs}" return f"{self.gmnd} | {self.krs}"
class ParcelIntersection(UuidModel):
""" ParcelIntersection is an intermediary model, which is used to configure the
M2M relation between Parcel and Geometry.
Based on uuids, we will not have (practically) any problems on outrunning primary keys
and extending the model with calculated_on timestamp, we can 'hide' entries while they
are being recalculated and keep track on the last time they have been calculated this
way.
Please note: The calculated_on describes when the relation between the Parcel and the Geometry
has been established. The updated_on field of Parcel describes when this Parcel has been
changed the last time.
"""
parcel = models.ForeignKey(Parcel, on_delete=models.CASCADE)
geometry = models.ForeignKey("konova.Geometry", on_delete=models.CASCADE)
calculated_on = models.DateTimeField(auto_now_add=True, null=True, blank=True)

View File

@ -4,13 +4,19 @@ from celery import shared_task
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
@shared_task @shared_task
def celery_update_parcels(geometry_id: str, recheck: bool = True): def celery_update_parcels(geometry_id: str, recheck: bool = True):
from konova.models import Geometry from konova.models import Geometry, ParcelIntersection
try: try:
geom = Geometry.objects.get(id=geometry_id) geom = Geometry.objects.get(id=geometry_id)
geom.parcels.clear() objs = geom.parcelintersection_set.all()
for obj in objs:
obj.calculated_on = None
ParcelIntersection.objects.bulk_update(
objs,
["calculated_on"]
)
geom.update_parcels() geom.update_parcels()
except ObjectDoesNotExist: except ObjectDoesNotExist:
if recheck: if recheck:

View File

@ -47,43 +47,58 @@ class BaseTestCase(TestCase):
class Meta: class Meta:
abstract = True abstract = True
@classmethod def setUp(self) -> None:
def setUpTestData(cls): """ Setup data before each test run
cls.create_users()
cls.create_groups()
cls.intervention = cls.create_dummy_intervention()
cls.compensation = cls.create_dummy_compensation()
cls.eco_account = cls.create_dummy_eco_account()
cls.ema = cls.create_dummy_ema()
cls.deduction = cls.create_dummy_deduction()
cls.create_dummy_states()
cls.create_dummy_action()
cls.codes = cls.create_dummy_codes()
@classmethod Returns:
def create_users(cls):
"""
super().setUp()
self.create_users()
self.create_groups()
self.intervention = self.create_dummy_intervention()
self.compensation = self.create_dummy_compensation()
self.eco_account = self.create_dummy_eco_account()
self.ema = self.create_dummy_ema()
self.deduction = self.create_dummy_deduction()
self.create_dummy_states()
self.create_dummy_action()
self.codes = self.create_dummy_codes()
# Set the default group as only group for the user
default_group = self.groups.get(name=DEFAULT_GROUP)
self.superuser.groups.set([default_group])
# Create fresh logged in client and a non-logged in client (anon) for each test
self.client_user = Client()
self.client_user.login(username=self.superuser.username, password=self.superuser_pw)
self.client_anon = Client()
def create_users(self):
# Create superuser and regular user # Create superuser and regular user
cls.superuser = User.objects.create_superuser( self.superuser = User.objects.create_superuser(
username="root", username="root",
email="root@root.com", email="root@root.com",
password=cls.superuser_pw, password=self.superuser_pw,
) )
cls.user = User.objects.create_user( self.user = User.objects.create_user(
username="user1", username="user1",
email="user@root.com", email="user@root.com",
password=cls.user_pw password=self.user_pw
) )
cls.users = User.objects.all() self.users = User.objects.all()
@classmethod
def create_groups(cls): def create_groups(self):
# Create groups # Create groups
for group_data in GROUPS_DATA: for group_data in GROUPS_DATA:
name = group_data.get("name") name = group_data.get("name")
Group.objects.get_or_create( Group.objects.get_or_create(
name=name, name=name,
) )
cls.groups = Group.objects.all() self.groups = Group.objects.all()
@staticmethod @staticmethod
def create_dummy_string(prefix: str = ""): def create_dummy_string(prefix: str = ""):
@ -94,8 +109,7 @@ class BaseTestCase(TestCase):
""" """
return f"{prefix}{generate_random_string(3, True)}" return f"{prefix}{generate_random_string(3, True)}"
@classmethod def create_dummy_intervention(self):
def create_dummy_intervention(cls):
""" Creates an intervention which can be used for tests """ Creates an intervention which can be used for tests
Returns: Returns:
@ -103,7 +117,7 @@ class BaseTestCase(TestCase):
""" """
# Create dummy data # Create dummy data
# Create log entry # Create log entry
action = UserActionLogEntry.get_created_action(cls.superuser) action = UserActionLogEntry.get_created_action(self.superuser)
# Create legal data object (without M2M laws first) # Create legal data object (without M2M laws first)
legal_data = Legal.objects.create() legal_data = Legal.objects.create()
# Create responsible data object # Create responsible data object
@ -122,32 +136,30 @@ class BaseTestCase(TestCase):
intervention.generate_access_token(make_unique=True) intervention.generate_access_token(make_unique=True)
return intervention return intervention
@classmethod def create_dummy_compensation(self):
def create_dummy_compensation(cls):
""" Creates a compensation which can be used for tests """ Creates a compensation which can be used for tests
Returns: Returns:
""" """
if cls.intervention is None: if self.intervention is None:
cls.intervention = cls.create_dummy_intervention() self.intervention = self.create_dummy_intervention()
# Create dummy data # Create dummy data
# Create log entry # Create log entry
action = UserActionLogEntry.get_created_action(cls.superuser) action = UserActionLogEntry.get_created_action(self.superuser)
geometry = Geometry.objects.create() geometry = Geometry.objects.create()
# Finally create main object, holding the other objects # Finally create main object, holding the other objects
compensation = Compensation.objects.create( compensation = Compensation.objects.create(
identifier="TEST", identifier="TEST",
title="Test_title", title="Test_title",
intervention=cls.intervention, intervention=self.intervention,
created=action, created=action,
geometry=geometry, geometry=geometry,
comment="Test", comment="Test",
) )
return compensation return compensation
@classmethod def create_dummy_eco_account(self):
def create_dummy_eco_account(cls):
""" Creates an eco account which can be used for tests """ Creates an eco account which can be used for tests
Returns: Returns:
@ -155,7 +167,7 @@ class BaseTestCase(TestCase):
""" """
# Create dummy data # Create dummy data
# Create log entry # Create log entry
action = UserActionLogEntry.get_created_action(cls.superuser) action = UserActionLogEntry.get_created_action(self.superuser)
geometry = Geometry.objects.create() geometry = Geometry.objects.create()
# Create responsible data object # Create responsible data object
lega_data = Legal.objects.create() lega_data = Legal.objects.create()
@ -172,8 +184,7 @@ class BaseTestCase(TestCase):
) )
return eco_account return eco_account
@classmethod def create_dummy_ema(self):
def create_dummy_ema(cls):
""" Creates an ema which can be used for tests """ Creates an ema which can be used for tests
Returns: Returns:
@ -181,7 +192,7 @@ class BaseTestCase(TestCase):
""" """
# Create dummy data # Create dummy data
# Create log entry # Create log entry
action = UserActionLogEntry.get_created_action(cls.superuser) action = UserActionLogEntry.get_created_action(self.superuser)
geometry = Geometry.objects.create() geometry = Geometry.objects.create()
# Create responsible data object # Create responsible data object
responsible_data = Responsibility.objects.create() responsible_data = Responsibility.objects.create()
@ -196,41 +207,37 @@ class BaseTestCase(TestCase):
) )
return ema return ema
@classmethod def create_dummy_deduction(self):
def create_dummy_deduction(cls):
return EcoAccountDeduction.objects.create( return EcoAccountDeduction.objects.create(
account=cls.create_dummy_eco_account(), account=self.create_dummy_eco_account(),
intervention=cls.create_dummy_intervention(), intervention=self.create_dummy_intervention(),
surface=100, surface=100,
) )
@classmethod def create_dummy_states(self):
def create_dummy_states(cls):
""" Creates an intervention which can be used for tests """ Creates an intervention which can be used for tests
Returns: Returns:
""" """
cls.comp_state = CompensationState.objects.create( self.comp_state = CompensationState.objects.create(
surface=10.00, surface=10.00,
biotope_type=None, biotope_type=None,
) )
return cls.comp_state return self.comp_state
@classmethod def create_dummy_action(self):
def create_dummy_action(cls):
""" Creates an intervention which can be used for tests """ Creates an intervention which can be used for tests
Returns: Returns:
""" """
cls.comp_action = CompensationAction.objects.create( self.comp_action = CompensationAction.objects.create(
amount=10 amount=10
) )
return cls.comp_action return self.comp_action
@classmethod def create_dummy_codes(self):
def create_dummy_codes(cls):
""" Creates some dummy KonovaCodes which can be used for testing """ Creates some dummy KonovaCodes which can be used for testing
Returns: Returns:
@ -256,8 +263,7 @@ class BaseTestCase(TestCase):
polygon = polygon.transform(3857, clone=True) polygon = polygon.transform(3857, clone=True)
return MultiPolygon(polygon, srid=3857) # 3857 is the default srid used for MultiPolygonField in the form return MultiPolygon(polygon, srid=3857) # 3857 is the default srid used for MultiPolygonField in the form
@classmethod def fill_out_intervention(self, intervention: Intervention) -> Intervention:
def fill_out_intervention(cls, intervention: Intervention) -> Intervention:
""" Adds all required (dummy) data to an intervention """ Adds all required (dummy) data to an intervention
Args: Args:
@ -277,13 +283,12 @@ class BaseTestCase(TestCase):
intervention.legal.process_type = KonovaCode.objects.get(id=3) intervention.legal.process_type = KonovaCode.objects.get(id=3)
intervention.legal.save() intervention.legal.save()
intervention.legal.laws.set([KonovaCode.objects.get(id=(4))]) intervention.legal.laws.set([KonovaCode.objects.get(id=(4))])
intervention.geometry.geom = cls.create_dummy_geometry() intervention.geometry.geom = self.create_dummy_geometry()
intervention.geometry.save() intervention.geometry.save()
intervention.save() intervention.save()
return intervention return intervention
@classmethod def fill_out_compensation(self, compensation: Compensation) -> Compensation:
def fill_out_compensation(cls, compensation: Compensation) -> Compensation:
""" Adds all required (dummy) data to a compensation """ Adds all required (dummy) data to a compensation
Args: Args:
@ -292,15 +297,14 @@ class BaseTestCase(TestCase):
Returns: Returns:
compensation (Compensation): The modified compensation compensation (Compensation): The modified compensation
""" """
compensation.after_states.add(cls.comp_state) compensation.after_states.add(self.comp_state)
compensation.before_states.add(cls.comp_state) compensation.before_states.add(self.comp_state)
compensation.actions.add(cls.comp_action) compensation.actions.add(self.comp_action)
compensation.geometry.geom = cls.create_dummy_geometry() compensation.geometry.geom = self.create_dummy_geometry()
compensation.geometry.save() compensation.geometry.save()
return compensation return compensation
@classmethod def get_conservation_office_code(self):
def get_conservation_office_code(cls):
""" Returns a dummy KonovaCode as conservation office code """ Returns a dummy KonovaCode as conservation office code
Returns: Returns:
@ -313,39 +317,37 @@ class BaseTestCase(TestCase):
codelist.codes.add(code) codelist.codes.add(code)
return code return code
@classmethod def fill_out_ema(self, ema):
def fill_out_ema(cls, ema):
""" Adds all required (dummy) data to an Ema """ Adds all required (dummy) data to an Ema
Returns: Returns:
""" """
ema.responsible.conservation_office = cls.get_conservation_office_code() ema.responsible.conservation_office = self.get_conservation_office_code()
ema.responsible.conservation_file_number = "test" ema.responsible.conservation_file_number = "test"
ema.responsible.handler = "handler" ema.responsible.handler = "handler"
ema.responsible.save() ema.responsible.save()
ema.after_states.add(cls.comp_state) ema.after_states.add(self.comp_state)
ema.before_states.add(cls.comp_state) ema.before_states.add(self.comp_state)
ema.actions.add(cls.comp_action) ema.actions.add(self.comp_action)
ema.geometry.geom = cls.create_dummy_geometry() ema.geometry.geom = self.create_dummy_geometry()
ema.geometry.save() ema.geometry.save()
return ema return ema
@classmethod def fill_out_eco_account(self, eco_account):
def fill_out_eco_account(cls, eco_account):
""" Adds all required (dummy) data to an EcoAccount """ Adds all required (dummy) data to an EcoAccount
Returns: Returns:
""" """
eco_account.legal.registration_date = "2022-01-01" eco_account.legal.registration_date = "2022-01-01"
eco_account.legal.save() eco_account.legal.save()
eco_account.responsible.conservation_office = cls.get_conservation_office_code() eco_account.responsible.conservation_office = self.get_conservation_office_code()
eco_account.responsible.conservation_file_number = "test" eco_account.responsible.conservation_file_number = "test"
eco_account.responsible.handler = "handler" eco_account.responsible.handler = "handler"
eco_account.responsible.save() eco_account.responsible.save()
eco_account.after_states.add(cls.comp_state) eco_account.after_states.add(self.comp_state)
eco_account.before_states.add(cls.comp_state) eco_account.before_states.add(self.comp_state)
eco_account.actions.add(cls.comp_action) eco_account.actions.add(self.comp_action)
eco_account.geometry.geom = cls.create_dummy_geometry() eco_account.geometry.geom = self.create_dummy_geometry()
eco_account.geometry.save() eco_account.geometry.save()
eco_account.deductable_surface = eco_account.get_state_after_surface_sum() eco_account.deductable_surface = eco_account.get_state_after_surface_sum()
eco_account.save() eco_account.save()
@ -390,7 +392,10 @@ class BaseViewTestCase(BaseTestCase):
@classmethod @classmethod
def setUpTestData(cls) -> None: def setUpTestData(cls) -> None:
super().setUpTestData() super().setUpTestData()
cls.login_url = reverse("simple-sso-login")
def setUp(self) -> None:
super().setUp()
self.login_url = reverse("simple-sso-login")
def assert_url_success(self, client: Client, urls: list): def assert_url_success(self, client: Client, urls: list):
""" Assert for all given urls a direct 200 response """ Assert for all given urls a direct 200 response
@ -549,22 +554,6 @@ class BaseWorkflowTestCase(BaseTestCase):
def setUpTestData(cls): def setUpTestData(cls):
super().setUpTestData() super().setUpTestData()
def setUp(self) -> None:
""" Setup data before each test run
Returns:
"""
super().setUp()
# Set the default group as only group for the user
default_group = self.groups.get(name=DEFAULT_GROUP)
self.superuser.groups.set([default_group])
# Create fresh logged in client and a non-logged in client (anon) for each test
self.client_user = Client()
self.client_user.login(username=self.superuser.username, password=self.superuser_pw)
self.client_anon = Client()
def assert_object_is_deleted(self, obj): def assert_object_is_deleted(self, obj):
""" Provides a quick check whether an object has been removed from the database or not """ Provides a quick check whether an object has been removed from the database or not

View File

@ -18,6 +18,10 @@ MISSING_GROUP_PERMISSION = _("You need to be part of another user group.")
CHECKED_RECORDED_RESET = _("Status of Checked and Recorded reseted") CHECKED_RECORDED_RESET = _("Status of Checked and Recorded reseted")
# FILES
FILE_TYPE_UNSUPPORTED = _("Unsupported file type")
FILE_SIZE_TOO_LARGE = _("File too large")
# ECO ACCOUNT # ECO ACCOUNT
CANCEL_ACC_RECORDED_OR_DEDUCTED = _("Action canceled. Eco account is recorded or deductions exist. Only conservation office member can perform this action.") CANCEL_ACC_RECORDED_OR_DEDUCTED = _("Action canceled. Eco account is recorded or deductions exist. Only conservation office member can perform this action.")
@ -30,26 +34,32 @@ ADDED_COMPENSATION_STATE = _("Added compensation state")
# COMPENSATION STATE # COMPENSATION STATE
COMPENSATION_STATE_REMOVED = _("State removed") COMPENSATION_STATE_REMOVED = _("State removed")
COMPENSATION_STATE_EDITED = _("State edited")
COMPENSATION_STATE_ADDED = _("State added") COMPENSATION_STATE_ADDED = _("State added")
# COMPENSATION ACTION # COMPENSATION ACTION
COMPENSATION_ACTION_ADDED = _("Action added") COMPENSATION_ACTION_ADDED = _("Action added")
COMPENSATION_ACTION_EDITED = _("Action edited")
COMPENSATION_ACTION_REMOVED = _("Action removed") COMPENSATION_ACTION_REMOVED = _("Action removed")
# DEDUCTIONS # DEDUCTIONS
DEDUCTION_ADDED = _("Deduction added") DEDUCTION_ADDED = _("Deduction added")
DEDUCTION_EDITED = _("Deduction edited")
DEDUCTION_REMOVED = _("Deduction removed") DEDUCTION_REMOVED = _("Deduction removed")
# DEADLINE # DEADLINE
DEADLINE_ADDED = _("Deadline added") DEADLINE_ADDED = _("Deadline added")
DEADLINE_EDITED = _("Deadline edited")
DEADLINE_REMOVED = _("Deadline removed") DEADLINE_REMOVED = _("Deadline removed")
# PAYMENTS # PAYMENTS
PAYMENT_ADDED = _("Payment added") PAYMENT_ADDED = _("Payment added")
PAYMENT_EDITED = _("Payment edited")
PAYMENT_REMOVED = _("Payment removed") PAYMENT_REMOVED = _("Payment removed")
# REVOCATIONS # REVOCATIONS
REVOCATION_ADDED = _("Revocation added") REVOCATION_ADDED = _("Revocation added")
REVOCATION_EDITED = _("Revocation edited")
REVOCATION_REMOVED = _("Revocation removed") REVOCATION_REMOVED = _("Revocation removed")
# DOCUMENTS # DOCUMENTS

Binary file not shown.

View File

@ -5,7 +5,7 @@
# #
#: compensation/filters.py:122 compensation/forms/modalForms.py:35 #: compensation/filters.py:122 compensation/forms/modalForms.py:35
#: compensation/forms/modalForms.py:46 compensation/forms/modalForms.py:62 #: compensation/forms/modalForms.py:46 compensation/forms/modalForms.py:62
#: compensation/forms/modalForms.py:306 compensation/forms/modalForms.py:399 #: compensation/forms/modalForms.py:332 compensation/forms/modalForms.py:425
#: intervention/forms/forms.py:54 intervention/forms/forms.py:156 #: intervention/forms/forms.py:54 intervention/forms/forms.py:156
#: intervention/forms/forms.py:168 intervention/forms/modalForms.py:124 #: intervention/forms/forms.py:168 intervention/forms/modalForms.py:124
#: intervention/forms/modalForms.py:137 intervention/forms/modalForms.py:150 #: intervention/forms/modalForms.py:137 intervention/forms/modalForms.py:150
@ -18,15 +18,15 @@
#: konova/filters/mixins.py:270 konova/filters/mixins.py:315 #: konova/filters/mixins.py:270 konova/filters/mixins.py:315
#: konova/filters/mixins.py:353 konova/filters/mixins.py:354 #: konova/filters/mixins.py:353 konova/filters/mixins.py:354
#: konova/filters/mixins.py:385 konova/filters/mixins.py:386 #: konova/filters/mixins.py:385 konova/filters/mixins.py:386
#: konova/forms.py:140 konova/forms.py:241 konova/forms.py:312 #: konova/forms.py:141 konova/forms.py:242 konova/forms.py:313
#: konova/forms.py:356 konova/forms.py:366 konova/forms.py:379 #: konova/forms.py:357 konova/forms.py:367 konova/forms.py:380
#: konova/forms.py:391 konova/forms.py:409 user/forms.py:42 #: konova/forms.py:392 konova/forms.py:410 user/forms.py:42
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-02-08 15:16+0100\n" "POT-Creation-Date: 2022-02-09 12:52+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -75,7 +75,7 @@ msgstr "Bericht generieren"
msgid "Select a timespan and the desired conservation office" msgid "Select a timespan and the desired conservation office"
msgstr "Wählen Sie die Zeitspanne und die gewünschte Eintragungsstelle" msgstr "Wählen Sie die Zeitspanne und die gewünschte Eintragungsstelle"
#: analysis/forms.py:69 konova/forms.py:188 #: analysis/forms.py:69 konova/forms.py:189
msgid "Continue" msgid "Continue"
msgstr "Weiter" msgstr "Weiter"
@ -95,7 +95,7 @@ msgstr ""
#: analysis/templates/analysis/reports/includes/eco_account/amount.html:3 #: analysis/templates/analysis/reports/includes/eco_account/amount.html:3
#: analysis/templates/analysis/reports/includes/intervention/amount.html:3 #: analysis/templates/analysis/reports/includes/intervention/amount.html:3
#: analysis/templates/analysis/reports/includes/old_data/amount.html:3 #: analysis/templates/analysis/reports/includes/old_data/amount.html:3
#: compensation/forms/modalForms.py:383 #: compensation/forms/modalForms.py:409
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:34 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:34
#: intervention/templates/intervention/detail/includes/deductions.html:31 #: intervention/templates/intervention/detail/includes/deductions.html:31
msgid "Amount" msgid "Amount"
@ -152,7 +152,7 @@ msgstr "Geprüft"
#: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:9 #: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:9
#: analysis/templates/analysis/reports/includes/intervention/laws.html:20 #: analysis/templates/analysis/reports/includes/intervention/laws.html:20
#: analysis/templates/analysis/reports/includes/old_data/amount.html:18 #: analysis/templates/analysis/reports/includes/old_data/amount.html:18
#: compensation/tables.py:46 compensation/tables.py:219 #: compensation/tables.py:46 compensation/tables.py:222
#: compensation/templates/compensation/detail/compensation/view.html:77 #: compensation/templates/compensation/detail/compensation/view.html:77
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:31 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:31
#: compensation/templates/compensation/detail/eco_account/view.html:44 #: compensation/templates/compensation/detail/eco_account/view.html:44
@ -213,7 +213,7 @@ msgstr "Abbuchungen"
#: analysis/templates/analysis/reports/includes/eco_account/deductions.html:9 #: analysis/templates/analysis/reports/includes/eco_account/deductions.html:9
#: analysis/templates/analysis/reports/includes/eco_account/deductions.html:11 #: analysis/templates/analysis/reports/includes/eco_account/deductions.html:11
#: compensation/forms/modalForms.py:167 #: compensation/forms/modalForms.py:193
#: compensation/templates/compensation/detail/compensation/includes/states-after.html:36 #: compensation/templates/compensation/detail/compensation/includes/states-after.html:36
#: compensation/templates/compensation/detail/compensation/includes/states-before.html:36 #: compensation/templates/compensation/detail/compensation/includes/states-before.html:36
#: compensation/templates/compensation/detail/eco_account/includes/states-after.html:36 #: compensation/templates/compensation/detail/eco_account/includes/states-after.html:36
@ -239,7 +239,6 @@ msgstr "Kompensationsart"
#: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:15 #: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:15
#: analysis/templates/analysis/reports/includes/old_data/amount.html:29 #: analysis/templates/analysis/reports/includes/old_data/amount.html:29
#: compensation/tables.py:90
#: compensation/templates/compensation/detail/compensation/view.html:19 #: compensation/templates/compensation/detail/compensation/view.html:19
#: konova/templates/konova/includes/quickstart/compensations.html:4 #: konova/templates/konova/includes/quickstart/compensations.html:4
#: templates/navbars/navbar.html:28 #: templates/navbars/navbar.html:28
@ -284,8 +283,8 @@ msgid "Type"
msgstr "Typ" msgstr "Typ"
#: analysis/templates/analysis/reports/includes/old_data/amount.html:24 #: analysis/templates/analysis/reports/includes/old_data/amount.html:24
#: intervention/forms/modalForms.py:322 intervention/forms/modalForms.py:329 #: compensation/tables.py:89 intervention/forms/modalForms.py:322
#: intervention/tables.py:88 #: intervention/forms/modalForms.py:329 intervention/tables.py:88
#: intervention/templates/intervention/detail/view.html:19 #: intervention/templates/intervention/detail/view.html:19
#: konova/templates/konova/includes/quickstart/interventions.html:4 #: konova/templates/konova/includes/quickstart/interventions.html:4
#: templates/navbars/navbar.html:22 #: templates/navbars/navbar.html:22
@ -293,7 +292,7 @@ msgid "Intervention"
msgstr "Eingriff" msgstr "Eingriff"
#: analysis/templates/analysis/reports/includes/old_data/amount.html:34 #: analysis/templates/analysis/reports/includes/old_data/amount.html:34
#: compensation/tables.py:263 #: compensation/tables.py:266
#: compensation/templates/compensation/detail/eco_account/view.html:19 #: compensation/templates/compensation/detail/eco_account/view.html:19
#: intervention/forms/modalForms.py:295 intervention/forms/modalForms.py:302 #: intervention/forms/modalForms.py:295 intervention/forms/modalForms.py:302
#: konova/templates/konova/includes/quickstart/ecoaccounts.html:4 #: konova/templates/konova/includes/quickstart/ecoaccounts.html:4
@ -314,7 +313,7 @@ msgid "Show only unrecorded"
msgstr "Nur unverzeichnete anzeigen" msgstr "Nur unverzeichnete anzeigen"
#: compensation/forms/forms.py:32 compensation/tables.py:25 #: compensation/forms/forms.py:32 compensation/tables.py:25
#: compensation/tables.py:194 ema/tables.py:29 intervention/forms/forms.py:28 #: compensation/tables.py:197 ema/tables.py:29 intervention/forms/forms.py:28
#: intervention/tables.py:24 #: intervention/tables.py:24
#: intervention/templates/intervention/detail/includes/compensations.html:30 #: intervention/templates/intervention/detail/includes/compensations.html:30
msgid "Identifier" msgid "Identifier"
@ -326,7 +325,7 @@ msgid "Generated automatically"
msgstr "Automatisch generiert" msgstr "Automatisch generiert"
#: compensation/forms/forms.py:44 compensation/tables.py:30 #: compensation/forms/forms.py:44 compensation/tables.py:30
#: compensation/tables.py:199 #: compensation/tables.py:202
#: compensation/templates/compensation/detail/compensation/includes/documents.html:28 #: compensation/templates/compensation/detail/compensation/includes/documents.html:28
#: compensation/templates/compensation/detail/compensation/view.html:31 #: compensation/templates/compensation/detail/compensation/view.html:31
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:28 #: compensation/templates/compensation/detail/eco_account/includes/documents.html:28
@ -341,7 +340,7 @@ msgstr "Automatisch generiert"
#: intervention/templates/intervention/detail/includes/documents.html:28 #: intervention/templates/intervention/detail/includes/documents.html:28
#: intervention/templates/intervention/detail/view.html:31 #: intervention/templates/intervention/detail/view.html:31
#: intervention/templates/intervention/report/report.html:12 #: intervention/templates/intervention/report/report.html:12
#: konova/forms.py:355 #: konova/forms.py:356
msgid "Title" msgid "Title"
msgstr "Bezeichnung" msgstr "Bezeichnung"
@ -354,7 +353,7 @@ msgid "Compensation XY; Location ABC"
msgstr "Kompensation XY; Flur ABC" msgstr "Kompensation XY; Flur ABC"
#: compensation/forms/forms.py:57 compensation/forms/modalForms.py:61 #: compensation/forms/forms.py:57 compensation/forms/modalForms.py:61
#: compensation/forms/modalForms.py:305 compensation/forms/modalForms.py:398 #: compensation/forms/modalForms.py:331 compensation/forms/modalForms.py:424
#: compensation/templates/compensation/detail/compensation/includes/actions.html:35 #: compensation/templates/compensation/detail/compensation/includes/actions.html:35
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:34 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:34
#: compensation/templates/compensation/detail/compensation/includes/documents.html:31 #: compensation/templates/compensation/detail/compensation/includes/documents.html:31
@ -368,11 +367,11 @@ msgstr "Kompensation XY; Flur ABC"
#: intervention/templates/intervention/detail/includes/documents.html:31 #: intervention/templates/intervention/detail/includes/documents.html:31
#: intervention/templates/intervention/detail/includes/payments.html:34 #: intervention/templates/intervention/detail/includes/payments.html:34
#: intervention/templates/intervention/detail/includes/revocation.html:38 #: intervention/templates/intervention/detail/includes/revocation.html:38
#: konova/forms.py:390 konova/templates/konova/includes/comment_card.html:16 #: konova/forms.py:391 konova/templates/konova/includes/comment_card.html:16
msgid "Comment" msgid "Comment"
msgstr "Kommentar" msgstr "Kommentar"
#: compensation/forms/forms.py:59 compensation/forms/modalForms.py:400 #: compensation/forms/forms.py:59 compensation/forms/modalForms.py:426
#: intervention/forms/forms.py:182 #: intervention/forms/forms.py:182
msgid "Additional comment" msgid "Additional comment"
msgstr "Zusätzlicher Kommentar" msgstr "Zusätzlicher Kommentar"
@ -458,7 +457,7 @@ msgstr "Vereinbarungsdatum"
msgid "When did the parties agree on this?" msgid "When did the parties agree on this?"
msgstr "Wann wurde dieses Ökokonto offiziell vereinbart?" msgstr "Wann wurde dieses Ökokonto offiziell vereinbart?"
#: compensation/forms/forms.py:354 compensation/views/eco_account.py:103 #: compensation/forms/forms.py:354 compensation/views/eco_account.py:105
msgid "New Eco-Account" msgid "New Eco-Account"
msgstr "Neues Ökokonto" msgstr "Neues Ökokonto"
@ -483,8 +482,8 @@ msgstr "Fällig am"
msgid "Due on which date" msgid "Due on which date"
msgstr "Zahlung wird an diesem Datum erwartet" msgstr "Zahlung wird an diesem Datum erwartet"
#: compensation/forms/modalForms.py:63 compensation/forms/modalForms.py:307 #: compensation/forms/modalForms.py:63 compensation/forms/modalForms.py:333
#: intervention/forms/modalForms.py:151 konova/forms.py:392 #: intervention/forms/modalForms.py:151 konova/forms.py:393
msgid "Additional comment, maximum {} letters" msgid "Additional comment, maximum {} letters"
msgstr "Zusätzlicher Kommentar, maximal {} Zeichen" msgstr "Zusätzlicher Kommentar, maximal {} Zeichen"
@ -496,47 +495,47 @@ msgstr "Neue Ersatzzahlung zu Eingriff '{}' hinzufügen"
msgid "If there is no date you can enter, please explain why." msgid "If there is no date you can enter, please explain why."
msgstr "Falls Sie kein Datum angeben können, erklären Sie bitte weshalb." msgstr "Falls Sie kein Datum angeben können, erklären Sie bitte weshalb."
#: compensation/forms/modalForms.py:131 compensation/forms/modalForms.py:143 #: compensation/forms/modalForms.py:157 compensation/forms/modalForms.py:169
msgid "Biotope Type" msgid "Biotope Type"
msgstr "Biotoptyp" msgstr "Biotoptyp"
#: compensation/forms/modalForms.py:134 #: compensation/forms/modalForms.py:160
msgid "Select the biotope type" msgid "Select the biotope type"
msgstr "Biotoptyp wählen" msgstr "Biotoptyp wählen"
#: compensation/forms/modalForms.py:148 compensation/forms/modalForms.py:160 #: compensation/forms/modalForms.py:174 compensation/forms/modalForms.py:186
msgid "Biotope additional type" msgid "Biotope additional type"
msgstr "Zusatzbezeichnung" msgstr "Zusatzbezeichnung"
#: compensation/forms/modalForms.py:151 #: compensation/forms/modalForms.py:177
msgid "Select an additional biotope type" msgid "Select an additional biotope type"
msgstr "Zusatzbezeichnung wählen" msgstr "Zusatzbezeichnung wählen"
#: compensation/forms/modalForms.py:170 intervention/forms/modalForms.py:313 #: compensation/forms/modalForms.py:196 intervention/forms/modalForms.py:313
msgid "in m²" msgid "in m²"
msgstr "" msgstr ""
#: compensation/forms/modalForms.py:181 #: compensation/forms/modalForms.py:207
msgid "New state" msgid "New state"
msgstr "Neuer Zustand" msgstr "Neuer Zustand"
#: compensation/forms/modalForms.py:182 #: compensation/forms/modalForms.py:208
msgid "Insert data for the new state" msgid "Insert data for the new state"
msgstr "Geben Sie die Daten des neuen Zustandes ein" msgstr "Geben Sie die Daten des neuen Zustandes ein"
#: compensation/forms/modalForms.py:189 konova/forms.py:190 #: compensation/forms/modalForms.py:215 konova/forms.py:191
msgid "Object removed" msgid "Object removed"
msgstr "Objekt entfernt" msgstr "Objekt entfernt"
#: compensation/forms/modalForms.py:277 #: compensation/forms/modalForms.py:303
msgid "Deadline Type" msgid "Deadline Type"
msgstr "Fristart" msgstr "Fristart"
#: compensation/forms/modalForms.py:280 #: compensation/forms/modalForms.py:306
msgid "Select the deadline type" msgid "Select the deadline type"
msgstr "Fristart wählen" msgstr "Fristart wählen"
#: compensation/forms/modalForms.py:289 #: compensation/forms/modalForms.py:315
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:31 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:31
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:31 #: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:31
#: ema/templates/ema/detail/includes/deadlines.html:31 #: ema/templates/ema/detail/includes/deadlines.html:31
@ -544,27 +543,27 @@ msgstr "Fristart wählen"
msgid "Date" msgid "Date"
msgstr "Datum" msgstr "Datum"
#: compensation/forms/modalForms.py:292 #: compensation/forms/modalForms.py:318
msgid "Select date" msgid "Select date"
msgstr "Datum wählen" msgstr "Datum wählen"
#: compensation/forms/modalForms.py:319 #: compensation/forms/modalForms.py:345
msgid "New deadline" msgid "New deadline"
msgstr "Neue Frist" msgstr "Neue Frist"
#: compensation/forms/modalForms.py:320 #: compensation/forms/modalForms.py:346
msgid "Insert data for the new deadline" msgid "Insert data for the new deadline"
msgstr "Geben Sie die Daten der neuen Frist ein" msgstr "Geben Sie die Daten der neuen Frist ein"
#: compensation/forms/modalForms.py:337 #: compensation/forms/modalForms.py:363
msgid "Action Type" msgid "Action Type"
msgstr "Maßnahmentyp" msgstr "Maßnahmentyp"
#: compensation/forms/modalForms.py:340 #: compensation/forms/modalForms.py:366
msgid "Select the action type" msgid "Select the action type"
msgstr "Maßnahmentyp wählen" msgstr "Maßnahmentyp wählen"
#: compensation/forms/modalForms.py:349 #: compensation/forms/modalForms.py:375
#: compensation/templates/compensation/detail/compensation/includes/actions.html:40 #: compensation/templates/compensation/detail/compensation/includes/actions.html:40
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:39 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:39
#: compensation/templates/compensation/detail/compensation/includes/documents.html:36 #: compensation/templates/compensation/detail/compensation/includes/documents.html:36
@ -590,31 +589,31 @@ msgstr "Maßnahmentyp wählen"
msgid "Action" msgid "Action"
msgstr "Aktionen" msgstr "Aktionen"
#: compensation/forms/modalForms.py:354 compensation/forms/modalForms.py:366 #: compensation/forms/modalForms.py:380 compensation/forms/modalForms.py:392
msgid "Action Type detail" msgid "Action Type detail"
msgstr "Zusatzmerkmal" msgstr "Zusatzmerkmal"
#: compensation/forms/modalForms.py:357 #: compensation/forms/modalForms.py:383
msgid "Select the action type detail" msgid "Select the action type detail"
msgstr "Zusatzmerkmal wählen" msgstr "Zusatzmerkmal wählen"
#: compensation/forms/modalForms.py:371 #: compensation/forms/modalForms.py:397
msgid "Unit" msgid "Unit"
msgstr "Einheit" msgstr "Einheit"
#: compensation/forms/modalForms.py:374 #: compensation/forms/modalForms.py:400
msgid "Select the unit" msgid "Select the unit"
msgstr "Einheit wählen" msgstr "Einheit wählen"
#: compensation/forms/modalForms.py:386 #: compensation/forms/modalForms.py:412
msgid "Insert the amount" msgid "Insert the amount"
msgstr "Menge eingeben" msgstr "Menge eingeben"
#: compensation/forms/modalForms.py:411 #: compensation/forms/modalForms.py:437
msgid "New action" msgid "New action"
msgstr "Neue Maßnahme" msgstr "Neue Maßnahme"
#: compensation/forms/modalForms.py:412 #: compensation/forms/modalForms.py:438
msgid "Insert data for the new action" msgid "Insert data for the new action"
msgstr "Geben Sie die Daten der neuen Maßnahme ein" msgstr "Geben Sie die Daten der neuen Maßnahme ein"
@ -657,35 +656,35 @@ msgstr ""
"Es wurde bereits mehr Fläche abgebucht, als Sie nun als abbuchbar einstellen " "Es wurde bereits mehr Fläche abgebucht, als Sie nun als abbuchbar einstellen "
"wollen. Kontaktieren Sie die für die Abbuchungen verantwortlichen Nutzer!" "wollen. Kontaktieren Sie die für die Abbuchungen verantwortlichen Nutzer!"
#: compensation/tables.py:35 compensation/tables.py:204 ema/tables.py:39 #: compensation/tables.py:35 compensation/tables.py:207 ema/tables.py:39
#: intervention/tables.py:34 konova/filters/mixins.py:98 #: intervention/tables.py:34 konova/filters/mixins.py:98
msgid "Parcel gmrkng" msgid "Parcel gmrkng"
msgstr "Gemarkung" msgstr "Gemarkung"
#: compensation/tables.py:52 compensation/tables.py:225 ema/tables.py:50 #: compensation/tables.py:52 compensation/tables.py:228 ema/tables.py:50
#: intervention/tables.py:51 #: intervention/tables.py:51
msgid "Editable" msgid "Editable"
msgstr "Freigegeben" msgstr "Freigegeben"
#: compensation/tables.py:58 compensation/tables.py:231 ema/tables.py:56 #: compensation/tables.py:58 compensation/tables.py:234 ema/tables.py:56
#: intervention/tables.py:57 #: intervention/tables.py:57
msgid "Last edit" msgid "Last edit"
msgstr "Zuletzt bearbeitet" msgstr "Zuletzt bearbeitet"
#: compensation/tables.py:90 compensation/tables.py:263 ema/tables.py:89 #: compensation/tables.py:89 compensation/tables.py:266 ema/tables.py:89
#: intervention/tables.py:88 #: intervention/tables.py:88
msgid "Open {}" msgid "Open {}"
msgstr "Öffne {}" msgstr "Öffne {}"
#: compensation/tables.py:111 intervention/tables.py:111 #: compensation/tables.py:114 intervention/tables.py:111
msgid "Not checked yet" msgid "Not checked yet"
msgstr "Noch nicht geprüft" msgstr "Noch nicht geprüft"
#: compensation/tables.py:116 intervention/tables.py:116 #: compensation/tables.py:119 intervention/tables.py:116
msgid "Checked on {} by {}" msgid "Checked on {} by {}"
msgstr "Am {} von {} geprüft worden" msgstr "Am {} von {} geprüft worden"
#: compensation/tables.py:157 #: compensation/tables.py:160
#: compensation/templates/compensation/detail/compensation/view.html:80 #: compensation/templates/compensation/detail/compensation/view.html:80
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:58 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:58
#: compensation/templates/compensation/detail/eco_account/view.html:47 #: compensation/templates/compensation/detail/eco_account/view.html:47
@ -695,32 +694,32 @@ msgstr "Am {} von {} geprüft worden"
msgid "Not recorded yet" msgid "Not recorded yet"
msgstr "Noch nicht verzeichnet" msgstr "Noch nicht verzeichnet"
#: compensation/tables.py:162 compensation/tables.py:323 ema/tables.py:136 #: compensation/tables.py:165 compensation/tables.py:326 ema/tables.py:136
#: intervention/tables.py:162 #: intervention/tables.py:162
msgid "Recorded on {} by {}" msgid "Recorded on {} by {}"
msgstr "Am {} von {} verzeichnet worden" msgstr "Am {} von {} verzeichnet worden"
#: compensation/tables.py:186 compensation/tables.py:345 ema/tables.py:159 #: compensation/tables.py:189 compensation/tables.py:348 ema/tables.py:159
#: intervention/tables.py:185 #: intervention/tables.py:185
msgid "Full access granted" msgid "Full access granted"
msgstr "Für Sie freigegeben - Datensatz kann bearbeitet werden" msgstr "Für Sie freigegeben - Datensatz kann bearbeitet werden"
#: compensation/tables.py:186 compensation/tables.py:345 ema/tables.py:159 #: compensation/tables.py:189 compensation/tables.py:348 ema/tables.py:159
#: intervention/tables.py:185 #: intervention/tables.py:185
msgid "Access not granted" msgid "Access not granted"
msgstr "Nicht freigegeben - Datensatz nur lesbar" msgstr "Nicht freigegeben - Datensatz nur lesbar"
#: compensation/tables.py:209 #: compensation/tables.py:212
#: compensation/templates/compensation/detail/eco_account/view.html:35 #: compensation/templates/compensation/detail/eco_account/view.html:35
#: konova/templates/konova/widgets/progressbar.html:3 #: konova/templates/konova/widgets/progressbar.html:3
msgid "Available" msgid "Available"
msgstr "Verfügbar" msgstr "Verfügbar"
#: compensation/tables.py:240 #: compensation/tables.py:243
msgid "Eco Accounts" msgid "Eco Accounts"
msgstr "Ökokonten" msgstr "Ökokonten"
#: compensation/tables.py:318 #: compensation/tables.py:321
msgid "Not recorded yet. Can not be used for deductions, yet." msgid "Not recorded yet. Can not be used for deductions, yet."
msgstr "" msgstr ""
"Noch nicht verzeichnet. Kann noch nicht für Abbuchungen genutzt werden." "Noch nicht verzeichnet. Kann noch nicht für Abbuchungen genutzt werden."
@ -822,7 +821,7 @@ msgstr "Dokumente"
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:14 #: compensation/templates/compensation/detail/eco_account/includes/documents.html:14
#: ema/templates/ema/detail/includes/documents.html:14 #: ema/templates/ema/detail/includes/documents.html:14
#: intervention/templates/intervention/detail/includes/documents.html:14 #: intervention/templates/intervention/detail/includes/documents.html:14
#: konova/forms.py:408 #: konova/forms.py:409
msgid "Add new document" msgid "Add new document"
msgstr "Neues Dokument hinzufügen" msgstr "Neues Dokument hinzufügen"
@ -993,7 +992,7 @@ msgid "Recorded on"
msgstr "Verzeichnet am" msgstr "Verzeichnet am"
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:65 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:65
#: intervention/templates/intervention/detail/includes/deductions.html:60 #: intervention/templates/intervention/detail/includes/deductions.html:63
msgid "Remove Deduction" msgid "Remove Deduction"
msgstr "Abbuchung entfernen" msgstr "Abbuchung entfernen"
@ -1085,64 +1084,64 @@ msgstr "Kompensationen - Übersicht"
msgid "Compensation {} edited" msgid "Compensation {} edited"
msgstr "Kompensation {} bearbeitet" msgstr "Kompensation {} bearbeitet"
#: compensation/views/compensation.py:159 compensation/views/eco_account.py:161 #: compensation/views/compensation.py:159 compensation/views/eco_account.py:163
#: ema/views.py:230 intervention/views.py:313 #: ema/views.py:230 intervention/views.py:305
msgid "Edit {}" msgid "Edit {}"
msgstr "Bearbeite {}" msgstr "Bearbeite {}"
#: compensation/views/compensation.py:238 compensation/views/eco_account.py:317 #: compensation/views/compensation.py:238 compensation/views/eco_account.py:347
#: ema/views.py:191 intervention/views.py:491 #: ema/views.py:191 intervention/views.py:483
msgid "Log" msgid "Log"
msgstr "Log" msgstr "Log"
#: compensation/views/compensation.py:487 compensation/views/eco_account.py:590 #: compensation/views/compensation.py:487 compensation/views/eco_account.py:620
#: ema/views.py:477 intervention/views.py:609 #: ema/views.py:477 intervention/views.py:629
msgid "Report {}" msgid "Report {}"
msgstr "Bericht {}" msgstr "Bericht {}"
#: compensation/views/eco_account.py:60 #: compensation/views/eco_account.py:62
msgid "Eco-account - Overview" msgid "Eco-account - Overview"
msgstr "Ökokonten - Übersicht" msgstr "Ökokonten - Übersicht"
#: compensation/views/eco_account.py:93 #: compensation/views/eco_account.py:95
msgid "Eco-Account {} added" msgid "Eco-Account {} added"
msgstr "Ökokonto {} hinzugefügt" msgstr "Ökokonto {} hinzugefügt"
#: compensation/views/eco_account.py:151 #: compensation/views/eco_account.py:153
msgid "Eco-Account {} edited" msgid "Eco-Account {} edited"
msgstr "Ökokonto {} bearbeitet" msgstr "Ökokonto {} bearbeitet"
#: compensation/views/eco_account.py:264 #: compensation/views/eco_account.py:266
msgid "Eco-account removed" msgid "Eco-account removed"
msgstr "Ökokonto entfernt" msgstr "Ökokonto entfernt"
#: compensation/views/eco_account.py:338 ema/views.py:272 #: compensation/views/eco_account.py:368 ema/views.py:272
#: intervention/views.py:562 #: intervention/views.py:582
msgid "{} unrecorded" msgid "{} unrecorded"
msgstr "{} entzeichnet" msgstr "{} entzeichnet"
#: compensation/views/eco_account.py:338 ema/views.py:272 #: compensation/views/eco_account.py:368 ema/views.py:272
#: intervention/views.py:562 #: intervention/views.py:582
msgid "{} recorded" msgid "{} recorded"
msgstr "{} verzeichnet" msgstr "{} verzeichnet"
#: compensation/views/eco_account.py:663 ema/views.py:543 #: compensation/views/eco_account.py:693 ema/views.py:543
#: intervention/views.py:388 #: intervention/views.py:380
msgid "{} has already been shared with you" msgid "{} has already been shared with you"
msgstr "{} wurde bereits für Sie freigegeben" msgstr "{} wurde bereits für Sie freigegeben"
#: compensation/views/eco_account.py:668 ema/views.py:548 #: compensation/views/eco_account.py:698 ema/views.py:548
#: intervention/views.py:393 #: intervention/views.py:385
msgid "{} has been shared with you" msgid "{} has been shared with you"
msgstr "{} ist nun für Sie freigegeben" msgstr "{} ist nun für Sie freigegeben"
#: compensation/views/eco_account.py:675 ema/views.py:555 #: compensation/views/eco_account.py:705 ema/views.py:555
#: intervention/views.py:400 #: intervention/views.py:392
msgid "Share link invalid" msgid "Share link invalid"
msgstr "Freigabelink ungültig" msgstr "Freigabelink ungültig"
#: compensation/views/eco_account.py:698 ema/views.py:578 #: compensation/views/eco_account.py:728 ema/views.py:578
#: intervention/views.py:423 #: intervention/views.py:415
msgid "Share settings updated" msgid "Share settings updated"
msgstr "Freigabe Einstellungen aktualisiert" msgstr "Freigabe Einstellungen aktualisiert"
@ -1314,7 +1313,7 @@ msgstr "Kompensationen und Zahlungen geprüft"
msgid "Run check" msgid "Run check"
msgstr "Prüfung vornehmen" msgstr "Prüfung vornehmen"
#: intervention/forms/modalForms.py:213 konova/forms.py:474 #: intervention/forms/modalForms.py:213 konova/forms.py:475
msgid "" msgid ""
"I, {} {}, confirm that all necessary control steps have been performed by " "I, {} {}, confirm that all necessary control steps have been performed by "
"myself." "myself."
@ -1338,7 +1337,7 @@ msgstr "Neue Abbuchung"
msgid "Enter the information for a new deduction from a chosen eco-account" msgid "Enter the information for a new deduction from a chosen eco-account"
msgstr "Geben Sie die Informationen für eine neue Abbuchung ein." msgstr "Geben Sie die Informationen für eine neue Abbuchung ein."
#: intervention/forms/modalForms.py:366 #: intervention/forms/modalForms.py:381
msgid "" msgid ""
"Eco-account {} is not recorded yet. You can only deduct from recorded " "Eco-account {} is not recorded yet. You can only deduct from recorded "
"accounts." "accounts."
@ -1346,7 +1345,7 @@ msgstr ""
"Ökokonto {} ist noch nicht verzeichnet. Abbuchungen können nur von " "Ökokonto {} ist noch nicht verzeichnet. Abbuchungen können nur von "
"verzeichneten Ökokonten erfolgen." "verzeichneten Ökokonten erfolgen."
#: intervention/forms/modalForms.py:379 #: intervention/forms/modalForms.py:391
msgid "" msgid ""
"The account {} has not enough surface for a deduction of {} m². There are " "The account {} has not enough surface for a deduction of {} m². There are "
"only {} m² left" "only {} m² left"
@ -1374,6 +1373,10 @@ msgstr "Ökokonto gelöscht! Abbuchung ungültig!"
msgid "Eco-account not recorded! Deduction invalid!" msgid "Eco-account not recorded! Deduction invalid!"
msgstr "Ökokonto nicht verzeichnet! Abbuchung ungültig!" msgstr "Ökokonto nicht verzeichnet! Abbuchung ungültig!"
#: intervention/templates/intervention/detail/includes/deductions.html:60
msgid "Edit Deduction"
msgstr "Abbuchung bearbeiten"
#: intervention/templates/intervention/detail/includes/payments.html:8 #: intervention/templates/intervention/detail/includes/payments.html:8
#: intervention/templates/intervention/report/report.html:73 #: intervention/templates/intervention/report/report.html:73
msgid "Payments" msgid "Payments"
@ -1389,6 +1392,10 @@ msgid "Amount"
msgstr "Betrag" msgstr "Betrag"
#: intervention/templates/intervention/detail/includes/payments.html:59 #: intervention/templates/intervention/detail/includes/payments.html:59
msgid "Edit payment"
msgstr "Zahlung bearbeitet"
#: intervention/templates/intervention/detail/includes/payments.html:62
msgid "Remove payment" msgid "Remove payment"
msgstr "Zahlung entfernen" msgstr "Zahlung entfernen"
@ -1449,23 +1456,19 @@ msgstr "Eingriffe - Übersicht"
msgid "Intervention {} added" msgid "Intervention {} added"
msgstr "Eingriff {} hinzugefügt" msgstr "Eingriff {} hinzugefügt"
#: intervention/views.py:252 #: intervention/views.py:293
msgid "This intervention has {} revocations"
msgstr "Dem Eingriff liegen {} Widersprüche vor"
#: intervention/views.py:301
msgid "Intervention {} edited" msgid "Intervention {} edited"
msgstr "Eingriff {} bearbeitet" msgstr "Eingriff {} bearbeitet"
#: intervention/views.py:337 #: intervention/views.py:329
msgid "{} removed" msgid "{} removed"
msgstr "{} entfernt" msgstr "{} entfernt"
#: intervention/views.py:444 #: intervention/views.py:436
msgid "Check performed" msgid "Check performed"
msgstr "Prüfung durchgeführt" msgstr "Prüfung durchgeführt"
#: intervention/views.py:567 #: intervention/views.py:587
msgid "There are errors on this intervention:" msgid "There are errors on this intervention:"
msgstr "Es liegen Fehler in diesem Eingriff vor:" msgstr "Es liegen Fehler in diesem Eingriff vor:"
@ -1557,73 +1560,73 @@ msgstr "Speichern"
msgid "Not editable" msgid "Not editable"
msgstr "Nicht editierbar" msgstr "Nicht editierbar"
#: konova/forms.py:139 konova/forms.py:311 #: konova/forms.py:140 konova/forms.py:312
msgid "Confirm" msgid "Confirm"
msgstr "Bestätige" msgstr "Bestätige"
#: konova/forms.py:151 konova/forms.py:320 #: konova/forms.py:152 konova/forms.py:321
msgid "Remove" msgid "Remove"
msgstr "Löschen" msgstr "Löschen"
#: konova/forms.py:153 #: konova/forms.py:154
msgid "You are about to remove {} {}" msgid "You are about to remove {} {}"
msgstr "Sie sind dabei {} {} zu löschen" msgstr "Sie sind dabei {} {} zu löschen"
#: konova/forms.py:240 konova/utils/quality.py:44 konova/utils/quality.py:46 #: konova/forms.py:241 konova/utils/quality.py:44 konova/utils/quality.py:46
#: templates/form/collapsable/form.html:45 #: templates/form/collapsable/form.html:45
msgid "Geometry" msgid "Geometry"
msgstr "Geometrie" msgstr "Geometrie"
#: konova/forms.py:321 #: konova/forms.py:322
msgid "Are you sure?" msgid "Are you sure?"
msgstr "Sind Sie sicher?" msgstr "Sind Sie sicher?"
#: konova/forms.py:365 #: konova/forms.py:366
msgid "Created on" msgid "Created on"
msgstr "Erstellt" msgstr "Erstellt"
#: konova/forms.py:367 #: konova/forms.py:368
msgid "When has this file been created? Important for photos." msgid "When has this file been created? Important for photos."
msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?" msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?"
#: konova/forms.py:378 #: konova/forms.py:379
#: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231 #: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231
msgid "File" msgid "File"
msgstr "Datei" msgstr "Datei"
#: konova/forms.py:380 #: konova/forms.py:381
msgid "Allowed formats: pdf, jpg, png. Max size 15 MB." msgid "Allowed formats: pdf, jpg, png. Max size 15 MB."
msgstr "Formate: pdf, jpg, png. Maximal 15 MB." msgstr "Formate: pdf, jpg, png. Maximal 15 MB."
#: konova/forms.py:426 #: konova/forms.py:427
msgid "Unsupported file type" msgid "Unsupported file type"
msgstr "Dateiformat nicht unterstützt" msgstr "Dateiformat nicht unterstützt"
#: konova/forms.py:433 #: konova/forms.py:434
msgid "File too large" msgid "File too large"
msgstr "Datei zu groß" msgstr "Datei zu groß"
#: konova/forms.py:442 #: konova/forms.py:443
msgid "Added document" msgid "Added document"
msgstr "Dokument hinzugefügt" msgstr "Dokument hinzugefügt"
#: konova/forms.py:465 #: konova/forms.py:466
msgid "Confirm record" msgid "Confirm record"
msgstr "Verzeichnen bestätigen" msgstr "Verzeichnen bestätigen"
#: konova/forms.py:473 #: konova/forms.py:474
msgid "Record data" msgid "Record data"
msgstr "Daten verzeichnen" msgstr "Daten verzeichnen"
#: konova/forms.py:480 #: konova/forms.py:481
msgid "Confirm unrecord" msgid "Confirm unrecord"
msgstr "Entzeichnen bestätigen" msgstr "Entzeichnen bestätigen"
#: konova/forms.py:481 #: konova/forms.py:482
msgid "Unrecord data" msgid "Unrecord data"
msgstr "Daten entzeichnen" msgstr "Daten entzeichnen"
#: konova/forms.py:482 #: konova/forms.py:483
msgid "I, {} {}, confirm that this data must be unrecorded." msgid "I, {} {}, confirm that this data must be unrecorded."
msgstr "" msgstr ""
"Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen." "Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen."
@ -1825,69 +1828,97 @@ msgid "State removed"
msgstr "Zustand gelöscht" msgstr "Zustand gelöscht"
#: konova/utils/message_templates.py:33 #: konova/utils/message_templates.py:33
msgid "State edited"
msgstr "Zustand bearbeitet"
#: konova/utils/message_templates.py:34
msgid "State added" msgid "State added"
msgstr "Zustand hinzugefügt" msgstr "Zustand hinzugefügt"
#: konova/utils/message_templates.py:36 #: konova/utils/message_templates.py:37
msgid "Action added" msgid "Action added"
msgstr "Maßnahme hinzugefügt" msgstr "Maßnahme hinzugefügt"
#: konova/utils/message_templates.py:37 #: konova/utils/message_templates.py:38
msgid "Action edited"
msgstr "Maßnahme bearbeitet"
#: konova/utils/message_templates.py:39
msgid "Action removed" msgid "Action removed"
msgstr "Maßnahme entfernt" msgstr "Maßnahme entfernt"
#: konova/utils/message_templates.py:40 #: konova/utils/message_templates.py:42
msgid "Deduction added" msgid "Deduction added"
msgstr "Abbuchung hinzugefügt" msgstr "Abbuchung hinzugefügt"
#: konova/utils/message_templates.py:41 #: konova/utils/message_templates.py:43
msgid "Deduction edited"
msgstr "Abbuchung bearbeitet"
#: konova/utils/message_templates.py:44
msgid "Deduction removed" msgid "Deduction removed"
msgstr "Abbuchung entfernt" msgstr "Abbuchung entfernt"
#: konova/utils/message_templates.py:44 #: konova/utils/message_templates.py:47
msgid "Deadline added" msgid "Deadline added"
msgstr "Frist/Termin hinzugefügt" msgstr "Frist/Termin hinzugefügt"
#: konova/utils/message_templates.py:45 #: konova/utils/message_templates.py:48
msgid "Deadline edited"
msgstr "Frist/Termin bearbeitet"
#: konova/utils/message_templates.py:49
msgid "Deadline removed" msgid "Deadline removed"
msgstr "Frist/Termin gelöscht" msgstr "Frist/Termin gelöscht"
#: konova/utils/message_templates.py:48 #: konova/utils/message_templates.py:52
msgid "Payment added" msgid "Payment added"
msgstr "Zahlung hinzugefügt" msgstr "Zahlung hinzugefügt"
#: konova/utils/message_templates.py:49 #: konova/utils/message_templates.py:53
msgid "Payment edited"
msgstr "Zahlung bearbeitet"
#: konova/utils/message_templates.py:54
msgid "Payment removed" msgid "Payment removed"
msgstr "Zahlung gelöscht" msgstr "Zahlung gelöscht"
#: konova/utils/message_templates.py:52 #: konova/utils/message_templates.py:57
msgid "Revocation added" msgid "Revocation added"
msgstr "Widerspruch hinzugefügt" msgstr "Widerspruch hinzugefügt"
#: konova/utils/message_templates.py:53 #: konova/utils/message_templates.py:58
msgid "Revocation edited"
msgstr "Widerspruch bearbeitet"
#: konova/utils/message_templates.py:59
msgid "Revocation removed" msgid "Revocation removed"
msgstr "Widerspruch entfernt" msgstr "Widerspruch entfernt"
#: konova/utils/message_templates.py:56 #: konova/utils/message_templates.py:62
msgid "Document '{}' deleted" msgid "Document '{}' deleted"
msgstr "Dokument '{}' gelöscht" msgstr "Dokument '{}' gelöscht"
#: konova/utils/message_templates.py:57 #: konova/utils/message_templates.py:63
msgid "Document added" msgid "Document added"
msgstr "Dokument hinzugefügt" msgstr "Dokument hinzugefügt"
#: konova/utils/message_templates.py:60 #: konova/utils/message_templates.py:66
msgid "Edited general data" msgid "Edited general data"
msgstr "Allgemeine Daten bearbeitet" msgstr "Allgemeine Daten bearbeitet"
#: konova/utils/message_templates.py:61 #: konova/utils/message_templates.py:67
msgid "Added deadline" msgid "Added deadline"
msgstr "Frist/Termin hinzugefügt" msgstr "Frist/Termin hinzugefügt"
#: konova/utils/message_templates.py:64 #: konova/utils/message_templates.py:70
msgid "Geometry conflict detected with {}" msgid "Geometry conflict detected with {}"
msgstr "Geometriekonflikt mit folgenden Einträgen erkannt: {}" msgstr "Geometriekonflikt mit folgenden Einträgen erkannt: {}"
#: konova/utils/message_templates.py:73
msgid "This intervention has {} revocations"
msgstr "Dem Eingriff liegen {} Widersprüche vor"
#: konova/utils/messenger.py:70 #: konova/utils/messenger.py:70
msgid "{} checked" msgid "{} checked"
msgstr "{} geprüft" msgstr "{} geprüft"
@ -3942,9 +3973,6 @@ msgstr ""
#~ msgid "No file given!" #~ msgid "No file given!"
#~ msgstr "Keine Datei angegeben!" #~ msgstr "Keine Datei angegeben!"
#~ msgid "Added payment"
#~ msgstr "Zahlung hinzufügen"
#~ msgid "Added state" #~ msgid "Added state"
#~ msgstr "Zustand hinzugefügt" #~ msgstr "Zustand hinzugefügt"