Compare commits
11 commits
master
...
aureplop/k
Author | SHA1 | Date | |
---|---|---|---|
|
84bbe78e50 | ||
|
fcf4a25745 | ||
|
13f01020f7 | ||
|
2b62c3a785 | ||
|
d36a813e15 | ||
|
09290131d5 | ||
|
40ceaf411a | ||
|
c17ed416c4 | ||
|
085a068020 | ||
|
8ea5775d61 | ||
|
ded824bddd |
|
@ -1,17 +1,26 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.models import Permission
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.forms import widgets
|
from django.forms import widgets
|
||||||
|
|
||||||
|
from .models import Group, Permission
|
||||||
|
|
||||||
class KFetPermissionsField(forms.ModelMultipleChoiceField):
|
|
||||||
def __init__(self, *args, **kwargs):
|
class GroupsField(forms.ModelMultipleChoiceField):
|
||||||
queryset = Permission.objects.filter(
|
def __init__(self, **kwargs):
|
||||||
content_type__in=ContentType.objects.filter(app_label="kfet")
|
kwargs.setdefault("queryset", Group.objects.all())
|
||||||
)
|
kwargs.setdefault("widget", widgets.CheckboxSelectMultiple)
|
||||||
super().__init__(
|
super().__init__(**kwargs)
|
||||||
queryset=queryset, widget=widgets.CheckboxSelectMultiple, *args, **kwargs
|
|
||||||
)
|
|
||||||
|
class BasePermissionsField(forms.ModelMultipleChoiceField):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
kwargs.setdefault("widget", widgets.CheckboxSelectMultiple)
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def label_from_instance(self, obj):
|
def label_from_instance(self, obj):
|
||||||
return obj.name
|
return obj.name
|
||||||
|
|
||||||
|
|
||||||
|
class CorePermissionsField(BasePermissionsField):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
kwargs.setdefault("queryset", Permission.kfetcore.all())
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
|
@ -1,48 +1,27 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import User
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from .fields import KFetPermissionsField
|
from utils.forms import KeepUnselectableModelFormMixin
|
||||||
|
|
||||||
|
from .fields import CorePermissionsField, GroupsField
|
||||||
|
from .models import Group
|
||||||
|
|
||||||
|
|
||||||
class GroupForm(forms.ModelForm):
|
class GroupForm(KeepUnselectableModelFormMixin, forms.ModelForm):
|
||||||
permissions = KFetPermissionsField()
|
permissions = CorePermissionsField(label=_("Permissions"), required=False)
|
||||||
|
|
||||||
def clean_name(self):
|
keep_unselectable_fields = ["permissions"]
|
||||||
name = self.cleaned_data["name"]
|
|
||||||
return "K-Fêt %s" % name
|
|
||||||
|
|
||||||
def clean_permissions(self):
|
|
||||||
kfet_perms = self.cleaned_data["permissions"]
|
|
||||||
# TODO: With Django >=1.11, the QuerySet method 'difference' can be
|
|
||||||
# used.
|
|
||||||
# other_groups = self.instance.permissions.difference(
|
|
||||||
# self.fields['permissions'].queryset
|
|
||||||
# )
|
|
||||||
if self.instance.pk is None:
|
|
||||||
return kfet_perms
|
|
||||||
other_perms = self.instance.permissions.exclude(
|
|
||||||
pk__in=[p.pk for p in self.fields["permissions"].queryset]
|
|
||||||
)
|
|
||||||
return list(kfet_perms) + list(other_perms)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Group
|
model = Group
|
||||||
fields = ["name", "permissions"]
|
fields = ["name", "permissions"]
|
||||||
|
|
||||||
|
|
||||||
class UserGroupForm(forms.ModelForm):
|
class UserGroupForm(KeepUnselectableModelFormMixin, forms.ModelForm):
|
||||||
groups = forms.ModelMultipleChoiceField(
|
groups = GroupsField(label=_("Statut équipe"), required=False)
|
||||||
Group.objects.filter(name__icontains="K-Fêt"),
|
|
||||||
label="Statut équipe",
|
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def clean_groups(self):
|
keep_unselectable_fields = ["groups"]
|
||||||
kfet_groups = self.cleaned_data.get("groups")
|
|
||||||
if self.instance.pk is None:
|
|
||||||
return kfet_groups
|
|
||||||
other_groups = self.instance.groups.exclude(name__icontains="K-Fêt")
|
|
||||||
return list(kfet_groups) + list(other_groups)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
|
|
39
kfet/auth/migrations/0002_local_group_and_perm.py
Normal file
39
kfet/auth/migrations/0002_local_group_and_perm.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [("kfetauth", "0001_initial")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Group",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"group_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
parent_link=True,
|
||||||
|
serialize=False,
|
||||||
|
primary_key=True,
|
||||||
|
auto_created=True,
|
||||||
|
to="auth.Group",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
options={"verbose_name": "Groupe", "verbose_name_plural": "Groupes"},
|
||||||
|
bases=("auth.group",),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Permission",
|
||||||
|
fields=[],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Permission",
|
||||||
|
"verbose_name_plural": "Permissions",
|
||||||
|
"proxy": True,
|
||||||
|
},
|
||||||
|
bases=("auth.permission",),
|
||||||
|
),
|
||||||
|
]
|
26
kfet/auth/migrations/0003_existing_groups.py
Normal file
26
kfet/auth/migrations/0003_existing_groups.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def existing_groups(apps, schema_editor):
|
||||||
|
Group = apps.get_model("auth", "Group")
|
||||||
|
KFetGroup = apps.get_model("kfetauth", "Group")
|
||||||
|
|
||||||
|
for group in Group.objects.filter(name__icontains="K-Fêt"):
|
||||||
|
kf_group = KFetGroup(group_ptr=group, pk=group.pk, name=group.name)
|
||||||
|
kf_group.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
"""
|
||||||
|
Data migration.
|
||||||
|
|
||||||
|
Previously, K-Fêt groups were identified with the presence of 'K-Fêt' in
|
||||||
|
their name.
|
||||||
|
"""
|
||||||
|
|
||||||
|
dependencies = [("kfetauth", "0002_local_group_and_perm")]
|
||||||
|
|
||||||
|
operations = [migrations.RunPython(existing_groups)]
|
|
@ -1,5 +1,10 @@
|
||||||
|
from django.contrib.auth.models import (
|
||||||
|
Group as DjangoGroup,
|
||||||
|
Permission as DjangoPermission,
|
||||||
|
)
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class GenericTeamTokenManager(models.Manager):
|
class GenericTeamTokenManager(models.Manager):
|
||||||
|
@ -14,3 +19,38 @@ class GenericTeamToken(models.Model):
|
||||||
token = models.CharField(max_length=50, unique=True)
|
token = models.CharField(max_length=50, unique=True)
|
||||||
|
|
||||||
objects = GenericTeamTokenManager()
|
objects = GenericTeamTokenManager()
|
||||||
|
|
||||||
|
|
||||||
|
class Group(DjangoGroup):
|
||||||
|
def give_admin_access(self):
|
||||||
|
perms = Permission.kfetcore.all()
|
||||||
|
self.permissions.add(*perms)
|
||||||
|
|
||||||
|
def give_staff_access(self):
|
||||||
|
perms = Permission.kfetcore.filter(codename__in=["is_team", "perform_deposit"])
|
||||||
|
self.permissions.add(*perms)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Groupe")
|
||||||
|
verbose_name_plural = _("Groupes")
|
||||||
|
|
||||||
|
|
||||||
|
KFET_CORE_APP_LABELS = ["kfet", "kfetauth"]
|
||||||
|
|
||||||
|
|
||||||
|
class CorePermissionManager(models.Manager):
|
||||||
|
def get_queryset(self):
|
||||||
|
return (
|
||||||
|
super()
|
||||||
|
.get_queryset()
|
||||||
|
.filter(content_type__app_label__in=KFET_CORE_APP_LABELS)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Permission(DjangoPermission):
|
||||||
|
kfetcore = CorePermissionManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
verbose_name = _("Permission")
|
||||||
|
verbose_name_plural = _("Permissions")
|
||||||
|
|
|
@ -1,70 +1,172 @@
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.contrib.auth.models import AnonymousUser, Group, Permission, User
|
from django.contrib.auth.models import AnonymousUser, Group as DjangoGroup, User
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.test import RequestFactory, TestCase
|
from django.test import RequestFactory, TestCase
|
||||||
|
|
||||||
from kfet.forms import UserGroupForm
|
|
||||||
from kfet.models import Account
|
from kfet.models import Account
|
||||||
|
|
||||||
from . import KFET_GENERIC_TRIGRAMME, KFET_GENERIC_USERNAME
|
from . import KFET_GENERIC_TRIGRAMME, KFET_GENERIC_USERNAME
|
||||||
from .backends import AccountBackend, GenericBackend
|
from .backends import AccountBackend, GenericBackend
|
||||||
|
from .fields import CorePermissionsField, GroupsField
|
||||||
|
from .forms import GroupForm, UserGroupForm
|
||||||
from .middleware import TemporaryAuthMiddleware
|
from .middleware import TemporaryAuthMiddleware
|
||||||
from .models import GenericTeamToken
|
from .models import GenericTeamToken, Group, Permission
|
||||||
from .utils import get_kfet_generic_user
|
from .utils import get_kfet_generic_user
|
||||||
from .views import GenericLoginView
|
from .views import GenericLoginView
|
||||||
|
|
||||||
##
|
##
|
||||||
# Forms
|
# Forms and form fields
|
||||||
##
|
##
|
||||||
|
|
||||||
|
|
||||||
class UserGroupFormTests(TestCase):
|
class CorePermissionsFieldTests(TestCase):
|
||||||
"""Test suite for UserGroupForm."""
|
def setUp(self):
|
||||||
|
kf_ct = ContentType.objects.get_for_model(GenericTeamToken)
|
||||||
|
self.kf_perm1 = Permission.objects.create(content_type=kf_ct, codename="code1")
|
||||||
|
self.kf_perm2 = Permission.objects.create(content_type=kf_ct, codename="code2")
|
||||||
|
self.kf_perm3 = Permission.objects.create(content_type=kf_ct, codename="code3")
|
||||||
|
|
||||||
|
ot_ct = ContentType.objects.get_for_model(Permission)
|
||||||
|
self.ot_perm = Permission.objects.create(content_type=ot_ct, codename="code")
|
||||||
|
|
||||||
|
def test_choices(self):
|
||||||
|
"""
|
||||||
|
Only K-Fêt permissions are selectable.
|
||||||
|
"""
|
||||||
|
field = CorePermissionsField()
|
||||||
|
for p in [self.kf_perm1, self.kf_perm2, self.kf_perm3]:
|
||||||
|
self.assertIn(p, field.queryset)
|
||||||
|
self.assertNotIn(self.ot_perm, field.queryset)
|
||||||
|
|
||||||
|
|
||||||
|
class GroupsFieldTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.kf_group1 = Group.objects.create(name="KF Group1")
|
||||||
|
self.kf_group2 = Group.objects.create(name="KF Group2")
|
||||||
|
self.kf_group3 = Group.objects.create(name="KF Group3")
|
||||||
|
self.ot_group = DjangoGroup.objects.create(name="OT Group")
|
||||||
|
|
||||||
|
def test_choices(self):
|
||||||
|
"""
|
||||||
|
Only K-Fêt groups are selectable.
|
||||||
|
"""
|
||||||
|
field = GroupsField()
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
field.queryset,
|
||||||
|
map(repr, [self.kf_group1, self.kf_group2, self.kf_group3]),
|
||||||
|
ordered=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GroupFormTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user = User.objects.create(username="user")
|
||||||
|
self.kf_group = Group.objects.create(name="A Group")
|
||||||
|
|
||||||
|
self.kf_perm1 = Permission.kfetcore.get(codename="is_team")
|
||||||
|
self.kf_perm2 = Permission.kfetcore.get(codename="add_checkout")
|
||||||
|
|
||||||
|
ot_ct = ContentType.objects.get_for_model(Permission)
|
||||||
|
self.ot_perm = Permission.objects.create(content_type=ot_ct, codename="cool")
|
||||||
|
|
||||||
|
def test_creation(self):
|
||||||
|
data = {"name": "Another Group", "permissions": [self.kf_perm1.pk]}
|
||||||
|
form = GroupForm(data)
|
||||||
|
instance = form.save()
|
||||||
|
|
||||||
|
self.assertEqual(instance.name, "Another Group")
|
||||||
|
self.assertQuerysetEqual(instance.permissions.all(), map(repr, [self.kf_perm1]))
|
||||||
|
|
||||||
|
def test_keep_others(self):
|
||||||
|
"""
|
||||||
|
Non-kfet permissions of Group are kept when the form is submitted.
|
||||||
|
Regression test for #168.
|
||||||
|
"""
|
||||||
|
self.kf_group.permissions.add(self.ot_perm)
|
||||||
|
|
||||||
|
selected = [self.kf_perm1, self.kf_perm2]
|
||||||
|
|
||||||
|
data = {"name": "A Group", "permissions": [perm.pk for perm in selected]}
|
||||||
|
form = GroupForm(data, instance=self.kf_group)
|
||||||
|
form.save()
|
||||||
|
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
self.kf_group.permissions.all(),
|
||||||
|
map(repr, [self.ot_perm] + selected),
|
||||||
|
ordered=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UserGroupFormTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# create user
|
# create user
|
||||||
self.user = User.objects.create(username="foo", password="foo")
|
self.user = User.objects.create(username="foo", password="foo")
|
||||||
|
|
||||||
# create some K-Fêt groups
|
# create some K-Fêt groups
|
||||||
prefix_name = "K-Fêt "
|
self.kf_group1 = Group.objects.create(name="KF Group1")
|
||||||
names = ["Group 1", "Group 2", "Group 3"]
|
self.kf_group2 = Group.objects.create(name="KF Group2")
|
||||||
self.kfet_groups = [
|
self.kf_group3 = Group.objects.create(name="KF Group3")
|
||||||
Group.objects.create(name=prefix_name + name) for name in names
|
|
||||||
]
|
|
||||||
|
|
||||||
# create a non-K-Fêt group
|
# create a non-K-Fêt group
|
||||||
self.other_group = Group.objects.create(name="Other group")
|
self.ot_group = DjangoGroup.objects.create(name="OT Group")
|
||||||
|
|
||||||
def test_choices(self):
|
|
||||||
"""Only K-Fêt groups are selectable."""
|
|
||||||
form = UserGroupForm(instance=self.user)
|
|
||||||
groups_field = form.fields["groups"]
|
|
||||||
self.assertQuerysetEqual(
|
|
||||||
groups_field.queryset, [repr(g) for g in self.kfet_groups], ordered=False
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_keep_others(self):
|
def test_keep_others(self):
|
||||||
"""User stays in its non-K-Fêt groups."""
|
"""
|
||||||
user = self.user
|
User stays in its non-K-Fêt groups.
|
||||||
|
Regression test for #161.
|
||||||
|
"""
|
||||||
# add user to a non-K-Fêt group
|
# add user to a non-K-Fêt group
|
||||||
user.groups.add(self.other_group)
|
self.user.groups.add(self.ot_group)
|
||||||
|
|
||||||
# add user to some K-Fêt groups through UserGroupForm
|
# add user to some K-Fêt groups through UserGroupForm
|
||||||
data = {"groups": [group.pk for group in self.kfet_groups]}
|
selected = [self.kf_group1, self.kf_group2]
|
||||||
form = UserGroupForm(data, instance=user)
|
data = {"groups": [str(g.pk) for g in selected]}
|
||||||
|
form = UserGroupForm(data, instance=self.user)
|
||||||
form.is_valid()
|
|
||||||
form.save()
|
form.save()
|
||||||
|
|
||||||
|
transform = lambda g: g.pk
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
user.groups.all(),
|
self.user.groups.all(),
|
||||||
[repr(g) for g in [self.other_group] + self.kfet_groups],
|
map(transform, [self.ot_group] + selected),
|
||||||
ordered=False,
|
ordered=False,
|
||||||
|
transform=transform,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Models
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionTests(TestCase):
|
||||||
|
def test_manager_kfet(self):
|
||||||
|
"""
|
||||||
|
'kfet' manager only returns K-Fêt permissions.
|
||||||
|
"""
|
||||||
|
kf_ct = ContentType.objects.get_for_model(GenericTeamToken)
|
||||||
|
kf_perm1 = Permission.objects.create(content_type=kf_ct, codename="code1")
|
||||||
|
kf_perm2 = Permission.objects.create(content_type=kf_ct, codename="code2")
|
||||||
|
kf_perm3 = Permission.objects.create(content_type=kf_ct, codename="code3")
|
||||||
|
|
||||||
|
self.assertEqual(Permission.kfetcore.get(codename="code1"), kf_perm1)
|
||||||
|
self.assertEqual(Permission.kfetcore.get(codename="code2"), kf_perm2)
|
||||||
|
self.assertEqual(Permission.kfetcore.get(codename="code3"), kf_perm3)
|
||||||
|
|
||||||
|
ot_ct = ContentType.objects.get_for_model(Permission)
|
||||||
|
Permission.objects.create(content_type=ot_ct, codename="code")
|
||||||
|
|
||||||
|
with self.assertRaises(Permission.DoesNotExist):
|
||||||
|
Permission.kfetcore.get(codename="code")
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# K-Fêt generic user object
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
class KFetGenericUserTests(TestCase):
|
class KFetGenericUserTests(TestCase):
|
||||||
def test_exists(self):
|
def test_exists(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth import authenticate, login
|
from django.contrib.auth import authenticate, login
|
||||||
from django.contrib.auth.decorators import permission_required
|
from django.contrib.auth.decorators import permission_required
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth.views import redirect_to_login
|
from django.contrib.auth.views import redirect_to_login
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.core.urlresolvers import reverse, reverse_lazy
|
from django.core.urlresolvers import reverse, reverse_lazy
|
||||||
|
@ -15,7 +15,7 @@ from django.views.generic import View
|
||||||
from django.views.generic.edit import CreateView, UpdateView
|
from django.views.generic.edit import CreateView, UpdateView
|
||||||
|
|
||||||
from .forms import GroupForm
|
from .forms import GroupForm
|
||||||
from .models import GenericTeamToken
|
from .models import GenericTeamToken, Group
|
||||||
|
|
||||||
|
|
||||||
class GenericLoginView(View):
|
class GenericLoginView(View):
|
||||||
|
@ -113,23 +113,24 @@ def account_group(request):
|
||||||
user_pre = Prefetch(
|
user_pre = Prefetch(
|
||||||
"user_set", queryset=User.objects.select_related("profile__account_kfet")
|
"user_set", queryset=User.objects.select_related("profile__account_kfet")
|
||||||
)
|
)
|
||||||
groups = Group.objects.filter(name__icontains="K-Fêt").prefetch_related(
|
groups = Group.objects.prefetch_related("permissions", user_pre)
|
||||||
"permissions", user_pre
|
|
||||||
)
|
|
||||||
return render(request, "kfet/account_group.html", {"groups": groups})
|
return render(request, "kfet/account_group.html", {"groups": groups})
|
||||||
|
|
||||||
|
|
||||||
class AccountGroupCreate(SuccessMessageMixin, CreateView):
|
class BaseAccountGroupFormViewMixin:
|
||||||
model = Group
|
model = Group
|
||||||
template_name = "kfet/account_group_form.html"
|
|
||||||
form_class = GroupForm
|
form_class = GroupForm
|
||||||
|
template_name = "kfet/account_group_form.html"
|
||||||
|
success_url = reverse_lazy("kfet.account.group")
|
||||||
|
|
||||||
|
|
||||||
|
class AccountGroupFormViewMixin(SuccessMessageMixin, BaseAccountGroupFormViewMixin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AccountGroupCreate(AccountGroupFormViewMixin, CreateView):
|
||||||
success_message = "Nouveau groupe : %(name)s"
|
success_message = "Nouveau groupe : %(name)s"
|
||||||
success_url = reverse_lazy("kfet.account.group")
|
|
||||||
|
|
||||||
|
|
||||||
class AccountGroupUpdate(SuccessMessageMixin, UpdateView):
|
class AccountGroupUpdate(AccountGroupFormViewMixin, UpdateView):
|
||||||
queryset = Group.objects.filter(name__icontains="K-Fêt")
|
|
||||||
template_name = "kfet/account_group_form.html"
|
|
||||||
form_class = GroupForm
|
|
||||||
success_message = "Groupe modifié : %(name)s"
|
success_message = "Groupe modifié : %(name)s"
|
||||||
success_url = reverse_lazy("kfet.account.group")
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import os
|
||||||
import random
|
import random
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.contrib.auth.models import ContentType, Group, Permission, User
|
from django.contrib.auth.models import User
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ from kfet.models import (
|
||||||
Article,
|
Article,
|
||||||
Checkout,
|
Checkout,
|
||||||
CheckoutStatement,
|
CheckoutStatement,
|
||||||
|
Group,
|
||||||
Supplier,
|
Supplier,
|
||||||
SupplierArticle,
|
SupplierArticle,
|
||||||
)
|
)
|
||||||
|
@ -33,23 +34,11 @@ class Command(MyBaseCommand):
|
||||||
# Groupes
|
# Groupes
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
Group.objects.filter(name__icontains="K-Fêt").delete()
|
group_chef, _ = Group.objects.get_or_create(name="K-Fêt César")
|
||||||
|
group_boy, _ = Group.objects.get_or_create(name="K-Fêt Légionnaire")
|
||||||
|
|
||||||
group_chef = Group(name="K-Fêt César")
|
group_chef.give_admin_access()
|
||||||
group_boy = Group(name="K-Fêt Légionnaire")
|
group_boy.give_staff_access()
|
||||||
|
|
||||||
group_chef.save()
|
|
||||||
group_boy.save()
|
|
||||||
|
|
||||||
permissions_chef = Permission.objects.filter(
|
|
||||||
content_type__in=ContentType.objects.filter(app_label="kfet")
|
|
||||||
)
|
|
||||||
permissions_boy = Permission.objects.filter(
|
|
||||||
codename__in=["is_team", "perform_deposit"]
|
|
||||||
)
|
|
||||||
|
|
||||||
group_chef.permissions.add(*permissions_chef)
|
|
||||||
group_boy.permissions.add(*permissions_boy)
|
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# Comptes
|
# Comptes
|
||||||
|
|
|
@ -13,7 +13,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from gestioncof.models import CofProfile
|
from gestioncof.models import CofProfile
|
||||||
|
|
||||||
from .auth import KFET_GENERIC_TRIGRAMME
|
from .auth import KFET_GENERIC_TRIGRAMME
|
||||||
from .auth.models import GenericTeamToken # noqa
|
from .auth.models import GenericTeamToken, Group, Permission # noqa
|
||||||
from .config import kfet_config
|
from .config import kfet_config
|
||||||
from .utils import to_ukf
|
from .utils import to_ukf
|
||||||
|
|
||||||
|
|
|
@ -1,49 +1,10 @@
|
||||||
{% extends 'kfet/base_form.html' %}
|
{% extends 'kfet/base_form.html' %}
|
||||||
{% load staticfiles %}
|
|
||||||
{% load widget_tweaks %}
|
|
||||||
|
|
||||||
{% block extra_head %}
|
|
||||||
<link rel="stylesheet" text="text/css" href="{% static 'kfet/css/multiple-select.css' %}">
|
|
||||||
<script src="{% static 'kfet/js/multiple-select.js' %}"></script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block title %}Permissions - Édition{% endblock %}
|
{% block title %}Permissions - Édition{% endblock %}
|
||||||
{% block header-title %}Modification des permissions{% endblock %}
|
{% block header-title %}Modification des permissions{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
<form action="" method="post" class="form-horizontal">
|
{% include "kfet/form_full_snippet.html" with authz=perms.kfet.manage_perms submit_text="Enregistrer" %}
|
||||||
{% csrf_token %}
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="{{ form.name.id_for_label }}" class="col-sm-2 control-label">{{ form.name.label }}</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon">K-Fêt</span>
|
|
||||||
{{ form.name|add_class:"form-control" }}
|
|
||||||
</div>
|
|
||||||
{% if form.name.errors %}
|
|
||||||
<span class="help-block">{{ form.name.errors }}</span>
|
|
||||||
{% endif %}
|
|
||||||
{% if form.name.help_text %}
|
|
||||||
<span class="help-block">{{ form.name.help_text }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% include "kfet/form_field_snippet.html" with field=form.permissions %}
|
|
||||||
{% if not perms.kfet.manage_perms %}
|
|
||||||
{% include "kfet/form_authentication_snippet.html" %}
|
|
||||||
{% endif %}
|
|
||||||
{% include "kfet/form_submit_snippet.html" with value="Enregistrer" %}
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(document).ready(function() {
|
|
||||||
let $name_input = $("#id_name");
|
|
||||||
let raw_name = $name_input.val();
|
|
||||||
let prefix = "K-Fêt ";
|
|
||||||
if (raw_name.startsWith(prefix))
|
|
||||||
$name_input.val(raw_name.substring(prefix.length));
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
{% if field|widget_type == "checkboxselectmultiple" %}
|
{% if field|widget_type == "checkboxselectmultiple" %}
|
||||||
<ul class="list-unstyled checkbox-select-multiple">
|
<ul class="list-unstyled checkbox-select-multiple">
|
||||||
{% for choice in form.permissions %}
|
{% for choice in field %}
|
||||||
<li class="col-sm-6 col-lg-4">
|
<li class="col-sm-6 col-lg-4">
|
||||||
<label for="{{ choice.id_for_label }}">
|
<label for="{{ choice.id_for_label }}">
|
||||||
{{ choice.tag }} {{ choice.choice_label }}
|
{{ choice.tag }} {{ choice.choice_label }}
|
||||||
|
@ -17,10 +17,10 @@
|
||||||
{{ field|add_class:'form-control' }}
|
{{ field|add_class:'form-control' }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if field.errors %}
|
{% if field.errors %}
|
||||||
<span class="help-block">{{field.errors}}</span>
|
<span class="help-block">{{ field.errors }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if field.help_text %}
|
{% if field.help_text %}
|
||||||
<span class="help-block">{{field.help_text}}</span>
|
<span class="help-block">{{ field.help_text }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,11 +3,12 @@ from datetime import datetime, timedelta
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.contrib.auth.models import Group
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from kfet.auth.models import Group
|
||||||
|
|
||||||
from ..config import kfet_config
|
from ..config import kfet_config
|
||||||
from ..models import (
|
from ..models import (
|
||||||
Account,
|
Account,
|
||||||
|
@ -398,7 +399,7 @@ class AccountGroupCreateViewTests(ViewTestCaseMixin, TestCase):
|
||||||
r = self.client.post(self.url, self.post_data)
|
r = self.client.post(self.url, self.post_data)
|
||||||
self.assertRedirects(r, reverse("kfet.account.group"))
|
self.assertRedirects(r, reverse("kfet.account.group"))
|
||||||
|
|
||||||
group = Group.objects.get(name="K-Fêt The Group")
|
group = Group.objects.get(name="The Group")
|
||||||
|
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
group.permissions.all(),
|
group.permissions.all(),
|
||||||
|
@ -452,7 +453,7 @@ class AccountGroupUpdateViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.group.refresh_from_db()
|
self.group.refresh_from_db()
|
||||||
|
|
||||||
self.assertEqual(self.group.name, "K-Fêt The Group")
|
self.assertEqual(self.group.name, "The Group")
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
self.group.permissions.all(),
|
self.group.permissions.all(),
|
||||||
map(repr, [self.perms["kfet.is_team"], self.perms["kfet.manage_perms"]]),
|
map(repr, [self.perms["kfet.is_team"], self.perms["kfet.manage_perms"]]),
|
||||||
|
|
62
utils/forms.py
Normal file
62
utils/forms.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
class KeepUnselectableModelFormMixin:
|
||||||
|
"""
|
||||||
|
Keep unselectable items of queryset-based fields.
|
||||||
|
|
||||||
|
Mixin for 'ModelForm'.
|
||||||
|
|
||||||
|
Attribute
|
||||||
|
keep_unselectable_fields (list of field names)
|
||||||
|
|
||||||
|
This adding is performed in 'save' method. Specifically, if 'commit' arg
|
||||||
|
is False, it's done in 'save_m2m' method.
|
||||||
|
|
||||||
|
These fields must have a 'queryset' attribute (the selectable items), like
|
||||||
|
'ModelMultipleChoiceField' (default field for ManyToMany model fields).
|
||||||
|
"""
|
||||||
|
|
||||||
|
keep_unselectable_fields = []
|
||||||
|
|
||||||
|
def get_unselectable(self, field_name):
|
||||||
|
"""
|
||||||
|
Returns 'field_name' model field items of instance which can't be
|
||||||
|
selected with the corresponding form field.
|
||||||
|
|
||||||
|
Should be used before 'form.save' call, or 'form.save_m2m' call if
|
||||||
|
'commit=False' was passed as argument to 'form.save'.
|
||||||
|
"""
|
||||||
|
if self.instance.pk:
|
||||||
|
previous = getattr(self.instance, field_name).all()
|
||||||
|
selectable = self.fields[field_name].queryset
|
||||||
|
return previous.exclude(pk__in=[o.pk for o in selectable])
|
||||||
|
else:
|
||||||
|
# Instance is being created, there is no previous item.
|
||||||
|
return []
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
# Use 'commit=False' to get the 'save_m2m' method.
|
||||||
|
instance = super().save(commit=False)
|
||||||
|
|
||||||
|
_save_m2m = self.save_m2m
|
||||||
|
|
||||||
|
def save_m2m():
|
||||||
|
# Get the unselectable items.
|
||||||
|
# Force evaluate because those items are going to change.
|
||||||
|
unselectable_f = {
|
||||||
|
f_name: list(self.get_unselectable(f_name))
|
||||||
|
for f_name in self.keep_unselectable_fields
|
||||||
|
}
|
||||||
|
# Default 'save_m2m' use 'set' method of m2m relationships with
|
||||||
|
# fields' cleaned data.
|
||||||
|
_save_m2m()
|
||||||
|
# Add the unselectable elements.
|
||||||
|
for f_name, unselectable in unselectable_f.items():
|
||||||
|
getattr(instance, f_name).add(*unselectable)
|
||||||
|
|
||||||
|
# Implement the default behavior of 'save' method, with our own
|
||||||
|
# 'save_m2m'.
|
||||||
|
if commit:
|
||||||
|
instance.save()
|
||||||
|
save_m2m()
|
||||||
|
else:
|
||||||
|
self.save_m2m = save_m2m
|
||||||
|
return instance
|
0
utils/tests/__init__.py
Normal file
0
utils/tests/__init__.py
Normal file
131
utils/tests/test_forms.py
Normal file
131
utils/tests/test_forms.py
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.models import Permission
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from utils.forms import KeepUnselectableModelFormMixin
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class KeepUnselectableModelFormMixinTests(TestCase):
|
||||||
|
def _get_form_cls(self):
|
||||||
|
"""
|
||||||
|
We recreate a new form class for each test, because `queryset` may not
|
||||||
|
be reevaluated and may lack the permissions created by `setUp`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class ExampleForm(KeepUnselectableModelFormMixin, forms.ModelForm):
|
||||||
|
user_permissions = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Permission.objects.filter(codename__startswith="selec")
|
||||||
|
)
|
||||||
|
|
||||||
|
keep_unselectable_fields = ["user_permissions"]
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ("username", "user_permissions")
|
||||||
|
|
||||||
|
return ExampleForm
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
ct = ContentType.objects.get_for_model(Permission)
|
||||||
|
|
||||||
|
self.unselec_perm1 = Permission.objects.create(
|
||||||
|
content_type=ct, codename="unselec_perm1", name="Unselectable 1"
|
||||||
|
)
|
||||||
|
self.unselec_perm2 = Permission.objects.create(
|
||||||
|
content_type=ct, codename="unselec_perm2", name="Unselectable 2"
|
||||||
|
)
|
||||||
|
|
||||||
|
# These two perms are the only selectable permissions from
|
||||||
|
# 'permissions' field of ExampleForm.
|
||||||
|
self.selec_perm1 = Permission.objects.create(
|
||||||
|
content_type=ct, codename="selec_perm1", name="Selectable 1"
|
||||||
|
)
|
||||||
|
self.selec_perm2 = Permission.objects.create(
|
||||||
|
content_type=ct, codename="selec_perm2", name="Selectable 2"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_creation(self):
|
||||||
|
"""
|
||||||
|
The mixin functions properly when instance is being created.
|
||||||
|
"""
|
||||||
|
ExampleForm = self._get_form_cls()
|
||||||
|
data = {"username": "user", "user_permissions": [self.selec_perm1.pk]}
|
||||||
|
form = ExampleForm(data)
|
||||||
|
|
||||||
|
instance = form.save()
|
||||||
|
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
instance.user_permissions.all(), map(repr, [self.selec_perm1])
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_creation_commit_false(self):
|
||||||
|
"""
|
||||||
|
When instance is being created and 'save' method is called with
|
||||||
|
'commit=False', 'save_m2m' method functions properly.
|
||||||
|
|
||||||
|
https://docs.djangoproject.com/en/1.11/topics/forms/modelforms/#the-save-method
|
||||||
|
"""
|
||||||
|
ExampleForm = self._get_form_cls()
|
||||||
|
data = {"username": "user", "user_permissions": [self.selec_perm1.pk]}
|
||||||
|
form = ExampleForm(data)
|
||||||
|
|
||||||
|
instance = form.save(commit=False)
|
||||||
|
|
||||||
|
with self.assertRaises(User.DoesNotExist):
|
||||||
|
User.objects.get(username="user")
|
||||||
|
|
||||||
|
instance.save()
|
||||||
|
form.save_m2m()
|
||||||
|
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
instance.user_permissions.all(), map(repr, [self.selec_perm1])
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_existing(self):
|
||||||
|
"""
|
||||||
|
Unselectable items of an instance are kept.
|
||||||
|
"""
|
||||||
|
instance = User.objects.create(username="user")
|
||||||
|
# Link instance with an unselectable and a selectable permissions.
|
||||||
|
instance.user_permissions.add(self.unselec_perm1, self.selec_perm2)
|
||||||
|
|
||||||
|
ExampleForm = self._get_form_cls()
|
||||||
|
data = {"username": "user", "user_permissions": [self.selec_perm1.pk]}
|
||||||
|
form = ExampleForm(data, instance=instance)
|
||||||
|
|
||||||
|
instance = form.save()
|
||||||
|
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
instance.user_permissions.all(),
|
||||||
|
map(repr, [self.selec_perm1, self.unselec_perm1]),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_existing_commit_false(self):
|
||||||
|
"""
|
||||||
|
When 'save' is called with 'commit=False', unselectable items of an
|
||||||
|
instance are kept by 'save_m2m'.
|
||||||
|
"""
|
||||||
|
instance = User.objects.create(username="user")
|
||||||
|
# Link instance with an unselectable and a selectable permissions.
|
||||||
|
instance.user_permissions.add(self.unselec_perm1, self.selec_perm2)
|
||||||
|
|
||||||
|
ExampleForm = self._get_form_cls()
|
||||||
|
data = {"username": "changed", "user_permissions": [self.selec_perm1.pk]}
|
||||||
|
form = ExampleForm(data, instance=instance)
|
||||||
|
|
||||||
|
instance = form.save(commit=False)
|
||||||
|
|
||||||
|
with self.assertRaises(User.DoesNotExist):
|
||||||
|
User.objects.get(username="changed")
|
||||||
|
|
||||||
|
instance.save()
|
||||||
|
form.save_m2m()
|
||||||
|
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
instance.user_permissions.all(),
|
||||||
|
map(repr, [self.selec_perm1, self.unselec_perm1]),
|
||||||
|
)
|
Loading…
Reference in a new issue