diff --git a/konova/autocompletes.py b/konova/autocompletes.py index e6036f0..288ee02 100644 --- a/konova/autocompletes.py +++ b/konova/autocompletes.py @@ -108,6 +108,29 @@ class ShareTeamAutocomplete(Select2QuerySetView): return qs +class TeamAdminAutocomplete(Select2QuerySetView): + """ Autocomplete for share with teams + + """ + def get_queryset(self): + if self.request.user.is_anonymous: + return User.objects.none() + qs = User.objects.filter( + id__in=self.forwarded.get("members", []) + ).exclude( + id__in=self.forwarded.get("admins", []) + ) + if self.q: + # Due to privacy concerns only a full username match will return the proper user entry + qs = qs.filter( + name__icontains=self.q + ) + qs = qs.order_by( + "username" + ) + return qs + + class KonovaCodeAutocomplete(Select2GroupQuerySetView): """ Provides simple autocomplete functionality for codes diff --git a/konova/forms.py b/konova/forms.py index 72a4468..0a341e0 100644 --- a/konova/forms.py +++ b/konova/forms.py @@ -326,7 +326,7 @@ class SimpleGeomForm(BaseForm): features = [] features_json = geom.get("features", []) for feature in features_json: - g = gdal.OGRGeometry(json.dumps(feature["geometry"]), srs=DEFAULT_SRID_RLP) + g = gdal.OGRGeometry(json.dumps(feature.get("geometry", feature)), srs=DEFAULT_SRID_RLP) if g.geom_type not in ["Polygon", "MultiPolygon"]: self.add_error("geom", _("Only surfaces allowed. Points or lines must be buffered.")) is_valid = False diff --git a/konova/tests/test_autocompletes.py b/konova/tests/test_autocompletes.py index 95a3508..1533d57 100644 --- a/konova/tests/test_autocompletes.py +++ b/konova/tests/test_autocompletes.py @@ -72,6 +72,7 @@ class AutocompleteTestCase(BaseTestCase): "codes-conservation-office-autocomplete", "share-user-autocomplete", "share-team-autocomplete", + "team-admin-autocomplete", ] for test in tests: self.client.login(username=self.superuser.username, password=self.superuser_pw) diff --git a/konova/tests/test_views.py b/konova/tests/test_views.py index afc381d..1ad90dd 100644 --- a/konova/tests/test_views.py +++ b/konova/tests/test_views.py @@ -274,7 +274,7 @@ class BaseTestCase(TestCase): team = Team.objects.get_or_create( name="Testteam", description="Testdescription", - admin=self.superuser, + admins__in=[self.superuser], )[0] team.users.add(self.superuser) diff --git a/konova/urls.py b/konova/urls.py index 75ac011..e012683 100644 --- a/konova/urls.py +++ b/konova/urls.py @@ -21,7 +21,7 @@ from konova.autocompletes import EcoAccountAutocomplete, \ InterventionAutocomplete, CompensationActionCodeAutocomplete, BiotopeCodeAutocomplete, LawCodeAutocomplete, \ RegistrationOfficeCodeAutocomplete, ConservationOfficeCodeAutocomplete, ProcessTypeCodeAutocomplete, \ ShareUserAutocomplete, BiotopeExtraCodeAutocomplete, CompensationActionDetailCodeAutocomplete, \ - ShareTeamAutocomplete, HandlerCodeAutocomplete + ShareTeamAutocomplete, HandlerCodeAutocomplete, TeamAdminAutocomplete from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG from konova.sso.sso import KonovaSSOClient from konova.views import logout_view, home_view, get_geom_parcels, get_geom_parcels_content, map_client_proxy_view @@ -58,6 +58,7 @@ urlpatterns = [ path("atcmplt/codes/handler", HandlerCodeAutocomplete.as_view(), name="codes-handler-autocomplete"), path("atcmplt/share/u", ShareUserAutocomplete.as_view(), name="share-user-autocomplete"), path("atcmplt/share/t", ShareTeamAutocomplete.as_view(), name="share-team-autocomplete"), + path("atcmplt/team/admin", TeamAdminAutocomplete.as_view(), name="team-admin-autocomplete"), ] if DEBUG: diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index 5e1ebe0..7cf9a79 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 e735ce6..27578e3 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -26,7 +26,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-30 09:16+0200\n" +"POT-Creation-Date: 2022-05-30 11:51+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -65,7 +65,7 @@ msgstr "Verantwortliche Stelle" #: intervention/forms/forms.py:64 intervention/forms/forms.py:81 #: intervention/forms/forms.py:97 intervention/forms/forms.py:113 #: intervention/forms/forms.py:154 intervention/forms/modalForms.py:49 -#: intervention/forms/modalForms.py:63 user/forms.py:196 +#: intervention/forms/modalForms.py:63 user/forms.py:196 user/forms.py:260 msgid "Click for selection" msgstr "Auswählen..." @@ -138,11 +138,11 @@ msgstr "Zuständigkeitsbereich" #: analysis/templates/analysis/reports/includes/intervention/amount.html:17 #: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:8 #: analysis/templates/analysis/reports/includes/intervention/laws.html:17 -#: compensation/tables.py:41 +#: compensation/tables.py:38 #: compensation/templates/compensation/detail/compensation/view.html:64 -#: intervention/tables.py:40 +#: intervention/tables.py:38 #: intervention/templates/intervention/detail/view.html:68 -#: user/models/user_action.py:20 +#: user/models/user_action.py:21 msgid "Checked" msgstr "Geprüft" @@ -154,14 +154,14 @@ msgstr "Geprüft" #: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:9 #: analysis/templates/analysis/reports/includes/intervention/laws.html:20 #: analysis/templates/analysis/reports/includes/old_data/amount.html:18 -#: compensation/tables.py:47 compensation/tables.py:230 -#: compensation/templates/compensation/detail/compensation/view.html:78 +#: compensation/tables.py:44 compensation/tables.py:219 +#: compensation/templates/compensation/detail/compensation/view.html:83 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:31 #: compensation/templates/compensation/detail/eco_account/view.html:45 #: ema/tables.py:44 ema/templates/ema/detail/view.html:35 -#: intervention/tables.py:46 -#: intervention/templates/intervention/detail/view.html:82 -#: user/models/user_action.py:21 +#: intervention/tables.py:44 +#: intervention/templates/intervention/detail/view.html:87 +#: user/models/user_action.py:22 msgid "Recorded" msgstr "Verzeichnet" @@ -198,7 +198,7 @@ msgid "Other registration office" msgstr "Andere Zulassungsbehörden" #: analysis/templates/analysis/reports/includes/compensation/card_compensation.html:11 -#: compensation/tables.py:68 +#: compensation/tables.py:65 #: intervention/templates/intervention/detail/includes/compensations.html:8 #: intervention/templates/intervention/report/report.html:45 msgid "Compensations" @@ -227,7 +227,7 @@ msgid "Surface" msgstr "Fläche" #: analysis/templates/analysis/reports/includes/intervention/card_intervention.html:10 -#: intervention/tables.py:67 +#: intervention/tables.py:65 msgid "Interventions" msgstr "Eingriffe" @@ -285,8 +285,8 @@ msgid "Type" msgstr "Typ" #: analysis/templates/analysis/reports/includes/old_data/amount.html:24 -#: compensation/tables.py:90 intervention/forms/modalForms.py:375 -#: intervention/forms/modalForms.py:382 intervention/tables.py:89 +#: compensation/tables.py:87 intervention/forms/modalForms.py:375 +#: intervention/forms/modalForms.py:382 intervention/tables.py:87 #: intervention/templates/intervention/detail/view.html:19 #: konova/templates/konova/includes/quickstart/interventions.html:4 #: templates/navbars/navbar.html:22 @@ -294,7 +294,7 @@ msgid "Intervention" msgstr "Eingriff" #: analysis/templates/analysis/reports/includes/old_data/amount.html:34 -#: compensation/tables.py:274 +#: compensation/tables.py:263 #: compensation/templates/compensation/detail/eco_account/view.html:20 #: intervention/forms/modalForms.py:348 intervention/forms/modalForms.py:355 #: konova/templates/konova/includes/quickstart/ecoaccounts.html:4 @@ -314,9 +314,9 @@ msgstr "Vor" msgid "Show only unrecorded" msgstr "Nur unverzeichnete anzeigen" -#: compensation/forms/forms.py:32 compensation/tables.py:26 -#: compensation/tables.py:205 ema/tables.py:29 intervention/forms/forms.py:28 -#: intervention/tables.py:25 +#: compensation/forms/forms.py:32 compensation/tables.py:23 +#: compensation/tables.py:194 ema/tables.py:29 intervention/forms/forms.py:28 +#: intervention/tables.py:23 #: intervention/templates/intervention/detail/includes/compensations.html:30 msgid "Identifier" msgstr "Kennung" @@ -326,8 +326,8 @@ msgstr "Kennung" msgid "Generated automatically" msgstr "Automatisch generiert" -#: compensation/forms/forms.py:44 compensation/tables.py:31 -#: compensation/tables.py:210 +#: compensation/forms/forms.py:44 compensation/tables.py:28 +#: compensation/tables.py:199 #: compensation/templates/compensation/detail/compensation/includes/documents.html:28 #: compensation/templates/compensation/detail/compensation/view.html:32 #: compensation/templates/compensation/detail/eco_account/includes/documents.html:28 @@ -337,7 +337,7 @@ msgstr "Automatisch generiert" #: ema/tables.py:34 ema/templates/ema/detail/includes/documents.html:28 #: ema/templates/ema/detail/view.html:31 #: ema/templates/ema/report/report.html:12 intervention/forms/forms.py:40 -#: intervention/tables.py:30 +#: intervention/tables.py:28 #: intervention/templates/intervention/detail/includes/compensations.html:33 #: intervention/templates/intervention/detail/includes/documents.html:28 #: intervention/templates/intervention/detail/view.html:31 @@ -675,62 +675,62 @@ msgstr "" "Es wurde bereits mehr Fläche abgebucht, als Sie nun als abbuchbar einstellen " "wollen. Kontaktieren Sie die für die Abbuchungen verantwortlichen Nutzer!" -#: compensation/tables.py:36 compensation/tables.py:215 ema/tables.py:39 -#: intervention/tables.py:35 konova/filters/mixins.py:98 +#: compensation/tables.py:33 compensation/tables.py:204 ema/tables.py:39 +#: intervention/tables.py:33 konova/filters/mixins.py:98 msgid "Parcel gmrkng" msgstr "Gemarkung" -#: compensation/tables.py:53 compensation/tables.py:236 ema/tables.py:50 -#: intervention/tables.py:52 +#: compensation/tables.py:50 compensation/tables.py:225 ema/tables.py:50 +#: intervention/tables.py:50 msgid "Editable" msgstr "Freigegeben" -#: compensation/tables.py:59 compensation/tables.py:242 ema/tables.py:56 -#: intervention/tables.py:58 +#: compensation/tables.py:56 compensation/tables.py:231 ema/tables.py:56 +#: intervention/tables.py:56 msgid "Last edit" msgstr "Zuletzt bearbeitet" -#: compensation/tables.py:90 compensation/tables.py:274 ema/tables.py:89 -#: intervention/tables.py:89 +#: compensation/tables.py:87 compensation/tables.py:263 ema/tables.py:89 +#: intervention/tables.py:87 msgid "Open {}" msgstr "Öffne {}" -#: compensation/tables.py:170 -#: compensation/templates/compensation/detail/compensation/view.html:81 +#: compensation/tables.py:163 +#: compensation/templates/compensation/detail/compensation/view.html:86 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:58 #: compensation/templates/compensation/detail/eco_account/view.html:48 -#: ema/tables.py:131 ema/templates/ema/detail/view.html:38 -#: intervention/tables.py:167 -#: intervention/templates/intervention/detail/view.html:85 +#: ema/tables.py:130 ema/templates/ema/detail/view.html:38 +#: intervention/tables.py:161 +#: intervention/templates/intervention/detail/view.html:90 msgid "Not recorded yet" msgstr "Noch nicht verzeichnet" -#: compensation/tables.py:175 compensation/tables.py:334 ema/tables.py:136 -#: intervention/tables.py:172 +#: compensation/tables.py:166 compensation/tables.py:321 ema/tables.py:133 +#: intervention/tables.py:164 msgid "Recorded on {} by {}" msgstr "Am {} von {} verzeichnet worden" -#: compensation/tables.py:197 compensation/tables.py:356 ema/tables.py:157 -#: intervention/tables.py:193 +#: compensation/tables.py:186 compensation/tables.py:343 ema/tables.py:154 +#: intervention/tables.py:185 msgid "Full access granted" msgstr "Für Sie freigegeben - Datensatz kann bearbeitet werden" -#: compensation/tables.py:197 compensation/tables.py:356 ema/tables.py:157 -#: intervention/tables.py:193 +#: compensation/tables.py:186 compensation/tables.py:343 ema/tables.py:154 +#: intervention/tables.py:185 msgid "Access not granted" msgstr "Nicht freigegeben - Datensatz nur lesbar" -#: compensation/tables.py:220 +#: compensation/tables.py:209 #: compensation/templates/compensation/detail/eco_account/view.html:36 #: konova/templates/konova/widgets/progressbar.html:3 msgid "Available" msgstr "Verfügbar" -#: compensation/tables.py:251 +#: compensation/tables.py:240 msgid "Eco Accounts" msgstr "Ökokonten" -#: compensation/tables.py:329 +#: compensation/tables.py:318 msgid "Not recorded yet. Can not be used for deductions, yet." msgstr "" "Noch nicht verzeichnet. Kann noch nicht für Abbuchungen genutzt werden." @@ -782,7 +782,7 @@ msgstr "Menge" #: intervention/templates/intervention/detail/includes/documents.html:39 #: intervention/templates/intervention/detail/includes/payments.html:39 #: intervention/templates/intervention/detail/includes/revocation.html:43 -#: templates/log.html:10 user/templates/user/team/index.html:32 +#: templates/log.html:10 user/templates/user/team/index.html:33 msgid "Action" msgstr "Aktionen" @@ -975,43 +975,43 @@ msgstr "Nein" msgid "Is Coherence keeping compensation" msgstr "Ist Kohärenzsicherungsmaßnahme" -#: compensation/templates/compensation/detail/compensation/view.html:71 -#: intervention/templates/intervention/detail/view.html:75 +#: compensation/templates/compensation/detail/compensation/view.html:76 +#: intervention/templates/intervention/detail/view.html:80 msgid "Checked on " msgstr "Geprüft am " -#: compensation/templates/compensation/detail/compensation/view.html:71 -#: compensation/templates/compensation/detail/compensation/view.html:85 +#: compensation/templates/compensation/detail/compensation/view.html:76 +#: compensation/templates/compensation/detail/compensation/view.html:90 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:56 #: compensation/templates/compensation/detail/eco_account/view.html:52 #: ema/templates/ema/detail/view.html:42 -#: intervention/templates/intervention/detail/view.html:75 -#: intervention/templates/intervention/detail/view.html:89 +#: intervention/templates/intervention/detail/view.html:80 +#: intervention/templates/intervention/detail/view.html:94 msgid "by" msgstr "von" -#: compensation/templates/compensation/detail/compensation/view.html:85 +#: compensation/templates/compensation/detail/compensation/view.html:90 #: compensation/templates/compensation/detail/eco_account/view.html:52 #: ema/templates/ema/detail/view.html:42 -#: intervention/templates/intervention/detail/view.html:89 +#: intervention/templates/intervention/detail/view.html:94 msgid "Recorded on " msgstr "Verzeichnet am" -#: compensation/templates/compensation/detail/compensation/view.html:92 +#: compensation/templates/compensation/detail/compensation/view.html:97 #: compensation/templates/compensation/detail/eco_account/view.html:75 #: compensation/templates/compensation/report/compensation/report.html:24 #: compensation/templates/compensation/report/eco_account/report.html:37 #: ema/templates/ema/detail/view.html:61 #: ema/templates/ema/report/report.html:24 -#: intervention/templates/intervention/detail/view.html:108 +#: intervention/templates/intervention/detail/view.html:113 #: intervention/templates/intervention/report/report.html:87 msgid "Last modified" msgstr "Zuletzt bearbeitet" -#: compensation/templates/compensation/detail/compensation/view.html:106 +#: compensation/templates/compensation/detail/compensation/view.html:111 #: compensation/templates/compensation/detail/eco_account/view.html:89 #: ema/templates/ema/detail/view.html:75 -#: intervention/templates/intervention/detail/view.html:122 +#: intervention/templates/intervention/detail/view.html:127 msgid "Shared with" msgstr "Freigegeben für" @@ -1050,7 +1050,7 @@ msgstr "Eingriffskennung" #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:37 #: intervention/templates/intervention/detail/includes/deductions.html:34 -#: user/models/user_action.py:23 +#: user/models/user_action.py:24 msgid "Created" msgstr "Erstellt" @@ -1087,8 +1087,8 @@ msgstr "Keine Flächenmenge für Abbuchungen eingegeben. Bitte bearbeiten." #: intervention/templates/intervention/detail/view.html:55 #: intervention/templates/intervention/detail/view.html:59 #: intervention/templates/intervention/detail/view.html:63 -#: intervention/templates/intervention/detail/view.html:95 -#: intervention/templates/intervention/detail/view.html:99 +#: intervention/templates/intervention/detail/view.html:100 +#: intervention/templates/intervention/detail/view.html:104 msgid "Missing" msgstr "fehlt" @@ -1142,17 +1142,17 @@ msgid "Compensation {} edited" msgstr "Kompensation {} bearbeitet" #: compensation/views/compensation.py:182 compensation/views/eco_account.py:173 -#: ema/views.py:240 intervention/views.py:332 +#: ema/views.py:240 intervention/views.py:338 msgid "Edit {}" msgstr "Bearbeite {}" -#: compensation/views/compensation.py:261 compensation/views/eco_account.py:359 -#: ema/views.py:194 intervention/views.py:536 +#: compensation/views/compensation.py:268 compensation/views/eco_account.py:359 +#: ema/views.py:194 intervention/views.py:542 msgid "Log" msgstr "Log" -#: compensation/views/compensation.py:605 compensation/views/eco_account.py:727 -#: ema/views.py:558 intervention/views.py:682 +#: compensation/views/compensation.py:612 compensation/views/eco_account.py:727 +#: ema/views.py:558 intervention/views.py:688 msgid "Report {}" msgstr "Bericht {}" @@ -1173,32 +1173,32 @@ msgid "Eco-account removed" msgstr "Ökokonto entfernt" #: compensation/views/eco_account.py:380 ema/views.py:282 -#: intervention/views.py:635 +#: intervention/views.py:641 msgid "{} unrecorded" msgstr "{} entzeichnet" #: compensation/views/eco_account.py:380 ema/views.py:282 -#: intervention/views.py:635 +#: intervention/views.py:641 msgid "{} recorded" msgstr "{} verzeichnet" #: compensation/views/eco_account.py:804 ema/views.py:628 -#: intervention/views.py:433 +#: intervention/views.py:439 msgid "{} has already been shared with you" msgstr "{} wurde bereits für Sie freigegeben" #: compensation/views/eco_account.py:809 ema/views.py:633 -#: intervention/views.py:438 +#: intervention/views.py:444 msgid "{} has been shared with you" msgstr "{} ist nun für Sie freigegeben" #: compensation/views/eco_account.py:816 ema/views.py:640 -#: intervention/views.py:445 +#: intervention/views.py:451 msgid "Share link invalid" msgstr "Freigabelink ungültig" #: compensation/views/eco_account.py:839 ema/views.py:663 -#: intervention/views.py:468 +#: intervention/views.py:474 msgid "Share settings updated" msgstr "Freigabe Einstellungen aktualisiert" @@ -1292,14 +1292,14 @@ msgid "Intervention handler detail" msgstr "Detailangabe zum Eingriffsverursacher" #: intervention/forms/forms.py:173 -#: intervention/templates/intervention/detail/view.html:96 +#: intervention/templates/intervention/detail/view.html:101 #: intervention/templates/intervention/report/report.html:79 #: intervention/utils/quality.py:73 msgid "Registration date" msgstr "Datum Zulassung bzw. Satzungsbeschluss" #: intervention/forms/forms.py:185 -#: intervention/templates/intervention/detail/view.html:100 +#: intervention/templates/intervention/detail/view.html:105 #: intervention/templates/intervention/report/report.html:83 msgid "Binding on" msgstr "Datum Bestandskraft bzw. Rechtskraft" @@ -1471,7 +1471,7 @@ msgid "Remove payment" msgstr "Zahlung entfernen" #: intervention/templates/intervention/detail/includes/revocation.html:8 -#: intervention/templates/intervention/detail/view.html:104 +#: intervention/templates/intervention/detail/view.html:109 msgid "Revocations" msgstr "Widersprüche" @@ -1493,7 +1493,7 @@ msgstr "Widerspruch entfernen" msgid "Intervention handler" msgstr "Eingriffsverursacher" -#: intervention/templates/intervention/detail/view.html:103 +#: intervention/templates/intervention/detail/view.html:108 msgid "Exists" msgstr "vorhanden" @@ -1532,19 +1532,19 @@ msgstr "Eingriffe - Übersicht" msgid "Intervention {} added" msgstr "Eingriff {} hinzugefügt" -#: intervention/views.py:320 +#: intervention/views.py:326 msgid "Intervention {} edited" msgstr "Eingriff {} bearbeitet" -#: intervention/views.py:356 +#: intervention/views.py:362 msgid "{} removed" msgstr "{} entfernt" -#: intervention/views.py:489 +#: intervention/views.py:495 msgid "Check performed" msgstr "Prüfung durchgeführt" -#: intervention/views.py:640 +#: intervention/views.py:646 msgid "There are errors on this intervention:" msgstr "Es liegen Fehler in diesem Eingriff vor:" @@ -2058,7 +2058,8 @@ msgstr "Am {} von {} geprüft worden" #: konova/utils/message_templates.py:87 msgid "Data has changed since last check on {} by {}" -msgstr "Daten wurden nach der letzten Prüfung geändert. Letzte Prüfung am {} durch {}" +msgstr "" +"Daten wurden nach der letzten Prüfung geändert. Letzte Prüfung am {} durch {}" #: konova/utils/message_templates.py:88 msgid "Current data not checked yet" @@ -2547,11 +2548,11 @@ msgstr "Neuen Token generieren" msgid "A new token needs to be validated by an administrator!" msgstr "Neue Tokens müssen durch Administratoren freigeschaltet werden!" -#: user/forms.py:168 user/forms.py:172 user/forms.py:332 user/forms.py:337 +#: user/forms.py:168 user/forms.py:172 user/forms.py:351 user/forms.py:356 msgid "Team name" msgstr "Team Name" -#: user/forms.py:179 user/forms.py:345 user/templates/user/team/index.html:30 +#: user/forms.py:179 user/forms.py:364 user/templates/user/team/index.html:30 msgid "Description" msgstr "Beschreibung" @@ -2579,43 +2580,61 @@ msgstr "" "Sie werden standardmäßig der Administrator dieses Teams. Sie müssen sich " "selbst nicht zur Liste der Mitglieder hinzufügen." -#: user/forms.py:218 user/forms.py:279 +#: user/forms.py:218 user/forms.py:296 msgid "Name already taken. Try another." msgstr "Name bereits vergeben. Probieren Sie einen anderen." -#: user/forms.py:249 -msgid "Admin" -msgstr "Administrator" +#: user/forms.py:248 +msgid "Admins" +msgstr "Administratoren" #: user/forms.py:250 msgid "Administrators manage team details and members" msgstr "Administratoren verwalten die Teamdaten und Mitglieder" -#: user/forms.py:263 -msgid "Selected admin ({}) needs to be a member of this team." -msgstr "Gewählter Administrator ({}) muss ein Mitglied des Teams sein." +#: user/forms.py:273 +msgid "Selected admins need to be members of this team." +msgstr "Gewählte Administratoren müssen Teammitglieder sein." + +#: user/forms.py:280 +msgid "There must be at least one admin on this team." +msgstr "Es muss mindestens einen Administrator für das Team geben." -#: user/forms.py:291 user/templates/user/team/index.html:54 +#: user/forms.py:308 user/templates/user/team/index.html:60 msgid "Edit team" msgstr "Team bearbeiten" -#: user/forms.py:323 user/templates/user/team/index.html:50 +#: user/forms.py:336 +msgid "" +"ATTENTION!\n" +"\n" +"Removing the team means all members will lose their access to data, based on " +"this team! \n" +"\n" +"Are you sure to remove this team?" +msgstr "" +"ACHTUNG!\n\n" +"Wenn dieses Team gelöscht wird, verlieren alle Teammitglieder den Zugriff auf die Daten, die nur über dieses Team freigegeben sind!\n" +"\n" +"Sind Sie sicher, dass Sie dieses Team löschen möchten?" + +#: user/forms.py:342 user/templates/user/team/index.html:56 msgid "Leave team" msgstr "Team verlassen" -#: user/forms.py:356 +#: user/forms.py:375 msgid "Team" msgstr "Team" -#: user/models/user_action.py:22 +#: user/models/user_action.py:23 msgid "Unrecorded" msgstr "Entzeichnet" -#: user/models/user_action.py:24 +#: user/models/user_action.py:25 msgid "Edited" msgstr "Bearbeitet" -#: user/models/user_action.py:25 +#: user/models/user_action.py:26 msgid "Deleted" msgstr "Gelöscht" @@ -2632,8 +2651,8 @@ msgid "Name" msgstr "" #: user/templates/user/index.html:21 -msgid "Groups" -msgstr "Gruppen" +msgid "Permissions" +msgstr "Berechtigungen" #: user/templates/user/index.html:34 msgid "" @@ -2689,7 +2708,11 @@ msgstr "Neues Team hinzufügen" msgid "Members" msgstr "Mitglieder" -#: user/templates/user/team/index.html:57 +#: user/templates/user/team/index.html:32 +msgid "Administrator" +msgstr "" + +#: user/templates/user/team/index.html:63 msgid "Remove team" msgstr "Team entfernen" @@ -2741,19 +2764,19 @@ msgstr "API Nutzer Token" msgid "New team added" msgstr "Neues Team hinzugefügt" -#: user/views.py:191 +#: user/views.py:192 msgid "Team edited" msgstr "Team bearbeitet" -#: user/views.py:204 +#: user/views.py:206 msgid "Team removed" msgstr "Team gelöscht" -#: user/views.py:218 +#: user/views.py:220 msgid "You are not a member of this team" msgstr "Sie sind kein Mitglied dieses Teams" -#: user/views.py:225 +#: user/views.py:227 msgid "Left Team" msgstr "Team verlassen" @@ -4256,6 +4279,9 @@ msgstr "" msgid "Unable to connect to qpid with SASL mechanism %s" msgstr "" +#~ msgid "Groups" +#~ msgstr "Gruppen" + #~ msgid "Show more..." #~ msgstr "Mehr anzeigen..." diff --git a/templates/form/table/generic_table_form.html b/templates/form/table/generic_table_form.html index a89ee4b..5c00b89 100644 --- a/templates/form/table/generic_table_form.html +++ b/templates/form/table/generic_table_form.html @@ -11,7 +11,7 @@ {% if form.form_caption is not None %} - {{ form.form_caption }} + {{ form.form_caption|linebreaks }} {% endif %}
diff --git a/templates/modal/modal_form.html b/templates/modal/modal_form.html index 6f47b12..741b539 100644 --- a/templates/modal/modal_form.html +++ b/templates/modal/modal_form.html @@ -16,7 +16,7 @@ diff --git a/user/admin.py b/user/admin.py index f4ef9fc..d564066 100644 --- a/user/admin.py +++ b/user/admin.py @@ -68,22 +68,16 @@ class TeamAdmin(admin.ModelAdmin): list_display = [ "name", "description", - "admin", ] search_fields = [ "name", "description", ] filter_horizontal = [ - "users" + "users", + "admins", ] - def formfield_for_foreignkey(self, db_field, request, **kwargs): - if db_field.name == "admin": - team_id = request.resolver_match.kwargs.get("object_id", None) - kwargs["queryset"] = User.objects.filter(teams__id__in=[team_id]) - return super().formfield_for_foreignkey(db_field, request, **kwargs) - admin.site.register(User, UserAdmin) admin.site.register(Team, TeamAdmin) diff --git a/user/forms.py b/user/forms.py index 4a657af..f6831ff 100644 --- a/user/forms.py +++ b/user/forms.py @@ -230,7 +230,7 @@ class NewTeamModalForm(BaseModalForm): team = Team.objects.create( name=self.cleaned_data.get("name", None), description=self.cleaned_data.get("description", None), - admin=self.user, + admins__in=[self.user], ) members = self.cleaned_data.get("members", User.objects.none()) if self.user.id not in members: @@ -244,23 +244,40 @@ class NewTeamModalForm(BaseModalForm): class EditTeamModalForm(NewTeamModalForm): - admin = forms.ModelChoiceField( + admins = forms.ModelMultipleChoiceField( + label=_("Admins"), label_suffix="", - label=_("Admin"), help_text=_("Administrators manage team details and members"), - queryset=User.objects.none(), - empty_label=None, + required=True, + queryset=User.objects.all(), + widget=autocomplete.ModelSelect2Multiple( + url="team-admin-autocomplete", + forward=[ + "members", + "admins", + ], + attrs={ + "data-placeholder": _("Click for selection"), + }, + ), ) - def __is_admin_valid(self): - admin = self.cleaned_data.get("admin", None) - members = self.cleaned_data.get("members", None) - _is_valid = admin in members + def __is_admins_valid(self): + admins = set(self.cleaned_data.get("admins", {})) + members = set(self.cleaned_data.get("members", {})) + _is_valid = admins.issubset(members) if not _is_valid: self.add_error( - "members", - _("Selected admin ({}) needs to be a member of this team.").format(admin.username) + "admins", + _("Selected admins need to be members of this team.") + ) + + _is_admin_length_valid = len(admins) > 0 + if not _is_admin_length_valid: + self.add_error( + "admins", + _("There must be at least one admin on this team.") ) return _is_valid @@ -283,7 +300,7 @@ class EditTeamModalForm(NewTeamModalForm): def is_valid(self): super_valid = super().is_valid() - admin_valid = self.__is_admin_valid() + admin_valid = self.__is_admins_valid() return super_valid and admin_valid def __init__(self, *args, **kwargs): @@ -293,13 +310,13 @@ class EditTeamModalForm(NewTeamModalForm): self.cancel_redirect = reverse("user:team-index") members = self.instance.users.all() - self.fields["admin"].queryset = members + #self.fields["admins"].queryset = members form_data = { "members": members, "name": self.instance.name, "description": self.instance.description, - "admin": self.instance.admin, + "admins": self.instance.admins.all(), } self.load_initial_data(form_data) @@ -307,14 +324,16 @@ class EditTeamModalForm(NewTeamModalForm): with transaction.atomic(): self.instance.name = self.cleaned_data.get("name", None) self.instance.description = self.cleaned_data.get("description", None) - self.instance.admin = self.cleaned_data.get("admin", None) self.instance.save() self.instance.users.set(self.cleaned_data.get("members", [])) + self.instance.admins.set(self.cleaned_data.get("admins", [])) return self.instance class RemoveTeamModalForm(RemoveModalForm): - pass + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.form_caption = _("ATTENTION!\n\nRemoving the team means all members will lose their access to data, based on this team! \n\nAre you sure to remove this team?") class LeaveTeamModalForm(RemoveModalForm): diff --git a/user/migrations/0004_auto_20220530_1105.py b/user/migrations/0004_auto_20220530_1105.py new file mode 100644 index 0000000..1c6ea83 --- /dev/null +++ b/user/migrations/0004_auto_20220530_1105.py @@ -0,0 +1,35 @@ +# Generated by Django 3.1.3 on 2022-05-30 09:05 + +from django.conf import settings +from django.db import migrations, models + + +def migrate_fkey_admin_to_m2m(apps, schema_editor): + Team = apps.get_model('user', 'Team') + all_teams = Team.objects.all() + for team in all_teams: + admin = team.admin + if admin is None: + continue + team.admins.add(admin) + team.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0003_team'), + ] + + operations = [ + migrations.AddField( + model_name='team', + name='admins', + field=models.ManyToManyField(blank=True, related_name='_team_admins_+', to=settings.AUTH_USER_MODEL), + ), + migrations.RunPython(migrate_fkey_admin_to_m2m), + migrations.RemoveField( + model_name='team', + name='admin', + ), + ] diff --git a/user/models/team.py b/user/models/team.py index f14c7e0..7908115 100644 --- a/user/models/team.py +++ b/user/models/team.py @@ -11,7 +11,7 @@ class Team(UuidModel): name = models.CharField(max_length=500, null=True, blank=True) description = models.TextField(null=True, blank=True) users = models.ManyToManyField("user.User", blank=True, related_name="teams") - admin = models.ForeignKey("user.User", blank=True, null=True, related_name="+", on_delete=models.SET_NULL) + admins = models.ManyToManyField("user.User", blank=True, related_name="+") def __str__(self): return self.name @@ -104,6 +104,19 @@ class Team(UuidModel): """ self.users.remove(user) - if self.admin == user: - self.admin = self.users.first() - self.save() + self.admins.remove(user) + self.save() + + def is_user_admin(self, user) -> bool: + """ Returns whether a given user is an admin of the team + + Args: + user (User): The user + + Returns: + user_is_admin (bool): Whether the user is an admin or not + """ + user_is_admin = self.admins.filter( + id=user.id + ).exists() + return user_is_admin diff --git a/user/templates/user/index.html b/user/templates/user/index.html index f8fd616..cfe3bd0 100644 --- a/user/templates/user/index.html +++ b/user/templates/user/index.html @@ -18,7 +18,7 @@ {{user.email}} - {% trans 'Groups' %} + {% trans 'Permissions' %} {% for group in user.groups.all %} {% trans group.name %} diff --git a/user/templates/user/team/index.html b/user/templates/user/team/index.html index 3cd08e7..6356051 100644 --- a/user/templates/user/team/index.html +++ b/user/templates/user/team/index.html @@ -28,6 +28,7 @@ {% trans 'Name' %} {% trans 'Description' %} {% trans 'Members' %} + {% trans 'Administrator' %} {% trans 'Action' %} @@ -45,11 +46,16 @@ {{member.username}} {% endfor %} + + {% for admin in team.admins.all %} + {{admin.username}} + {% endfor %} + - {% if team.admin == user %} + {% if user in team.admins.all %} diff --git a/user/views.py b/user/views.py index 13c974a..b6e7f09 100644 --- a/user/views.py +++ b/user/views.py @@ -183,7 +183,8 @@ def new_team_view(request: HttpRequest): @login_required def edit_team_view(request: HttpRequest, id: str): team = get_object_or_404(Team, id=id) - if request.user != team.admin: + user_is_admin = team.is_user_admin(request.user) + if not user_is_admin: raise Http404() form = EditTeamModalForm(request.POST or None, instance=team, request=request) return form.process_request( @@ -196,7 +197,8 @@ def edit_team_view(request: HttpRequest, id: str): @login_required def remove_team_view(request: HttpRequest, id: str): team = get_object_or_404(Team, id=id) - if request.user != team.admin: + user_is_admin = team.is_user_admin(request.user) + if not user_is_admin: raise Http404() form = RemoveTeamModalForm(request.POST or None, instance=team, request=request) return form.process_request(