#169 Admin on teams

* adds admin column on team index view
* refactors Team model, so multiple members can become admins
* adds team migration for switch from fkey->m2m structure
* renames 'Group' to 'Permission' on user index view to avoid confusion between 'Groups' and Teams
* adds new autocomplete route for team-admin selection based on already selected members of the TeamForm
pull/170/head
mpeltriaux 2 years ago
parent b790921e42
commit 170e5798ec

@ -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

@ -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

@ -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)

@ -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)

@ -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:

Binary file not shown.

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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..."

@ -11,7 +11,7 @@
</h4>
{% if form.form_caption is not None %}
<small>
{{ form.form_caption }}
{{ form.form_caption|linebreaks }}
</small>
{% endif %}
<form method="post" action="{{ form.action_url }}" {% for attr_key, attr_val in form.form_attrs.items %} {{attr_key}}="{{attr_val}}"{% endfor %}>

@ -16,7 +16,7 @@
<div class="modal-body">
<article>
{{ form.form_caption }}
{{ form.form_caption|linebreaks }}
</article>
{% include 'form/table/generic_table_form_body.html' %}
</div>

@ -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)

@ -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):

@ -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',
),
]

@ -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.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

@ -18,7 +18,7 @@
<td>{{user.email}}</td>
</tr>
<tr>
<th scope="row">{% trans 'Groups' %}</th>
<th scope="row">{% trans 'Permissions' %}</th>
<td>
{% for group in user.groups.all %}
<span class="badge badge-pill rlp-r">{% trans group.name %}</span>

@ -28,6 +28,7 @@
<th scope="col" class="align-middle">{% trans 'Name' %}</th>
<th scope="col" class="align-middle w-20">{% trans 'Description' %}</th>
<th scope="col" class="align-middle">{% trans 'Members' %}</th>
<th scope="col" class="align-middle">{% trans 'Administrator' %}</th>
<th scope="col" class="align-middle">{% trans 'Action' %}</th>
</tr>
</thead>
@ -45,11 +46,16 @@
<span class="badge badge-pill rlp-r">{{member.username}}</span>
{% endfor %}
</td>
<td>
{% for admin in team.admins.all %}
<span class="badge badge-pill rlp-r">{{admin.username}}</span>
{% endfor %}
</td>
<td>
<button class="btn rlp-r btn-modal" data-form-url="{% url 'user:team-leave' team.id %}" title="{% trans 'Leave team' %}">
{% fa5_icon 'sign-out-alt' %}
</button>
{% if team.admin == user %}
{% if user in team.admins.all %}
<button class="btn rlp-r btn-modal" data-form-url="{% url 'user:team-edit' team.id %}" title="{% trans 'Edit team' %}">
{% fa5_icon 'edit' %}
</button>

@ -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(

Loading…
Cancel
Save