diff --git a/jdav_web/jdav_web/settings.py b/jdav_web/jdav_web/settings.py index a71263d..2f5356e 100644 --- a/jdav_web/jdav_web/settings.py +++ b/jdav_web/jdav_web/settings.py @@ -195,6 +195,8 @@ ADMINS = (('admin', 'christian@merten-moser.de'),) # Celery and Redis setup BROKER_URL = os.environ.get('BROKER_URL', 'redis://localhost:6379/0') +CLOUD_LINK = 'https://cloud.jdav-ludwigsburg.de/index.php/s/qxQCTR8JqYSXXCQ' + # JET options (admin interface) JET_SIDE_MENU_COMPACT = True @@ -214,36 +216,36 @@ JET_SIDE_MENU_ITEMS = [ {'name': 'solarschedule'}, ]}, {'app_label': 'ludwigsburgalpin', 'permissions': ['ludwigsburgalpin'], 'items': [ - {'name': 'termin'}, + {'name': 'termin', 'permissions': ['ludwigsburgalpin.view_termin']}, ]}, {'app_label': 'mailer', 'items': [ - {'name': 'message'}, - {'name': 'emailaddress'}, + {'name': 'message', 'permissions': ['mailer.view_message']}, + {'name': 'emailaddress', 'permissions': ['mailer.view_emailaddress']}, ]}, {'app_label': 'finance', 'items': [ - {'name': 'statementunsubmitted'}, - {'name': 'statementsubmitted'}, - {'name': 'statementconfirmed'}, - {'name': 'ledger'}, - {'name': 'bill'}, - {'name': 'transaction'}, + {'name': 'statementunsubmitted', 'permissions': ['finance.view_statementunsubmitted']}, + {'name': 'statementsubmitted', 'permissions': ['finance.view_statementsubmitted']}, + {'name': 'statementconfirmed', 'permissions': ['finance.view_statementconfirmed']}, + {'name': 'ledger', 'permissions': ['finance.view_ledger']}, + {'name': 'bill', 'permissions': ['finance.view_bill', 'finance.view_bill_admin']}, + {'name': 'transaction', 'permissions': ['finance.view_transaction']}, ]}, {'app_label': 'members', 'items': [ - {'name': 'member'}, - {'name': 'group'}, - {'name': 'membernotelist'}, - {'name': 'freizeit'}, - {'name': 'klettertreff'}, + {'name': 'member', 'permissions': ['members.view_member']}, + {'name': 'group', 'permissions': ['members.view_group']}, + {'name': 'membernotelist', 'permissions': ['members.view_membernotelist']}, + {'name': 'freizeit', 'permissions': ['members.view_freizeit']}, + {'name': 'klettertreff', 'permissions': ['members.view_klettertreff']}, {'name': 'activitycategory', 'permissions': ['members.view_activitycategory']}, {'name': 'memberunconfirmedproxy', 'permissions': ['members.view_memberunconfirmedproxy']}, {'name': 'memberwaitinglist', 'permissions': ['members.view_memberwaitinglist']}, ]}, - {'app_label': 'material', 'items': [ + {'app_label': 'material', 'permissions': ['material'], 'items': [ {'name': 'materialcategory', 'permissions': ['material.view_materialcategory']}, - {'name': 'materialpart'}, + {'name': 'materialpart', 'permissions': ['material.view_materialpart']}, ]}, {'label': 'Externe Links', 'items' : [ - { 'label': 'Packlisten und Co.', 'url': 'https://cloud.jdav-ludwigsburg.de/index.php/s/qxQCTR8JqYSXXCQ'} + { 'label': 'Packlisten und Co.', 'url': CLOUD_LINK } ]}, ] @@ -405,3 +407,7 @@ CONGRATULATE_MEMBERS_MAX = 10 ALLOWANCE_PER_DAY = 10 MAX_NIGHT_COST = 11 + +# testing + +TEST_MAIL = "post@flavigny.de" diff --git a/jdav_web/members/admin.py b/jdav_web/members/admin.py index fd5a292..4ec178b 100644 --- a/jdav_web/members/admin.py +++ b/jdav_web/members/admin.py @@ -8,6 +8,7 @@ import unicodedata import random import string from functools import partial, update_wrapper +from django.forms.models import BaseInlineFormSet from django.contrib.admin.templatetags.admin_urls import add_preserved_filters from django.template.loader import get_template @@ -24,12 +25,14 @@ from django.db.models import TextField, ManyToManyField, ForeignKey, Count,\ Sum, Case, Q, F, When, Value, IntegerField, Subquery, OuterRef from django.forms import Textarea, RadioSelect, TypedChoiceField from django.shortcuts import render +from django.core.exceptions import PermissionDenied from .pdf import render_tex import nested_admin from .models import (Member, Group, Freizeit, MemberNoteList, NewMemberOnList, Klettertreff, - MemberWaitingList, LJPProposal, Intervention, + MemberWaitingList, LJPProposal, Intervention, PermissionMember, + PermissionGroup, KlettertreffAttendee, ActivityCategory, OldMemberOnList, MemberList, annotate_activity_score, RegistrationPassword, MemberUnconfirmedProxy) from finance.models import Statement, Bill @@ -38,6 +41,54 @@ from django.conf import settings #from easy_select2 import apply_select2 +class FilteredMemberFieldMixin: + def formfield_for_foreignkey(self, db_field, request=None, **kwargs): + """ + Override the queryset for member foreign key fields. + """ + field = super().formfield_for_foreignkey(db_field, request, **kwargs) + if db_field.related_model != Member: + return field + + if request is None: + field.queryset = Member.objects.none() + elif request.user.has_perm('members.may_list_everyone'): + field.queryset = Member.objects.all() + elif not hasattr(request.user, 'member'): + field.queryset = Member.objects.none() + else: + field.queryset = request.user.member.filter_queryset_by_permissions() + return field + + def formfield_for_manytomany(self, db_field, request=None, **kwargs): + """ + Override the queryset for member many to many fields. + """ + field = super().formfield_for_foreignkey(db_field, request, **kwargs) + if db_field.related_model != Member: + return field + + if request is None: + field.queryset = Member.objects.none() + elif request.user.has_perm('members.may_list_everyone'): + field.queryset = Member.objects.all() + elif not hasattr(request.user, 'member'): + field.queryset = Member.objects.none() + else: + field.queryset = request.user.member.filter_queryset_by_permissions() + return field + + +class PermissionOnGroupInline(admin.StackedInline): + model = PermissionGroup + extra = 1 + + +class PermissionOnMemberInline(admin.StackedInline): + model = PermissionMember + extra = 1 + + class RegistrationFilter(admin.SimpleListFilter): title = _('Registration complete') parameter_name = 'registered' @@ -82,10 +133,12 @@ class MemberAdmin(admin.ModelAdmin): fields = ['prename', 'lastname', 'email', 'email_parents', 'cc_email_parents', 'street', 'plz', 'town', 'phone_number', 'phone_number_parents', 'birth_date', 'group', 'iban', 'gets_newsletter', 'registered', 'registration_form', 'active', 'echoed', 'comments'] - list_display = ('name', 'birth_date', 'age', 'get_group', 'gets_newsletter', + list_display = ('name_text_or_link', 'birth_date', 'age', 'get_group', 'gets_newsletter', 'registered', 'active', 'echoed', 'comments', 'activity_score') search_fields = ('prename', 'lastname', 'email') list_filter = ('group', 'gets_newsletter', RegistrationFilter, 'active') + list_display_links = None + inlines = [PermissionOnMemberInline] #formfield_overrides = { # ManyToManyField: {'widget': forms.CheckboxSelectMultiple}, # ForeignKey: {'widget': apply_select2(forms.Select)} @@ -94,6 +147,32 @@ class MemberAdmin(admin.ModelAdmin): #ordering = ('activity_score',) actions = ['send_mail_to', 'request_echo'] + sensitive_fields = ['iban', 'registration_form', 'comments'] + + def has_view_permission(self, request, obj=None): + user = request.user + if request.user.has_perm('members.may_view_everyone'): + return True + + if not hasattr(user, 'member'): + return False + + if obj is None: + return True + return request.user.member.may_view(obj) + + def has_change_permission(self, request, obj=None): + user = request.user + if request.user.has_perm('members.may_change_everyone'): + return True + + if not hasattr(user, 'member'): + return False + + if obj is None: + return True + return request.user.member.may_change(obj) + def get_fields(self, request, obj=None): if request.user.has_perm('members.may_set_auth_user'): if 'user' not in self.fields: @@ -105,17 +184,32 @@ class MemberAdmin(admin.ModelAdmin): def get_queryset(self, request): queryset = super().get_queryset(request) + if request.user.has_perm('members.may_list_everyone'): + return annotate_activity_score(queryset) + + if not hasattr(request.user, 'member'): + return Member.objects.none() + + queryset = request.user.member.filter_queryset_by_permissions(queryset, annotate=True) return annotate_activity_score(queryset) def change_view(self, request, object_id, form_url="", extra_context=None): - extra_context = extra_context or {} - extra_context['qualities'] =\ - Member.objects.get(pk=object_id).get_skills() - extra_context['activities'] =\ - Member.objects.get(pk=object_id).get_activities() - return super(MemberAdmin, self).change_view(request, object_id, - form_url=form_url, - extra_context=extra_context) + try: + extra_context = extra_context or {} + extra_context['qualities'] =\ + Member.objects.get(pk=object_id).get_skills() + extra_context['activities'] =\ + Member.objects.get(pk=object_id).get_activities() + return super(MemberAdmin, self).change_view(request, object_id, + form_url=form_url, + extra_context=extra_context) + except Member.DoesNotExist: + return super().change_view(request, object_id) + except PermissionDenied: + member = Member.objects.get(pk=object_id) + messages.error(request, + _("You are not allowed to view %(name)s.") % {'name': member.name}) + return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name))) def send_mail_to(self, request, queryset): member_pks = [m.pk for m in queryset] @@ -128,11 +222,11 @@ class MemberAdmin(admin.ModelAdmin): if not member.gets_newsletter: continue send_mail(_("Echo required"), - settings.ECHO_TEXT.format(name=member.prename, link=get_echo_link(member)), - settings.DEFAULT_SENDING_MAIL, - [member.email, member.email_parents] if member.email_parents and member.cc_email_parents - else member.email) - messages.success(request, _("Successfully requested echo from selected members.")) + settings.ECHO_TEXT.format(name=member.prename, link=get_echo_link(member)), + settings.DEFAULT_SENDING_MAIL, + [member.email, member.email_parents] if member.email_parents and member.cc_email_parents + else member.email) + messages.success(request, _("Successfully requested echo from selected members.")) request_echo.short_description = _('Request echo from selected members') def activity_score(self, obj): @@ -152,6 +246,16 @@ class MemberAdmin(admin.ModelAdmin): activity_score.admin_order_field = '_activity_score' activity_score.short_description = _('activity') + def name_text_or_link(self, obj): + name = obj.name + if not hasattr(obj, '_viewable') or obj._viewable: + return format_html('{name}'.format( + link=reverse('admin:%s_%s_change' % (self.opts.app_label, self.opts.model_name), args=(obj.pk,)), + name=obj.name)) + else: + return obj.name + name_text_or_link.short_description = _('Name') + class MemberUnconfirmedAdmin(admin.ModelAdmin): fields = ['prename', 'lastname', 'email', 'email_parents', 'cc_email_parents', 'street', 'plz', @@ -272,7 +376,7 @@ class MemberWaitingListAdmin(admin.ModelAdmin): waiter.invited_for_group = group waiter.save() waiter.invite_to_group() - messages.success(request, + messages.success(request, _("Successfully invited %(name)s to %(group)s.") % {'name': waiter.name, 'group': waiter.invited_for_group.name}) return HttpResponseRedirect(request.get_full_path()) @@ -327,7 +431,7 @@ class MemberWaitingListAdmin(admin.ModelAdmin): waiter.invited_for_group = group waiter.save() waiter.invite_to_group() - messages.success(request, + messages.success(request, _("Successfully invited %(name)s to %(group)s.") % {'name': waiter.name, 'group': waiter.invited_for_group.name}) return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (waiter._meta.app_label, waiter._meta.model_name))) @@ -355,14 +459,15 @@ class GroupAdminForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(GroupAdminForm, self).__init__(*args, **kwargs) - self.fields['leiters'].queryset = Member.objects.filter(group__name='Jugendleiter') + if 'leiters' in self.fields: + self.fields['leiters'].queryset = Member.objects.filter(group__name='Jugendleiter') class GroupAdmin(admin.ModelAdmin): fields = ['name', 'year_from', 'year_to', 'leiters'] form = GroupAdminForm list_display = ('name', 'year_from', 'year_to') - inlines = [RegistrationPasswordInline] + inlines = [RegistrationPasswordInline, PermissionOnGroupInline] class ActivityCategoryAdmin(admin.ModelAdmin): @@ -386,11 +491,12 @@ class FreizeitAdminForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(FreizeitAdminForm, self).__init__(*args, **kwargs) - self.fields['jugendleiter'].queryset = Member.objects.filter(group__name='Jugendleiter') + q = self.fields['jugendleiter'].queryset + self.fields['jugendleiter'].queryset = q.filter(group__name='Jugendleiter') #self.fields['add_member'].queryset = Member.objects.filter(prename__startswith='F') -class BillOnStatementInline(admin.TabularInline): +class BillOnStatementInline(FilteredMemberFieldMixin, admin.TabularInline): model = Bill extra = 0 sortable_options = [] @@ -434,7 +540,7 @@ class LJPOnListInline(nested_admin.NestedStackedInline): inlines = [InterventionOnLJPInline] -class MemberOnListInline(GenericTabularInline): +class MemberOnListInline(FilteredMemberFieldMixin, GenericTabularInline): model = NewMemberOnList extra = 0 formfield_overrides = { @@ -579,7 +685,8 @@ class MemberListAdmin(admin.ModelAdmin): messages.info(request, "Teilnehmerlist(en) erfolgreich erstellt.") migrate_to_notelist.short_description = "Aus Teilnehmerliste(n) Notizliste erstellen" -class FreizeitAdmin(nested_admin.NestedModelAdmin): + +class FreizeitAdmin(FilteredMemberFieldMixin, nested_admin.NestedModelAdmin): inlines = [MemberOnListInline, LJPOnListInline, StatementOnListInline] form = FreizeitAdminForm list_display = ['__str__', 'date'] @@ -598,24 +705,56 @@ class FreizeitAdmin(nested_admin.NestedModelAdmin): def __init__(self, *args, **kwargs): super(FreizeitAdmin, self).__init__(*args, **kwargs) + def get_queryset(self, request): + queryset = super().get_queryset(request) + if request.user.has_perm('members.may_list_all_excursions'): + return queryset + + 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 + + def may_view_excursion(self, request, memberlist): + return request.user.has_perm('members.may_view_everyone') or \ + ( hasattr(request.user, 'member') and \ + all([request.user.member.may_view(m.member) for m in memberlist.membersonlist.all()]) ) + + def not_allowed_view(self, request, memberlist): + messages.error(request, + _("You are not allowed to view all members on excursion %(name)s.") % {'name': memberlist.name}) + return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name))) + def crisis_intervention_list(self, request, queryset): - for memberlist in queryset: - context = dict(memberlist=memberlist) - return render_tex(memberlist.name + "_Krisenliste", 'members/crisis_intervention_list.tex', context) + # this ensures legacy compatibilty + memberlist = queryset[0] + if not self.may_view_excursion(request, memberlist): + return self.not_allowed_view(request, memberlist) + context = dict(memberlist=memberlist, settings=settings) + return render_tex(memberlist.name + "_Krisenliste", 'members/crisis_intervention_list.tex', context) crisis_intervention_list.short_description = _('Generate crisis intervention list') def notes_list(self, request, queryset): - for memberlist in queryset: - people, skills = memberlist.skill_summary - context = dict(memberlist=memberlist, people=people, skills=skills) - return render_tex(memberlist.name + "_Notizen", 'members/notes_list.tex', context) + # this ensures legacy compatibilty + memberlist = queryset[0] + if not self.may_view_excursion(request, memberlist): + return self.not_allowed_view(request, memberlist) + people, skills = memberlist.skill_summary + context = dict(memberlist=memberlist, people=people, skills=skills, settings=settings) + return render_tex(memberlist.name + "_Notizen", 'members/notes_list.tex', context) notes_list.short_description = _('Generate overview') def seminar_report(self, request, queryset): - for memberlist in queryset: - context = dict(memberlist=memberlist) - title = memberlist.ljpproposal.title if hasattr(memberlist, 'ljpproposal') else memberlist.name - return render_tex(title + "_Seminarbericht", 'members/seminar_report.tex', context) + # this ensures legacy compatibilty + memberlist = queryset[0] + if not self.may_view_excursion(request, memberlist): + return self.not_allowed_view(request, memberlist) + context = dict(memberlist=memberlist, settings=settings) + title = memberlist.ljpproposal.title if hasattr(memberlist, 'ljpproposal') else memberlist.name + return render_tex(title + "_Seminarbericht", 'members/seminar_report.tex', context) seminar_report.short_description = _('Generate seminar report') def get_urls(self): diff --git a/jdav_web/members/models.py b/jdav_web/members/models.py index 3ba2b82..f9caafc 100644 --- a/jdav_web/members/models.py +++ b/jdav_web/members/models.py @@ -309,6 +309,104 @@ class Member(Person): settings.DEFAULT_SENDING_MAIL, jl.email) + def filter_queryset_by_permissions(self, queryset=None, annotate=False): + if queryset is None: + queryset = Member.objects.all() + + # every member may list themself + pks = [self.pk] + view_pks = [self.pk] + if hasattr(self, 'permissions'): + pks += [ m.pk for m in self.permissions.list_members.all() ] + view_pks += [ m.pk for m in self.permissions.view_members.all() ] + + for group in self.permissions.list_groups.all(): + pks += [ m.pk for m in group.member_set.all() ] + + for group in self.permissions.view_groups.all(): + view_pks += [ m.pk for m in group.member_set.all() ] + + for group in self.group.all(): + if hasattr(group, 'permissions'): + pks += [ m.pk for m in group.permissions.list_members.all() ] + view_pks += [ m.pk for m in group.permissions.view_members.all() ] + + for gr in group.permissions.list_groups.all(): + pks += [ m.pk for m in gr.member_set.all()] + + for gr in group.permissions.view_groups.all(): + view_pks += [ m.pk for m in gr.member_set.all()] + + filtered = queryset.filter(pk__in=pks) + if not annotate: + return filtered + + return filtered.annotate(_viewable=Case(When(pk__in=view_pks, then=Value(True)), default=Value(False), output_field=models.BooleanField())) + + def may_list(self, other): + if self.pk == other.pk: + return True + + if hasattr(self, 'permissions'): + if other in self.permissions.list_members.all(): + return True + + if any([gr in other.group.all() for gr in self.permissions.list_groups.all()]): + return True + + for group in self.group.all(): + if hasattr(group, 'permissions'): + if other in group.permissions.list_members.all(): + return True + + if any([gr in other.group.all() for gr in group.permissions.list_groups.all()]): + return True + + return False + + def may_view(self, other): + if self.pk == other.pk: + return True + + if hasattr(self, 'permissions'): + if other in self.permissions.view_members.all(): + return True + + if any([gr in other.group.all() for gr in self.permissions.view_groups.all()]): + return True + + for group in self.group.all(): + if hasattr(group, 'permissions'): + if other in group.permissions.view_members.all(): + return True + + if any([gr in other.group.all() for gr in group.permissions.view_groups.all()]): + return True + + return False + + def may_change(self, other): + if self.pk == other.pk: + return True + + if hasattr(self, 'permissions'): + if other in self.permissions.change_members.all(): + return True + + if any([gr in other.group.all() for gr in self.permissions.change_groups.all()]): + return True + + for group in self.group.all(): + if hasattr(group, 'permissions'): + if other in group.permissions.change_members.all(): + return True + + if any([gr in other.group.all() for gr in group.permissions.change_groups.all()]): + return True + + return False + + class MemberUnconfirmedManager(models.Manager): def get_queryset(self): return super().get_queryset().filter(confirmed=False) @@ -866,3 +964,33 @@ def annotate_activity_score(queryset): + F('_jugendleiter_klettertreff_score') + 3 * F('_jugendleiter_freizeit_score')) ) return 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) + + # 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) + + +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) + + # 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) diff --git a/jdav_web/members/tests.py b/jdav_web/members/tests.py index 7ce503c..a3b0d8a 100644 --- a/jdav_web/members/tests.py +++ b/jdav_web/members/tests.py @@ -1,3 +1,89 @@ from django.test import TestCase +from django.utils import timezone +from django.conf import settings +from .models import Member, Group, PermissionMember, PermissionGroup + # Create your tests here. + +class MemberTestCase(TestCase): + def setUp(self): + self.jl = Group.objects.create(name="Jugendleiter") + self.alp = Group.objects.create(name="Alpenfuechse") + self.spiel = Group.objects.create(name="Spielkinder") + + self.fritz = Member.objects.create(prename="Fritz", lastname="Wulter", birth_date=timezone.now().date(), + email=settings.TEST_MAIL) + self.fritz.group.add(self.jl) + self.fritz.group.add(self.alp) + self.fritz.save() + self.lara = Member.objects.create(prename="Lara", lastname="Wallis", birth_date=timezone.now().date(), + email=settings.TEST_MAIL) + self.lara.group.add(self.alp) + self.lara.save() + self.fridolin = Member.objects.create(prename="Fridolin", lastname="Spargel", birth_date=timezone.now().date(), + email=settings.TEST_MAIL) + self.fridolin.group.add(self.alp) + self.fridolin.group.add(self.spiel) + self.fridolin.save() + + self.lise = Member.objects.create(prename="Lise", lastname="Lotte", birth_date=timezone.now().date(), + email=settings.TEST_MAIL) + + p1 = PermissionMember.objects.create(member=self.fritz) + p1.view_members.add(self.lara) + p1.change_members.add(self.lara) + p1.view_groups.add(self.spiel) + + self.ja = Group.objects.create(name="Jugendausschuss") + self.peter = Member.objects.create(prename="Peter", lastname="Keks", birth_date=timezone.now().date(), + email=settings.TEST_MAIL) + self.anna = Member.objects.create(prename="Anna", lastname="Keks", birth_date=timezone.now().date(), + email=settings.TEST_MAIL) + self.lisa = Member.objects.create(prename="Lisa", lastname="Keks", birth_date=timezone.now().date(), + email=settings.TEST_MAIL) + self.peter.group.add(self.ja) + self.anna.group.add(self.ja) + self.lisa.group.add(self.ja) + + p2 = PermissionGroup.objects.create(group=self.ja) + p2.list_groups.add(self.ja) + + def test_may(self): + self.assertTrue(self.fritz.may_view(self.lara)) + self.assertTrue(self.fritz.may_change(self.lara)) + self.assertTrue(self.fritz.may_view(self.fridolin)) + self.assertFalse(self.fritz.may_change(self.fridolin)) + + # every member should be able to list, view and change themselves + for member in Member.objects.all(): + self.assertTrue(member.may_list(member)) + self.assertTrue(member.may_view(member)) + self.assertTrue(member.may_change(member)) + + # every member of Jugendausschuss should be able to view every other member of Jugendausschuss + for member in self.ja.member_set.all(): + for other in self.ja.member_set.all(): + self.assertTrue(member.may_list(other)) + if member != other: + self.assertFalse(member.may_view(other)) + self.assertFalse(member.may_change(other)) + + def test_filter_queryset(self): + # lise may only list herself + self.assertEqual(set(self.lise.filter_queryset_by_permissions()), set([self.lise])) + + for member in Member.objects.all(): + # passing the empty queryset as starting queryset, should give the empty queryset back + self.assertEqual(member.filter_queryset_by_permissions(Member.objects.none()).count(), 0) + # passing all objects as start queryset should give the same result as not giving any start queryset + self.assertEqual(set(member.filter_queryset_by_permissions(Member.objects.all())), + set(member.filter_queryset_by_permissions())) + + + def test_compare_filter_queryset_may_list(self): + # filter_queryset and filtering manually by may_list should be the same + for member in Member.objects.all(): + s1 = set(member.filter_queryset_by_permissions()) + s2 = set(other for other in Member.objects.all() if member.may_list(other)) + self.assertEqual(s1, s2)