diff --git a/compensation/forms/compensation.py b/compensation/forms/compensation.py index cc3fa57..bb1532d 100644 --- a/compensation/forms/compensation.py +++ b/compensation/forms/compensation.py @@ -213,7 +213,6 @@ class EditCompensationForm(NewCompensationForm): action = UserActionLogEntry.get_edited_action(user) # Fetch data from cleaned POST values - identifier = self.cleaned_data.get("identifier", None) title = self.cleaned_data.get("title", None) intervention = self.cleaned_data.get("intervention", None) is_cef = self.cleaned_data.get("is_cef", None) @@ -221,7 +220,6 @@ class EditCompensationForm(NewCompensationForm): is_pik = self.cleaned_data.get("is_pik", None) comment = self.cleaned_data.get("comment", None) - self.instance.identifier = identifier self.instance.title = title self.instance.intervention = intervention self.instance.is_cef = is_cef diff --git a/compensation/forms/eco_account.py b/compensation/forms/eco_account.py index 0352383..4244302 100644 --- a/compensation/forms/eco_account.py +++ b/compensation/forms/eco_account.py @@ -192,7 +192,6 @@ class EditEcoAccountForm(NewEcoAccountForm): def save(self, user: User, geom_form: SimpleGeomForm): with transaction.atomic(): # Fetch data from cleaned POST values - identifier = self.cleaned_data.get("identifier", None) title = self.cleaned_data.get("title", None) registration_date = self.cleaned_data.get("registration_date", None) handler_type = self.cleaned_data.get("handler_type", None) @@ -219,7 +218,6 @@ class EditEcoAccountForm(NewEcoAccountForm): self.instance.legal.save() # Update main oject data - self.instance.identifier = identifier self.instance.title = title self.instance.deductable_surface = surface self.instance.comment = comment diff --git a/compensation/models/compensation.py b/compensation/models/compensation.py index eaf8ccb..49accd4 100644 --- a/compensation/models/compensation.py +++ b/compensation/models/compensation.py @@ -315,7 +315,6 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin, PikMixin): def get_detail_url_absolute(self): return BASE_URL + self.get_detail_url() - def save(self, *args, **kwargs): if self.identifier is None or len(self.identifier) == 0: # Create new identifier is none was given diff --git a/compensation/tests/compensation/test_workflow.py b/compensation/tests/compensation/test_workflow.py index ee520a5..3f02448 100644 --- a/compensation/tests/compensation/test_workflow.py +++ b/compensation/tests/compensation/test_workflow.py @@ -125,10 +125,16 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase): self.compensation = self.fill_out_compensation(self.compensation) pre_edit_log_count = self.compensation.log.count() + self.assertTrue(self.compensation.is_shared_with(self.superuser)) + + old_identifier = self.compensation.identifier new_title = self.create_dummy_string() new_identifier = self.create_dummy_string() new_comment = self.create_dummy_string() - new_geometry = MultiPolygon(srid=4326) # Create an empty geometry + new_geometry = MultiPolygon( + self.compensation.geometry.geom.buffer(10), + srid=self.compensation.geometry.geom.srid + ) # Create a geometry which differs from the stored one geojson = self.create_geojson(new_geometry) check_on_elements = { @@ -151,19 +157,21 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase): check_on_elements = { self.compensation.title: new_title, - self.compensation.identifier: new_identifier, self.compensation.comment: new_comment, } for k, v in check_on_elements.items(): self.assertEqual(k, v) - self.assert_equal_geometries(self.compensation.geometry.geom, new_geometry) + # Expect identifier to not be editable + self.assertEqual(self.compensation.identifier, old_identifier, msg="Identifier was editable!") # Expect logs to be set self.assertEqual(pre_edit_log_count + 1, self.compensation.log.count()) self.assertEqual(self.compensation.log.first().action, UserAction.EDITED) + self.assert_equal_geometries(self.compensation.geometry.geom, new_geometry) + def test_checkability(self): """ This tests if the checkability of the compensation (which is defined by the linked intervention's checked diff --git a/compensation/tests/ecoaccount/test_workflow.py b/compensation/tests/ecoaccount/test_workflow.py index 3bfaffe..85f7db5 100644 --- a/compensation/tests/ecoaccount/test_workflow.py +++ b/compensation/tests/ecoaccount/test_workflow.py @@ -82,6 +82,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase): url = reverse("compensation:acc:edit", args=(self.eco_account.id,)) pre_edit_log_count = self.eco_account.log.count() + old_identifier = self.eco_account.identifier new_title = self.create_dummy_string() new_identifier = self.create_dummy_string() new_comment = self.create_dummy_string() @@ -114,7 +115,6 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase): check_on_elements = { self.eco_account.title: new_title, - self.eco_account.identifier: new_identifier, self.eco_account.deductable_surface: test_deductable_surface, self.eco_account.deductable_rest: test_deductable_surface - deductions_surface, self.eco_account.comment: new_comment, @@ -123,6 +123,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase): for k, v in check_on_elements.items(): self.assertEqual(k, v) + self.assertEqual(self.eco_account.identifier, old_identifier) self.assert_equal_geometries(self.eco_account.geometry.geom, new_geometry) # Expect logs to be set diff --git a/ema/forms.py b/ema/forms.py index d6b77a4..26bbc2d 100644 --- a/ema/forms.py +++ b/ema/forms.py @@ -133,7 +133,6 @@ class EditEmaForm(NewEmaForm): def save(self, user: User, geom_form: SimpleGeomForm): with transaction.atomic(): # Fetch data from cleaned POST values - identifier = self.cleaned_data.get("identifier", None) title = self.cleaned_data.get("title", None) handler_type = self.cleaned_data.get("handler_type", None) handler_detail = self.cleaned_data.get("handler_detail", None) @@ -154,7 +153,6 @@ class EditEmaForm(NewEmaForm): self.instance.responsible.save() # Update main oject data - self.instance.identifier = identifier self.instance.title = title self.instance.comment = comment self.instance.is_pik = is_pik diff --git a/ema/tests/test_workflow.py b/ema/tests/test_workflow.py index 3e5bd29..c622811 100644 --- a/ema/tests/test_workflow.py +++ b/ema/tests/test_workflow.py @@ -80,6 +80,7 @@ class EmaWorkflowTestCase(BaseWorkflowTestCase): self.ema = self.fill_out_ema(self.ema) pre_edit_log_count = self.ema.log.count() + old_identifier = self.ema.identifier new_title = self.create_dummy_string() new_identifier = self.create_dummy_string() new_comment = self.create_dummy_string() @@ -106,13 +107,13 @@ class EmaWorkflowTestCase(BaseWorkflowTestCase): check_on_elements = { self.ema.title: new_title, - self.ema.identifier: new_identifier, self.ema.comment: new_comment, } for k, v in check_on_elements.items(): self.assertEqual(k, v) + self.assertEqual(self.ema.identifier, old_identifier) self.assert_equal_geometries(self.ema.geometry.geom, new_geometry) # Expect logs to be set diff --git a/ema/tests/unit/test_forms.py b/ema/tests/unit/test_forms.py index ff87b9f..d66fd14 100644 --- a/ema/tests/unit/test_forms.py +++ b/ema/tests/unit/test_forms.py @@ -130,7 +130,7 @@ class EditEmaFormTestCase(BaseTestCase): self.assertIsNotNone(obj.responsible.handler) self.assertEqual(obj.responsible.conservation_office, data["conservation_office"]) self.assertEqual(obj.responsible.conservation_file_number, data["conservation_file_number"]) - self.assertEqual(obj.identifier, data["identifier"]) + self.assertNotEqual(obj.identifier, data["identifier"], msg="Identifier editable via form!") self.assertEqual(obj.comment, data["comment"]) last_log = obj.log.first() diff --git a/intervention/forms/intervention.py b/intervention/forms/intervention.py index 72986ad..629bfe2 100644 --- a/intervention/forms/intervention.py +++ b/intervention/forms/intervention.py @@ -345,7 +345,6 @@ class EditInterventionForm(NewInterventionForm): """ with transaction.atomic(): - identifier = self.cleaned_data.get("identifier", None) title = self.cleaned_data.get("title", None) process_type = self.cleaned_data.get("type", None) laws = self.cleaned_data.get("laws", None) @@ -379,7 +378,6 @@ class EditInterventionForm(NewInterventionForm): self.instance.log.add(user_action) - self.instance.identifier = identifier self.instance.title = title self.instance.comment = comment self.instance.modified = user_action diff --git a/intervention/forms/modals/check.py b/intervention/forms/modals/check.py index cc06e63..6111134 100644 --- a/intervention/forms/modals/check.py +++ b/intervention/forms/modals/check.py @@ -33,7 +33,7 @@ class CheckModalForm(BaseModalForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.form_title = _("Run check") - self.form_caption = _("I, {} {}, confirm that all necessary control steps have been performed by myself.").format(self.user.first_name, self.user.last_name) + self.form_caption = _("The necessary control steps have been performed:").format(self.user.first_name, self.user.last_name) self.valid = False def _are_deductions_valid(self): diff --git a/intervention/tables.py b/intervention/tables.py index 39cae2a..76d4018 100644 --- a/intervention/tables.py +++ b/intervention/tables.py @@ -33,6 +33,11 @@ class InterventionTable(BaseTable, TableRenderMixin, TableOrderMixin): verbose_name=_("Parcel gmrkng"), orderable=False, accessor="geometry", + attrs={ + "th": { + "class": "w-25", + } + } ) c = tables.Column( verbose_name=_("Checked"), diff --git a/intervention/views/intervention.py b/intervention/views/intervention.py index e04f628..3371167 100644 --- a/intervention/views/intervention.py +++ b/intervention/views/intervention.py @@ -39,7 +39,7 @@ def index_view(request: HttpRequest): """ template = "generic_index.html" - # Filtering by user access is performed in table filter inside of InterventionTableFilter class + # Filtering by user access is performed in table filter inside InterventionTableFilter class interventions = Intervention.objects.filter( deleted=None, # not deleted ).select_related( diff --git a/konova/admin.py b/konova/admin.py index 07d692d..ff82ac2 100644 --- a/konova/admin.py +++ b/konova/admin.py @@ -151,7 +151,7 @@ class ResubmissionAdmin(BaseResourceAdmin): # Outcommented for a cleaner admin backend on production -#admin.site.register(Geometry, GeometryAdmin) +admin.site.register(Geometry, GeometryAdmin) #admin.site.register(Parcel, ParcelAdmin) #admin.site.register(District, DistrictAdmin) #admin.site.register(Municipal, MunicipalAdmin) diff --git a/konova/forms/geometry_form.py b/konova/forms/geometry_form.py index 59664fd..75b6e97 100644 --- a/konova/forms/geometry_form.py +++ b/konova/forms/geometry_form.py @@ -98,12 +98,14 @@ class SimpleGeomForm(BaseForm): if g.geom_type not in accepted_ogr_types: self.add_error("geom", _("Only surfaces allowed. Points or lines must be buffered.")) - is_valid = False + is_valid &= False return is_valid + is_valid &= self.__is_area_valid(g) + polygon = Polygon.from_ewkt(g.ewkt) - is_valid = polygon.valid - if not is_valid: + is_valid &= polygon.valid + if not polygon.valid: self.add_error("geom", polygon.valid_reason) return is_valid @@ -137,6 +139,24 @@ class SimpleGeomForm(BaseForm): return num_vertices <= GEOM_MAX_VERTICES + def __is_area_valid(self, geom: gdal.OGRGeometry): + """ Checks whether the area is at least > 1m² + + Returns: + + """ + is_area_valid = geom.area > 1 # > 1m² (SRID:25832) + + if not is_area_valid: + self.add_error( + "geom", + _("Geometry must be greater than 1m². Currently is {}m²").format( + float(geom.area) + ) + ) + + return is_area_valid + def __simplify_geometry(self, geom, max_vert: int): """ Simplifies a geometry diff --git a/konova/forms/modals/record_form.py b/konova/forms/modals/record_form.py index 812b697..b27111a 100644 --- a/konova/forms/modals/record_form.py +++ b/konova/forms/modals/record_form.py @@ -27,7 +27,7 @@ class RecordModalForm(BaseModalForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.form_title = _("Record data") - self.form_caption = _("I, {} {}, confirm that all necessary control steps have been performed by myself.").format(self.user.first_name, self.user.last_name) + self.form_caption = _("The necessary control steps have been performed:").format(self.user.first_name, self.user.last_name) # Disable automatic w-100 setting for this type of modal form. Looks kinda strange self.fields["confirm"].widget.attrs["class"] = "" diff --git a/konova/management/commands/sanitize_db.py b/konova/management/commands/sanitize_db.py index b296b2a..e8ee1ce 100644 --- a/konova/management/commands/sanitize_db.py +++ b/konova/management/commands/sanitize_db.py @@ -61,15 +61,25 @@ class Command(BaseKonovaCommand): action=UserAction.CREATED ) - intervention_log_entries_ids = self.get_all_log_entries_ids(Intervention) - attached_log_entries_id = intervention_log_entries_ids.union( - self.get_all_log_entries_ids(Compensation), - self.get_all_log_entries_ids(EcoAccount), - self.get_all_log_entries_ids(Ema), + EIV_log_entries_ids = self.get_all_log_entries_ids(Intervention) + self._write_warning(f" EIV: {EIV_log_entries_ids.count()} attached log entries") + KOM_log_entries_ids = self.get_all_log_entries_ids(Compensation) + self._write_warning(f" KOM: {KOM_log_entries_ids.count()} attached log entries") + OEK_log_entries_ids = self.get_all_log_entries_ids(EcoAccount) + self._write_warning(f" OEK: {OEK_log_entries_ids.count()} attached log entries") + EMA_log_entries_ids = self.get_all_log_entries_ids(Ema) + self._write_warning(f" EMA: {EMA_log_entries_ids.count()} attached log entries") + + unattached_log_entries = all_log_entries.exclude( + id__in=EIV_log_entries_ids + ).exclude( + id__in=KOM_log_entries_ids + ).exclude( + id__in=OEK_log_entries_ids + ).exclude( + id__in=EMA_log_entries_ids ) - unattached_log_entries = all_log_entries.exclude(id__in=attached_log_entries_id) - num_entries = unattached_log_entries.count() if num_entries > 0: self._write_error(f"Found {num_entries} log entries not attached to anything. Delete now...") @@ -108,14 +118,21 @@ class Command(BaseKonovaCommand): self._write_warning("=== Sanitize compensation actions ===") all_actions = CompensationAction.objects.all() - compensation_action_ids = self.get_all_action_ids(Compensation) - attached_action_ids = compensation_action_ids.union( - self.get_all_action_ids(EcoAccount), - self.get_all_action_ids(Ema), + kom_action_ids = self.get_all_action_ids(Compensation) + self._write_warning(f" KOM: {kom_action_ids.count()} attached actions") + oek_action_ids = self.get_all_action_ids(EcoAccount) + self._write_warning(f" OEK: {oek_action_ids.count()} attached actions") + ema_action_ids = self.get_all_action_ids(Ema) + self._write_warning(f" EMA: {ema_action_ids.count()} attached actions") + + unattached_actions = all_actions.exclude( + id__in=kom_action_ids + ).exclude( + id__in=oek_action_ids + ).exclude( + id__in=ema_action_ids ) - unattached_actions = all_actions.exclude(id__in=attached_action_ids) - num_entries = unattached_actions.count() if num_entries > 0: self._write_error(f"Found {num_entries} actions not attached to anything. Delete now...") @@ -125,7 +142,7 @@ class Command(BaseKonovaCommand): self._write_success("No unattached actions found.") self._break_line() - def get_all_deadline_ids(self, cls): + def _get_all_deadline_ids(self, cls): """ Getter for all deadline ids of a model Args: @@ -154,14 +171,21 @@ class Command(BaseKonovaCommand): self._write_warning("=== Sanitize deadlines ===") all_deadlines = Deadline.objects.all() - compensation_deadline_ids = self.get_all_deadline_ids(Compensation) - attached_deadline_ids = compensation_deadline_ids.union( - self.get_all_deadline_ids(EcoAccount), - self.get_all_deadline_ids(Ema), + kom_deadline_ids = self._get_all_deadline_ids(Compensation) + self._write_warning(f" KOM: {kom_deadline_ids.count()} attached deadlines") + oek_deadline_ids = self._get_all_deadline_ids(EcoAccount) + self._write_warning(f" OEK: {kom_deadline_ids.count()} attached deadlines") + ema_deadline_ids = self._get_all_deadline_ids(Ema) + self._write_warning(f" EMA: {kom_deadline_ids.count()} attached deadlines") + + unattached_deadlines = all_deadlines.exclude( + id__in=kom_deadline_ids + ).exclude( + id__in=oek_deadline_ids + ).exclude( + id__in=ema_deadline_ids ) - unattached_deadlines = all_deadlines.exclude(id__in=attached_deadline_ids) - num_entries = unattached_deadlines.count() if num_entries > 0: self._write_error(f"Found {num_entries} deadlines not attached to anything. Delete now...") @@ -171,7 +195,7 @@ class Command(BaseKonovaCommand): self._write_success("No unattached deadlines found.") self._break_line() - def get_all_geometry_ids(self, cls): + def _get_all_geometry_ids(self, cls): """ Getter for all geometry ids of a model Args: @@ -200,15 +224,25 @@ class Command(BaseKonovaCommand): self._write_warning("=== Sanitize geometries ===") all_geometries = Geometry.objects.all() - compensation_geometry_ids = self.get_all_geometry_ids(Compensation) - attached_geometry_ids = compensation_geometry_ids.union( - self.get_all_geometry_ids(Intervention), - self.get_all_geometry_ids(EcoAccount), - self.get_all_geometry_ids(Ema), + kom_geometry_ids = self._get_all_geometry_ids(Compensation) + self._write_warning(f" KOM: {kom_geometry_ids.count()} attached geometries") + eiv_geometry_ids = self._get_all_geometry_ids(Intervention) + self._write_warning(f" EIV: {eiv_geometry_ids.count()} attached geometries") + oek_geometry_ids = self._get_all_geometry_ids(EcoAccount) + self._write_warning(f" OEK: {oek_geometry_ids.count()} attached geometries") + ema_geometry_ids = self._get_all_geometry_ids(Ema) + self._write_warning(f" EMA: {ema_geometry_ids.count()} attached geometries") + + unattached_geometries = all_geometries.exclude( + id__in=kom_geometry_ids + ).exclude( + id__in=eiv_geometry_ids + ).exclude( + id__in=oek_geometry_ids + ).exclude( + id__in=ema_geometry_ids ) - unattached_geometries = all_geometries.exclude(id__in=attached_geometry_ids) - num_entries = unattached_geometries.count() if num_entries > 0: self._write_error(f"Found {num_entries} geometries not attached to anything. Delete now...") @@ -218,7 +252,7 @@ class Command(BaseKonovaCommand): self._write_success("No unattached geometries found.") self._break_line() - def get_all_state_ids(self, cls): + def _get_all_state_ids(self, cls): """ Getter for all states (before and after) of a class Args: @@ -254,14 +288,19 @@ class Command(BaseKonovaCommand): """ self._write_warning("=== Sanitize compensation states ===") all_states = CompensationState.objects.all() - compensation_state_ids = self.get_all_state_ids(Compensation) - account_state_ids = self.get_all_state_ids(EcoAccount) - ema_state_ids = self.get_all_state_ids(Ema) - attached_state_ids = compensation_state_ids.union(account_state_ids, ema_state_ids) + + kom_state_ids = self._get_all_state_ids(Compensation) + oek_state_ids = self._get_all_state_ids(EcoAccount) + ema_state_ids = self._get_all_state_ids(Ema) unattached_states = all_states.exclude( - id__in=attached_state_ids + id__in=kom_state_ids + ).exclude( + id__in=oek_state_ids + ).exclude( + id__in=ema_state_ids ) + num_unattached_states = unattached_states.count() if num_unattached_states > 0: self._write_error(f"Found {num_unattached_states} unused compensation states. Delete now...") diff --git a/konova/migrations/0015_geometry_parcel_calculation_end_and_more.py b/konova/migrations/0015_geometry_parcel_calculation_end_and_more.py new file mode 100644 index 0000000..c3eac77 --- /dev/null +++ b/konova/migrations/0015_geometry_parcel_calculation_end_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.1 on 2024-01-09 10:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('konova', '0014_resubmission'), + ] + + operations = [ + migrations.AddField( + model_name='geometry', + name='parcel_update_end', + field=models.DateTimeField(blank=True, db_comment='When the last parcel calculation finished', help_text='When the last parcel calculation finished', null=True), + ), + migrations.AddField( + model_name='geometry', + name='parcel_update_start', + field=models.DateTimeField(blank=True, db_comment='When the last parcel calculation started', help_text='When the last parcel calculation started', null=True), + ), + ] diff --git a/konova/migrations/0016_remove_parcelintersection_calculated_on.py b/konova/migrations/0016_remove_parcelintersection_calculated_on.py new file mode 100644 index 0000000..c4a0f92 --- /dev/null +++ b/konova/migrations/0016_remove_parcelintersection_calculated_on.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.1 on 2024-02-16 07:34 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('konova', '0015_geometry_parcel_calculation_end_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='parcelintersection', + name='calculated_on', + ), + ] diff --git a/konova/models/geometry.py b/konova/models/geometry.py index 81ba2d7..254ace4 100644 --- a/konova/models/geometry.py +++ b/konova/models/geometry.py @@ -8,19 +8,31 @@ Created on: 15.11.21 import json from django.contrib.gis.db.models import MultiPolygonField +from django.core.exceptions import ObjectDoesNotExist from django.db import models, transaction from django.utils import timezone from konova.models import BaseResource, UuidModel from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP from konova.utils.schneider.fetcher import ParcelFetcher -from konova.utils.wfs.spatial import ParcelWFSFetcher class Geometry(BaseResource): """ Geometry model """ + parcel_update_start = models.DateTimeField( + blank=True, + null=True, + db_comment="When the last parcel calculation started", + help_text="When the last parcel calculation started" + ) + parcel_update_end = models.DateTimeField( + blank=True, + null=True, + db_comment="When the last parcel calculation finished", + help_text="When the last parcel calculation finished", + ) geom = MultiPolygonField(null=True, blank=True, srid=DEFAULT_SRID_RLP) def __str__(self): @@ -109,82 +121,14 @@ class Geometry(BaseResource): objs += set_objs return objs - @transaction.atomic - def update_parcels_wfs(self): - """ Updates underlying parcel information using the WFS of LVermGeo - - Returns: - + def get_data_object(self): """ - from konova.models import Parcel, District, ParcelIntersection, Municipal, ParcelGroup - - if self.geom.empty: - # Nothing to do - return - - parcel_fetcher = ParcelWFSFetcher( - geometry_id=self.id, - ) - typename = "ave:Flurstueck" - fetched_parcels = parcel_fetcher.get_features( - typename - ) - _now = timezone.now() - underlying_parcels = [] - for result in fetched_parcels: - parcel_properties = result["properties"] - # There could be parcels which include the word 'Flur', - # which needs to be deleted and just keep the numerical values - ## THIS CAN BE REMOVED IN THE FUTURE, WHEN 'Flur' WON'T OCCUR ANYMORE! - flr_val = parcel_properties["flur"].replace("Flur ", "") - district = District.objects.get_or_create( - key=parcel_properties["kreisschl"], - name=parcel_properties["kreis"], - )[0] - municipal = Municipal.objects.get_or_create( - key=parcel_properties["gmdschl"], - name=parcel_properties["gemeinde"], - district=district, - )[0] - parcel_group = ParcelGroup.objects.get_or_create( - key=parcel_properties["gemaschl"], - name=parcel_properties["gemarkung"], - municipal=municipal, - )[0] - flrstck_nnr = parcel_properties['flstnrnen'] - if not flrstck_nnr: - flrstck_nnr = None - flrstck_zhlr = parcel_properties['flstnrzae'] - if not flrstck_zhlr: - flrstck_zhlr = None - parcel_obj = Parcel.objects.get_or_create( - district=district, - municipal=municipal, - parcel_group=parcel_group, - flr=flr_val, - flrstck_nnr=flrstck_nnr, - flrstck_zhlr=flrstck_zhlr, - )[0] - parcel_obj.district = district - parcel_obj.updated_on = _now - parcel_obj.save() - underlying_parcels.append(parcel_obj) - - # Update the linked parcels - self.parcels.clear() - 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"] - ) + Getter for the specific data object which is related to this geometry + """ + objs = self.get_data_objects() + assert (len(objs) <= 1) + result = objs.pop() + return result def update_parcels(self): """ Updates underlying parcel information @@ -192,82 +136,149 @@ class Geometry(BaseResource): Returns: """ - from konova.models import Parcel, District, ParcelIntersection, Municipal, ParcelGroup - if self.geom.empty: # Nothing to do return + self._set_parcel_update_start_time() + self._perform_parcel_update() + self._set_parcel_update_end_time() + + def _perform_parcel_update(self): + """ + Performs the main logic of parcel updating. + """ + from konova.models import Parcel, District, Municipal, ParcelGroup + parcel_fetcher = ParcelFetcher( geometry=self ) fetched_parcels = parcel_fetcher.get_parcels() - _now = timezone.now() - underlying_parcels = [] + + districts = {} + municipals = {} + parcel_groups = {} + + parcels_to_update = [] + parcels_to_create = [] for result in fetched_parcels: # There could be parcels which include the word 'Flur', # which needs to be deleted and just keep the numerical values ## THIS CAN BE REMOVED IN THE FUTURE, WHEN 'Flur' WON'T OCCUR ANYMORE! flr_val = result["flur"].replace("Flur ", "") - district = District.objects.get_or_create( - key=result["kreisschl"], - name=result["kreis"], - )[0] - municipal = Municipal.objects.get_or_create( - key=result["gmdschl"], - name=result["gemeinde"], - district=district, - )[0] - parcel_group = ParcelGroup.objects.get_or_create( - key=result["gemaschl"], - name=result["gemarkung"], - municipal=municipal, - )[0] + + # Get district (cache in dict) + try: + district = districts["kreisschl"] + except KeyError: + district = District.objects.get_or_create( + key=result["kreisschl"], + name=result["kreis"], + )[0] + districts[district.key] = district + + # Get municipal (cache in dict) + try: + municipal = municipals["gmdschl"] + except KeyError: + municipal = Municipal.objects.get_or_create( + key=result["gmdschl"], + name=result["gemeinde"], + district=district, + )[0] + municipals[municipal.key] = municipal + + # Get parcel group (cache in dict) + try: + parcel_group = parcel_groups["gemaschl"] + except KeyError: + parcel_group = ParcelGroup.objects.get_or_create( + key=result["gemaschl"], + name=result["gemarkung"], + municipal=municipal, + )[0] + parcel_groups[parcel_group.key] = parcel_group + + # Preprocess parcel data flrstck_nnr = result['flstnrnen'] - if not flrstck_nnr: - flrstck_nnr = None - flrstck_zhlr = result['flstnrzae'] - if not flrstck_zhlr: - flrstck_zhlr = None - parcel_obj = Parcel.objects.get_or_create( - district=district, - municipal=municipal, - parcel_group=parcel_group, - flr=flr_val, - flrstck_nnr=flrstck_nnr, - flrstck_zhlr=flrstck_zhlr, - )[0] - parcel_obj.district = district - parcel_obj.updated_on = _now - parcel_obj.save() - underlying_parcels.append(parcel_obj) - - # Update the linked parcels - self.parcels.clear() - self.parcels.set(underlying_parcels) + match flrstck_nnr: + case "": + flrstck_nnr = None - # 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, + flrstck_zhlr = result['flstnrzae'] + match flrstck_zhlr: + case "": + flrstck_zhlr = None + + try: + # Try to fetch parcel from db. If it already exists, just update timestamp. + parcel_obj = Parcel.objects.get( + district=district, + municipal=municipal, + parcel_group=parcel_group, + flr=flr_val, + flrstck_nnr=flrstck_nnr, + flrstck_zhlr=flrstck_zhlr, + ) + parcel_obj.updated_on = _now + parcels_to_update.append(parcel_obj) + except ObjectDoesNotExist: + # If not existing, create object but do not commit, yet + parcel_obj = Parcel( + district=district, + municipal=municipal, + parcel_group=parcel_group, + flr=flr_val, + flrstck_nnr=flrstck_nnr, + flrstck_zhlr=flrstck_zhlr, + updated_on=_now, + ) + parcels_to_create.append(parcel_obj) + + # Create new parcels + Parcel.objects.bulk_create( + parcels_to_create, + batch_size=500 ) - for entry in intersections_without_ts: - entry.calculated_on = _now - ParcelIntersection.objects.bulk_update( - intersections_without_ts, - ["calculated_on"] + # Update existing parcels + Parcel.objects.bulk_update( + parcels_to_update, + [ + "updated_on" + ], + batch_size=500 ) + # Update linking to geometry + parcel_ids = [x.id for x in parcels_to_update] + [x.id for x in parcels_to_create] + underlying_parcels = Parcel.objects.filter(id__in=parcel_ids) + self.parcels.set(underlying_parcels) + + @transaction.atomic + def _set_parcel_update_start_time(self): + """ + Sets the current time for the parcel calculation begin + """ + self.parcel_update_start = timezone.now() + self.parcel_update_end = None + self.save() + + @transaction.atomic + def _set_parcel_update_end_time(self): + """ + Sets the current time for the parcel calculation end + """ + self.parcel_update_end = timezone.now() + self.save() + def get_underlying_parcels(self): """ Getter for related parcels and their districts Returns: parcels (QuerySet): The related parcels as queryset """ - parcels = self.parcels.filter( - parcelintersection__calculated_on__isnull=False, - ).prefetch_related( + parcels = self.parcels.prefetch_related( "district", "municipal", ).order_by( @@ -292,17 +303,6 @@ class Geometry(BaseResource): municipals = Municipal.objects.filter(id__in=municipals).order_by("name") return municipals - def count_underlying_parcels(self): - """ Getter for number of underlying parcels - - Returns: - - """ - num_parcels = self.parcels.filter( - parcelintersection__calculated_on__isnull=False, - ).count() - return num_parcels - def as_feature_collection(self, srid=DEFAULT_SRID_RLP): """ Returns a FeatureCollection structure holding all polygons of the MultiPolygon as single features @@ -337,6 +337,36 @@ class Geometry(BaseResource): } return geojson + @property + def complexity_factor(self) -> float: + """ Calculates a factor to estimate the complexity of a Geometry + + 0 = very low complexity + 1 = very high complexity + + ASSUMPTION: + The envelope is the bounding box of a geometry. If the geometry's area is similar to the area of it's bounding + box, it is considered as rather simple, since it seems to be a closer shape like a simple box. + If the geometry has a very big bounding box, but the geometry's own area is rather small, + compared to the one of the bounding box, the complexity can be higher. + + Example: + geometry area similar to bounding box --> geometry / bounding_box ~ 1 + geometry area far smaller than bb --> geometry / bounding_box ~ 0 + + Result is being inverted for better understanding of 'low' and 'high' complexity. + + Returns: + complexity_factor (float): The estimated complexity + """ + if self.geom.empty: + return 0 + + geom_envelope = self.geom.envelope + diff = geom_envelope - self.geom + complexity_factor = 1 - self.geom.area / diff.area + return complexity_factor + class GeometryConflict(UuidModel): """ diff --git a/konova/models/object.py b/konova/models/object.py index e2acaeb..f1a41c9 100644 --- a/konova/models/object.py +++ b/konova/models/object.py @@ -672,17 +672,6 @@ class GeoReferencedMixin(models.Model): result = self.geometry.get_underlying_parcels() return result - def count_underlying_parcels(self): - """ Getter for number of underlying parcels - - Returns: - - """ - result = 0 - if self.geometry is not None: - result = self.geometry.count_underlying_parcels() - return result - def set_geometry_conflict_message(self, request: HttpRequest): if self.geometry is None: return request diff --git a/konova/models/parcel.py b/konova/models/parcel.py index cc91e00..7cf0044 100644 --- a/konova/models/parcel.py +++ b/konova/models/parcel.py @@ -160,19 +160,9 @@ class Parcel(UuidModel): class ParcelIntersection(UuidModel): - """ ParcelIntersection is an intermediary model, which is used to configure the + """ + ParcelIntersection is an intermediary model, which is used to add extras to 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) diff --git a/konova/settings.py b/konova/settings.py index e1c2fb4..e1e0e46 100644 --- a/konova/settings.py +++ b/konova/settings.py @@ -46,4 +46,8 @@ DEFAULT_GROUP = "Default" ZB_GROUP = "Registration office" ETS_GROUP = "Conservation office" +# GEOMETRY +## Max number of allowed vertices. Geometries larger will be simplified until they reach this threshold GEOM_MAX_VERTICES = 10000 +## Max seconds to wait for a parcel calculation result before a new request will be started (default: 30 minutes) +GEOM_THRESHOLD_RECALCULATION_SECONDS = 60 * 30 diff --git a/konova/tasks.py b/konova/tasks.py index aa8a65b..ea68cde 100644 --- a/konova/tasks.py +++ b/konova/tasks.py @@ -10,15 +10,9 @@ def celery_update_parcels(geometry_id: str, recheck: bool = True): from konova.models import Geometry, ParcelIntersection try: geom = Geometry.objects.get(id=geometry_id) - objs = geom.parcelintersection_set.all() - for obj in objs: - obj.calculated_on = None - ParcelIntersection.objects.bulk_update( - objs, - ["calculated_on"] - ) - + geom.parcels.clear() geom.update_parcels() + except ObjectDoesNotExist: if recheck: sleep(5) diff --git a/konova/tests/test_views.py b/konova/tests/test_views.py index c540e78..860b361 100644 --- a/konova/tests/test_views.py +++ b/konova/tests/test_views.py @@ -146,7 +146,6 @@ class BaseTestCase(TestCase): geometry = Geometry.objects.create() # Finally create main object, holding the other objects intervention = Intervention.objects.create( - identifier="TEST", title="Test_title", responsible=responsibility_data, legal=legal_data, @@ -174,7 +173,6 @@ class BaseTestCase(TestCase): geometry = Geometry.objects.create() # Finally create main object, holding the other objects compensation = Compensation.objects.create( - identifier="TEST", title="Test_title", intervention=interv, created=action, @@ -200,10 +198,8 @@ class BaseTestCase(TestCase): responsible_data.handler = handler responsible_data.save() - identifier = EcoAccount().generate_new_identifier() # Finally create main object, holding the other objects eco_account = EcoAccount.objects.create( - identifier=identifier, title="Test_title", deductable_surface=500, legal=lega_data, @@ -230,7 +226,6 @@ class BaseTestCase(TestCase): responsible_data.save() # Finally create main object, holding the other objects ema = Ema.objects.create( - identifier="TEST", title="Test_title", responsible=responsible_data, created=action, @@ -474,7 +469,7 @@ class BaseTestCase(TestCase): eco_account.save() return eco_account - def assert_equal_geometries(self, geom1: MultiPolygon, geom2: MultiPolygon): + def assert_equal_geometries(self, geom1: MultiPolygon, geom2: MultiPolygon, tolerance = 0.001): """ Assert for geometries to be equal Transforms the geometries to matching srids before checking @@ -491,7 +486,6 @@ class BaseTestCase(TestCase): self.assertTrue(True) return - tolerance = 0.001 if geom1.srid != geom2.srid: # Due to prior possible transformation of any of these geometries, we need to make sure there exists a # transformation from one coordinate system into the other, which is valid diff --git a/konova/tests/unit/test_forms.py b/konova/tests/unit/test_forms.py index 3cbb437..190a756 100644 --- a/konova/tests/unit/test_forms.py +++ b/konova/tests/unit/test_forms.py @@ -152,7 +152,7 @@ class RecordModalFormTestCase(BaseTestCase): ) self.assertEqual(form.form_title, str(_("Record data"))) self.assertEqual(form.form_caption, str( - _("I, {} {}, confirm that all necessary control steps have been performed by myself.").format( + _("The necessary control steps have been performed:").format( self.user.first_name, self.user.last_name ) diff --git a/konova/utils/schneider/fetcher.py b/konova/utils/schneider/fetcher.py index 0bc87f7..87aa751 100644 --- a/konova/utils/schneider/fetcher.py +++ b/konova/utils/schneider/fetcher.py @@ -28,7 +28,11 @@ class ParcelFetcher: self.geometry = geometry # Reduce size of geometry to avoid "intersections" because of exact border matching - geom = geometry.geom.buffer(-0.001) + buffer_threshold = 0.001 + geom = geometry.geom.buffer(-buffer_threshold) + if geom.area < buffer_threshold: + # Fallback for malicious geometries which are way too small and would disappear on negative buffering + geom = geometry.geom self.geojson = geom.ewkt self.results = [] diff --git a/konova/utils/tables.py b/konova/utils/tables.py index d64c766..4878548 100644 --- a/konova/utils/tables.py +++ b/konova/utils/tables.py @@ -173,9 +173,13 @@ class TableRenderMixin: Returns: """ + value_orig = value max_length = 75 if len(value) > max_length: value = f"{value[:max_length]}..." + value = format_html( + f'
{value}
' + ) return value def render_d(self, value, record: GeoReferencedMixin): diff --git a/konova/utils/wfs/spatial.py b/konova/utils/wfs/spatial.py deleted file mode 100644 index a3d4bd6..0000000 --- a/konova/utils/wfs/spatial.py +++ /dev/null @@ -1,189 +0,0 @@ -""" -Author: Michel Peltriaux -Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany -Contact: michel.peltriaux@sgdnord.rlp.de -Created on: 17.12.21 - -""" -import json -from abc import abstractmethod -from json import JSONDecodeError -from time import sleep - -import requests -from django.contrib.gis.db.models.functions import AsGML, MakeValid -from django.db.models import Func, F -from requests.auth import HTTPDigestAuth - -from konova.settings import PARCEL_WFS_USER, PARCEL_WFS_PW, PROXIES - - -class AbstractWFSFetcher: - """ Base class for fetching WFS data - - """ - # base_url represents not the capabilities url but the parameter-free base url - base_url = None - version = None - auth_user = None - auth_pw = None - auth_digest_obj = None - - class Meta: - abstract = True - - def __init__(self, base_url: str, version: str = "1.1.0", auth_user: str = None, auth_pw: str = None, *args, **kwargs): - self.base_url = base_url - self.version = version - self.auth_pw = auth_pw - self.auth_user = auth_user - - self._create_auth_obj() - - def _create_auth_obj(self): - if self.auth_pw is not None and self.auth_user is not None: - self.auth_digest_obj = HTTPDigestAuth( - self.auth_user, - self.auth_pw - ) - - @abstractmethod - def get_features(self, feature_identifier: str, filter_str: str): - raise NotImplementedError - - -class ParcelWFSFetcher(AbstractWFSFetcher): - """ Fetches features from a special parcel WFS - - """ - geometry_id = None - geometry_property_name = None - count = 100 - - def __init__(self, geometry_id: str, geometry_property_name: str = "msGeometry", *args, **kwargs): - super().__init__( - version="2.0.0", - base_url="https://www.geoportal.rlp.de/registry/wfs/519", - auth_user=PARCEL_WFS_USER, - auth_pw=PARCEL_WFS_PW, - *args, - **kwargs - ) - self.geometry_id = geometry_id - self.geometry_property_name = geometry_property_name - - def _create_spatial_filter(self, - geometry_operation: str): - """ Creates a xml spatial filter according to the WFS filter specification - - The geometry needs to be shrinked by a very small factor (-0.01) before a GML can be created for intersection - checking. Otherwise perfect parcel outline placement on top of a neighbouring parcel would result in an - intersection hit, despite the fact they do not truly intersect just because their vertices match. - - Args: - geometry_operation (str): One of the WFS supported spatial filter operations (according to capabilities) - - Returns: - spatial_filter (str): The spatial filter element - """ - from konova.models import Geometry - - geom = Geometry.objects.filter( - id=self.geometry_id - ).annotate( - smaller=Func(F('geom'), -0.001, function="ST_Buffer") # same as geometry.geom_small_buffered but for QuerySet - ).annotate( - gml=AsGML(MakeValid('smaller')) - ).first() - geom_gml = geom.gml - spatial_filter = f"<{geometry_operation}>{self.geometry_property_name}{geom_gml}" - return spatial_filter - - def _create_post_data(self, - geometry_operation: str, - typenames: str = None, - start_index: int = 0, - ): - """ Creates a POST body content for fetching features - - Args: - geometry_operation (str): One of the WFS supported spatial filter operations (according to capabilities) - - Returns: - _filter (str): A proper xml WFS filter - """ - start_index = str(start_index) - spatial_filter = self._create_spatial_filter( - geometry_operation - ) - _filter = f'{spatial_filter}' - return _filter - - def get_features(self, - typenames: str, - spatial_operator: str = "Intersects", - filter_srid: str = None, - start_index: int = 0, - rerun_on_exception: bool = True - ): - """ Fetches features from the WFS using POST - - POST is required since GET has a character limit around 4000. Having a larger filter would result in errors, - which do not occur in case of POST. - - Args: - typenames (str): References to parameter 'typenames' in a WFS GetFeature request - spatial_operator (str): Defines the spatial operation for filtering - filter_srid (str): Defines the spatial reference system, the geometry shall be transformed into for filtering - start_index (str): References to parameter 'startindex' in a - - Returns: - features (list): A list of returned features - """ - found_features = [] - while start_index is not None: - post_body = self._create_post_data( - spatial_operator, - typenames, - start_index - ) - response = requests.post( - url=self.base_url, - data=post_body, - auth=self.auth_digest_obj, - proxies=PROXIES, - ) - - content = response.content.decode("utf-8") - try: - # Check if collection is an exception and does not contain the requested data - content = json.loads(content) - except JSONDecodeError as e: - if rerun_on_exception: - # Wait a second before another try - sleep(1) - self.get_features( - typenames, - spatial_operator, - filter_srid, - start_index, - rerun_on_exception=False - ) - else: - e.msg += content - raise e - fetched_features = content.get( - "features", - {}, - ) - - found_features += fetched_features - - if len(fetched_features) < self.count: - # The response was not 'full', so we got everything to fetch - start_index = None - else: - # If a 'full' response returned, there might be more to fetch. Increase the start_index! - start_index += self.count - - return found_features diff --git a/konova/views/geometry.py b/konova/views/geometry.py index bf34e61..767762f 100644 --- a/konova/views/geometry.py +++ b/konova/views/geometry.py @@ -10,10 +10,13 @@ from django.contrib.gis.geos import MultiPolygon from django.http import HttpResponse, HttpRequest from django.shortcuts import get_object_or_404 from django.template.loader import render_to_string +from django.utils import timezone from django.views import View from konova.models import Geometry +from konova.settings import GEOM_THRESHOLD_RECALCULATION_SECONDS from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP +from konova.tasks import celery_update_parcels class GeomParcelsView(LoginRequiredMixin, View): @@ -30,24 +33,43 @@ class GeomParcelsView(LoginRequiredMixin, View): Returns: A rendered piece of HTML """ - # HTTP code 286 states that the HTMX should stop polling for updates - # https://htmx.org/docs/#polling - status_code = 286 template = "konova/includes/parcels/parcel_table_frame.html" + geom = get_object_or_404(Geometry, id=id) - parcels = geom.get_underlying_parcels() geos_geom = geom.geom or MultiPolygon(srid=DEFAULT_SRID_RLP) + geometry_exists = not geos_geom.empty and geos_geom.area > 0 + geom_parcel_update_started = geom.parcel_update_start is not None + geom_parcel_update_finished = geom.parcel_update_end is not None + + parcels = geom.get_underlying_parcels() + parcels_are_available = len(parcels) > 0 - geometry_exists = not geos_geom.empty - parcels_are_currently_calculated = geometry_exists and geos_geom.area > 0 and len(parcels) == 0 - parcels_available = len(parcels) > 0 + waiting_too_long = self._check_waiting_too_long(geom) + + if geometry_exists and not parcels_are_available and waiting_too_long: + # Trigger calculation again - process may have failed silently + celery_update_parcels.delay(geom.id) + parcels_are_currently_calculated = True + else: + parcels_are_currently_calculated = ( + geometry_exists and + not parcels_are_available and + geom_parcel_update_started and + not geom_parcel_update_finished + ) if parcels_are_currently_calculated: # Parcels are being calculated right now. Change the status code, so polling stays active for fetching - # resutls after the calculation + # results after the calculation status_code = 200 + else: + # HTTP code 286 states that the HTMX should stop polling for updates + # https://htmx.org/docs/#polling + status_code = 286 - if parcels_available or not geometry_exists: + if parcels_are_available or not geometry_exists: + # Default case: Parcels are calculated or there is no geometry at all + # (so there will be no parcels to expect) municipals = geom.get_underlying_municipals(parcels) rpp = 100 @@ -69,6 +91,23 @@ class GeomParcelsView(LoginRequiredMixin, View): else: return HttpResponse(None, status=404) + def _check_waiting_too_long(self, geom: Geometry): + """ Check whether the client is waiting too long for a parcel calculation result + + Depending on the geometry's modified attribute + + """ + # Scale time to wait longer with increasing geometry complexity + complexity_factor = geom.complexity_factor + 1 + wait_for_seconds = int(GEOM_THRESHOLD_RECALCULATION_SECONDS * complexity_factor) + try: + pcs_diff = (timezone.now() - geom.parcel_update_start).seconds + except TypeError: + pcs_diff = wait_for_seconds + + waiting_too_long = (pcs_diff >= wait_for_seconds) + return waiting_too_long + class GeomParcelsContentView(LoginRequiredMixin, View): diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index cbfcbda..1db23bb 100644 Binary files a/locale/de/LC_MESSAGES/django.mo and b/locale/de/LC_MESSAGES/django.mo differ diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 4eef6ff..cd34fbe 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -29,7 +29,7 @@ #: konova/filters/mixins/office.py:25 konova/filters/mixins/office.py:56 #: konova/filters/mixins/office.py:57 konova/filters/mixins/record.py:23 #: konova/filters/mixins/self_created.py:24 konova/filters/mixins/share.py:23 -#: konova/forms/geometry_form.py:32 konova/forms/modals/document_form.py:26 +#: konova/forms/geometry_form.py:33 konova/forms/modals/document_form.py:26 #: konova/forms/modals/document_form.py:36 #: konova/forms/modals/document_form.py:50 #: konova/forms/modals/document_form.py:62 @@ -43,7 +43,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-08 11:30+0200\n" +"POT-Creation-Date: 2024-01-09 09:46+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -90,8 +90,8 @@ msgstr "Verantwortliche Stelle" #: intervention/forms/intervention.py:67 intervention/forms/intervention.py:84 #: intervention/forms/intervention.py:100 #: intervention/forms/intervention.py:116 -#: intervention/forms/intervention.py:157 intervention/forms/modals/share.py:41 -#: intervention/forms/modals/share.py:55 user/forms/modals/team.py:48 +#: intervention/forms/intervention.py:157 intervention/forms/modals/share.py:40 +#: intervention/forms/modals/share.py:54 user/forms/modals/team.py:48 #: user/forms/modals/team.py:112 msgid "Click for selection" msgstr "Auswählen..." @@ -267,8 +267,8 @@ msgstr "" #: ema/templates/ema/detail/includes/states-after.html:36 #: ema/templates/ema/detail/includes/states-before.html:36 #: intervention/forms/modals/deduction.py:47 -#: templates/email/other/deduction_changed.html:31 -#: templates/email/other/deduction_changed_team.html:31 +#: templates/email/other/deduction_changed.html:32 +#: templates/email/other/deduction_changed_team.html:32 msgid "Surface" msgstr "Fläche" @@ -321,7 +321,7 @@ msgstr "" #: intervention/templates/intervention/detail/view.html:39 #: intervention/templates/intervention/report/report.html:20 msgid "Law" -msgstr "Gesetz" +msgstr "Rechtsgrundlage" #: analysis/templates/analysis/reports/includes/old_data/amount.html:17 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:33 @@ -336,8 +336,8 @@ msgstr "Typ" #: intervention/forms/modals/deduction.py:65 intervention/tables.py:87 #: intervention/templates/intervention/detail/view.html:19 #: konova/templates/konova/includes/quickstart/interventions.html:4 -#: templates/email/other/deduction_changed.html:26 -#: templates/email/other/deduction_changed_team.html:26 +#: templates/email/other/deduction_changed.html:27 +#: templates/email/other/deduction_changed_team.html:27 #: templates/navbars/navbar.html:22 msgid "Intervention" msgstr "Eingriff" @@ -447,7 +447,7 @@ msgid "Select the intervention for which this compensation compensates" msgstr "Wählen Sie den Eingriff, für den diese Kompensation bestimmt ist" #: compensation/forms/compensation.py:114 -#: compensation/views/compensation/compensation.py:120 +#: compensation/views/compensation/compensation.py:119 msgid "New compensation" msgstr "Neue Kompensation" @@ -474,7 +474,7 @@ msgid "When did the parties agree on this?" msgstr "Wann wurde dieses Ökokonto offiziell vereinbart?" #: compensation/forms/eco_account.py:72 -#: compensation/views/eco_account/eco_account.py:101 +#: compensation/views/eco_account/eco_account.py:100 msgid "New Eco-Account" msgstr "Neues Ökokonto" @@ -691,11 +691,11 @@ msgstr "Zahlung wird an diesem Datum erwartet" msgid "Add a payment for intervention '{}'" msgstr "Neue Ersatzzahlung zu Eingriff '{}' hinzufügen" -#: compensation/forms/modals/payment.py:86 +#: compensation/forms/modals/payment.py:89 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." -#: compensation/forms/modals/payment.py:105 +#: compensation/forms/modals/payment.py:108 #: intervention/templates/intervention/detail/includes/payments.html:59 msgid "Edit payment" msgstr "Zahlung bearbeiten" @@ -959,7 +959,6 @@ msgstr "Log anzeigen" #: compensation/templates/compensation/detail/eco_account/includes/controls.html:41 #: ema/templates/ema/detail/includes/controls.html:41 #: intervention/templates/intervention/detail/includes/controls.html:46 -#: venv/lib/python3.7/site-packages/django/forms/formsets.py:391 msgid "Delete" msgstr "Löschen" @@ -1101,7 +1100,6 @@ msgstr "Fehlende Flächenmengen laut Zielzustand: " #: compensation/templates/compensation/report/eco_account/report.html:27 #: ema/templates/ema/detail/view.html:64 #: ema/templates/ema/report/report.html:27 -#: venv/lib/python3.7/site-packages/django/forms/widgets.py:710 msgid "Yes" msgstr "Ja" @@ -1115,7 +1113,6 @@ msgstr "Ja" #: compensation/templates/compensation/report/eco_account/report.html:29 #: ema/templates/ema/detail/view.html:66 #: ema/templates/ema/report/report.html:29 -#: venv/lib/python3.7/site-packages/django/forms/widgets.py:711 msgid "No" msgstr "Nein" @@ -1187,7 +1184,7 @@ msgstr "weitere Nutzer" #: compensation/templates/compensation/detail/eco_account/includes/controls.html:18 #: ema/templates/ema/detail/includes/controls.html:18 -#: intervention/forms/modals/share.py:63 +#: intervention/forms/modals/share.py:62 #: intervention/templates/intervention/detail/includes/controls.html:18 #: intervention/tests/unit/test_forms.py:150 msgid "Share" @@ -1290,18 +1287,18 @@ msgstr "" msgid "Responsible data" msgstr "Daten zu den verantwortlichen Stellen" -#: compensation/views/compensation/compensation.py:58 +#: compensation/views/compensation/compensation.py:57 msgid "Compensations - Overview" msgstr "Kompensationen - Übersicht" -#: compensation/views/compensation/compensation.py:181 +#: compensation/views/compensation/compensation.py:180 #: konova/utils/message_templates.py:40 msgid "Compensation {} edited" msgstr "Kompensation {} bearbeitet" -#: compensation/views/compensation/compensation.py:196 -#: compensation/views/eco_account/eco_account.py:173 ema/views/ema.py:231 -#: intervention/views/intervention.py:252 +#: compensation/views/compensation/compensation.py:195 +#: compensation/views/eco_account/eco_account.py:172 ema/views/ema.py:230 +#: intervention/views/intervention.py:251 msgid "Edit {}" msgstr "Bearbeite {}" @@ -1311,23 +1308,23 @@ msgstr "Bearbeite {}" msgid "Report {}" msgstr "Bericht {}" -#: compensation/views/eco_account/eco_account.py:53 +#: compensation/views/eco_account/eco_account.py:52 msgid "Eco-account - Overview" msgstr "Ökokonten - Übersicht" -#: compensation/views/eco_account/eco_account.py:86 +#: compensation/views/eco_account/eco_account.py:85 msgid "Eco-Account {} added" msgstr "Ökokonto {} hinzugefügt" -#: compensation/views/eco_account/eco_account.py:158 +#: compensation/views/eco_account/eco_account.py:157 msgid "Eco-Account {} edited" msgstr "Ökokonto {} bearbeitet" -#: compensation/views/eco_account/eco_account.py:287 +#: compensation/views/eco_account/eco_account.py:286 msgid "Eco-account removed" msgstr "Ökokonto entfernt" -#: ema/forms.py:42 ema/tests/unit/test_forms.py:27 ema/views/ema.py:102 +#: ema/forms.py:42 ema/tests/unit/test_forms.py:27 ema/views/ema.py:101 msgid "New EMA" msgstr "Neue EMA hinzufügen" @@ -1355,19 +1352,19 @@ msgstr "" msgid "Payment funded compensation" msgstr "Ersatzzahlungsmaßnahme" -#: ema/views/ema.py:53 +#: ema/views/ema.py:52 msgid "EMAs - Overview" msgstr "EMAs - Übersicht" -#: ema/views/ema.py:86 +#: ema/views/ema.py:85 msgid "EMA {} added" msgstr "EMA {} hinzugefügt" -#: ema/views/ema.py:216 +#: ema/views/ema.py:215 msgid "EMA {} edited" msgstr "EMA {} bearbeitet" -#: ema/views/ema.py:255 +#: ema/views/ema.py:254 msgid "EMA removed" msgstr "EMA entfernt" @@ -1431,7 +1428,7 @@ msgstr "Datum Bestandskraft bzw. Rechtskraft" #: intervention/forms/intervention.py:216 #: intervention/tests/unit/test_forms.py:36 -#: intervention/views/intervention.py:105 +#: intervention/views/intervention.py:104 msgid "New intervention" msgstr "Neuer Eingriff" @@ -1455,11 +1452,9 @@ msgstr "Prüfung vornehmen" #: intervention/forms/modals/check.py:36 konova/forms/modals/record_form.py:30 #: konova/tests/unit/test_forms.py:155 msgid "" -"I, {} {}, confirm that all necessary control steps have been performed by " -"myself." +"The necessary control steps have been performed:" msgstr "" -"Ich, {} {}, bestätige, dass die notwendigen Kontrollschritte durchgeführt " -"wurden:" +"Die notwendigen Kontrollschritte wurden durchgeführt:" #: intervention/forms/modals/deduction.py:33 msgid "Only recorded accounts can be selected for deductions" @@ -1525,19 +1520,19 @@ msgstr "Widerspruch hinzufügen" msgid "Edit revocation" msgstr "Widerspruch bearbeiten" -#: intervention/forms/modals/share.py:21 +#: intervention/forms/modals/share.py:20 msgid "Share link" msgstr "Freigabelink" -#: intervention/forms/modals/share.py:23 +#: intervention/forms/modals/share.py:22 msgid "Send this link to users who you want to have writing access on the data" msgstr "Einzelne Nutzer erhalten über diesen Link Zugriff auf die Daten" -#: intervention/forms/modals/share.py:33 +#: intervention/forms/modals/share.py:32 msgid "Add team to share with" msgstr "Team hinzufügen" -#: intervention/forms/modals/share.py:35 +#: intervention/forms/modals/share.py:34 msgid "" "Multiple selection possible - You can only select teams which do not already " "have access." @@ -1545,11 +1540,11 @@ msgstr "" "Mehrfachauswahl möglich - Sie können nur Teams wählen, für die der Eintrag " "noch nicht freigegeben wurde." -#: intervention/forms/modals/share.py:47 +#: intervention/forms/modals/share.py:46 msgid "Add user to share with" msgstr "Nutzer einzeln hinzufügen" -#: intervention/forms/modals/share.py:49 +#: intervention/forms/modals/share.py:48 msgid "" "Multiple selection possible - You can only select users which do not already " "have access. Enter the full username." @@ -1557,7 +1552,7 @@ msgstr "" "Mehrfachauswahl möglich - Sie können nur Nutzer wählen, für die der Eintrag " "noch nicht freigegeben wurde. Geben Sie den ganzen Nutzernamen an." -#: intervention/forms/modals/share.py:64 +#: intervention/forms/modals/share.py:63 #: intervention/tests/unit/test_forms.py:151 msgid "Share settings for {}" msgstr "Freigabe Einstellungen für {}" @@ -1666,19 +1661,19 @@ msgstr "" msgid "Check performed" msgstr "Prüfung durchgeführt" -#: intervention/views/intervention.py:57 +#: intervention/views/intervention.py:56 msgid "Interventions - Overview" msgstr "Eingriffe - Übersicht" -#: intervention/views/intervention.py:90 +#: intervention/views/intervention.py:89 msgid "Intervention {} added" msgstr "Eingriff {} hinzugefügt" -#: intervention/views/intervention.py:235 +#: intervention/views/intervention.py:234 msgid "Intervention {} edited" msgstr "Eingriff {} bearbeitet" -#: intervention/views/intervention.py:277 +#: intervention/views/intervention.py:276 msgid "{} removed" msgstr "{} entfernt" @@ -1796,22 +1791,25 @@ msgstr "Speichern" msgid "Not editable" msgstr "Nicht editierbar" -#: konova/forms/geometry_form.py:31 konova/utils/quality.py:44 +#: konova/forms/geometry_form.py:32 konova/utils/quality.py:44 #: konova/utils/quality.py:46 templates/form/collapsable/form.html:45 msgid "Geometry" msgstr "Geometrie" -#: konova/forms/geometry_form.py:100 +#: konova/forms/geometry_form.py:101 msgid "Only surfaces allowed. Points or lines must be buffered." msgstr "" "Nur Flächen erlaubt. Punkte oder Linien müssen zu Flächen gepuffert werden." +#: konova/forms/geometry_form.py:154 +msgid "Geometry must be greater than 1m². Currently is {}m²" +msgstr "Geometrie muss größer als 1m² sein. Aktueller Flächeninhalt: {}m²" + #: konova/forms/modals/document_form.py:37 msgid "When has this file been created? Important for photos." msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?" #: konova/forms/modals/document_form.py:49 -#: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231 msgid "File" msgstr "Datei" @@ -1915,11 +1913,11 @@ msgstr "Kontrolle am" msgid "Other" msgstr "Sonstige" -#: konova/sub_settings/django_settings.py:160 +#: konova/sub_settings/django_settings.py:166 msgid "German" msgstr "" -#: konova/sub_settings/django_settings.py:161 +#: konova/sub_settings/django_settings.py:167 msgid "English" msgstr "" @@ -2005,39 +2003,39 @@ msgstr "In Zwischenablage kopiert" msgid "Search" msgstr "Suchen" -#: konova/utils/mailer.py:71 konova/utils/mailer.py:154 +#: konova/utils/mailer.py:69 konova/utils/mailer.py:146 msgid "{} - Shared access removed" msgstr "{} - Zugriff entzogen" -#: konova/utils/mailer.py:98 konova/utils/mailer.py:126 +#: konova/utils/mailer.py:94 konova/utils/mailer.py:120 msgid "{} - Shared access given" msgstr "{} - Zugriff freigegeben" -#: konova/utils/mailer.py:182 konova/utils/mailer.py:347 +#: konova/utils/mailer.py:172 konova/utils/mailer.py:325 msgid "{} - Shared data unrecorded" msgstr "{} - Freigegebene Daten entzeichnet" -#: konova/utils/mailer.py:210 konova/utils/mailer.py:320 +#: konova/utils/mailer.py:198 konova/utils/mailer.py:300 msgid "{} - Shared data recorded" msgstr "{} - Freigegebene Daten verzeichnet" -#: konova/utils/mailer.py:238 konova/utils/mailer.py:401 +#: konova/utils/mailer.py:224 konova/utils/mailer.py:375 msgid "{} - Shared data checked" msgstr "{} - Freigegebene Daten geprüft" -#: konova/utils/mailer.py:265 konova/utils/mailer.py:429 +#: konova/utils/mailer.py:249 konova/utils/mailer.py:401 msgid "{} - Deduction changed" msgstr "{} - Abbuchung geändert" -#: konova/utils/mailer.py:293 konova/utils/mailer.py:374 +#: konova/utils/mailer.py:275 konova/utils/mailer.py:350 msgid "{} - Shared data deleted" msgstr "{} - Freigegebene Daten gelöscht" -#: konova/utils/mailer.py:450 templates/email/api/verify_token.html:4 +#: konova/utils/mailer.py:422 templates/email/api/verify_token.html:4 msgid "Request for new API token" msgstr "Anfrage für neuen API Token" -#: konova/utils/mailer.py:475 +#: konova/utils/mailer.py:447 msgid "Resubmission - {}" msgstr "Wiedervorlage - {}" @@ -2046,7 +2044,6 @@ msgid "no further details" msgstr "keine weitere Angabe" #: konova/utils/message_templates.py:13 -#: venv/lib/python3.7/site-packages/django/forms/widgets.py:709 msgid "Unknown" msgstr "Unbekannt" @@ -2417,8 +2414,8 @@ msgstr "" #: templates/email/checking/shared_data_checked_team.html:30 #: templates/email/deleting/shared_data_deleted.html:30 #: templates/email/deleting/shared_data_deleted_team.html:30 -#: templates/email/other/deduction_changed.html:41 -#: templates/email/other/deduction_changed_team.html:41 +#: templates/email/other/deduction_changed.html:42 +#: templates/email/other/deduction_changed_team.html:42 #: templates/email/recording/shared_data_recorded.html:30 #: templates/email/recording/shared_data_recorded_team.html:30 #: templates/email/recording/shared_data_unrecorded.html:30 @@ -2515,8 +2512,8 @@ msgstr "der folgende Datensatz wurde soeben gelöscht " #: templates/email/deleting/shared_data_deleted.html:27 #: templates/email/deleting/shared_data_deleted_team.html:27 -#: templates/email/other/deduction_changed.html:38 -#: templates/email/other/deduction_changed_team.html:38 +#: templates/email/other/deduction_changed.html:39 +#: templates/email/other/deduction_changed_team.html:39 msgid "" "If this should not have been happened, please contact us. See the signature " "for details." @@ -2534,24 +2531,24 @@ msgstr "Abbuchung geändert" msgid "a deduction of this eco account has changed:" msgstr "eine Abbuchung des Ökokontos hat sich geändert:" -#: templates/email/other/deduction_changed.html:16 -#: templates/email/other/deduction_changed_team.html:16 +#: templates/email/other/deduction_changed.html:17 +#: templates/email/other/deduction_changed_team.html:17 msgid "Attribute" msgstr "Attribute" -#: templates/email/other/deduction_changed.html:17 -#: templates/email/other/deduction_changed_team.html:17 +#: templates/email/other/deduction_changed.html:18 +#: templates/email/other/deduction_changed_team.html:18 msgid "Old" msgstr "Alt" -#: templates/email/other/deduction_changed.html:18 -#: templates/email/other/deduction_changed_team.html:18 +#: templates/email/other/deduction_changed.html:19 +#: templates/email/other/deduction_changed_team.html:19 #: templates/generic_index.html:43 user/templates/user/team/index.html:22 msgid "New" msgstr "Neu" -#: templates/email/other/deduction_changed.html:21 -#: templates/email/other/deduction_changed_team.html:21 +#: templates/email/other/deduction_changed.html:22 +#: templates/email/other/deduction_changed_team.html:22 msgid "EcoAccount" msgstr "Ökokonto" @@ -2877,11 +2874,11 @@ msgstr "" "Mehrfachauswahl möglich - Sie können nur Nutzer wählen, die noch nicht " "Mitglieder dieses Teams sind. Geben Sie den ganzen Nutzernamen an." -#: user/forms/modals/team.py:56 +#: user/forms/modals/team.py:56 user/tests/unit/test_forms.py:31 msgid "Create new team" msgstr "Neues Team anlegen" -#: user/forms/modals/team.py:57 +#: user/forms/modals/team.py:57 user/tests/unit/test_forms.py:32 msgid "" "You will become the administrator for this group by default. You do not need " "to add yourself to the list of members." @@ -2910,10 +2907,11 @@ msgid "There must be at least one admin on this team." msgstr "Es muss mindestens einen Administrator für das Team geben." #: user/forms/modals/team.py:160 user/templates/user/team/index.html:60 +#: user/tests/unit/test_forms.py:88 msgid "Edit team" msgstr "Team bearbeiten" -#: user/forms/modals/team.py:187 +#: user/forms/modals/team.py:187 user/tests/unit/test_forms.py:165 msgid "" "ATTENTION!\n" "\n" @@ -2930,6 +2928,7 @@ msgstr "" "Sind Sie sicher, dass Sie dieses Team löschen möchten?" #: user/forms/modals/team.py:197 user/templates/user/team/index.html:56 +#: user/tests/unit/test_forms.py:198 msgid "Leave team" msgstr "Team verlassen" @@ -2961,7 +2960,7 @@ msgstr "Benachrichtigungen" msgid "Select the situations when you want to receive a notification" msgstr "Wann wollen Sie per E-Mail benachrichtigt werden?" -#: user/forms/user.py:38 +#: user/forms/user.py:38 user/tests/unit/test_forms.py:234 msgid "Edit notifications" msgstr "Benachrichtigungen bearbeiten" @@ -2969,11 +2968,11 @@ msgstr "Benachrichtigungen bearbeiten" msgid "Token" msgstr "" -#: user/forms/user.py:88 +#: user/forms/user.py:88 user/tests/unit/test_forms.py:260 msgid "Create new token" msgstr "Neuen Token generieren" -#: user/forms/user.py:89 +#: user/forms/user.py:89 user/tests/unit/test_forms.py:261 msgid "A new token needs to be validated by an administrator!" msgstr "Neue Tokens müssen durch Administratoren freigeschaltet werden!" @@ -3123,1504 +3122,169 @@ msgstr "Sie sind kein Mitglied dieses Teams" msgid "Left Team" msgstr "Team verlassen" -#: venv/lib/python3.7/site-packages/bootstrap4/components.py:17 -#: venv/lib/python3.7/site-packages/bootstrap4/templates/bootstrap4/form_errors.html:3 -#: venv/lib/python3.7/site-packages/bootstrap4/templates/bootstrap4/messages.html:4 -msgid "close" -msgstr "Schließen" - -#: venv/lib/python3.7/site-packages/click/_termui_impl.py:496 -#, python-brace-format -msgid "{editor}: Editing failed" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/_termui_impl.py:500 -#, python-brace-format -msgid "{editor}: Editing failed: {e}" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/_unicodefun.py:20 -msgid "" -"Click will abort further execution because Python was configured to use " -"ASCII as encoding for the environment. Consult https://click.palletsprojects." -"com/unicode-support/ for mitigation steps." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/_unicodefun.py:56 -msgid "" -"Additional information: on this system no suitable UTF-8 locales were " -"discovered. This most likely requires resolving by reconfiguring the locale " -"system." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/_unicodefun.py:65 -msgid "" -"This system supports the C.UTF-8 locale which is recommended. You might be " -"able to resolve your issue by exporting the following environment variables:" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/_unicodefun.py:75 -#, python-brace-format -msgid "" -"This system lists some UTF-8 supporting locales that you can pick from. The " -"following suitable locales were discovered: {locales}" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/_unicodefun.py:93 -msgid "" -"Click discovered that you exported a UTF-8 locale but the locale system " -"could not pick up from it because it does not exist. The exported locale is " -"{locale!r} but it is not supported." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/core.py:1095 -msgid "Aborted!" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/core.py:1279 -#: venv/lib/python3.7/site-packages/click/decorators.py:434 -msgid "Show this message and exit." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/core.py:1308 -#: venv/lib/python3.7/site-packages/click/core.py:1334 -#, python-brace-format -msgid "(Deprecated) {text}" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/core.py:1351 -msgid "Options" -msgstr "Optionen" - -#: venv/lib/python3.7/site-packages/click/core.py:1375 -#, python-brace-format -msgid "Got unexpected extra argument ({args})" -msgid_plural "Got unexpected extra arguments ({args})" -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/click/core.py:1390 -msgid "DeprecationWarning: The command {name!r} is deprecated." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/core.py:1607 -msgid "Commands" -msgstr "Befehle" - -#: venv/lib/python3.7/site-packages/click/core.py:1639 -msgid "Missing command." -msgstr "Befehl fehlt" - -#: venv/lib/python3.7/site-packages/click/core.py:1717 -msgid "No such command {name!r}." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/core.py:2258 -msgid "Value must be an iterable." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/core.py:2278 -#, python-brace-format -msgid "Takes {nargs} values but 1 was given." -msgid_plural "Takes {nargs} values but {len} were given." -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/click/core.py:2701 -#, python-brace-format -msgid "env var: {var}" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/core.py:2724 -msgid "(dynamic)" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/core.py:2735 -#, python-brace-format -msgid "default: {default}" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/core.py:2748 -msgid "required" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/decorators.py:339 -#, python-format -msgid "%(prog)s, version %(version)s" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/decorators.py:403 -msgid "Show the version and exit." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/exceptions.py:43 -#: venv/lib/python3.7/site-packages/click/exceptions.py:79 -#, python-brace-format -msgid "Error: {message}" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/exceptions.py:71 -#, python-brace-format -msgid "Try '{command} {option}' for help." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/exceptions.py:120 -#, python-brace-format -msgid "Invalid value: {message}" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/exceptions.py:122 -#, python-brace-format -msgid "Invalid value for {param_hint}: {message}" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/exceptions.py:178 -msgid "Missing argument" -msgstr "Argument fehlt" - -#: venv/lib/python3.7/site-packages/click/exceptions.py:180 -msgid "Missing option" -msgstr "Option fehlt" - -#: venv/lib/python3.7/site-packages/click/exceptions.py:182 -msgid "Missing parameter" -msgstr "Parameter fehlt" - -#: venv/lib/python3.7/site-packages/click/exceptions.py:184 -#, python-brace-format -msgid "Missing {param_type}" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/exceptions.py:191 -#, python-brace-format -msgid "Missing parameter: {param_name}" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/exceptions.py:211 -#, python-brace-format -msgid "No such option: {name}" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/exceptions.py:223 -#, python-brace-format -msgid "Did you mean {possibility}?" -msgid_plural "(Possible options: {possibilities})" -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/click/exceptions.py:261 -msgid "unknown error" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/exceptions.py:268 -msgid "Could not open file {filename!r}: {message}" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/parser.py:231 -msgid "Argument {name!r} takes {nargs} values." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/parser.py:413 -msgid "Option {name!r} does not take a value." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/parser.py:474 -msgid "Option {name!r} requires an argument." -msgid_plural "Option {name!r} requires {nargs} arguments." -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/click/shell_completion.py:316 -msgid "Shell completion is not supported for Bash versions older than 4.4." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/shell_completion.py:322 -msgid "Couldn't detect Bash version, shell completion is not supported." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/termui.py:161 -msgid "Repeat for confirmation" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/termui.py:178 -msgid "Error: The value you entered was invalid." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/termui.py:180 -#, python-brace-format -msgid "Error: {e.message}" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/termui.py:191 -msgid "Error: The two entered values do not match." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/termui.py:247 -msgid "Error: invalid input" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/termui.py:798 -msgid "Press any key to continue..." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/types.py:258 -#, python-brace-format -msgid "" -"Choose from:\n" -"\t{choices}" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/types.py:290 -msgid "{value!r} is not {choice}." -msgid_plural "{value!r} is not one of {choices}." -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/click/types.py:380 -msgid "{value!r} does not match the format {format}." -msgid_plural "{value!r} does not match the formats {formats}." -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/click/types.py:402 -msgid "{value!r} is not a valid {number_type}." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/types.py:458 -#, python-brace-format -msgid "{value} is not in the range {range}." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/types.py:599 -msgid "{value!r} is not a valid boolean." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/types.py:623 -msgid "{value!r} is not a valid UUID." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/types.py:801 -msgid "file" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/types.py:803 -msgid "directory" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/types.py:805 -msgid "path" -msgstr "" - -#: venv/lib/python3.7/site-packages/click/types.py:851 -msgid "{name} {filename!r} does not exist." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/types.py:860 -msgid "{name} {filename!r} is a file." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/types.py:868 -msgid "{name} {filename!r} is a directory." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/types.py:876 -msgid "{name} {filename!r} is not writable." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/types.py:884 -msgid "{name} {filename!r} is not readable." -msgstr "" - -#: venv/lib/python3.7/site-packages/click/types.py:951 -#, python-brace-format -msgid "{len_type} values are required, but {len_value} was given." -msgid_plural "{len_type} values are required, but {len_value} were given." -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/django/contrib/messages/apps.py:7 -msgid "Messages" -msgstr "Nachrichten" - -#: venv/lib/python3.7/site-packages/django/contrib/sitemaps/apps.py:7 -msgid "Site Maps" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/contrib/staticfiles/apps.py:9 -msgid "Static Files" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/contrib/syndication/apps.py:7 -msgid "Syndication" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/paginator.py:48 -msgid "That page number is not an integer" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/paginator.py:50 -msgid "That page number is less than 1" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/paginator.py:55 -msgid "That page contains no results" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:20 -msgid "Enter a valid value." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:91 -#: venv/lib/python3.7/site-packages/django/forms/fields.py:671 -msgid "Enter a valid URL." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:145 -msgid "Enter a valid integer." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:156 -msgid "Enter a valid email address." -msgstr "" - -#. Translators: "letters" means latin letters: a-z and A-Z. -#: venv/lib/python3.7/site-packages/django/core/validators.py:230 -msgid "" -"Enter a valid “slug” consisting of letters, numbers, underscores or hyphens." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:237 -msgid "" -"Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or " -"hyphens." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:246 -#: venv/lib/python3.7/site-packages/django/core/validators.py:266 -msgid "Enter a valid IPv4 address." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:251 -#: venv/lib/python3.7/site-packages/django/core/validators.py:267 -msgid "Enter a valid IPv6 address." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:261 -#: venv/lib/python3.7/site-packages/django/core/validators.py:265 -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:295 -msgid "Enter only digits separated by commas." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:301 -#, python-format -msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:334 -#, python-format -msgid "Ensure this value is less than or equal to %(limit_value)s." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:343 -#, python-format -msgid "Ensure this value is greater than or equal to %(limit_value)s." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:353 -#, python-format -msgid "" -"Ensure this value has at least %(limit_value)d character (it has " -"%(show_value)d)." -msgid_plural "" -"Ensure this value has at least %(limit_value)d characters (it has " -"%(show_value)d)." -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:368 -#, python-format -msgid "" -"Ensure this value has at most %(limit_value)d character (it has " -"%(show_value)d)." -msgid_plural "" -"Ensure this value has at most %(limit_value)d characters (it has " -"%(show_value)d)." -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:387 -#: venv/lib/python3.7/site-packages/django/forms/fields.py:292 -#: venv/lib/python3.7/site-packages/django/forms/fields.py:327 -msgid "Enter a number." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:389 -#, python-format -msgid "Ensure that there are no more than %(max)s digit in total." -msgid_plural "Ensure that there are no more than %(max)s digits in total." -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:394 -#, python-format -msgid "Ensure that there are no more than %(max)s decimal place." -msgid_plural "Ensure that there are no more than %(max)s decimal places." -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:399 -#, python-format -msgid "" -"Ensure that there are no more than %(max)s digit before the decimal point." -msgid_plural "" -"Ensure that there are no more than %(max)s digits before the decimal point." -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:461 -#, python-format -msgid "" -"File extension “%(extension)s” is not allowed. Allowed extensions are: " -"%(allowed_extensions)s." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/core/validators.py:513 -msgid "Null characters are not allowed." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/db/models/base.py:1190 -#: venv/lib/python3.7/site-packages/django/forms/models.py:760 -msgid "and" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/db/models/base.py:1192 -#, python-format -msgid "%(model_name)s with this %(field_labels)s already exists." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:100 -#, python-format -msgid "Value %(value)r is not a valid choice." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:101 -msgid "This field cannot be null." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:102 -msgid "This field cannot be blank." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:103 -#, python-format -msgid "%(model_name)s with this %(field_label)s already exists." -msgstr "" - -#. Translators: The 'lookup_type' is one of 'date', 'year' or 'month'. -#. Eg: "Title must be unique for pub_date year" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:107 -#, python-format -msgid "" -"%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:126 -#, python-format -msgid "Field of type: %(field_type)s" -msgstr "" +#~ msgid "close" +#~ msgstr "Schließen" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:939 -#, python-format -msgid "“%(value)s” value must be either True or False." -msgstr "" +#~ msgid "Options" +#~ msgstr "Optionen" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:940 -#, python-format -msgid "“%(value)s” value must be either True, False, or None." -msgstr "" +#~ msgid "Commands" +#~ msgstr "Befehle" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:942 -msgid "Boolean (Either True or False)" -msgstr "" +#~ msgid "Missing command." +#~ msgstr "Befehl fehlt" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:983 -#, python-format -msgid "String (up to %(max_length)s)" -msgstr "" +#~ msgid "Missing argument" +#~ msgstr "Argument fehlt" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1047 -msgid "Comma-separated integers" -msgstr "" +#~ msgid "Missing option" +#~ msgstr "Option fehlt" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1096 -#, python-format -msgid "" -"“%(value)s” value has an invalid date format. It must be in YYYY-MM-DD " -"format." -msgstr "" +#~ msgid "Missing parameter" +#~ msgstr "Parameter fehlt" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1098 -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1241 -#, python-format -msgid "" -"“%(value)s” value has the correct format (YYYY-MM-DD) but it is an invalid " -"date." -msgstr "" +#~ msgid "Messages" +#~ msgstr "Nachrichten" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1101 -msgid "Date (without time)" -msgstr "" +#~ msgid "This field is required." +#~ msgstr "Pflichtfeld" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1239 -#, python-format -msgid "" -"“%(value)s” value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." -"uuuuuu]][TZ] format." -msgstr "" +#~ msgid "Monday" +#~ msgstr "Montag" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1243 -#, python-format -msgid "" -"“%(value)s” value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" -"[TZ]) but it is an invalid date/time." -msgstr "" +#~ msgid "Tuesday" +#~ msgstr "Dienstag" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1247 -msgid "Date (with time)" -msgstr "" +#~ msgid "Wednesday" +#~ msgstr "Mittwoch" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1395 -#, python-format -msgid "“%(value)s” value must be a decimal number." -msgstr "" +#~ msgid "Thursday" +#~ msgstr "Donnerstag" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1397 -msgid "Decimal number" -msgstr "" +#~ msgid "Friday" +#~ msgstr "Freitag" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1536 -#, python-format -msgid "" -"“%(value)s” value has an invalid format. It must be in [DD] [[HH:]MM:]ss[." -"uuuuuu] format." -msgstr "" +#~ msgid "Saturday" +#~ msgstr "Samstag" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1539 -msgid "Duration" -msgstr "" +#~ msgid "Sunday" +#~ msgstr "Sonntag" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1589 -msgid "Email address" -msgstr "" +#~ msgid "Mon" +#~ msgstr "Mo" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1612 -msgid "File path" -msgstr "" +#~ msgid "Tue" +#~ msgstr "Di" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1678 -#, python-format -msgid "“%(value)s” value must be a float." -msgstr "" +#~ msgid "Wed" +#~ msgstr "Mi" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1680 -msgid "Floating point number" -msgstr "" +#~ msgid "Thu" +#~ msgstr "Do" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1718 -#, python-format -msgid "“%(value)s” value must be an integer." -msgstr "" +#~ msgid "Fri" +#~ msgstr "Fr" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1720 -msgid "Integer" -msgstr "" +#~ msgid "Sat" +#~ msgstr "Sa" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1803 -msgid "Big (8 byte) integer" -msgstr "" +#~ msgid "Sun" +#~ msgstr "So" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1819 -msgid "IPv4 address" -msgstr "" +#~ msgid "January" +#~ msgstr "Januar" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1850 -msgid "IP address" -msgstr "" +#~ msgid "February" +#~ msgstr "Februar" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1930 -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1931 -#, python-format -msgid "“%(value)s” value must be either None, True or False." -msgstr "" +#~ msgid "March" +#~ msgstr "März" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1933 -msgid "Boolean (Either True, False or None)" -msgstr "" +#~ msgid "May" +#~ msgstr "Mai" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1976 -msgid "Positive big integer" -msgstr "" +#~ msgid "June" +#~ msgstr "Juni" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:1989 -msgid "Positive integer" -msgstr "" +#~ msgid "July" +#~ msgstr "Juli" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:2002 -msgid "Positive small integer" -msgstr "" +#~ msgid "October" +#~ msgstr "Oktober" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:2016 -#, python-format -msgid "Slug (up to %(max_length)s)" -msgstr "" +#~ msgid "December" +#~ msgstr "Dezember" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:2048 -msgid "Small integer" -msgstr "" +#~ msgid "mar" +#~ msgstr "mär" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:2055 -msgid "Text" -msgstr "" +#~ msgid "may" +#~ msgstr "mai" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:2083 -#, python-format -msgid "" -"“%(value)s” value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " -"format." -msgstr "" +#~ msgid "oct" +#~ msgstr "okt" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:2085 -#, python-format -msgid "" -"“%(value)s” value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " -"invalid time." -msgstr "" +#~ msgid "dec" +#~ msgstr "dez" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:2088 -msgid "Time" -msgstr "" +#~ msgctxt "abbrev. month" +#~ msgid "March" +#~ msgstr "Mär" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:2214 -msgid "URL" -msgstr "" +#~ msgctxt "abbrev. month" +#~ msgid "May" +#~ msgstr "Mai" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:2236 -msgid "Raw binary data" -msgstr "" +#~ msgctxt "abbrev. month" +#~ msgid "June" +#~ msgstr "Juni" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:2301 -#, python-format -msgid "“%(value)s” is not a valid UUID." -msgstr "" +#~ msgctxt "abbrev. month" +#~ msgid "July" +#~ msgstr "Juli" -#: venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:2303 -msgid "Universally unique identifier" -msgstr "" +#~ msgctxt "abbrev. month" +#~ msgid "Oct." +#~ msgstr "Okt." -#: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:379 -msgid "Image" -msgstr "" +#~ msgctxt "abbrev. month" +#~ msgid "Dec." +#~ msgstr "Dez." -#: venv/lib/python3.7/site-packages/django/db/models/fields/json.py:18 -msgid "A JSON object" -msgstr "" +#~ msgctxt "alt. month" +#~ msgid "January" +#~ msgstr "Januar" -#: venv/lib/python3.7/site-packages/django/db/models/fields/json.py:20 -msgid "Value must be valid JSON." -msgstr "" +#~ msgctxt "alt. month" +#~ msgid "February" +#~ msgstr "Februar" -#: venv/lib/python3.7/site-packages/django/db/models/fields/related.py:790 -#, python-format -msgid "%(model)s instance with %(field)s %(value)r does not exist." -msgstr "" +#~ msgctxt "alt. month" +#~ msgid "March" +#~ msgstr "März" -#: venv/lib/python3.7/site-packages/django/db/models/fields/related.py:792 -msgid "Foreign Key (type determined by related field)" -msgstr "" +#~ msgctxt "alt. month" +#~ msgid "May" +#~ msgstr "Mai" -#: venv/lib/python3.7/site-packages/django/db/models/fields/related.py:1045 -msgid "One-to-one relationship" -msgstr "" +#~ msgctxt "alt. month" +#~ msgid "June" +#~ msgstr "Juni" -#: venv/lib/python3.7/site-packages/django/db/models/fields/related.py:1099 -#, python-format -msgid "%(from)s-%(to)s relationship" -msgstr "" +#~ msgctxt "alt. month" +#~ msgid "July" +#~ msgstr "Juli" -#: venv/lib/python3.7/site-packages/django/db/models/fields/related.py:1100 -#, python-format -msgid "%(from)s-%(to)s relationships" -msgstr "" +#~ msgctxt "alt. month" +#~ msgid "October" +#~ msgstr "Oktober" -#: venv/lib/python3.7/site-packages/django/db/models/fields/related.py:1142 -msgid "Many-to-many relationship" -msgstr "" +#~ msgctxt "alt. month" +#~ msgid "December" +#~ msgstr "Dezember" -#. Translators: If found as last label character, these punctuation -#. characters will prevent the default label_suffix to be appended to the label -#: venv/lib/python3.7/site-packages/django/forms/boundfield.py:150 -msgid ":?.!" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:54 -msgid "This field is required." -msgstr "Pflichtfeld" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:247 -msgid "Enter a whole number." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:398 -#: venv/lib/python3.7/site-packages/django/forms/fields.py:1139 -msgid "Enter a valid date." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:422 -#: venv/lib/python3.7/site-packages/django/forms/fields.py:1140 -msgid "Enter a valid time." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:450 -msgid "Enter a valid date/time." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:484 -msgid "Enter a valid duration." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:485 -#, python-brace-format -msgid "The number of days must be between {min_days} and {max_days}." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:545 -msgid "No file was submitted. Check the encoding type on the form." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:546 -msgid "No file was submitted." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:547 -msgid "The submitted file is empty." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:549 -#, python-format -msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." -msgid_plural "" -"Ensure this filename has at most %(max)d characters (it has %(length)d)." -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:552 -msgid "Please either submit a file or check the clear checkbox, not both." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:613 -msgid "" -"Upload a valid image. The file you uploaded was either not an image or a " -"corrupted image." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:775 -#: venv/lib/python3.7/site-packages/django/forms/fields.py:865 -#: venv/lib/python3.7/site-packages/django/forms/models.py:1296 -#, python-format -msgid "Select a valid choice. %(value)s is not one of the available choices." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:866 -#: venv/lib/python3.7/site-packages/django/forms/fields.py:981 -#: venv/lib/python3.7/site-packages/django/forms/models.py:1295 -msgid "Enter a list of values." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:982 -msgid "Enter a complete value." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:1198 -msgid "Enter a valid UUID." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/fields.py:1228 -msgid "Enter a valid JSON." -msgstr "" - -#. Translators: This is the default suffix added to form field labels -#: venv/lib/python3.7/site-packages/django/forms/forms.py:78 -msgid ":" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/forms.py:205 -#, python-format -msgid "(Hidden field %(name)s) %(error)s" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/formsets.py:93 -msgid "ManagementForm data is missing or has been tampered with" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/formsets.py:345 -#, python-format -msgid "Please submit %d or fewer forms." -msgid_plural "Please submit %d or fewer forms." -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/django/forms/formsets.py:352 -#, python-format -msgid "Please submit %d or more forms." -msgid_plural "Please submit %d or more forms." -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/django/forms/formsets.py:379 -#: venv/lib/python3.7/site-packages/django/forms/formsets.py:386 -msgid "Order" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/models.py:755 -#, python-format -msgid "Please correct the duplicate data for %(field)s." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/models.py:759 -#, python-format -msgid "Please correct the duplicate data for %(field)s, which must be unique." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/models.py:765 -#, python-format -msgid "" -"Please correct the duplicate data for %(field_name)s which must be unique " -"for the %(lookup)s in %(date_field)s." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/models.py:774 -msgid "Please correct the duplicate values below." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/models.py:1096 -msgid "The inline value did not match the parent instance." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/models.py:1180 -msgid "Select a valid choice. That choice is not one of the available choices." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/models.py:1298 -#, python-format -msgid "“%(pk)s” is not a valid value." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/utils.py:167 -#, python-format -msgid "" -"%(datetime)s couldn’t be interpreted in time zone %(current_timezone)s; it " -"may be ambiguous or it may not exist." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/widgets.py:398 -msgid "Clear" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/widgets.py:399 -msgid "Currently" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/forms/widgets.py:400 -msgid "Change" -msgstr "" - -#. Translators: Please do not add spaces around commas. -#: venv/lib/python3.7/site-packages/django/template/defaultfilters.py:790 -msgid "yes,no,maybe" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/template/defaultfilters.py:819 -#: venv/lib/python3.7/site-packages/django/template/defaultfilters.py:836 -#, python-format -msgid "%(size)d byte" -msgid_plural "%(size)d bytes" -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/django/template/defaultfilters.py:838 -#, python-format -msgid "%s KB" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/template/defaultfilters.py:840 -#, python-format -msgid "%s MB" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/template/defaultfilters.py:842 -#, python-format -msgid "%s GB" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/template/defaultfilters.py:844 -#, python-format -msgid "%s TB" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/template/defaultfilters.py:846 -#, python-format -msgid "%s PB" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dateformat.py:65 -msgid "p.m." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dateformat.py:66 -msgid "a.m." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dateformat.py:71 -msgid "PM" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dateformat.py:72 -msgid "AM" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dateformat.py:149 -msgid "midnight" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dateformat.py:151 -msgid "noon" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:6 -msgid "Monday" -msgstr "Montag" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:6 -msgid "Tuesday" -msgstr "Dienstag" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:6 -msgid "Wednesday" -msgstr "Mittwoch" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:6 -msgid "Thursday" -msgstr "Donnerstag" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:6 -msgid "Friday" -msgstr "Freitag" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:7 -msgid "Saturday" -msgstr "Samstag" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:7 -msgid "Sunday" -msgstr "Sonntag" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:10 -msgid "Mon" -msgstr "Mo" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:10 -msgid "Tue" -msgstr "Di" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:10 -msgid "Wed" -msgstr "Mi" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:10 -msgid "Thu" -msgstr "Do" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:10 -msgid "Fri" -msgstr "Fr" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:11 -msgid "Sat" -msgstr "Sa" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:11 -msgid "Sun" -msgstr "So" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:14 -msgid "January" -msgstr "Januar" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:14 -msgid "February" -msgstr "Februar" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:14 -msgid "March" -msgstr "März" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:14 -msgid "April" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:14 -msgid "May" -msgstr "Mai" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:14 -msgid "June" -msgstr "Juni" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:15 -msgid "July" -msgstr "Juli" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:15 -msgid "August" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:15 -msgid "September" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:15 -msgid "October" -msgstr "Oktober" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:15 -msgid "November" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:16 -msgid "December" -msgstr "Dezember" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:19 -msgid "jan" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:19 -msgid "feb" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:19 -msgid "mar" -msgstr "mär" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:19 -msgid "apr" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:19 -msgid "may" -msgstr "mai" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:19 -msgid "jun" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:20 -msgid "jul" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:20 -msgid "aug" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:20 -msgid "sep" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:20 -msgid "oct" -msgstr "okt" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:20 -msgid "nov" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:20 -msgid "dec" -msgstr "dez" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:23 -msgctxt "abbrev. month" -msgid "Jan." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:24 -msgctxt "abbrev. month" -msgid "Feb." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:25 -msgctxt "abbrev. month" -msgid "March" -msgstr "Mär" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:26 -msgctxt "abbrev. month" -msgid "April" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:27 -msgctxt "abbrev. month" -msgid "May" -msgstr "Mai" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:28 -msgctxt "abbrev. month" -msgid "June" -msgstr "Juni" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:29 -msgctxt "abbrev. month" -msgid "July" -msgstr "Juli" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:30 -msgctxt "abbrev. month" -msgid "Aug." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:31 -msgctxt "abbrev. month" -msgid "Sept." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:32 -msgctxt "abbrev. month" -msgid "Oct." -msgstr "Okt." - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:33 -msgctxt "abbrev. month" -msgid "Nov." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:34 -msgctxt "abbrev. month" -msgid "Dec." -msgstr "Dez." - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:37 -msgctxt "alt. month" -msgid "January" -msgstr "Januar" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:38 -msgctxt "alt. month" -msgid "February" -msgstr "Februar" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:39 -msgctxt "alt. month" -msgid "March" -msgstr "März" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:40 -msgctxt "alt. month" -msgid "April" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:41 -msgctxt "alt. month" -msgid "May" -msgstr "Mai" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:42 -msgctxt "alt. month" -msgid "June" -msgstr "Juni" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:43 -msgctxt "alt. month" -msgid "July" -msgstr "Juli" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:44 -msgctxt "alt. month" -msgid "August" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:45 -msgctxt "alt. month" -msgid "September" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:46 -msgctxt "alt. month" -msgid "October" -msgstr "Oktober" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:47 -msgctxt "alt. month" -msgid "November" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/dates.py:48 -msgctxt "alt. month" -msgid "December" -msgstr "Dezember" - -#: venv/lib/python3.7/site-packages/django/utils/ipv6.py:8 -msgid "This is not a valid IPv6 address." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/text.py:70 -#, python-format -msgctxt "String to return when truncating text" -msgid "%(truncated_text)s…" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/text.py:236 -msgid "or" -msgstr "oder" - -#. Translators: This string is used as a separator between list elements -#: venv/lib/python3.7/site-packages/django/utils/text.py:255 -#: venv/lib/python3.7/site-packages/django/utils/timesince.py:83 -msgid ", " -msgstr "" - -#: venv/lib/python3.7/site-packages/django/utils/timesince.py:9 -#, python-format -msgid "%d year" -msgid_plural "%d years" -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/django/utils/timesince.py:10 -#, python-format -msgid "%d month" -msgid_plural "%d months" -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/django/utils/timesince.py:11 -#, python-format -msgid "%d week" -msgid_plural "%d weeks" -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/django/utils/timesince.py:12 -#, python-format -msgid "%d day" -msgid_plural "%d days" -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/django/utils/timesince.py:13 -#, python-format -msgid "%d hour" -msgid_plural "%d hours" -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/django/utils/timesince.py:14 -#, python-format -msgid "%d minute" -msgid_plural "%d minutes" -msgstr[0] "" -msgstr[1] "" - -#: venv/lib/python3.7/site-packages/django/views/csrf.py:110 -msgid "Forbidden" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/csrf.py:111 -msgid "CSRF verification failed. Request aborted." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/csrf.py:115 -msgid "" -"You are seeing this message because this HTTPS site requires a “Referer " -"header” to be sent by your Web browser, but none was sent. This header is " -"required for security reasons, to ensure that your browser is not being " -"hijacked by third parties." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/csrf.py:120 -msgid "" -"If you have configured your browser to disable “Referer” headers, please re-" -"enable them, at least for this site, or for HTTPS connections, or for “same-" -"origin” requests." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/csrf.py:124 -msgid "" -"If you are using the tag or " -"including the “Referrer-Policy: no-referrer” header, please remove them. The " -"CSRF protection requires the “Referer” header to do strict referer checking. " -"If you’re concerned about privacy, use alternatives like for links to third-party sites." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/csrf.py:132 -msgid "" -"You are seeing this message because this site requires a CSRF cookie when " -"submitting forms. This cookie is required for security reasons, to ensure " -"that your browser is not being hijacked by third parties." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/csrf.py:137 -msgid "" -"If you have configured your browser to disable cookies, please re-enable " -"them, at least for this site, or for “same-origin” requests." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/csrf.py:142 -msgid "More information is available with DEBUG=True." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/generic/dates.py:41 -msgid "No year specified" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/generic/dates.py:61 -#: venv/lib/python3.7/site-packages/django/views/generic/dates.py:111 -#: venv/lib/python3.7/site-packages/django/views/generic/dates.py:208 -msgid "Date out of range" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/generic/dates.py:90 -msgid "No month specified" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/generic/dates.py:142 -msgid "No day specified" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/generic/dates.py:188 -msgid "No week specified" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/generic/dates.py:338 -#: venv/lib/python3.7/site-packages/django/views/generic/dates.py:367 -#, python-format -msgid "No %(verbose_name_plural)s available" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/generic/dates.py:589 -#, python-format -msgid "" -"Future %(verbose_name_plural)s not available because %(class_name)s." -"allow_future is False." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/generic/dates.py:623 -#, python-format -msgid "Invalid date string “%(datestr)s” given format “%(format)s”" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/generic/detail.py:54 -#, python-format -msgid "No %(verbose_name)s found matching the query" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/generic/list.py:67 -msgid "Page is not “last”, nor can it be converted to an int." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/generic/list.py:72 -#, python-format -msgid "Invalid page (%(page_number)s): %(message)s" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/generic/list.py:154 -#, python-format -msgid "Empty list and “%(class_name)s.allow_empty” is False." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/static.py:40 -msgid "Directory indexes are not allowed here." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/static.py:42 -#, python-format -msgid "“%(path)s” does not exist" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/static.py:80 -#, python-format -msgid "Index of %(directory)s" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/templates/default_urlconf.html:7 -msgid "Django: the Web framework for perfectionists with deadlines." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/templates/default_urlconf.html:346 -#, python-format -msgid "" -"View release notes for Django %(version)s" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/templates/default_urlconf.html:368 -msgid "The install worked successfully! Congratulations!" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/templates/default_urlconf.html:369 -#, python-format -msgid "" -"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " -"URLs." -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/templates/default_urlconf.html:384 -msgid "Django Documentation" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/templates/default_urlconf.html:385 -msgid "Topics, references, & how-to’s" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/templates/default_urlconf.html:396 -msgid "Tutorial: A Polling App" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/templates/default_urlconf.html:397 -msgid "Get started with Django" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/templates/default_urlconf.html:408 -msgid "Django Community" -msgstr "" - -#: venv/lib/python3.7/site-packages/django/views/templates/default_urlconf.html:409 -msgid "Connect, get help, or contribute" -msgstr "" - -#: venv/lib/python3.7/site-packages/fontawesome_5/fields.py:16 -msgid "A fontawesome icon field" -msgstr "" - -#: venv/lib/python3.7/site-packages/kombu/transport/qpid.py:1310 -#, python-format -msgid "Attempting to connect to qpid with SASL mechanism %s" -msgstr "" - -#: venv/lib/python3.7/site-packages/kombu/transport/qpid.py:1315 -#, python-format -msgid "Connected to qpid with SASL mechanism %s" -msgstr "" - -#: venv/lib/python3.7/site-packages/kombu/transport/qpid.py:1333 -#, python-format -msgid "Unable to connect to qpid with SASL mechanism %s" -msgstr "" +#~ msgid "or" +#~ msgstr "oder" #~ msgid "" #~ "Deductable surface can not be larger than existing surfaces in after " diff --git a/requirements.txt b/requirements.txt index 65e9bb9..908b968 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,7 +34,6 @@ pika==1.3.2 prompt-toolkit==3.0.43 psycopg==3.1.16 psycopg-binary==3.1.16 -psycopg2-binary==2.9.9 pyparsing==3.1.1 pypng==0.20220715.0 pyproj==3.6.1