Merge pull request '169_Unknown_admin_on_teams' (#170) from 169_Unknown_admin_on_teams into master

Reviewed-on: SGD-Nord/konova#170
pull/172/head
mpeltriaux 2 years ago
commit 8b6c8dc1aa

@ -109,7 +109,7 @@
<tr>
<th scope="row">{% trans 'Shared with' %}</th>
<td class="align-middle">
{% for team in obj.intervention.teams.all %}
{% for team in obj.intervention.shared_teams %}
{% include 'user/includes/team_data_modal_button.html' %}
{% endfor %}
<hr>

@ -87,7 +87,7 @@
<tr>
<th scope="row">{% trans 'Shared with' %}</th>
<td class="align-middle">
{% for team in obj.teams.all %}
{% for team in obj.shared_teams %}
{% include 'user/includes/team_data_modal_button.html' %}
{% endfor %}
<hr>

@ -73,11 +73,11 @@
<tr>
<th scope="row">{% trans 'Shared with' %}</th>
<td class="align-middle">
{% for team in obj.teams.all %}
{% for team in obj.shared_teams %}
{% include 'user/includes/team_data_modal_button.html' %}
{% endfor %}
<hr>
{% for user in obj.users.all %}
{% for user in obj.user.all %}
{% include 'user/includes/contact_modal_button.html' %}
{% endfor %}
</td>

@ -125,7 +125,7 @@
<tr>
<th scope="row">{% trans 'Shared with' %}</th>
<td class="align-middle">
{% for team in obj.teams.all %}
{% for team in obj.shared_teams %}
{% include 'user/includes/team_data_modal_button.html' %}
{% endfor %}
<hr>

@ -98,6 +98,18 @@ class DeadlineAdmin(admin.ModelAdmin):
]
class DeletableObjectMixinAdmin(admin.ModelAdmin):
class Meta:
abstract = True
def restore_deleted_data(self, request, queryset):
queryset = queryset.filter(
deleted__isnull=False
)
for entry in queryset:
entry.deleted.delete()
class BaseResourceAdmin(admin.ModelAdmin):
fields = [
"created",
@ -109,7 +121,7 @@ class BaseResourceAdmin(admin.ModelAdmin):
]
class BaseObjectAdmin(BaseResourceAdmin):
class BaseObjectAdmin(BaseResourceAdmin, DeletableObjectMixinAdmin):
search_fields = [
"identifier",
"title",
@ -126,13 +138,6 @@ class BaseObjectAdmin(BaseResourceAdmin):
"deleted",
]
def restore_deleted_data(self, request, queryset):
queryset = queryset.filter(
deleted__isnull=False
)
for entry in queryset:
entry.deleted.delete()
# Outcommented for a cleaner admin backend on production

