members: add translations for permissions, implemenet may_delete, filter unsubmitted statements default queryset by permissions

v1-0-stable
Christian Merten 3 years ago
parent 8f7c5605e9
commit 62e2a40d07
Signed by: christian.merten
GPG Key ID: D953D69721B948B3

@ -1,7 +1,7 @@
from django.contrib import admin, messages
from django.forms import Textarea
from django.http import HttpResponse, HttpResponseRedirect
from django.db.models import TextField
from django.db.models import TextField, Q
from django.urls import path, reverse
from functools import update_wrapper
from django.utils.translation import gettext_lazy as _
@ -35,8 +35,24 @@ class BillOnStatementInline(admin.TabularInline):
@admin.register(StatementUnSubmitted)
class StatementUnSubmittedAdmin(admin.ModelAdmin):
fields = ['short_description', 'explanation', 'excursion', 'submitted']
list_display = ['__str__', 'excursion']
inlines = [BillOnStatementInline]
def save_model(self, request, obj, form, change):
if not change and hasattr(request.user, 'member'):
obj.created_by = request.user.member
super().save_model(request, obj, form, change)
def get_queryset(self, request):
queryset = super().get_queryset(request)
if request.user.has_perm('members.may_list_all_statements'):
return queryset
if not hasattr(request.user, 'member'):
return Member.objects.none()
return queryset.filter(Q(created_by=request.user.member) | Q(excursion__jugendleiter=request.user.member))
def get_readonly_fields(self, request, obj=None):
readonly_fields = ['submitted', 'excursion']
if obj is not None and obj.submitted:

