#50 Overlaying geometries Tests

* adds test for geometry conflicts
* refactors rechecking of existing conflicts to avoid recursion in certain cases
* adds/updates translations
This commit is contained in:
mpeltriaux 2021-12-16 09:58:59 +01:00
parent 286ed609da
commit 5df84bb7a1
6 changed files with 149 additions and 81 deletions

View File

@ -20,7 +20,7 @@ class GeometryAdmin(admin.ModelAdmin):
class GeometryConflictAdmin(admin.ModelAdmin): class GeometryConflictAdmin(admin.ModelAdmin):
list_display = [ list_display = [
"conflicting_geometry", "conflicting_geometry",
"existing_geometry", "affected_geometry",
"detected_on", "detected_on",
] ]

View File

@ -24,7 +24,7 @@ class Geometry(BaseResource):
self.check_for_conflicts() self.check_for_conflicts()
def check_for_conflicts(self): def check_for_conflicts(self):
""" Checks for geometry overlaps """ Checks for new geometry overlaps
Creates a new GeometryConflict entry for each overlap to another geometry, which has already been there before Creates a new GeometryConflict entry for each overlap to another geometry, which has already been there before
@ -35,27 +35,40 @@ class Geometry(BaseResource):
if self.geom is None or (self.created is None and self.modified is None): if self.geom is None or (self.created is None and self.modified is None):
return None return None
check_timestamp_obj = self.modified or self.created self.recheck_existing_conflicts()
ts = check_timestamp_obj.timestamp
overlapping_geoms = Geometry.objects.filter( overlapping_geoms = Geometry.objects.filter(
Q(modified__timestamp__lte=ts) |
Q(created__timestamp__lte=ts),
geom__intersects=self.geom, geom__intersects=self.geom,
).exclude( ).exclude(
id=self.id id=self.id
).distinct() ).distinct()
# Drop known conflicts for this object to replace with new ones
self.conflicts_geometries.all().delete()
for match in overlapping_geoms: for match in overlapping_geoms:
GeometryConflict.objects.get_or_create(conflicting_geometry=self, existing_geometry=match) GeometryConflict.objects.get_or_create(conflicting_geometry=self, affected_geometry=match)
def recheck_existing_conflicts(self):
""" Rechecks GeometryConflict entries
If a conflict seems to be resolved due to no longer intersection between the two geometries, the entry
will be deleted.
Returns:
"""
all_conflicts_as_conflicting = self.conflicts_geometries.all()
still_conflicting_conflicts = all_conflicts_as_conflicting.filter(
affected_geometry__geom__intersects=self.geom
)
resolved_conflicts = all_conflicts_as_conflicting.exclude(id__in=still_conflicting_conflicts)
resolved_conflicts.delete()
all_conflicted_by_conflicts = self.conflicted_by_geometries.all()
still_conflicting_conflicts = all_conflicted_by_conflicts.filter(
conflicting_geometry__geom__intersects=self.geom
)
resolved_conflicts = all_conflicted_by_conflicts.exclude(id__in=still_conflicting_conflicts)
resolved_conflicts.delete()
# Rerun the conflict check for all conflicts where this object is not the cause but the one that already existed.
# It may be possible that this object has been edited, so the conflicts would be resolved in the newer entries.
existing_conflicts = self.conflicted_by_geometries.all()
for conflict in existing_conflicts:
conflicting_geom = conflict.conflicting_geometry
conflicting_geom.check_for_conflicts()
def get_data_objects(self): def get_data_objects(self):
""" Getter for all objects which are related to this geometry """ Getter for all objects which are related to this geometry
@ -90,7 +103,7 @@ class GeometryConflict(UuidModel):
help_text="The geometry which came second", help_text="The geometry which came second",
related_name="conflicts_geometries" related_name="conflicts_geometries"
) )
existing_geometry = models.ForeignKey( affected_geometry = models.ForeignKey(
Geometry, Geometry,
on_delete=models.CASCADE, on_delete=models.CASCADE,
help_text="The geometry which came first", help_text="The geometry which came first",
@ -99,4 +112,4 @@ class GeometryConflict(UuidModel):
detected_on = models.DateTimeField(auto_now_add=True, null=True) detected_on = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self): def __str__(self):
return f"{self.conflicting_geometry.id} conflicts with {self.existing_geometry.id}" return f"{self.conflicting_geometry.id} conflicts with {self.affected_geometry.id}"

View File

@ -425,7 +425,7 @@ class GeoReferencedMixin(models.Model):
add_message = False add_message = False
conflicts = self.geometry.conflicts_geometries.all() conflicts = self.geometry.conflicts_geometries.all()
for conflict in conflicts: for conflict in conflicts:
instance_objs += conflict.existing_geometry.get_data_objects() instance_objs += conflict.affected_geometry.get_data_objects()
add_message = True add_message = True
conflicts = self.geometry.conflicted_by_geometries.all() conflicts = self.geometry.conflicted_by_geometries.all()
for conflict in conflicts: for conflict in conflicts:

View File

@ -0,0 +1,49 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 15.12.21
"""
from django.contrib.gis.db.models.functions import Translate
from konova.models import Geometry, GeometryConflict
from konova.tests.test_views import BaseTestCase
from user.models import UserActionLogEntry
class GeometryTestCase(BaseTestCase):
@classmethod
def setUpTestData(cls):
super().setUpTestData()
geom = cls.create_dummy_geometry()
action = UserActionLogEntry.get_created_action(cls.superuser)
cls.geom_1 = Geometry.objects.create(
geom=geom,
created=action,
)
cls.geom_2 = Geometry.objects.create(
geom=geom,
created=action,
)
def test_geometry_conflict(self):
""" Tests whether a geometry conflict will be present in case of identical/overlaying geometries and
if the conflict will be resolved if one geometry is edited.
Returns:
"""
self.geom_1.check_for_conflicts()
conflict = GeometryConflict.objects.all().first()
self.assertEqual(conflict.conflicting_geometry, self.geom_2)
self.assertEqual(conflict.affected_geometry, self.geom_1)
# Move geom_2 somewhere else, expect the conflict to be resolved
Geometry.objects.filter(id=self.geom_2.id).update(
geom=Translate('geom', 100000, 100000)
)
self.geom_2.refresh_from_db()
self.geom_1.check_for_conflicts()
num_conflict = GeometryConflict.objects.all().count()
self.assertEqual(0, num_conflict)

Binary file not shown.

View File

@ -11,15 +11,15 @@
#: intervention/forms/forms.py:52 intervention/forms/forms.py:154 #: intervention/forms/forms.py:52 intervention/forms/forms.py:154
#: intervention/forms/forms.py:166 intervention/forms/modalForms.py:125 #: intervention/forms/forms.py:166 intervention/forms/modalForms.py:125
#: intervention/forms/modalForms.py:138 intervention/forms/modalForms.py:151 #: intervention/forms/modalForms.py:138 intervention/forms/modalForms.py:151
#: konova/forms.py:139 konova/forms.py:240 konova/forms.py:306 #: konova/forms.py:139 konova/forms.py:240 konova/forms.py:308
#: konova/forms.py:333 konova/forms.py:343 konova/forms.py:356 #: konova/forms.py:335 konova/forms.py:345 konova/forms.py:358
#: konova/forms.py:368 konova/forms.py:386 user/forms.py:38 #: konova/forms.py:370 konova/forms.py:388 user/forms.py:38
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-12-09 12:36+0100\n" "POT-Creation-Date: 2021-12-16 09:17+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -329,7 +329,7 @@ msgstr "Automatisch generiert"
#: intervention/templates/intervention/detail/includes/documents.html:28 #: intervention/templates/intervention/detail/includes/documents.html:28
#: intervention/templates/intervention/detail/view.html:31 #: intervention/templates/intervention/detail/view.html:31
#: intervention/templates/intervention/report/report.html:12 #: intervention/templates/intervention/report/report.html:12
#: konova/forms.py:332 #: konova/forms.py:334
msgid "Title" msgid "Title"
msgstr "Bezeichnung" msgstr "Bezeichnung"
@ -356,7 +356,7 @@ msgstr "Kompensation XY; Flur ABC"
#: intervention/templates/intervention/detail/includes/documents.html:31 #: intervention/templates/intervention/detail/includes/documents.html:31
#: intervention/templates/intervention/detail/includes/payments.html:34 #: intervention/templates/intervention/detail/includes/payments.html:34
#: intervention/templates/intervention/detail/includes/revocation.html:38 #: intervention/templates/intervention/detail/includes/revocation.html:38
#: konova/forms.py:367 konova/templates/konova/comment_card.html:16 #: konova/forms.py:369 konova/templates/konova/comment_card.html:16
msgid "Comment" msgid "Comment"
msgstr "Kommentar" msgstr "Kommentar"
@ -472,7 +472,7 @@ msgstr "Zahlung wird an diesem Datum erwartet"
#: compensation/forms/modalForms.py:62 compensation/forms/modalForms.py:239 #: compensation/forms/modalForms.py:62 compensation/forms/modalForms.py:239
#: compensation/forms/modalForms.py:317 intervention/forms/modalForms.py:152 #: compensation/forms/modalForms.py:317 intervention/forms/modalForms.py:152
#: konova/forms.py:369 #: konova/forms.py:371
msgid "Additional comment, maximum {} letters" msgid "Additional comment, maximum {} letters"
msgstr "Zusätzlicher Kommentar, maximal {} Zeichen" msgstr "Zusätzlicher Kommentar, maximal {} Zeichen"
@ -614,7 +614,7 @@ msgstr ""
msgid "Pieces" msgid "Pieces"
msgstr "Stück" msgstr "Stück"
#: compensation/models/compensation.py:62 konova/utils/message_templates.py:27 #: compensation/models/compensation.py:63 konova/utils/message_templates.py:27
msgid "Added deadline" msgid "Added deadline"
msgstr "Frist/Termin hinzugefügt" msgstr "Frist/Termin hinzugefügt"
@ -793,7 +793,7 @@ msgstr "Dokumente"
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:14 #: compensation/templates/compensation/detail/eco_account/includes/documents.html:14
#: ema/templates/ema/detail/includes/documents.html:14 #: ema/templates/ema/detail/includes/documents.html:14
#: intervention/templates/intervention/detail/includes/documents.html:14 #: intervention/templates/intervention/detail/includes/documents.html:14
#: konova/forms.py:385 #: konova/forms.py:387
msgid "Add new document" msgid "Add new document"
msgstr "Neues Dokument hinzufügen" msgstr "Neues Dokument hinzufügen"
@ -1056,42 +1056,42 @@ msgstr "Kompensation {} hinzugefügt"
msgid "Compensation {} edited" msgid "Compensation {} edited"
msgstr "Kompensation {} bearbeitet" msgstr "Kompensation {} bearbeitet"
#: compensation/views/compensation.py:229 compensation/views/eco_account.py:308 #: compensation/views/compensation.py:228 compensation/views/eco_account.py:307
#: ema/views.py:182 intervention/views.py:475 #: ema/views.py:181 intervention/views.py:474
msgid "Log" msgid "Log"
msgstr "Log" msgstr "Log"
#: compensation/views/compensation.py:252 #: compensation/views/compensation.py:251
msgid "Compensation removed" msgid "Compensation removed"
msgstr "Kompensation entfernt" msgstr "Kompensation entfernt"
#: compensation/views/compensation.py:273 compensation/views/eco_account.py:460 #: compensation/views/compensation.py:272 compensation/views/eco_account.py:459
#: ema/views.py:349 intervention/views.py:129 #: ema/views.py:348 intervention/views.py:129
msgid "Document added" msgid "Document added"
msgstr "Dokument hinzugefügt" msgstr "Dokument hinzugefügt"
#: compensation/views/compensation.py:342 compensation/views/eco_account.py:354 #: compensation/views/compensation.py:341 compensation/views/eco_account.py:353
#: ema/views.py:287 #: ema/views.py:286
msgid "State added" msgid "State added"
msgstr "Zustand hinzugefügt" msgstr "Zustand hinzugefügt"
#: compensation/views/compensation.py:363 compensation/views/eco_account.py:375 #: compensation/views/compensation.py:362 compensation/views/eco_account.py:374
#: ema/views.py:308 #: ema/views.py:307
msgid "Action added" msgid "Action added"
msgstr "Maßnahme hinzugefügt" msgstr "Maßnahme hinzugefügt"
#: compensation/views/compensation.py:384 compensation/views/eco_account.py:440 #: compensation/views/compensation.py:383 compensation/views/eco_account.py:439
#: ema/views.py:329 #: ema/views.py:328
msgid "Deadline added" msgid "Deadline added"
msgstr "Frist/Termin hinzugefügt" msgstr "Frist/Termin hinzugefügt"
#: compensation/views/compensation.py:406 compensation/views/eco_account.py:397 #: compensation/views/compensation.py:405 compensation/views/eco_account.py:396
#: ema/views.py:419 #: ema/views.py:418
msgid "State removed" msgid "State removed"
msgstr "Zustand gelöscht" msgstr "Zustand gelöscht"
#: compensation/views/compensation.py:428 compensation/views/eco_account.py:419 #: compensation/views/compensation.py:427 compensation/views/eco_account.py:418
#: ema/views.py:441 #: ema/views.py:440
msgid "Action removed" msgid "Action removed"
msgstr "Maßnahme entfernt" msgstr "Maßnahme entfernt"
@ -1103,45 +1103,45 @@ msgstr "Ökokonto {} hinzugefügt"
msgid "Eco-Account {} edited" msgid "Eco-Account {} edited"
msgstr "Ökokonto {} bearbeitet" msgstr "Ökokonto {} bearbeitet"
#: compensation/views/eco_account.py:256 #: compensation/views/eco_account.py:255
msgid "Eco-account removed" msgid "Eco-account removed"
msgstr "Ökokonto entfernt" msgstr "Ökokonto entfernt"
#: compensation/views/eco_account.py:284 #: compensation/views/eco_account.py:283
msgid "Deduction removed" msgid "Deduction removed"
msgstr "Abbuchung entfernt" msgstr "Abbuchung entfernt"
#: compensation/views/eco_account.py:329 ema/views.py:262 #: compensation/views/eco_account.py:328 ema/views.py:261
#: intervention/views.py:517 #: intervention/views.py:516
msgid "{} unrecorded" msgid "{} unrecorded"
msgstr "{} entzeichnet" msgstr "{} entzeichnet"
#: compensation/views/eco_account.py:329 ema/views.py:262 #: compensation/views/eco_account.py:328 ema/views.py:261
#: intervention/views.py:517 #: intervention/views.py:516
msgid "{} recorded" msgid "{} recorded"
msgstr "{} verzeichnet" msgstr "{} verzeichnet"
#: compensation/views/eco_account.py:530 intervention/views.py:498 #: compensation/views/eco_account.py:529 intervention/views.py:497
msgid "Deduction added" msgid "Deduction added"
msgstr "Abbuchung hinzugefügt" msgstr "Abbuchung hinzugefügt"
#: compensation/views/eco_account.py:613 ema/views.py:517 #: compensation/views/eco_account.py:612 ema/views.py:516
#: intervention/views.py:373 #: intervention/views.py:372
msgid "{} has already been shared with you" msgid "{} has already been shared with you"
msgstr "{} wurde bereits für Sie freigegeben" msgstr "{} wurde bereits für Sie freigegeben"
#: compensation/views/eco_account.py:618 ema/views.py:522 #: compensation/views/eco_account.py:617 ema/views.py:521
#: intervention/views.py:378 #: intervention/views.py:377
msgid "{} has been shared with you" msgid "{} has been shared with you"
msgstr "{} ist nun für Sie freigegeben" msgstr "{} ist nun für Sie freigegeben"
#: compensation/views/eco_account.py:625 ema/views.py:529 #: compensation/views/eco_account.py:624 ema/views.py:528
#: intervention/views.py:385 #: intervention/views.py:384
msgid "Share link invalid" msgid "Share link invalid"
msgstr "Freigabelink ungültig" msgstr "Freigabelink ungültig"
#: compensation/views/eco_account.py:648 ema/views.py:552 #: compensation/views/eco_account.py:647 ema/views.py:551
#: intervention/views.py:408 #: intervention/views.py:407
msgid "Share settings updated" msgid "Share settings updated"
msgstr "Freigabe Einstellungen aktualisiert" msgstr "Freigabe Einstellungen aktualisiert"
@ -1185,11 +1185,11 @@ msgstr "Ersatzzahlungsmaßnahme"
msgid "EMA {} added" msgid "EMA {} added"
msgstr "EMA {} hinzugefügt" msgstr "EMA {} hinzugefügt"
#: ema/views.py:211 #: ema/views.py:210
msgid "EMA {} edited" msgid "EMA {} edited"
msgstr "EMA {} bearbeitet" msgstr "EMA {} bearbeitet"
#: ema/views.py:243 #: ema/views.py:242
msgid "EMA removed" msgid "EMA removed"
msgstr "EMA entfernt" msgstr "EMA entfernt"
@ -1333,7 +1333,7 @@ msgstr "Kompensationen und Zahlungen geprüft"
msgid "Run check" msgid "Run check"
msgstr "Prüfung vornehmen" msgstr "Prüfung vornehmen"
#: intervention/forms/modalForms.py:196 konova/forms.py:451 #: intervention/forms/modalForms.py:196 konova/forms.py:453
msgid "" msgid ""
"I, {} {}, confirm that all necessary control steps have been performed by " "I, {} {}, confirm that all necessary control steps have been performed by "
"myself." "myself."
@ -1476,27 +1476,27 @@ msgstr "Eingriff {} hinzugefügt"
msgid "This intervention has {} revocations" msgid "This intervention has {} revocations"
msgstr "Dem Eingriff liegen {} Widersprüche vor" msgstr "Dem Eingriff liegen {} Widersprüche vor"
#: intervention/views.py:291 #: intervention/views.py:290
msgid "Intervention {} edited" msgid "Intervention {} edited"
msgstr "Eingriff {} bearbeitet" msgstr "Eingriff {} bearbeitet"
#: intervention/views.py:326 #: intervention/views.py:325
msgid "{} removed" msgid "{} removed"
msgstr "{} entfernt" msgstr "{} entfernt"
#: intervention/views.py:347 #: intervention/views.py:346
msgid "Revocation removed" msgid "Revocation removed"
msgstr "Widerspruch entfernt" msgstr "Widerspruch entfernt"
#: intervention/views.py:429 #: intervention/views.py:428
msgid "Check performed" msgid "Check performed"
msgstr "Prüfung durchgeführt" msgstr "Prüfung durchgeführt"
#: intervention/views.py:451 #: intervention/views.py:450
msgid "Revocation added" msgid "Revocation added"
msgstr "Widerspruch hinzugefügt" msgstr "Widerspruch hinzugefügt"
#: intervention/views.py:522 #: intervention/views.py:521
msgid "There are errors on this intervention:" msgid "There are errors on this intervention:"
msgstr "Es liegen Fehler in diesem Eingriff vor:" msgstr "Es liegen Fehler in diesem Eingriff vor:"
@ -1525,11 +1525,11 @@ msgstr "Speichern"
msgid "Not editable" msgid "Not editable"
msgstr "Nicht editierbar" msgstr "Nicht editierbar"
#: konova/forms.py:138 konova/forms.py:305 #: konova/forms.py:138 konova/forms.py:307
msgid "Confirm" msgid "Confirm"
msgstr "Bestätige" msgstr "Bestätige"
#: konova/forms.py:150 konova/forms.py:314 #: konova/forms.py:150 konova/forms.py:316
msgid "Remove" msgid "Remove"
msgstr "Löschen" msgstr "Löschen"
@ -1542,56 +1542,56 @@ msgstr "Sie sind dabei {} {} zu löschen"
msgid "Geometry" msgid "Geometry"
msgstr "Geometrie" msgstr "Geometrie"
#: konova/forms.py:315 #: konova/forms.py:317
msgid "Are you sure?" msgid "Are you sure?"
msgstr "Sind Sie sicher?" msgstr "Sind Sie sicher?"
#: konova/forms.py:342 #: konova/forms.py:344
msgid "Created on" msgid "Created on"
msgstr "Erstellt" msgstr "Erstellt"
#: konova/forms.py:344 #: konova/forms.py:346
msgid "When has this file been created? Important for photos." msgid "When has this file been created? Important for photos."
msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?" msgstr "Wann wurde diese Datei erstellt oder das Foto aufgenommen?"
#: konova/forms.py:355 #: konova/forms.py:357
#: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231 #: venv/lib/python3.7/site-packages/django/db/models/fields/files.py:231
msgid "File" msgid "File"
msgstr "Datei" msgstr "Datei"
#: konova/forms.py:357 #: konova/forms.py:359
msgid "Allowed formats: pdf, jpg, png. Max size 15 MB." msgid "Allowed formats: pdf, jpg, png. Max size 15 MB."
msgstr "Formate: pdf, jpg, png. Maximal 15 MB." msgstr "Formate: pdf, jpg, png. Maximal 15 MB."
#: konova/forms.py:403 #: konova/forms.py:405
msgid "Unsupported file type" msgid "Unsupported file type"
msgstr "Dateiformat nicht unterstützt" msgstr "Dateiformat nicht unterstützt"
#: konova/forms.py:410 #: konova/forms.py:412
msgid "File too large" msgid "File too large"
msgstr "Datei zu groß" msgstr "Datei zu groß"
#: konova/forms.py:419 #: konova/forms.py:421
msgid "Added document" msgid "Added document"
msgstr "Dokument hinzugefügt" msgstr "Dokument hinzugefügt"
#: konova/forms.py:442 #: konova/forms.py:444
msgid "Confirm record" msgid "Confirm record"
msgstr "Verzeichnen bestätigen" msgstr "Verzeichnen bestätigen"
#: konova/forms.py:450 #: konova/forms.py:452
msgid "Record data" msgid "Record data"
msgstr "Daten verzeichnen" msgstr "Daten verzeichnen"
#: konova/forms.py:457 #: konova/forms.py:459
msgid "Confirm unrecord" msgid "Confirm unrecord"
msgstr "Entzeichnen bestätigen" msgstr "Entzeichnen bestätigen"
#: konova/forms.py:458 #: konova/forms.py:460
msgid "Unrecord data" msgid "Unrecord data"
msgstr "Daten entzeichnen" msgstr "Daten entzeichnen"
#: konova/forms.py:459 #: konova/forms.py:461
msgid "I, {} {}, confirm that this data must be unrecorded." msgid "I, {} {}, confirm that this data must be unrecorded."
msgstr "" msgstr ""
"Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen." "Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen."
@ -1726,6 +1726,8 @@ msgid ""
"Action canceled. Eco account is recorded or deductions exist. Only " "Action canceled. Eco account is recorded or deductions exist. Only "
"conservation office member can perform this action." "conservation office member can perform this action."
msgstr "" msgstr ""
"Aktion abgebrochen. Ökokonto ist bereits verzeichnet oder Abbuchungen liegen vor. Nur "
"Eintragungsstellennutzer können diese Aktion jetzt durchführen."
#: konova/utils/message_templates.py:25 #: konova/utils/message_templates.py:25
msgid "Edited general data" msgid "Edited general data"
@ -1739,6 +1741,10 @@ msgstr "Zustand hinzugefügt"
msgid "Added compensation action" msgid "Added compensation action"
msgstr "Maßnahme hinzufügen" msgstr "Maßnahme hinzufügen"
#: konova/utils/message_templates.py:31
msgid "Geometry conflict detected with {}"
msgstr "Geometriekonflikt mit folgenden Einträgen erkannt: {}"
#: konova/utils/messenger.py:69 #: konova/utils/messenger.py:69
msgid "{} checked" msgid "{} checked"
msgstr "{} geprüft" msgstr "{} geprüft"