@ -96,7 +96,9 @@ class ShareTeamAutocomplete(Select2QuerySetView):
def get_queryset(self):
if self.request.user.is_anonymous:
return Team.objects.none()
qs = Team.objects.all()
qs = Team.objects.filter(
deleted__isnull=True
)
if self.q:
# Due to privacy concerns only a full username match will return the proper user entry
qs = qs.filter(
@ -108,6 +110,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

@ -87,25 +87,15 @@ class BaseResource(UuidModel):
super().delete()
class BaseObject(BaseResource):
"""
A basic object model, which specifies BaseResource.
class DeletableObjectMixin(models.Model):
""" Wraps deleted field and related functionality
Mainly used for intervention, compensation, ecoaccount
"""
identifier = models.CharField(max_length=1000, null=True, blank=True)
title = models.CharField(max_length=1000, null=True, blank=True)
deleted = models.ForeignKey("user.UserActionLogEntry", on_delete=models.SET_NULL, null=True, blank=True, related_name='+')
comment = models.TextField(null=True, blank=True)
log = models.ManyToManyField("user.UserActionLogEntry", blank=True, help_text="Keeps all user actions of an object", editable=False)
class Meta:
abstract = True
@abstractmethod
def set_status_messages(self, request: HttpRequest):
raise NotImplementedError
def mark_as_deleted(self, user, send_mail: bool = True):
""" Mark an entry as deleted
@ -140,6 +130,25 @@ class BaseObject(BaseResource):
self.save()
class BaseObject(BaseResource, DeletableObjectMixin):
"""
A basic object model, which specifies BaseResource.
Mainly used for intervention, compensation, ecoaccount
"""
identifier = models.CharField(max_length=1000, null=True, blank=True)
title = models.CharField(max_length=1000, null=True, blank=True)
comment = models.TextField(null=True, blank=True)
log = models.ManyToManyField("user.UserActionLogEntry", blank=True, help_text="Keeps all user actions of an object", editable=False)
class Meta:
abstract = True
@abstractmethod
def set_status_messages(self, request: HttpRequest):
raise NotImplementedError
def mark_as_edited(self, performing_user, request: HttpRequest = None, edit_comment: str = None):
""" In case the object or a related object changed the log history needs to be updated
@ -484,8 +493,8 @@ class ShareableObjectMixin(models.Model):
Returns:
"""
directly_shared = self.users.filter(id=user.id).exists()
team_shared = self.teams.filter(
directly_shared = self.shared_users.filter(id=user.id).exists()
team_shared = self.shared_teams.filter(
users__in=[user]
).exists()
is_shared = directly_shared or team_shared
@ -622,7 +631,9 @@ class ShareableObjectMixin(models.Model):
Returns:
teams (QuerySet)
"""
return self.teams.all()
return self.teams.filter(
deleted__isnull=True
)
@abstractmethod
def get_share_url(self):

@ -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,6 @@ class BaseTestCase(TestCase):
team = Team.objects.get_or_create(
name="Testteam",
description="Testdescription",
admin=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>

@ -1,6 +1,7 @@
from django.contrib import admin
from user.models import UserNotification, UserActionLogEntry, User, Team
from konova.admin import DeletableObjectMixinAdmin
from user.models import User, Team
class UserNotificationAdmin(admin.ModelAdmin):
@ -64,26 +65,28 @@ class UserActionLogEntryAdmin(admin.ModelAdmin):
]
class TeamAdmin(admin.ModelAdmin):
class TeamAdmin(DeletableObjectMixinAdmin, admin.ModelAdmin):
list_display = [
"name",
"description",
"admin",
"deleted",
]
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)
readonly_fields = [
"deleted"
]
actions = [
"restore_deleted_data"
]
admin.site.register(User, UserAdmin)
admin.site.register(Team, TeamAdmin)

@ -230,8 +230,8 @@ class NewTeamModalForm(BaseModalForm):
team = Team.objects.create(
name=self.cleaned_data.get("name", None),
description=self.cleaned_data.get("description", None),
admin=self.user,
)
team.admins.add(self.user)
members = self.cleaned_data.get("members", User.objects.none())
if self.user.id not in members:
members = members.union(
@ -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,12 @@ class EditTeamModalForm(NewTeamModalForm):
self.cancel_redirect = reverse("user:team-index")
members = self.instance.users.all()
self.fields["admin"].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 +323,20 @@ 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?")
def save(self):
self.instance.mark_as_deleted(self.user)
return self.instance
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',
),
]

@ -0,0 +1,19 @@
# Generated by Django 3.1.3 on 2022-05-30 12:42
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('user', '0004_auto_20220530_1105'),
]
operations = [
migrations.AddField(
model_name='team',
name='deleted',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'),
),
]

@ -1,21 +1,35 @@
from django.db import models
from konova.models import UuidModel
from konova.models import UuidModel, DeletableObjectMixin
from konova.utils.mailer import Mailer
from user.models import UserActionLogEntry
class Team(UuidModel):
class Team(UuidModel, DeletableObjectMixin):
""" Groups users in self managed teams. Can be used for multi-sharing of data
"""
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
def mark_as_deleted(self, user):
""" Creates an UserAction entry and stores it in the correct field
Args:
user (User): The performing user
Returns:
"""
delete_action = UserActionLogEntry.get_deleted_action(user, "Team deleted")
self.deleted = delete_action
self.save()
def send_mail_shared_access_given_team(self, obj_identifier, obj_title):
""" Sends a mail to the team members in case of given shared access
@ -104,6 +118,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

@ -160,3 +160,15 @@ class User(AbstractUser):
else:
token = self.api_token
return token
@property
def shared_teams(self):
""" Wrapper for fetching active teams of this user
Returns:
"""
shared_teams = self.teams.filter(
deleted__isnull=True
)
return shared_teams

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

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

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

@ -0,0 +1,112 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 30.05.22
"""
from django.test import Client
from django.contrib.auth.models import Group
from django.urls import reverse
from intervention.models import Revocation
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
from konova.tests.test_views import BaseViewTestCase
class UserViewTestCase(BaseViewTestCase):
@classmethod
def setUpTestData(cls) -> None:
super().setUpTestData()
def setUp(self) -> None:
super().setUp()
self.team.users.add(self.superuser)
self.team.admins.add(self.superuser)
# Prepare urls
self.index_url = reverse("user:index", args=())
self.notification_url = reverse("user:notifications", args=())
self.api_token_url = reverse("user:api-token", args=())
self.contact_url = reverse("user:contact", args=(self.superuser.id,))
self.team_url = reverse("user:team-index", args=())
self.new_team_url = reverse("user:team-new", args=())
self.data_team_url = reverse("user:team-data", args=(self.team.id,))
self.edit_team_url = reverse("user:team-edit", args=(self.team.id,))
self.remove_team_url = reverse("user:team-remove", args=(self.team.id,))
self.leave_team_url = reverse("user:team-leave", args=(self.team.id,))
def test_views_anonymous_user(self):
""" Check correct status code for all requests
Assumption: User not logged in
Returns:
"""
# Unknown client
client = Client()
login_redirect_base = f"{self.login_url}?next="
fail_urls = {
self.index_url: f"{login_redirect_base}{self.index_url}",
self.notification_url: f"{login_redirect_base}{self.notification_url}",
self.api_token_url: f"{login_redirect_base}{self.api_token_url}",
self.contact_url: f"{login_redirect_base}{self.contact_url}",
self.team_url: f"{login_redirect_base}{self.team_url}",
self.new_team_url: f"{login_redirect_base}{self.new_team_url}",
self.data_team_url: f"{login_redirect_base}{self.data_team_url}",
self.edit_team_url: f"{login_redirect_base}{self.edit_team_url}",
self.remove_team_url: f"{login_redirect_base}{self.remove_team_url}",
self.leave_team_url: f"{login_redirect_base}{self.leave_team_url}",
}
for url in fail_urls:
response = client.get(url, follow=True)
self.assertEqual(response.redirect_chain[0], (f"{self.login_url}?next={url}", 302), msg=f"Failed for {url}. Redirect chain is {response.redirect_chain}")
def test_views_logged_in(self):
""" Check correct status code for all requests
Assumption: User logged in but has no groups
Returns:
"""
# Login client
client = Client()
client.login(username=self.superuser.username, password=self.superuser_pw)
self.superuser.groups.set([])
success_urls = [
self.index_url,
self.notification_url,
self.contact_url,
self.team_url,
self.new_team_url,
self.data_team_url,
self.edit_team_url,
self.remove_team_url,
self.leave_team_url,
]
fail_urls = [
self.api_token_url, # expects default permission
]
self.assert_url_success(client, success_urls)
self.assert_url_fail(client, fail_urls)
# Check for modified default user permission
self.superuser.groups.add(
Group.objects.get(
name=DEFAULT_GROUP
)
)
success_url = [
self.api_token_url, # must work now
]
self.assert_url_success(client, success_url)

@ -0,0 +1,158 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 30.05.22
"""
from django.urls import reverse
from konova.tests.test_views import BaseWorkflowTestCase
from user.models import Team
class UserWorkflowTestCase(BaseWorkflowTestCase):
""" This test case adds workflow tests
"""
@classmethod
def setUpTestData(cls):
super().setUpTestData()
def setUp(self) -> None:
super().setUp()
# Add user to team
self.team.users.add(self.superuser)
def test_new_team(self):
"""
Check a normal creation of a new team.
Returns:
"""
team_name = self.create_dummy_string()
team_description = self.create_dummy_string()
new_url = reverse("user:team-new", args=())
post_data = {
"name": team_name,
"description": team_description,
"members": [self.superuser.id],
}
response = self.client_user.post(
new_url,
post_data
)
response_code = response.status_code
self.assertEqual(response_code, 302, msg=f"Unexpected status code received from response ({response_code})")
new_team = Team.objects.get(
name=team_name
)
self.assertEqual(new_team.description, team_description)
self.assertEqual([self.superuser], list(new_team.users.all()))
self.assertEqual([self.superuser], list(new_team.admins.all()), msg="Creator is not admin by default but should!")
def test_edit_team(self):
"""
Check editing of an existing team.
Returns:
"""
existing_team = self.team
existing_team_name = existing_team.name
existing_team_description = existing_team.description
edited_team_name = self.create_dummy_string()
edited_team_description = self.create_dummy_string()
new_url = reverse("user:team-edit", args=(existing_team.id,))
post_data = {
"name": edited_team_name,
"description": edited_team_description,
}
# Expect the first try to fail since user is member but not admin of the team
response = self.client_user.post(
new_url,
post_data
)
response_code = response.status_code
self.assertEqual(response_code, 404, msg=f"Unexpected status code received from response ({response_code})")
# Now add the user to the list of team admins and try again!
existing_team.admins.add(self.superuser)
response = self.client_user.post(
new_url,
post_data
)
response_code = response.status_code
self.assertEqual(response_code, 200, msg=f"Unexpected status code received from response ({response_code})")
existing_team.refresh_from_db()
self.assertEqual(existing_team.description, existing_team_description)
self.assertEqual(existing_team.name, existing_team_name)
self.assertEqual([self.superuser], list(existing_team.users.all()))
self.assertEqual([self.superuser], list(existing_team.admins.all()), msg="Creator is not admin by default but should!")
def test_leave_team(self):
"""
Checks leaving of a user from an existing team.
Returns:
"""
existing_team = self.team
new_url = reverse("user:team-leave", args=(existing_team.id,))
post_data = {
"confirm": True,
}
response = self.client_user.post(
new_url,
post_data
)
response_code = response.status_code
self.assertEqual(response_code, 302, msg=f"Unexpected status code received from response ({response_code})")
existing_team.refresh_from_db()
self.assertEqual([], list(existing_team.users.all()))
self.assertEqual([], list(existing_team.admins.all()))
def test_remove_team(self):
"""
Checks removing of an existing team.
Returns:
"""
existing_team = self.team
new_url = reverse("user:team-remove", args=(existing_team.id,))
post_data = {
"confirm": True,
}
# User is member but not admin. This response must fail!
response = self.client_user.post(
new_url,
post_data
)
response_code = response.status_code
self.assertEqual(response_code, 404, msg=f"Unexpected status code received from response ({response_code})")
# Add user to admins and try again
existing_team.admins.add(self.superuser)
response = self.client_user.post(
new_url,
post_data
)
response_code = response.status_code
self.assertEqual(response_code, 302, msg=f"Unexpected status code received from response ({response_code})")
existing_team.refresh_from_db()
self.assertIsNotNone(existing_team.deleted, msg="Deleted action not created")
self.assertNotIn(existing_team, self.superuser.shared_teams)

@ -163,7 +163,7 @@ def index_team_view(request: HttpRequest):
template = "user/team/index.html"
user = request.user
context = {
"teams": user.teams.all(),
"teams": user.shared_teams,
"tab_title": _("Teams"),
}
context = BaseContext(request, context).context
@ -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