@ -55,6 +55,11 @@ class Statement(models.Model):
confirmed = models.BooleanField(verbose_name=_('Confirmed'), default=False)
confirmed_date = models.DateTimeField(verbose_name=_('Paid on'), default=None, null=True)
created_by = models.ForeignKey(Member, verbose_name=_('Created by'),
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name='created_statements')
submitted_by = models.ForeignKey(Member, verbose_name=_('Submitted by'),
blank=True,
null=True,

@ -82,11 +82,13 @@ class FilteredMemberFieldMixin:
class PermissionOnGroupInline(admin.StackedInline):
model = PermissionGroup
extra = 1
can_delete = False
class PermissionOnMemberInline(admin.StackedInline):
model = PermissionMember
extra = 1
can_delete = False
class RegistrationFilter(admin.SimpleListFilter):
@ -173,6 +175,18 @@ class MemberAdmin(admin.ModelAdmin):
return True
return request.user.member.may_change(obj)
def has_delete_permission(self, request, obj=None):
user = request.user
if request.user.has_perm('members.may_delete_everyone'):
return True
if not hasattr(user, 'member'):
return False
if obj is None:
return True
return request.user.member.may_delete(obj)
def get_fields(self, request, obj=None):
if request.user.has_perm('members.may_set_auth_user'):
if 'user' not in self.fields:
@ -523,6 +537,11 @@ class StatementOnListInline(nested_admin.NestedStackedInline):
return self.fields
return super(StatementOnListInline, self).get_readonly_fields(request, obj)
def has_delete_permission(self, request, obj=None):
if obj is not None and hasattr(obj, 'statement') and obj.statement.submitted:
return False
return True
class InterventionOnLJPInline(admin.TabularInline):
model = Intervention
@ -705,6 +724,14 @@ class FreizeitAdmin(FilteredMemberFieldMixin, nested_admin.NestedModelAdmin):
def __init__(self, *args, **kwargs):
super(FreizeitAdmin, self).__init__(*args, **kwargs)
def save_model(self, request, obj, form, change):
print("saving model")
if not change and hasattr(request.user, 'member') and hasattr(obj, 'statement'):
print("setting obj statement created")
obj.statement.created_by = request.user.member
obj.statement.save()
super().save_model(request, obj, form, change)
def get_queryset(self, request):
queryset = super().get_queryset(request)
if request.user.has_perm('members.may_list_all_excursions'):
@ -713,10 +740,7 @@ class FreizeitAdmin(FilteredMemberFieldMixin, nested_admin.NestedModelAdmin):
if not hasattr(request.user, 'member'):
return Member.objects.none()
groups = request.user.member.leited_groups.all()
# one may view all leited groups and oneself
queryset = queryset.filter(Q(groups__in=groups) | Q(jugendleiter__pk=request.user.member.pk)).distinct()
return queryset
return Freizeit.filter_queryset_by_permissions(request.user.member, queryset)
def may_view_excursion(self, request, memberlist):
return request.user.has_perm('members.may_view_everyone') or \

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-03-20 18:48+0100\n"
"POT-Creation-Date: 2023-03-22 00:10+0100\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"
@ -18,139 +18,155 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: members/admin.py:42 members/models.py:175
#: members/admin.py:93 members/models.py:175
msgid "Registration complete"
msgstr "Anmeldung vollständig"
#: members/admin.py:48
#: members/admin.py:99
msgid "True"
msgstr "Ja"
#: members/admin.py:49
#: members/admin.py:100
msgid "False"
msgstr "Nein"
#: members/admin.py:50
#: members/admin.py:101
msgid "All"
msgstr "Alle"
#: members/admin.py:124
#: members/admin.py:211
#, python-format
msgid "You are not allowed to view %(name)s."
msgstr "Du hast nicht die notwendigen Rechte um %(name)s anzuschauen."
#: members/admin.py:218
msgid "Compose new mail to selected members"
msgstr "Neue Nachricht an ausgewählte Teilnehmer verfassen"
#: members/admin.py:130
#: members/admin.py:224
msgid "Echo required"
msgstr "Rückmeldung erforderlich"
#: members/admin.py:135
#: members/admin.py:229
msgid "Successfully requested echo from selected members."
msgstr ""
"Rückmeldungsaufforderung erfolgreich an ausgewählte Teilnehmer verschickt."
#: members/admin.py:136
#: members/admin.py:230
msgid "Request echo from selected members"
msgstr "Rückmeldungsaufforderung an ausgewählte Teilnehmer verschicken"
#: members/admin.py:153
#: members/admin.py:247
msgid "activity"
msgstr "Aktivität"
#: members/admin.py:182
#: members/admin.py:257 members/models.py:35
msgid "Name"
msgstr "Name"
#: members/admin.py:286
msgid "Successfully requested mail confirmation from selected registrations."
msgstr "Aufforderung zur Bestätigung der Email Adresse versendet."
#: members/admin.py:183
#: members/admin.py:287
msgid "Request mail confirmation from selected registrations"
msgstr "Aufforderung zur Bestätigung der Email Adresse versenden"
#: members/admin.py:190 members/admin.py:228
#: members/admin.py:294 members/admin.py:332
#, python-format
msgid "Successfully confirmed %(name)s."
msgstr "Registrierung von %(name)s erfolgreich bestätigt."
#: members/admin.py:194 members/admin.py:231
#: members/admin.py:298 members/admin.py:335
#, python-format
msgid "Can't confirm. %(name)s has unconfirmed email addresses."
msgstr "Bestätigung nicht möglich. %(name)s hat unbestätigte Emailadressen."
#: members/admin.py:199
#: members/admin.py:303
msgid "Successfully confirmed multiple registrations."
msgstr "Erfolgreich mehrere Registrierungen bestätigt."
#: members/admin.py:201
#: members/admin.py:305
msgid ""
"Failed to confirm some registrations because of unconfirmed email addresses."
msgstr ""
"Einige Bestätigungen fehlgeschlagen, weil Emailadressen noch nicht bestätigt "
"sind."
#: members/admin.py:202
#: members/admin.py:306
msgid "Confirm selected registrations"
msgstr "Ausgewählte Registrierungen bestätigen"
#: members/admin.py:222
#: members/admin.py:326
#, python-format
msgid "Successfully demoted %(name)s to waiter."
msgstr "%(name)s zurück auf die Warteliste gesetzt."
#: members/admin.py:223
#: members/admin.py:327
msgid "Demote selected registrations to waiters."
msgstr "Ausgewählte Registrierungen zurück auf die Warteliste setzen."
#: members/admin.py:238 members/models.py:270 members/models.py:690
#: members/admin.py:342 members/models.py:270 members/models.py:788
msgid "Group"
msgstr "Gruppe"
#: members/admin.py:258
#: members/admin.py:362
#, python-format
msgid "Successfully asked %(name)s to confirm their waiting status."
msgstr "Erfolgreich %(name)s aufgefordert den Wartelistenplatz zu bestätigen."
#: members/admin.py:259
#: members/admin.py:363
msgid "Ask selected waiters to confirm their waiting status"
msgstr "Wartende auffordern den Wartelistenplatz zu bestätigen"
#: members/admin.py:268 members/admin.py:324
#: members/admin.py:372 members/admin.py:428
msgid ""
"An error occurred while trying to invite said members. Please try again."
msgstr ""
"Beim Einladen dieser Personen ist ein Fehler aufgetreten. Bitte versuche es "
"nochmal. "
#: members/admin.py:276 members/admin.py:331
#: members/admin.py:380 members/admin.py:435
#, python-format
msgid "Successfully invited %(name)s to %(group)s."
msgstr "Erfolgreich %(name)s zu Gruppe %(group)s eingeladen."
#: members/admin.py:280 members/admin.py:336
#: members/admin.py:384 members/admin.py:440
msgid "Select group for invitation"
msgstr "Wähle Gruppe für Einladung aus"
#: members/admin.py:287
#: members/admin.py:391
msgid "Offer waiter a place in a group."
msgstr "Personen auf der Warteliste einen Gruppenplatz anbieten."
#: members/admin.py:375
#: members/admin.py:480
msgid "Difficulty"
msgstr "Schwierigkeit"
#: members/admin.py:378
#: members/admin.py:483
msgid "Tour type"
msgstr "Art der Tour"
#: members/admin.py:381 members/models.py:551
#: members/admin.py:486 members/models.py:649
msgid "Means of transportation"
msgstr "Verkehrsmittel"
#: members/admin.py:605
#: members/admin.py:728
#, python-format
msgid "You are not allowed to view all members on excursion %(name)s."
msgstr ""
"Du hast nicht die nötigen Rechte um alle Teilnehmer:innen der Freizeit "
"%(name)s anzusehen."
#: members/admin.py:738
msgid "Generate crisis intervention list"
msgstr "Kriseninterventionsliste erstellen"
#: members/admin.py:612
#: members/admin.py:748
msgid "Generate overview"
msgstr "Hinweise für Jugendleiter erstellen"
#: members/admin.py:619
#: members/admin.py:758
msgid "Generate seminar report"
msgstr "Seminarbericht erstellen"
@ -158,15 +174,11 @@ msgstr "Seminarbericht erstellen"
msgid "members"
msgstr "Teilnehmer"
#: members/models.py:35
msgid "Name"
msgstr "Name"
#: members/models.py:36
msgid "Description"
msgstr "Beschreibung"
#: members/models.py:42 members/models.py:432 members/models.py:531
#: members/models.py:42 members/models.py:530 members/models.py:629
#: members/templates/members/change_member.html:17
msgid "Activity"
msgstr "Aktivität"
@ -288,195 +300,235 @@ msgstr "Teilnehmer"
msgid "New unconfirmed registration for group %(group)s"
msgstr "Neue unbestätigte Registrierung für Gruppe %(group)s"
#: members/models.py:323
#: members/models.py:421
msgid "Unconfirmed registration"
msgstr "Unbestätigte Registrierung"
#: members/models.py:324
#: members/models.py:422
msgid "Unconfirmed registrations"
msgstr "Unbestätigte Registrierungen"
#: members/models.py:340
#: members/models.py:438
msgid "Last wait confirmation"
msgstr "Letzte Wartebestätigung"
#: members/models.py:351
#: members/models.py:449
msgid "Invited for group"
msgstr "Einladung zu Gruppe austehend"
#: members/models.py:355
#: members/models.py:453
msgid "Waiter"
msgstr "Wartende Person"
#: members/models.py:356
#: members/models.py:454
msgid "Waiters"
msgstr "Warteliste"
#: members/models.py:373
#: members/models.py:471
msgid "Waiting status confirmed"
msgstr "Wartelistenplatz bestätigt"
#: members/models.py:377
#: members/models.py:475
msgid "Waiting confirmation needed"
msgstr "Wartelistenplatzbestätigung erforderlich"
#: members/models.py:418
#: members/models.py:516
msgid "Good news"
msgstr "Gute Neuigkeiten"
#: members/models.py:434 members/models.py:533
#: members/models.py:532 members/models.py:631
msgid "Place"
msgstr "Ort"
#: members/models.py:435 members/models.py:534
#: members/models.py:533 members/models.py:632
msgid "Destination (optional)"
msgstr "Ziel (optional)"
#: members/models.py:437 members/models.py:668 members/models.py:686
#: members/models.py:535 members/models.py:766 members/models.py:784
msgid "Date"
msgstr "Datum"
#: members/models.py:438 members/models.py:537
#: members/models.py:536 members/models.py:635
msgid "End (optional)"
msgstr "Ende"
#: members/models.py:440 members/models.py:539
#: members/models.py:538 members/models.py:637
msgid "Groups"
msgstr "Gruppen"
#: members/models.py:448 members/models.py:555
#: members/models.py:546 members/models.py:653
msgid "Categories"
msgstr "Kategorien"
#: members/models.py:449 members/models.py:556
#: members/models.py:547 members/models.py:654
msgid "easy"
msgstr "leicht"
#: members/models.py:449 members/models.py:556
#: members/models.py:547 members/models.py:654
msgid "medium"
msgstr "mittel"
#: members/models.py:449 members/models.py:556
#: members/models.py:547 members/models.py:654
msgid "hard"
msgstr "schwer"
#: members/models.py:458
#: members/models.py:556
msgid "Memberlist"
msgstr "Teilnehmerliste"
#: members/models.py:459
#: members/models.py:557
msgid "Memberlists"
msgstr "Teilnehmerlisten"
#: members/models.py:477 members/models.py:485 members/models.py:493
#: members/models.py:504 members/models.py:721 members/models.py:728
#: members/models.py:575 members/models.py:583 members/models.py:591
#: members/models.py:602 members/models.py:819 members/models.py:826
msgid "Member"
msgstr "Teilnehmer"
#: members/models.py:479 members/models.py:498
#: members/models.py:577 members/models.py:596
msgid "Comment"
msgstr "Kommentar"
#: members/models.py:486 members/models.py:505 members/models.py:729
#: members/models.py:584 members/models.py:603 members/models.py:827
msgid "Members"
msgstr "Teilnehmer"
#: members/models.py:536
#: members/models.py:634
msgid "Begin"
msgstr "Anfang"
#: members/models.py:552
#: members/models.py:650
msgid "Kilometers traveled"
msgstr "Fahrstrecke in Kilometer"
#: members/models.py:667 members/models.py:743
#: members/models.py:765 members/models.py:841
msgid "Title"
msgstr "Titel"
#: members/models.py:687
#: members/models.py:785
msgid "Location"
msgstr "Ort"
#: members/models.py:688
#: members/models.py:786
msgid "Topic"
msgstr "Thema"
#: members/models.py:712
#: members/models.py:810
msgid "Jugendleiter"
msgstr "Jugendleiter"
#: members/models.py:715
#: members/models.py:813
msgid "Klettertreff"
msgstr "Klettertreff"
#: members/models.py:716
#: members/models.py:814
msgid "Klettertreffs"
msgstr "Klettertreffs"
#: members/models.py:734
#: members/models.py:832
msgid "Password"
msgstr "Passwort"
#: members/models.py:737
#: members/models.py:835
msgid "registration password"
msgstr "Registrierungspassort"
#: members/models.py:738
#: members/models.py:836
msgid "registration passwords"
msgstr "Registrierungspasswörter"
#: members/models.py:745
#: members/models.py:843
msgid "Alpinistic goals"
msgstr "Alpintechnische Ziele"
#: members/models.py:746
#: members/models.py:844
msgid "Pedagogic goals"
msgstr "Pädagogische Ziele"
#: members/models.py:747
#: members/models.py:845
msgid "Content and methods"
msgstr "Inhalte und Methoden"
#: members/models.py:748
#: members/models.py:846
msgid "Evaluation"
msgstr "Wertung"
#: members/models.py:749
#: members/models.py:847
msgid "Experiences and possible improvements"
msgstr "Erfahrungen und Verbesserungsvorschläge"
#: members/models.py:752
#: members/models.py:850
msgid "Excursion"
msgstr "Freizeit"
#: members/models.py:758 members/models.py:773
#: members/models.py:856 members/models.py:871
msgid "LJP Proposal"
msgstr "Seminarbericht"
#: members/models.py:759
#: members/models.py:857
msgid "LJP Proposals"
msgstr "Seminarberichte"
#: members/models.py:766
#: members/models.py:864
msgid "Starting time"
msgstr "Zeitpunkt"
#: members/models.py:767
#: members/models.py:865
msgid "Duration in hours"
msgstr "Dauer in Stunden"
#: members/models.py:770
#: members/models.py:868
msgid "Activity and method"
msgstr "Art der Aktion inkl. Methode"
#: members/models.py:778
#: members/models.py:876
msgid "Intervention"
msgstr "Aktion"
#: members/models.py:779
#: members/models.py:877
msgid "Interventions"
msgstr "Aktionen"
#: members/models.py:973 members/models.py:999
msgid "May list members"
msgstr "Darf folgende Teilnehmer:innen listen"
#: members/models.py:975 members/models.py:1001
msgid "May view members"
msgstr "Darf folgende Teilnehmer:innen anzeigen"
#: members/models.py:977 members/models.py:1003
msgid "May change members"
msgstr "Darf folgende Teilnehmer:innen ändern"
#: members/models.py:979 members/models.py:1005
msgid "May delete members"
msgstr "Darf folgende Teilnehmer:innen löschen"
#: members/models.py:983 members/models.py:1009
msgid "May list members of groups"
msgstr "Darf Teilnehmer:innen folgender Gruppen listen"
#: members/models.py:985 members/models.py:1011
msgid "May view members of groups"
msgstr "Darf Teilnehmer:innen folgender Gruppen anzeigen"
#: members/models.py:987 members/models.py:1013
msgid "May change members of groups"
msgstr "Darf Teilnehmer:innen folgender Gruppen ändern"
#: members/models.py:989 members/models.py:1015
msgid "May delete members of groups"
msgstr "Darf Teilnehmer:innen folgender Gruppen löschen"
#: members/models.py:992 members/models.py:993
msgid "Permissions"
msgstr "Berechtigungen"
#: members/models.py:1018 members/models.py:1019
msgid "Group permissions"
msgstr "Gruppenberechtigungen"
#: members/templates/admin/invite_for_group.html:17
#: members/templates/admin/invite_selected_for_group.html:17
msgid "Home"
@ -782,14 +834,14 @@ msgstr ""
"Danke %(prename)s für dein Interesse auf der Warteliste zu bleiben.\n"
"Dein Platz wurde bestätigt."
#: members/views.py:84 members/views.py:105 members/views.py:270
#: members/views.py:84 members/views.py:105 members/views.py:271
msgid "invalid"
msgstr "ungültig"
#: members/views.py:86 members/views.py:272
#: members/views.py:86 members/views.py:273
msgid "expired"
msgstr "abgelaufen"
#: members/views.py:115
#: members/views.py:116
msgid "The entered password is wrong."
msgstr "Das eingegebene Passwort ist falsch."

@ -406,6 +406,27 @@ class Member(Person):
return False
def may_delete(self, other):
if self.pk == other.pk:
return True
if hasattr(self, 'permissions'):
if other in self.permissions.delete_members.all():
return True
if any([gr in other.group.all() for gr in self.permissions.delete_groups.all()]):
return True
for group in self.group.all():
if hasattr(group, 'permissions'):
if other in group.permissions.delete_members.all():
return True
if any([gr in other.group.all() for gr in group.permissions.delete_groups.all()]):
return True
return False
class MemberUnconfirmedManager(models.Manager):
def get_queryset(self):
@ -757,6 +778,16 @@ class Freizeit(models.Model):
sks.append(dict(name=activity, skill_avg=skill_avg, skill_min=skill_min, skill_max=skill_max))
return (people, sks)
@staticmethod
def filter_queryset_by_permissions(member, queryset=None):
if queryset is None:
queryset = Freizeit.objects.all()
groups = member.leited_groups.all()
# one may view all leited groups and oneself
queryset = queryset.filter(Q(groups__in=groups) | Q(jugendleiter__pk=member.pk)).distinct()
return queryset
class MemberNoteList(models.Model):
"""
@ -969,28 +1000,58 @@ def annotate_activity_score(queryset):
class PermissionMember(models.Model):
member = models.OneToOneField(Member, on_delete=models.CASCADE, related_name='permissions')
# every member of view_members may view this member
list_members = models.ManyToManyField(Member, related_name='listable_by', blank=True)
view_members = models.ManyToManyField(Member, related_name='viewable_by', blank=True)
change_members = models.ManyToManyField(Member, related_name='changeable_by', blank=True)
delete_members = models.ManyToManyField(Member, related_name='deletable_by', blank=True)
list_members = models.ManyToManyField(Member, related_name='listable_by', blank=True,
verbose_name=_('May list members'))
view_members = models.ManyToManyField(Member, related_name='viewable_by', blank=True,
verbose_name=_('May view members'))
change_members = models.ManyToManyField(Member, related_name='changeable_by', blank=True,
verbose_name=_('May change members'))
delete_members = models.ManyToManyField(Member, related_name='deletable_by', blank=True,
verbose_name=_('May delete members'))
# every member in any view_group may view this member
list_groups = models.ManyToManyField(Group, related_name='listable_by', blank=True)
view_groups = models.ManyToManyField(Group, related_name='viewable_by', blank=True)
change_groups = models.ManyToManyField(Group, related_name='changeable_by', blank=True)
delete_groups = models.ManyToManyField(Group, related_name='deletable_by', blank=True)
list_groups = models.ManyToManyField(Group, related_name='listable_by', blank=True,
verbose_name=_('May list members of groups'))
view_groups = models.ManyToManyField(Group, related_name='viewable_by', blank=True,
verbose_name=_('May view members of groups'))
change_groups = models.ManyToManyField(Group, related_name='changeable_by', blank=True,
verbose_name=_('May change members of groups'))
delete_groups = models.ManyToManyField(Group, related_name='deletable_by', blank=True,
verbose_name=_('May delete members of groups'))
class Meta:
verbose_name = _('Permissions')
verbose_name_plural = _('Permissions')
def __str__(self):
return str(_('Permissions'))
class PermissionGroup(models.Model):
group = models.OneToOneField(Group, on_delete=models.CASCADE, related_name='permissions')
# every member of view_members may view all members of group
list_members = models.ManyToManyField(Member, related_name='group_members_listable_by', blank=True)
view_members = models.ManyToManyField(Member, related_name='group_members_viewable_by', blank=True)
change_members = models.ManyToManyField(Member, related_name='group_members_changeable_by_group', blank=True)
delete_members = models.ManyToManyField(Member, related_name='group_members_deletable_by', blank=True)
list_members = models.ManyToManyField(Member, related_name='group_members_listable_by', blank=True,
verbose_name=_('May list members'))
view_members = models.ManyToManyField(Member, related_name='group_members_viewable_by', blank=True,
verbose_name=_('May view members'))
change_members = models.ManyToManyField(Member, related_name='group_members_changeable_by_group', blank=True,
verbose_name=_('May change members'))
delete_members = models.ManyToManyField(Member, related_name='group_members_deletable_by', blank=True,
verbose_name=_('May delete members'))
# every member in any view_group may view all members of group
list_groups = models.ManyToManyField(Group, related_name='group_members_listable_by', blank=True)
view_groups = models.ManyToManyField(Group, related_name='group_members_viewable_by', blank=True)
change_groups = models.ManyToManyField(Group, related_name='group_members_changeable_by', blank=True)
delete_groups = models.ManyToManyField(Group, related_name='group_members_deletable_by', blank=True)
list_groups = models.ManyToManyField(Group, related_name='group_members_listable_by', blank=True,
verbose_name=_('May list members of groups'))
view_groups = models.ManyToManyField(Group, related_name='group_members_viewable_by', blank=True,
verbose_name=_('May view members of groups'))
change_groups = models.ManyToManyField(Group, related_name='group_members_changeable_by', blank=True,
verbose_name=_('May change members of groups'))
delete_groups = models.ManyToManyField(Group, related_name='group_members_deletable_by', blank=True,
verbose_name=_('May delete members of groups'))
class Meta:
verbose_name = _('Group permissions')
verbose_name_plural = _('Group permissions')
def __str__(self):
return str(_('Group permissions'))

Loading…
Cancel
Save