Compare commits
26 commits
master
...
aureplop/k
Author | SHA1 | Date | |
---|---|---|---|
|
e3e8608563 | ||
|
02f1e83420 | ||
|
6d90311ae1 | ||
|
a7dbc64e2b | ||
|
94ab754f82 | ||
|
13f01020f7 | ||
|
2b62c3a785 | ||
|
d36a813e15 | ||
|
94f21c062a | ||
|
e39f88991e | ||
|
03a0c58869 | ||
|
aa405d212a | ||
|
c524da22fe | ||
|
09290131d5 | ||
|
40ceaf411a | ||
|
097ee44131 | ||
|
2c76bea1e6 | ||
|
07f1a53532 | ||
|
82582866b4 | ||
|
5502c6876a | ||
|
df7594a105 | ||
|
e6fab703ee | ||
|
c17ed416c4 | ||
|
085a068020 | ||
|
8ea5775d61 | ||
|
ded824bddd |
55 changed files with 2283 additions and 520 deletions
|
@ -69,6 +69,7 @@ INSTALLED_APPS = [
|
|||
'autocomplete_light',
|
||||
'captcha',
|
||||
'django_cas_ng',
|
||||
'djangoformsetjs',
|
||||
'bootstrapform',
|
||||
'kfet',
|
||||
'kfet.open',
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import (absolute_import, division,
|
||||
print_function, unicode_literals)
|
||||
from builtins import *
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class KFetConfig(AppConfig):
|
||||
name = 'kfet'
|
||||
verbose_name = "Application K-Fêt"
|
||||
|
@ -15,5 +11,5 @@ class KFetConfig(AppConfig):
|
|||
|
||||
def register_config(self):
|
||||
import djconfig
|
||||
from kfet.forms import KFetConfigForm
|
||||
from .config import KFetConfigForm
|
||||
djconfig.register(KFetConfigForm)
|
||||
|
|
|
@ -1,20 +1,77 @@
|
|||
import re
|
||||
from itertools import groupby
|
||||
from operator import attrgetter
|
||||
|
||||
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.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .models import Group, Permission
|
||||
|
||||
DEFAULT_PERMS = ['view', 'add', 'change', 'delete']
|
||||
|
||||
DEFAULT_PERMS_LABELS = {
|
||||
'view': _("Voir"),
|
||||
'add': _("Ajouter"),
|
||||
'change': _("Modifier"),
|
||||
'delete': _("Supprimer"),
|
||||
}
|
||||
|
||||
|
||||
class KFetPermissionsField(forms.ModelMultipleChoiceField):
|
||||
class GroupsField(forms.ModelMultipleChoiceField):
|
||||
def __init__(self, **kwargs):
|
||||
kwargs.setdefault('queryset', Group.objects.all())
|
||||
kwargs.setdefault('widget', forms.CheckboxSelectMultiple)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
queryset = Permission.objects.filter(
|
||||
content_type__in=ContentType.objects.filter(app_label="kfet"),
|
||||
)
|
||||
super().__init__(
|
||||
queryset=queryset,
|
||||
widget=widgets.CheckboxSelectMultiple,
|
||||
*args, **kwargs
|
||||
)
|
||||
|
||||
def label_from_instance(self, obj):
|
||||
return obj.name
|
||||
class BasePermissionsField(forms.ModelMultipleChoiceField):
|
||||
def __init__(self, **kwargs):
|
||||
kwargs.setdefault('widget', forms.CheckboxSelectMultiple(attrs={
|
||||
'field_class': 'permissions-field',
|
||||
}))
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Contain permissions grouped by `ContentType`. Used as choices for
|
||||
# this field.
|
||||
grouped_choices = []
|
||||
|
||||
for ct, perms in groupby(self.queryset, attrgetter('content_type')):
|
||||
model_opts = ct.model_class()._meta
|
||||
choices = []
|
||||
|
||||
# This helps for the default permissions, if they exists, to appear
|
||||
# at the beginning of the permissions list and use custom labels.
|
||||
reg_defaults_p = re.compile(
|
||||
r'^(?P<p_type>({defaults}))_{model_name}$'
|
||||
.format(
|
||||
defaults='|'.join(DEFAULT_PERMS),
|
||||
model_name=model_opts.model_name,
|
||||
)
|
||||
)
|
||||
next_default_pos = 0
|
||||
|
||||
for p in perms:
|
||||
match = reg_defaults_p.match(p.codename)
|
||||
if match:
|
||||
# `p` is a default permission. Use shorter label and insert
|
||||
# after the last default permission seen in `choices`.
|
||||
p_type = match.group('p_type')
|
||||
choices.insert(
|
||||
next_default_pos,
|
||||
(p.id, DEFAULT_PERMS_LABELS[p_type])
|
||||
)
|
||||
next_default_pos += 1
|
||||
else:
|
||||
# Non-default permissions. Use the permission description,
|
||||
# instead of its `__str__`.
|
||||
choices.append((p.id, p.name))
|
||||
|
||||
grouped_choices.append((model_opts.verbose_name_plural, choices))
|
||||
|
||||
self.choices = grouped_choices
|
||||
|
||||
|
||||
class CorePermissionsField(BasePermissionsField):
|
||||
def __init__(self, **kwargs):
|
||||
kwargs.setdefault('queryset', Permission.kfetcore.all())
|
||||
super().__init__(**kwargs)
|
||||
|
|
|
@ -1,47 +1,27 @@
|
|||
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 GroupsField, CorePermissionsField
|
||||
from .models import Group
|
||||
|
||||
|
||||
class GroupForm(forms.ModelForm):
|
||||
permissions = KFetPermissionsField()
|
||||
class GroupForm(KeepUnselectableModelFormMixin, forms.ModelForm):
|
||||
permissions = CorePermissionsField(label='', required=False)
|
||||
|
||||
def clean_name(self):
|
||||
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)
|
||||
keep_unselectable_fields = ['permissions']
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = ['name', 'permissions']
|
||||
|
||||
|
||||
class UserGroupForm(forms.ModelForm):
|
||||
groups = forms.ModelMultipleChoiceField(
|
||||
Group.objects.filter(name__icontains='K-Fêt'),
|
||||
label='Statut équipe',
|
||||
required=False)
|
||||
class UserGroupForm(KeepUnselectableModelFormMixin, forms.ModelForm):
|
||||
groups = GroupsField(label=_("Statut équipe"), required=False)
|
||||
|
||||
def clean_groups(self):
|
||||
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)
|
||||
keep_unselectable_fields = ['groups']
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
|
|
54
kfet/auth/migrations/0002_create_group_permission_models.py
Normal file
54
kfet/auth/migrations/0002_create_group_permission_models.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('kfetauth', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# See also `kfetauth.0004` migration which deletes already created
|
||||
# permissions, if applicable.
|
||||
migrations.AlterModelOptions(
|
||||
name='genericteamtoken',
|
||||
options={
|
||||
'default_permissions': (),
|
||||
},
|
||||
),
|
||||
# See also `kfetauth.0003` migration which imports existing K-Fêt
|
||||
# groups.
|
||||
# See also `kfetauth.0004` migration which gives the default
|
||||
# permissions to `Group` objects which have the deleted
|
||||
# `kfet.manage_perms` permission.
|
||||
migrations.CreateModel(
|
||||
name='Group',
|
||||
fields=[
|
||||
('group_ptr', models.OneToOneField(
|
||||
parent_link=True,
|
||||
serialize=False,
|
||||
primary_key=True,
|
||||
auto_created=True,
|
||||
to='auth.Group',
|
||||
)),
|
||||
],
|
||||
options={
|
||||
'default_permissions': ('view', 'add', 'change'),
|
||||
'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',),
|
||||
),
|
||||
]
|
31
kfet/auth/migrations/0003_existing_groups.py
Normal file
31
kfet/auth/migrations/0003_existing_groups.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# -*- 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_create_group_permission_models'),
|
||||
('auth', '0006_require_contenttypes_0002'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(existing_groups),
|
||||
]
|
81
kfet/auth/migrations/0004_update_permissions.py
Normal file
81
kfet/auth/migrations/0004_update_permissions.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.db import migrations
|
||||
from django.db.models import Q
|
||||
|
||||
|
||||
def convert_manage_perms(apps, schema_editor):
|
||||
"""
|
||||
Use the permissions of `kfet.auth.models.Group` model, instead of using the
|
||||
`manage_perms` permission, which is deleted, of `kfet.models.Account`.
|
||||
|
||||
`Group` which have this last permission will get the permissions:
|
||||
`kfetauth.view_group`, `kfetauth.add_group` and `kfetauth.change_group`.
|
||||
"""
|
||||
Group = apps.get_model('auth', 'Group')
|
||||
Permission = apps.get_model('auth', 'Permission')
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
|
||||
try:
|
||||
old_p = Permission.objects.get(
|
||||
content_type__app_label='kfet',
|
||||
content_type__model='account',
|
||||
codename='manage_perms',
|
||||
)
|
||||
except Permission.DoesNotExist:
|
||||
return
|
||||
|
||||
groups = old_p.group_set.all()
|
||||
|
||||
ct_group, _ = ContentType.objects.get_or_create(
|
||||
app_label='kfetauth',
|
||||
model='group',
|
||||
)
|
||||
|
||||
view_p, _ = Permission.objects.get_or_create(
|
||||
content_type=ct_group, codename='view_group',
|
||||
defaults={'name': 'Can view Groupe'})
|
||||
add_p, _ = Permission.objects.get_or_create(
|
||||
content_type=ct_group, codename='add_group',
|
||||
defaults={'name': 'Can add Groupe'})
|
||||
change_p, _ = Permission.objects.get_or_create(
|
||||
content_type=ct_group, codename='change_group',
|
||||
defaults={'name': 'Can change Group'})
|
||||
|
||||
GroupPermission = Group.permissions.through
|
||||
|
||||
GroupPermission.objects.bulk_create([
|
||||
GroupPermission(permission=p, group=g)
|
||||
for g in groups
|
||||
for p in (view_p, add_p, change_p)
|
||||
])
|
||||
|
||||
old_p.delete()
|
||||
|
||||
|
||||
def delete_unused_permissions(apps, schema_editor):
|
||||
Permission = apps.get_model('auth', 'Permission')
|
||||
|
||||
to_delete_q = Q(content_type__model='genericteamtoken') | Q(
|
||||
content_type__model='group',
|
||||
codename='delete_group',
|
||||
)
|
||||
|
||||
to_delete_q &= Q(content_type__app_label='kfetauth')
|
||||
|
||||
Permission.objects.filter(to_delete_q).delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
"""
|
||||
Data migration about permissions.
|
||||
"""
|
||||
dependencies = [
|
||||
('kfetauth', '0003_existing_groups'),
|
||||
('auth', '0006_require_contenttypes_0002'),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(convert_manage_perms),
|
||||
migrations.RunPython(delete_unused_permissions),
|
||||
]
|
|
@ -1,5 +1,11 @@
|
|||
from django.contrib.auth.models import (
|
||||
Group as DjangoGroup, Permission as DjangoPermission,
|
||||
)
|
||||
from django.db import models
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from kfet.cms.models import CmsPermissionManager
|
||||
|
||||
|
||||
class GenericTeamTokenManager(models.Manager):
|
||||
|
@ -15,3 +21,42 @@ class GenericTeamToken(models.Model):
|
|||
token = models.CharField(max_length=50, unique=True)
|
||||
|
||||
objects = GenericTeamTokenManager()
|
||||
|
||||
class Meta:
|
||||
default_permissions = ()
|
||||
|
||||
|
||||
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")
|
||||
default_permissions = ('view', 'add', 'change')
|
||||
|
||||
|
||||
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()
|
||||
kfetcms = CmsPermissionManager()
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
||||
verbose_name = _("Permission")
|
||||
verbose_name_plural = _("Permissions")
|
||||
|
|
58
kfet/auth/templates/kfet/group_form.html
Normal file
58
kfet/auth/templates/kfet/group_form.html
Normal file
|
@ -0,0 +1,58 @@
|
|||
{% extends "kfet/base_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}
|
||||
{% if not form.instance.pk %}
|
||||
{% trans "Création d'un groupe" %}
|
||||
{% else %}
|
||||
{% blocktrans with name=form.instance.name %}
|
||||
Modification du groupe "{{ name }}"
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block header-title %}
|
||||
{% if not form.instance.pk %}
|
||||
{% trans "Création d'un groupe" %}
|
||||
{% else %}
|
||||
{% blocktrans with name=form.instance.name %}
|
||||
{{ name }}
|
||||
<small>Modification du groupe</small>
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
<form action="" method="post" class="group-form">
|
||||
{% csrf_token %}
|
||||
|
||||
{# Base form #}
|
||||
<div class="form-horizontal">
|
||||
{% include "kfet/form_snippet.html" with form=form %}
|
||||
</div>
|
||||
|
||||
{# Extra forms #}
|
||||
{% for extra in extras %}
|
||||
<h3>
|
||||
{{ extra.title }}<br>
|
||||
<small>{{ extra.description }}</small>
|
||||
</h3>
|
||||
|
||||
{% for extra_form in extra.forms %}
|
||||
<div class="extra-form">
|
||||
{% with as_panel=extra_form.as_admin_panel %}
|
||||
{% if as_panel %}
|
||||
{{ as_panel }}
|
||||
{% else %}
|
||||
{% include "kfet/form_snippet.html" with form=extra_form %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
{% include "kfet/form_submit_snippet.html" %}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
58
kfet/auth/templates/kfet/group_list.html
Normal file
58
kfet/auth/templates/kfet/group_list.html
Normal file
|
@ -0,0 +1,58 @@
|
|||
{% extends "kfet/base_col_2.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Groupes de comptes" %}{% endblock %}
|
||||
{% block header-title %}{% trans "Groupes de comptes" %}{% endblock %}
|
||||
|
||||
{% block fixed %}
|
||||
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary" href="{% url 'kfet.group.create' %}">
|
||||
<span class="glyphicon glyphicon-plus"></span><span>{% trans "Créer un groupe" %}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% for group in groups %}
|
||||
<section>
|
||||
<div class="heading">
|
||||
{{ group.name }}
|
||||
<div class="buttons">
|
||||
<a class="btn btn-default" href="{% url 'kfet.group.update' group.pk %}">
|
||||
<span class="glyphicon glyphicon-cog"></span><span class="hidden-xs">{% trans "Éditer" %}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Comptes</h3>
|
||||
{% with users=group.user_set.all %}
|
||||
{% if users %}
|
||||
<div class="sub-block column-sm-2 column-md-3">
|
||||
<ul>
|
||||
{% for user in group.user_set.all %}
|
||||
{% with kfet_user=user.profile.account_kfet %}
|
||||
<li>
|
||||
<a href="{{ kfet_user.get_absolute_url }}">{{ kfet_user }}</a>
|
||||
</li>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="sub-block">
|
||||
<p>
|
||||
{% blocktrans %}
|
||||
Aucun compte n'est associé à ce groupe. Rendez-vous sur la page
|
||||
d'édition d'un compte pour l'y ajouter.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</section>
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,76 +1,197 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from unittest import mock
|
||||
|
||||
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.urlresolvers import reverse
|
||||
from django.contrib.auth.models import AnonymousUser, Group, Permission, User
|
||||
from django.test import RequestFactory, TestCase
|
||||
|
||||
from kfet.forms import UserGroupForm
|
||||
from kfet.models import Account
|
||||
from kfet.tests.testcases import ViewTestCaseMixin
|
||||
from kfet.tests.utils import create_team, get_perms
|
||||
|
||||
from . import KFET_GENERIC_TRIGRAMME, KFET_GENERIC_USERNAME
|
||||
from .backends import AccountBackend, GenericBackend
|
||||
from .fields import GroupsField, CorePermissionsField
|
||||
from .middleware import TemporaryAuthMiddleware
|
||||
from .models import GenericTeamToken
|
||||
from .models import GenericTeamToken, Group, Permission
|
||||
from .utils import get_kfet_generic_user
|
||||
from .views import GenericLoginView
|
||||
|
||||
|
||||
##
|
||||
# Forms
|
||||
# Forms and form fields
|
||||
##
|
||||
|
||||
|
||||
class CorePermissionsFieldTests(TestCase):
|
||||
|
||||
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):
|
||||
from .forms import GroupForm
|
||||
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.
|
||||
"""
|
||||
from .forms import GroupForm
|
||||
self.kf_group.permissions.add(self.ot_perm)
|
||||
|
||||
selected = [self.kf_perm1, self.kf_perm2]
|
||||
|
||||
data = {
|
||||
'name': 'A Group',
|
||||
'permissions': [str(p.pk) for p 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):
|
||||
"""Test suite for UserGroupForm."""
|
||||
|
||||
def setUp(self):
|
||||
# create user
|
||||
self.user = User.objects.create(username="foo", password="foo")
|
||||
|
||||
# create some K-Fêt groups
|
||||
prefix_name = "K-Fêt "
|
||||
names = ["Group 1", "Group 2", "Group 3"]
|
||||
self.kfet_groups = [
|
||||
Group.objects.create(name=prefix_name+name)
|
||||
for name in names
|
||||
]
|
||||
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')
|
||||
|
||||
# create a non-K-Fêt group
|
||||
self.other_group = Group.objects.create(name="Other 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,
|
||||
)
|
||||
self.ot_group = DjangoGroup.objects.create(name="OT Group")
|
||||
|
||||
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.
|
||||
"""
|
||||
from .forms import UserGroupForm
|
||||
# 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
|
||||
data = {
|
||||
'groups': [group.pk for group in self.kfet_groups],
|
||||
}
|
||||
form = UserGroupForm(data, instance=user)
|
||||
|
||||
form.is_valid()
|
||||
selected = [self.kf_group1, self.kf_group2]
|
||||
data = {'groups': [str(g.pk) for g in selected]}
|
||||
form = UserGroupForm(data, instance=self.user)
|
||||
form.save()
|
||||
|
||||
transform = lambda g: g.pk
|
||||
self.assertQuerysetEqual(
|
||||
user.groups.all(),
|
||||
[repr(g) for g in [self.other_group] + self.kfet_groups],
|
||||
ordered=False,
|
||||
self.user.groups.all(),
|
||||
map(transform, [self.ot_group] + selected),
|
||||
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):
|
||||
|
||||
def test_exists(self):
|
||||
|
@ -139,6 +260,10 @@ class GenericLoginViewTests(TestCase):
|
|||
patcher_messages.start()
|
||||
self.addCleanup(patcher_messages.stop)
|
||||
|
||||
# Prevent querying the database too soon.
|
||||
from .views import GenericLoginView
|
||||
self.view_cls = GenericLoginView
|
||||
|
||||
user_acc = Account(trigramme='000')
|
||||
user_acc.save({'username': 'user'})
|
||||
self.user = user_acc.user
|
||||
|
@ -230,14 +355,14 @@ class GenericLoginViewTests(TestCase):
|
|||
"""
|
||||
token = GenericTeamToken.objects.create(token='valid')
|
||||
self._set_signed_cookie(
|
||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME, 'valid')
|
||||
self.client, self.view_cls.TOKEN_COOKIE_NAME, 'valid')
|
||||
|
||||
r = self.client.get(self.url)
|
||||
|
||||
self.assertRedirects(r, reverse('kfet.kpsul'))
|
||||
self.assertEqual(r.wsgi_request.user, self.generic_user)
|
||||
self._is_cookie_deleted(
|
||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME)
|
||||
self.client, self.view_cls.TOKEN_COOKIE_NAME)
|
||||
with self.assertRaises(GenericTeamToken.DoesNotExist):
|
||||
token.refresh_from_db()
|
||||
|
||||
|
@ -246,14 +371,14 @@ class GenericLoginViewTests(TestCase):
|
|||
If token is invalid, delete it and try again.
|
||||
"""
|
||||
self._set_signed_cookie(
|
||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME, 'invalid')
|
||||
self.client, self.view_cls.TOKEN_COOKIE_NAME, 'invalid')
|
||||
|
||||
r = self.client.get(self.url)
|
||||
|
||||
self.assertRedirects(r, self.url, fetch_redirect_response=False)
|
||||
self.assertEqual(r.wsgi_request.user, AnonymousUser())
|
||||
self._is_cookie_deleted(
|
||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME)
|
||||
self.client, self.view_cls.TOKEN_COOKIE_NAME)
|
||||
|
||||
def test_flow_ok(self):
|
||||
"""
|
||||
|
@ -269,6 +394,154 @@ class GenericLoginViewTests(TestCase):
|
|||
self.assertEqual(r.wsgi_request.path, '/k-fet/')
|
||||
|
||||
|
||||
class GroupListViewTests(ViewTestCaseMixin, TestCase):
|
||||
url_name = 'kfet.group'
|
||||
url_expected = '/k-fet/groupes/'
|
||||
|
||||
auth_user = 'team1'
|
||||
auth_forbidden = [None, 'user', 'team']
|
||||
|
||||
def get_users_extra(self):
|
||||
return {
|
||||
'team1': create_team('team1', '101', perms=[
|
||||
'kfetauth.view_group']),
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group1 = Group.objects.create(name='K-Fêt - Group1')
|
||||
self.group2 = Group.objects.create(name='K-Fêt - Group2')
|
||||
|
||||
def test_ok(self):
|
||||
r = self.client.get(self.url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
self.assertQuerysetEqual(
|
||||
r.context['groups'],
|
||||
map(repr, [self.group1, self.group2]),
|
||||
ordered=False,
|
||||
)
|
||||
|
||||
|
||||
class GroupCreateViewTests(ViewTestCaseMixin, TestCase):
|
||||
url_name = 'kfet.group.create'
|
||||
url_expected = '/k-fet/groupes/nouveau/'
|
||||
|
||||
http_methods = ['GET', 'POST']
|
||||
|
||||
auth_user = 'team1'
|
||||
auth_forbidden = [None, 'user', 'team']
|
||||
|
||||
def get_users_extra(self):
|
||||
return {
|
||||
'team1': create_team('team1', '101', perms=['kfetauth.add_group']),
|
||||
}
|
||||
|
||||
@property
|
||||
def post_data(self):
|
||||
return {
|
||||
'name': 'The Group',
|
||||
'permissions': [
|
||||
str(self.perms['kfet.is_team'].pk),
|
||||
str(self.perms['kfetauth.add_group'].pk),
|
||||
],
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.perms = get_perms(
|
||||
'kfet.is_team',
|
||||
'kfetauth.add_group',
|
||||
)
|
||||
|
||||
def test_get_ok(self):
|
||||
r = self.client.get(self.url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_post_ok(self):
|
||||
r = self.client.post(self.url, self.post_data)
|
||||
self.assertRedirects(
|
||||
r, reverse('kfet.group'),
|
||||
fetch_redirect_response=False, # Requires `kfetauth.view_group`.
|
||||
)
|
||||
|
||||
group = Group.objects.get(name='The Group')
|
||||
|
||||
self.assertQuerysetEqual(
|
||||
group.permissions.all(),
|
||||
map(repr, [
|
||||
self.perms['kfet.is_team'],
|
||||
self.perms['kfetauth.add_group'],
|
||||
]),
|
||||
ordered=False,
|
||||
)
|
||||
|
||||
|
||||
class GroupUpdateViewTests(ViewTestCaseMixin, TestCase):
|
||||
url_name = 'kfet.group.update'
|
||||
|
||||
http_methods = ['GET', 'POST']
|
||||
|
||||
auth_user = 'team1'
|
||||
auth_forbidden = [None, 'user', 'team']
|
||||
|
||||
@property
|
||||
def url_kwargs(self):
|
||||
return {'pk': self.group.pk}
|
||||
|
||||
@property
|
||||
def url_expected(self):
|
||||
return '/k-fet/groupes/{}/edition/'.format(self.group.pk)
|
||||
|
||||
def get_users_extra(self):
|
||||
return {
|
||||
'team1': create_team('team1', '101', perms=[
|
||||
'kfetauth.change_group']),
|
||||
}
|
||||
|
||||
@property
|
||||
def post_data(self):
|
||||
return {
|
||||
'name': 'The Group',
|
||||
'permissions': [
|
||||
str(self.perms['kfet.is_team'].pk),
|
||||
str(self.perms['kfetauth.change_group'].pk),
|
||||
],
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.perms = get_perms(
|
||||
'kfet.is_team',
|
||||
'kfetauth.change_group',
|
||||
)
|
||||
self.group = Group.objects.create(name='K-Fêt - Group')
|
||||
self.group.permissions = self.perms.values()
|
||||
|
||||
def test_get_ok(self):
|
||||
r = self.client.get(self.url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_post_ok(self):
|
||||
r = self.client.post(self.url, self.post_data)
|
||||
self.assertRedirects(
|
||||
r, reverse('kfet.group'),
|
||||
fetch_redirect_response=False, # Requires `kfetauth.view_group`.
|
||||
)
|
||||
|
||||
self.group.refresh_from_db()
|
||||
|
||||
self.assertEqual(self.group.name, 'The Group')
|
||||
self.assertQuerysetEqual(
|
||||
self.group.permissions.all(),
|
||||
map(repr, [
|
||||
self.perms['kfet.is_team'],
|
||||
self.perms['kfetauth.change_group'],
|
||||
]),
|
||||
ordered=False,
|
||||
)
|
||||
|
||||
|
||||
##
|
||||
# Temporary authentication
|
||||
#
|
||||
|
|
18
kfet/auth/urls.py
Normal file
18
kfet/auth/urls.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
from django.conf.urls import include, url
|
||||
|
||||
from . import views
|
||||
|
||||
group_patterns = [
|
||||
url(r'^$', views.group_index,
|
||||
name='kfet.group'),
|
||||
url(r'^nouveau/$', views.group_create,
|
||||
name='kfet.group.create'),
|
||||
url('^(?P<pk>\d+)/edition/$', views.group_update,
|
||||
name='kfet.group.update'),
|
||||
]
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^groupes/', include(group_patterns)),
|
||||
url(r'^login/generic', views.login_generic,
|
||||
name='kfet.login.generic'),
|
||||
]
|
|
@ -1,21 +1,22 @@
|
|||
from django.contrib import messages
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth import authenticate, get_user_model, login
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db.models import Prefetch
|
||||
from django.http import QueryDict
|
||||
from django.shortcuts import redirect, render
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import View
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.views.generic.edit import CreateView, UpdateView
|
||||
|
||||
from kfet.cms.views import get_kfetcms_group_formview_extra
|
||||
|
||||
from .forms import GroupForm
|
||||
from .models import GenericTeamToken
|
||||
from .models import GenericTeamToken, Group
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class GenericLoginView(View):
|
||||
|
@ -104,33 +105,105 @@ class GenericLoginView(View):
|
|||
login_generic = GenericLoginView.as_view()
|
||||
|
||||
|
||||
@permission_required('kfet.manage_perms')
|
||||
def account_group(request):
|
||||
@permission_required('kfetauth.view_group')
|
||||
def group_index(request):
|
||||
user_pre = Prefetch(
|
||||
'user_set',
|
||||
queryset=User.objects.select_related('profile__account_kfet'),
|
||||
)
|
||||
groups = (
|
||||
Group.objects
|
||||
.filter(name__icontains='K-Fêt')
|
||||
.prefetch_related('permissions', user_pre)
|
||||
)
|
||||
return render(request, 'kfet/account_group.html', {
|
||||
groups = Group.objects.prefetch_related(user_pre)
|
||||
return render(request, 'kfet/group_list.html', {
|
||||
'groups': groups,
|
||||
})
|
||||
|
||||
|
||||
class AccountGroupCreate(SuccessMessageMixin, CreateView):
|
||||
model = Group
|
||||
template_name = 'kfet/account_group_form.html'
|
||||
form_class = GroupForm
|
||||
success_message = 'Nouveau groupe : %(name)s'
|
||||
success_url = reverse_lazy('kfet.account.group')
|
||||
_group_formview_extras = None
|
||||
|
||||
|
||||
class AccountGroupUpdate(SuccessMessageMixin, 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_url = reverse_lazy('kfet.account.group')
|
||||
def get_group_formview_extras():
|
||||
global _group_formview_extras
|
||||
|
||||
if _group_formview_extras is None:
|
||||
_group_formview_extras = []
|
||||
|
||||
# Register additional group forms below.
|
||||
_group_formview_extras.append(
|
||||
get_kfetcms_group_formview_extra())
|
||||
|
||||
return [extra.copy() for extra in _group_formview_extras]
|
||||
|
||||
|
||||
@permission_required('kfetauth.add_group')
|
||||
def group_create(request):
|
||||
group = Group()
|
||||
|
||||
extras = get_group_formview_extras()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = GroupForm(request.POST, instance=group)
|
||||
for extra in extras:
|
||||
extra['forms'] = [
|
||||
form_cls(
|
||||
request.POST, request.FILES, instance=group, **form_kwargs)
|
||||
for form_cls, form_kwargs in extra['form_classes']
|
||||
]
|
||||
extra_forms = sum((extra['forms'] for extra in extras), [])
|
||||
|
||||
if form.is_valid() and all(form.is_valid() for form in extra_forms):
|
||||
group = form.save()
|
||||
for extra_form in extra_forms:
|
||||
extra_form.save()
|
||||
|
||||
messages.success(request, _("Nouveau groupe : {}").format(group))
|
||||
|
||||
return redirect('kfet.group')
|
||||
else:
|
||||
form = GroupForm(instance=group)
|
||||
for extra in extras:
|
||||
extra['forms'] = [
|
||||
form_cls(instance=group, **form_kwargs)
|
||||
for form_cls, form_kwargs in extra['form_classes']
|
||||
]
|
||||
|
||||
return render(request, 'kfet/group_form.html', {
|
||||
'form': form,
|
||||
'extras': extras,
|
||||
})
|
||||
|
||||
|
||||
@permission_required('kfetauth.change_group')
|
||||
def group_update(request, pk):
|
||||
group = get_object_or_404(Group, pk=pk)
|
||||
|
||||
extras = get_group_formview_extras()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = GroupForm(request.POST, instance=group)
|
||||
for extra in extras:
|
||||
extra['forms'] = [
|
||||
form_cls(
|
||||
request.POST, request.FILES, instance=group, **form_kwargs)
|
||||
for form_cls, form_kwargs in extra['form_classes']
|
||||
]
|
||||
extra_forms = sum((extra['forms'] for extra in extras), [])
|
||||
|
||||
if form.is_valid() and all(form.is_valid() for form in extra_forms):
|
||||
group = form.save()
|
||||
for extra_form in extra_forms:
|
||||
extra_form.save()
|
||||
|
||||
messages.success(request, _("Groupe modifié : {}").format(group))
|
||||
|
||||
return redirect('kfet.group')
|
||||
else:
|
||||
form = GroupForm(instance=group)
|
||||
for extra in extras:
|
||||
extra['forms'] = [
|
||||
form_cls(instance=group, **form_kwargs)
|
||||
for form_cls, form_kwargs in extra['form_classes']
|
||||
]
|
||||
|
||||
return render(request, 'kfet/group_form.html', {
|
||||
'form': form,
|
||||
'extras': extras,
|
||||
})
|
||||
|
|
196
kfet/cms/forms.py
Normal file
196
kfet/cms/forms.py
Normal file
|
@ -0,0 +1,196 @@
|
|||
from django import forms
|
||||
from django.contrib.auth.models import Group
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.wagtailusers.forms import GroupPagePermissionFormSet
|
||||
|
||||
from utils.forms import KeepUnselectableModelFormMixin
|
||||
|
||||
from kfet.auth.fields import BasePermissionsField
|
||||
from kfet.auth.models import Permission
|
||||
|
||||
|
||||
class CmsGroupForm(KeepUnselectableModelFormMixin, forms.ModelForm):
|
||||
"""
|
||||
Allow to change permissions related to the cms of a `Group` object.
|
||||
"""
|
||||
access_admin = forms.BooleanField(
|
||||
label=_("Accès à l'administration"),
|
||||
required=False,
|
||||
help_text=_(
|
||||
"<b>Attention :</b> Aucun paramétrage effectué dans cette section "
|
||||
"n'aura d'effet si cette case est décochée."
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = ()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
group = self.instance
|
||||
|
||||
if group.pk is not None:
|
||||
self.fields['access_admin'].initial = (
|
||||
group.permissions.filter(pk=self.access_admin_perm.pk).exists()
|
||||
)
|
||||
|
||||
def save(self):
|
||||
group = super().save()
|
||||
|
||||
if self.cleaned_data['access_admin']:
|
||||
group.permissions.add(self.access_admin_perm)
|
||||
else:
|
||||
group.permissions.remove(self.access_admin_perm)
|
||||
|
||||
return group
|
||||
|
||||
@cached_property
|
||||
def access_admin_perm(self):
|
||||
return Permission.objects.get(
|
||||
content_type__app_label='wagtailadmin',
|
||||
codename='access_admin',
|
||||
)
|
||||
|
||||
|
||||
class SnippetsCmsGroupForm(KeepUnselectableModelFormMixin, forms.ModelForm):
|
||||
permissions = BasePermissionsField(
|
||||
label='', required=False,
|
||||
queryset=Permission.kfetcms.all(),
|
||||
)
|
||||
|
||||
keep_unselectable_fields = ['permissions']
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = ('permissions',)
|
||||
|
||||
|
||||
def prepare_page_permissions_formset(pages):
|
||||
"""
|
||||
Create a new formset from base `GroupPagePermissionFormSet` Wagtail
|
||||
formset.
|
||||
|
||||
- The choices of `pages` field of forms are limited to `pages`.
|
||||
- If there is no initial data, add an initial with the first collection
|
||||
selected.
|
||||
- Make 'as_admin_panel' use a custom template.
|
||||
|
||||
Arguments
|
||||
pages (iterable of `Page`)
|
||||
|
||||
Returns
|
||||
A formset to select group permissions of pages.
|
||||
|
||||
"""
|
||||
class RestrictedFormSet(GroupPagePermissionFormSet):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Remove forms cache, as theirs options are changed.
|
||||
del self.forms
|
||||
|
||||
# 'initial' must be setup before accessing 'forms'.
|
||||
|
||||
self.initial = list(
|
||||
filter(lambda i: i['page'] in pages, self.initial))
|
||||
|
||||
# This is just little kindness.
|
||||
if not self.initial and pages:
|
||||
self.initial = [{'page': pages[0]}]
|
||||
|
||||
for form in self.forms:
|
||||
self.customize_form(form)
|
||||
|
||||
@property
|
||||
def empty_form(self):
|
||||
form = super().empty_form
|
||||
self.customize_form(form)
|
||||
return form
|
||||
|
||||
def customize_form(self, form):
|
||||
# Widget must be setup before queryset.
|
||||
form.fields['page'].widget = forms.Select()
|
||||
form.fields['page'].queryset = pages
|
||||
# Force use of `CheckboxInput` for `DELETE` field, as `HiddenInput`
|
||||
# is not compatible with `django-formset-js`.
|
||||
form.fields['DELETE'].widget = forms.CheckboxInput()
|
||||
|
||||
def as_admin_panel(self):
|
||||
# http://docs.wagtail.io/en/latest/reference/hooks.html#register-group-permission-panel
|
||||
template = 'kfet/permissions/page_permissions_formset.html'
|
||||
return render_to_string(template, {'formset': self})
|
||||
|
||||
return RestrictedFormSet
|
||||
|
||||
|
||||
def prepare_collection_member_permissions_formset(formset_cls, collections):
|
||||
"""
|
||||
Create a new formset from base `formset_cls`.
|
||||
|
||||
- The choices of `collections` field of forms produced by `formset_cls` are
|
||||
limited to `collections`.
|
||||
- If there is no initial data, add an initial with the first collection
|
||||
selected.
|
||||
- Make 'as_admin_panel' use a custom template.
|
||||
|
||||
Arguments
|
||||
formset_cls (subclass of
|
||||
`wagtail.wagtailcore.forms`
|
||||
`.BaseGroupCollectionMemberPermissionFormSet`
|
||||
):
|
||||
Formset to select group permissions of a collection member model.
|
||||
It should be rerieved from the `group_permission_panel` Wagtail
|
||||
hook. This includes the `Document` and `Image` models of Wagtail.
|
||||
collections (iterable of `wagtail.wagtailcore.models.Collection`)
|
||||
|
||||
Returns
|
||||
A formset to select group permissions of collection-related models.
|
||||
|
||||
"""
|
||||
class RestrictedFormSet(formset_cls):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Remove forms cache, as theirs options are changed.
|
||||
del self.forms
|
||||
|
||||
# 'initial' must be setup before accessing 'forms'.
|
||||
|
||||
self.initial = list(
|
||||
filter(lambda i: i['collection'] in collections, self.initial))
|
||||
|
||||
# This is just little kindness.
|
||||
if not self.initial and collections:
|
||||
self.initial = [{'collection': collections[0]}]
|
||||
|
||||
for form in self.forms:
|
||||
self.customize_form(form)
|
||||
|
||||
@property
|
||||
def empty_form(self):
|
||||
form = super().empty_form
|
||||
self.customize_form(form)
|
||||
return form
|
||||
|
||||
def customize_form(self, form):
|
||||
form.fields['collection'].queryset = collections
|
||||
# Force use of `CheckboxInput` for `DELETE` field, as `HiddenInput`
|
||||
# is not compatible with `django-formset-js`.
|
||||
form.fields['DELETE'].widget = forms.CheckboxInput()
|
||||
|
||||
def as_admin_panel(self):
|
||||
template = (
|
||||
'kfet/permissions/collection_member_permissions_formset.html')
|
||||
model = self.permission_queryset[0].content_type.model_class()
|
||||
return render_to_string(template, {
|
||||
'formset': self,
|
||||
'model_name': model._meta.verbose_name_plural,
|
||||
})
|
||||
|
||||
return RestrictedFormSet
|
|
@ -1,8 +1,14 @@
|
|||
from django.contrib.auth.models import Group
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import Q
|
||||
|
||||
from wagtail.wagtailcore.models import Page, Site
|
||||
from wagtail.wagtailcore.models import (
|
||||
GroupCollectionPermission, GroupPagePermission, Page, Site,
|
||||
)
|
||||
|
||||
from kfet.models import Group, Permission
|
||||
|
||||
from ...utils import get_kfet_root_collection, get_kfet_root_page
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
@ -33,3 +39,169 @@ class Command(BaseCommand):
|
|||
# Par défaut, il s'agit d'une copie du site K-Fêt (17-05)
|
||||
|
||||
call_command('loaddata', options['file'])
|
||||
|
||||
# Si les groupes K-Fêt existent, certaines permissions du CMS leur sont
|
||||
# données.
|
||||
|
||||
try:
|
||||
group_chef = Group.objects.get(name='K-Fêt César')
|
||||
except Group.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
self.add_admin_access(group_chef)
|
||||
|
||||
try:
|
||||
group_boy = Group.objects.get(name='K-Fêt Légionnaire')
|
||||
except Group.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
self.add_staff_access(group_boy)
|
||||
|
||||
def add_admin_access(self, group):
|
||||
"""
|
||||
Add all cms-related permissions to `group`.
|
||||
|
||||
Explicitly, permissions added are:
|
||||
- access admin of Wagtail,
|
||||
- all permissions for the kfet root page (by inheritance, this applies
|
||||
to all its descendants),
|
||||
- all permissions on the MemberTeam snippet,
|
||||
- add/change documents,
|
||||
- add/change images.
|
||||
|
||||
To avoid bugs related to permissions not added by this method, it is
|
||||
guaranteed the group has more or the same permissions than at the
|
||||
beginning.
|
||||
"""
|
||||
group.permissions.add(
|
||||
Permission.objects.get(
|
||||
content_type__app_label='wagtailadmin',
|
||||
codename='access_admin',
|
||||
),
|
||||
# Snippets permissions
|
||||
*Permission.kfetcms.all(),
|
||||
)
|
||||
|
||||
# Page permissions: set all for the kfet root page.
|
||||
|
||||
root_page = get_kfet_root_page()
|
||||
|
||||
p_types = ('add', 'edit', 'publish', 'bulk_delete', 'lock')
|
||||
|
||||
GroupPagePermission.objects.filter(
|
||||
group=group, page=root_page,
|
||||
permission_type__in=p_types,
|
||||
).delete()
|
||||
|
||||
GroupPagePermission.objects.bulk_create([
|
||||
GroupPagePermission(
|
||||
group=group, page=root_page,
|
||||
permission_type=p_type,
|
||||
)
|
||||
for p_type in p_types
|
||||
])
|
||||
|
||||
# Collection-based permissions: set all for the kfet root collection
|
||||
# for each known collection-based model (docs, images).
|
||||
|
||||
root_collection = get_kfet_root_collection()
|
||||
|
||||
collection_perms = Permission.objects.filter(
|
||||
Q(
|
||||
content_type__app_label='wagtaildocs',
|
||||
codename__in=['add_document', 'change_document'],
|
||||
) |
|
||||
Q(
|
||||
content_type__app_label='wagtailimages',
|
||||
codename__in=['add_image', 'change_image'],
|
||||
)
|
||||
)
|
||||
|
||||
GroupCollectionPermission.objects.filter(
|
||||
group=group, collection=root_collection,
|
||||
permission__in=collection_perms,
|
||||
).delete()
|
||||
|
||||
GroupCollectionPermission.objects.bulk_create([
|
||||
GroupCollectionPermission(
|
||||
group=group, collection=root_collection,
|
||||
permission=perm,
|
||||
)
|
||||
for perm in collection_perms
|
||||
])
|
||||
|
||||
def add_staff_access(self, group):
|
||||
"""
|
||||
Add a subset of cms-related permissions to `group`.
|
||||
|
||||
Permissions added are:
|
||||
- access admin of Wagtail,
|
||||
- add/edit permissions for the kfet root page (by inheritance, this
|
||||
applies to all its descendants),
|
||||
- all permissions on the MemberTeam snippet,
|
||||
- add/change own documents,
|
||||
- add/change own images.
|
||||
|
||||
Because 'publish' page permission type is not given, group members can
|
||||
only create or change pages as drafts.
|
||||
|
||||
To avoid bugs related to permissions not added by this method, it is
|
||||
guaranteed the group has more or the same permissions than at the
|
||||
beginning.
|
||||
"""
|
||||
group.permissions.add(
|
||||
Permission.objects.get(
|
||||
content_type__app_label='wagtailadmin',
|
||||
codename='access_admin',
|
||||
),
|
||||
*Permission.kfetcms.filter(codename__in=[
|
||||
'add_memberteam',
|
||||
]),
|
||||
)
|
||||
|
||||
# Give 'safe' operations permissions for the kfet root page.
|
||||
|
||||
root_page = get_kfet_root_page()
|
||||
|
||||
p_types = ('add', 'edit')
|
||||
|
||||
GroupPagePermission.objects.filter(
|
||||
group=group, page=root_page,
|
||||
permission_type__in=p_types,
|
||||
).delete()
|
||||
|
||||
GroupPagePermission.objects.bulk_create([
|
||||
GroupPagePermission(
|
||||
group=group, page=root_page,
|
||||
permission_type=p_type,
|
||||
)
|
||||
for p_type in p_types
|
||||
])
|
||||
|
||||
# Give 'safe' operations permissions for the collection-based models.
|
||||
|
||||
root_collection = get_kfet_root_collection()
|
||||
|
||||
collection_perms = Permission.objects.filter(
|
||||
Q(
|
||||
content_type__app_label='wagtaildocs',
|
||||
codename__in=['add_document'],
|
||||
) |
|
||||
Q(
|
||||
content_type__app_label='wagtailimages',
|
||||
codename__in=['add_image'],
|
||||
)
|
||||
)
|
||||
|
||||
GroupCollectionPermission.objects.filter(
|
||||
group=group, collection=root_collection,
|
||||
permission__in=collection_perms,
|
||||
).delete()
|
||||
|
||||
GroupCollectionPermission.objects.bulk_create([
|
||||
GroupCollectionPermission(
|
||||
group=group, collection=root_collection,
|
||||
permission=perm,
|
||||
)
|
||||
for perm in collection_perms
|
||||
])
|
||||
|
|
|
@ -11,7 +11,7 @@ from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
|
|||
from wagtail.wagtailsnippets.blocks import SnippetChooserBlock
|
||||
from wagtail.wagtailsnippets.models import register_snippet
|
||||
|
||||
from kfet.cms.context_processors import get_articles
|
||||
from .utils import get_page_model_names
|
||||
|
||||
|
||||
@register_snippet
|
||||
|
@ -60,6 +60,7 @@ class MenuBlock(blocks.StaticBlock):
|
|||
template = 'kfetcms/block_menu.html'
|
||||
|
||||
def get_context(self, *args, **kwargs):
|
||||
from .context_processors import get_articles
|
||||
context = super().get_context(*args, **kwargs)
|
||||
context.update(get_articles())
|
||||
return context
|
||||
|
@ -172,3 +173,18 @@ class KFetPage(Page):
|
|||
page.seo_title = page.title
|
||||
|
||||
return context
|
||||
|
||||
|
||||
##
|
||||
# Helpers for kfetauth app
|
||||
##
|
||||
|
||||
|
||||
class CmsPermissionManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return (
|
||||
super().get_queryset()
|
||||
.filter(content_type__app_label='kfetcms')
|
||||
# Permissions of Page-based models are unused.
|
||||
.exclude(content_type__model__in=get_page_model_names())
|
||||
)
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
{% load widget_tweaks %}
|
||||
{% load kfet_extras %}
|
||||
|
||||
<div class="form-inline" data-formset-form>
|
||||
{{ form.non_field_errors }}
|
||||
{% if form.DELETE %}{% include "kfet/formset_form_actions.html" %}{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.collection|add_class:"form-control input-sm" }}
|
||||
</div>
|
||||
{% for option in form.permissions %}
|
||||
<div class="checkbox">
|
||||
{% with p_type=formset.permission_types|get:forloop.counter0 %}
|
||||
{# p_type format: (identifier, short_label, long_label) #}
|
||||
<label title="{{ p_type.2 }}">
|
||||
{{ option.tag }} {{ p_type.1}}
|
||||
</label>
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
|
@ -0,0 +1,42 @@
|
|||
{% load i18n %}
|
||||
{% load formset_tags %}
|
||||
|
||||
<div data-formset-prefix="{{ formset.prefix }}">
|
||||
|
||||
<div class="h5">
|
||||
<button type="button" class="pull-right btn btn-default btn-sm" data-formset-add>
|
||||
{% trans "Ajouter pour une autre collection" %}
|
||||
</button>
|
||||
<b>{{ model_name|title }}</b>
|
||||
</div>
|
||||
|
||||
{{ formset.management_form }}
|
||||
|
||||
{% if formset.non_form_errors %}
|
||||
<span class="help-block">{{ formset.non_form_errors }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% with form_tpl="kfet/permissions/collection_member_permissions_form.html" %}
|
||||
|
||||
<div data-formset-body>
|
||||
{% for form in formset %}
|
||||
{% include form_tpl with form=form formset=formset only %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<script type="form-template" data-formset-empty-form>
|
||||
{% escapescript %}
|
||||
{% include form_tpl with form=formset.empty_form formset=formset only %}
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
|
||||
{% endwith %}
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$( function() {
|
||||
let $formset_container = $('[data-formset-prefix={{ formset.prefix }}]');
|
||||
$formset_container.formset();
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,20 @@
|
|||
{% load widget_tweaks %}
|
||||
{% load kfet_extras %}
|
||||
|
||||
<div class="form-inline" data-formset-form>
|
||||
{{ form.non_field_errors }}
|
||||
{% if form.DELETE %}{% include "kfet/formset_form_actions.html" %}{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.page|add_class:"form-control input-sm" }}
|
||||
</div>
|
||||
{% for option in form.permission_types %}
|
||||
<div class="checkbox">
|
||||
{% with p_type=formset.permission_types|get:forloop.counter0 %}
|
||||
{# p_type format: (identifier, short_label, long_label) #}
|
||||
<label title="{{ p_type.2 }}">
|
||||
{{ option.tag }} {{ p_type.1 }}
|
||||
</label>
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
|
@ -0,0 +1,42 @@
|
|||
{% load i18n %}
|
||||
{% load formset_tags %}
|
||||
|
||||
<div data-formset-prefix="{{ formset.prefix }}">
|
||||
|
||||
<div class="h5">
|
||||
<button type="button" class="pull-right btn btn-default btn-sm" data-formset-add>
|
||||
{% trans "Ajouter pour une autre page" %}
|
||||
</button>
|
||||
<b>{% trans "Pages" %}</b>
|
||||
</div>
|
||||
|
||||
{{ formset.management_form }}
|
||||
|
||||
{% if formset.non_form_errors %}
|
||||
<span class="help-block">{{ formset.non_form_errors }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% with form_tpl="kfet/permissions/page_permissions_form.html" %}
|
||||
|
||||
<div data-formset-body>
|
||||
{% for form in formset %}
|
||||
{% include form_tpl with form=form formset=formset only %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<script type="form-template" data-formset-empty-form>
|
||||
{% escapescript %}
|
||||
{% include form_tpl with form=formset.empty_form formset=formset only %}
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
|
||||
{% endwith %}
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$( function() {
|
||||
let $formset_container = $('[data-formset-prefix={{ formset.prefix }}]');
|
||||
$formset_container.formset();
|
||||
});
|
||||
</script>
|
35
kfet/cms/utils.py
Normal file
35
kfet/cms/utils.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
from django.apps import apps
|
||||
|
||||
from wagtail.wagtailcore.models import Collection, Page
|
||||
|
||||
|
||||
def get_kfet_root_page():
|
||||
"""
|
||||
Returns the K-Fêt root page, or 'None' if it does not exist.
|
||||
"""
|
||||
from .models import KFetPage
|
||||
return KFetPage.objects.first()
|
||||
|
||||
|
||||
def get_kfet_root_collection():
|
||||
"""
|
||||
Returns the K-Fêt root collection, or 'None' if it does not exist.
|
||||
"""
|
||||
return Collection.objects.filter(name='K-Fêt').first()
|
||||
|
||||
|
||||
def get_page_model_names():
|
||||
"""
|
||||
Returns all model names of `Page` subclasses.
|
||||
|
||||
This uses the django apps registry (instead of `ContentType.objects.all()`)
|
||||
in order to be usuable even before migrations are applied. E.g. this can be
|
||||
used in `Field.__init__`.
|
||||
|
||||
Note these model names are the same in `model` attribute of `ContentType`
|
||||
objects.
|
||||
"""
|
||||
return [
|
||||
model._meta.model_name
|
||||
for model in apps.get_models() if issubclass(model, Page)
|
||||
]
|
74
kfet/cms/views.py
Normal file
74
kfet/cms/views.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.wagtailadmin.forms import (
|
||||
BaseGroupCollectionMemberPermissionFormSet,
|
||||
)
|
||||
from wagtail.wagtailcore import hooks
|
||||
from wagtail.wagtailcore.models import Collection, Page
|
||||
|
||||
from .utils import get_kfet_root_collection, get_kfet_root_page
|
||||
|
||||
|
||||
def get_kfetcms_group_formview_extra():
|
||||
# Prevents querying the database too soon.
|
||||
from .forms import (
|
||||
CmsGroupForm, SnippetsCmsGroupForm, prepare_page_permissions_formset,
|
||||
prepare_collection_member_permissions_formset,
|
||||
)
|
||||
forms = []
|
||||
|
||||
# Misc cms-related permissions.
|
||||
forms.append((CmsGroupForm, {'prefix': 'kfetcms'}))
|
||||
|
||||
# Snippets permissions.
|
||||
forms.append((SnippetsCmsGroupForm, {'prefix': 'kfetcms-snippets'}))
|
||||
|
||||
# Setup PagePermissionsFormSet for kfet root page descendants only.
|
||||
|
||||
root_page = get_kfet_root_page()
|
||||
|
||||
if root_page:
|
||||
pages = Page.objects.descendant_of(root_page, inclusive=True)
|
||||
forms.append(
|
||||
(prepare_page_permissions_formset(pages=pages), {})
|
||||
)
|
||||
|
||||
# Setup CollectionPermissions for kfet root collection descendants only.
|
||||
|
||||
root_collection = get_kfet_root_collection()
|
||||
|
||||
if root_collection:
|
||||
collections = Collection.objects.descendant_of(
|
||||
root_collection, inclusive=True)
|
||||
|
||||
# Retrieve forms based on CollectionMemberPermissionFormSet displayed
|
||||
# by Wagtail admin site.
|
||||
# http://docs.wagtail.io/en/stable/reference/hooks.html#register-group-permission-panel
|
||||
collectionmember_form_classes = []
|
||||
for fn in hooks.get_hooks('register_group_permission_panel'):
|
||||
form_cls = fn()
|
||||
if issubclass(
|
||||
form_cls,
|
||||
BaseGroupCollectionMemberPermissionFormSet
|
||||
):
|
||||
collectionmember_form_classes.append(form_cls)
|
||||
|
||||
# Apply choices limit.
|
||||
for form_cls in collectionmember_form_classes:
|
||||
forms.append((
|
||||
prepare_collection_member_permissions_formset(
|
||||
form_cls, collections=collections),
|
||||
{},
|
||||
))
|
||||
|
||||
# The 'extra' definition of kfetcms.
|
||||
|
||||
extra = {
|
||||
'title': _("Site"),
|
||||
'description': _(
|
||||
"Permissions liées aux pages à contenu libre."
|
||||
),
|
||||
'form_classes': forms,
|
||||
}
|
||||
|
||||
return extra
|
|
@ -1,9 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
|
||||
from djconfig import config
|
||||
from djconfig.forms import ConfigForm
|
||||
|
||||
from .models import Account
|
||||
|
||||
|
||||
class KFetConfig(object):
|
||||
|
@ -19,8 +25,8 @@ class KFetConfig(object):
|
|||
if key == 'subvention_cof':
|
||||
# Allows accessing to the reduction as a subvention
|
||||
# Other reason: backward compatibility
|
||||
reduction_mult = 1 - self.reduction_cof/100
|
||||
return (1/reduction_mult - 1) * 100
|
||||
reduction_mult = 1 - self.reduction_cof / 100
|
||||
return (1 / reduction_mult - 1) * 100
|
||||
return getattr(config, self._get_dj_key(key))
|
||||
|
||||
def list(self):
|
||||
|
@ -30,8 +36,6 @@ class KFetConfig(object):
|
|||
(key, value) for each configuration entry as list.
|
||||
|
||||
"""
|
||||
# prevent circular imports
|
||||
from kfet.forms import KFetConfigForm
|
||||
return [(field.label, getattr(config, name), )
|
||||
for name, field in KFetConfigForm.base_fields.items()]
|
||||
|
||||
|
@ -46,9 +50,6 @@ class KFetConfig(object):
|
|||
Config entries are updated to given values.
|
||||
|
||||
"""
|
||||
# prevent circular imports
|
||||
from kfet.forms import KFetConfigForm
|
||||
|
||||
# get old config
|
||||
new_cfg = KFetConfigForm().initial
|
||||
# update to new config
|
||||
|
@ -69,3 +70,38 @@ class KFetConfig(object):
|
|||
|
||||
|
||||
kfet_config = KFetConfig()
|
||||
|
||||
|
||||
class KFetConfigForm(ConfigForm):
|
||||
|
||||
kfet_reduction_cof = forms.DecimalField(
|
||||
label='Réduction COF', initial=Decimal('20'),
|
||||
max_digits=6, decimal_places=2,
|
||||
help_text="Réduction, à donner en pourcentage, appliquée lors d'un "
|
||||
"achat par un-e membre du COF sur le montant en euros.",
|
||||
)
|
||||
kfet_addcost_amount = forms.DecimalField(
|
||||
label='Montant de la majoration (en €)', initial=Decimal('0'),
|
||||
required=False,
|
||||
max_digits=6, decimal_places=2,
|
||||
)
|
||||
kfet_addcost_for = forms.ModelChoiceField(
|
||||
label='Destinataire de la majoration', initial=None, required=False,
|
||||
help_text='Laissez vide pour désactiver la majoration.',
|
||||
queryset=(Account.objects
|
||||
.select_related('cofprofile', 'cofprofile__user')
|
||||
.all()),
|
||||
)
|
||||
kfet_overdraft_duration = forms.DurationField(
|
||||
label='Durée du découvert autorisé par défaut',
|
||||
initial=timedelta(days=1),
|
||||
)
|
||||
kfet_overdraft_amount = forms.DecimalField(
|
||||
label='Montant du découvert autorisé par défaut (en €)',
|
||||
initial=Decimal('20'),
|
||||
max_digits=6, decimal_places=2,
|
||||
)
|
||||
kfet_cancel_duration = forms.DurationField(
|
||||
label='Durée pour annuler une commande sans mot de passe',
|
||||
initial=timedelta(minutes=5),
|
||||
)
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
from django import forms
|
||||
|
@ -9,16 +7,12 @@ from django.contrib.auth.models import User
|
|||
from django.forms import modelformset_factory
|
||||
from django.utils import timezone
|
||||
|
||||
from djconfig.forms import ConfigForm
|
||||
|
||||
from kfet.models import (
|
||||
Account, Checkout, Article, OperationGroup, Operation,
|
||||
CheckoutStatement, ArticleCategory, AccountNegative, Transfer,
|
||||
TransferGroup, Supplier)
|
||||
from gestioncof.models import CofProfile
|
||||
|
||||
from .auth.forms import UserGroupForm # noqa
|
||||
|
||||
|
||||
# -----
|
||||
# Widgets
|
||||
|
@ -369,46 +363,6 @@ class AddcostForm(forms.Form):
|
|||
super(AddcostForm, self).clean()
|
||||
|
||||
|
||||
# -----
|
||||
# Settings forms
|
||||
# -----
|
||||
|
||||
|
||||
class KFetConfigForm(ConfigForm):
|
||||
|
||||
kfet_reduction_cof = forms.DecimalField(
|
||||
label='Réduction COF', initial=Decimal('20'),
|
||||
max_digits=6, decimal_places=2,
|
||||
help_text="Réduction, à donner en pourcentage, appliquée lors d'un "
|
||||
"achat par un-e membre du COF sur le montant en euros.",
|
||||
)
|
||||
kfet_addcost_amount = forms.DecimalField(
|
||||
label='Montant de la majoration (en €)', initial=Decimal('0'),
|
||||
required=False,
|
||||
max_digits=6, decimal_places=2,
|
||||
)
|
||||
kfet_addcost_for = forms.ModelChoiceField(
|
||||
label='Destinataire de la majoration', initial=None, required=False,
|
||||
help_text='Laissez vide pour désactiver la majoration.',
|
||||
queryset=(Account.objects
|
||||
.select_related('cofprofile', 'cofprofile__user')
|
||||
.all()),
|
||||
)
|
||||
kfet_overdraft_duration = forms.DurationField(
|
||||
label='Durée du découvert autorisé par défaut',
|
||||
initial=timedelta(days=1),
|
||||
)
|
||||
kfet_overdraft_amount = forms.DecimalField(
|
||||
label='Montant du découvert autorisé par défaut (en €)',
|
||||
initial=Decimal('20'),
|
||||
max_digits=6, decimal_places=2,
|
||||
)
|
||||
kfet_cancel_duration = forms.DurationField(
|
||||
label='Durée pour annuler une commande sans mot de passe',
|
||||
initial=timedelta(minutes=5),
|
||||
)
|
||||
|
||||
|
||||
class FilterHistoryForm(forms.Form):
|
||||
checkouts = forms.ModelMultipleChoiceField(queryset=Checkout.objects.all())
|
||||
accounts = forms.ModelMultipleChoiceField(queryset=Account.objects.all())
|
||||
|
|
|
@ -7,13 +7,15 @@ import random
|
|||
from datetime import timedelta
|
||||
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.models import User, Group, Permission, ContentType
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management import call_command
|
||||
|
||||
from gestioncof.management.base import MyBaseCommand
|
||||
from gestioncof.models import CofProfile
|
||||
from kfet.models import (Account, Checkout, CheckoutStatement, Supplier,
|
||||
SupplierArticle, Article)
|
||||
from kfet.models import (
|
||||
Account, Article, Checkout, CheckoutStatement, Group, Supplier,
|
||||
SupplierArticle, Article,
|
||||
)
|
||||
|
||||
# Où sont stockés les fichiers json
|
||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
||||
|
@ -28,22 +30,13 @@ class Command(MyBaseCommand):
|
|||
# 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_boy = Group(name="K-Fêt Légionnaire")
|
||||
|
||||
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)
|
||||
group_chef.give_admin_access()
|
||||
group_boy.give_staff_access()
|
||||
|
||||
# ---
|
||||
# Comptes
|
||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import unicode_literals
|
|||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
from kfet.forms import KFetConfigForm
|
||||
from kfet.config import KFetConfigForm
|
||||
|
||||
|
||||
def adapt_settings(apps, schema_editor):
|
||||
|
|
189
kfet/migrations/0062_change_models_opts.py
Normal file
189
kfet/migrations/0062_change_models_opts.py
Normal file
|
@ -0,0 +1,189 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('kfet', '0061_add_perms_config'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='account',
|
||||
options={
|
||||
'permissions': (
|
||||
('is_team', "Membre de l'équipe"),
|
||||
('manage_addcosts', 'Gérer les majorations'),
|
||||
(
|
||||
'edit_balance_account',
|
||||
"Modifier la balance d'un compte"
|
||||
),
|
||||
(
|
||||
'change_account_password',
|
||||
"Modifier le mot de passe d'une personne de l'équipe"
|
||||
),
|
||||
(
|
||||
'special_add_account',
|
||||
'Créer un compte avec une balance initiale'
|
||||
),
|
||||
('can_force_close', 'Fermer manuellement la K-Fêt')
|
||||
),
|
||||
'default_permissions': ('add', 'change'),
|
||||
'verbose_name_plural': 'Comptes',
|
||||
'verbose_name': 'Compte',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='accountnegative',
|
||||
options={
|
||||
'default_permissions': ('view', 'change',),
|
||||
'verbose_name_plural': 'Comptes en négatif',
|
||||
'verbose_name': 'Compte en négatif',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='article',
|
||||
options={
|
||||
'default_permissions': ('add', 'change'),
|
||||
'verbose_name_plural': 'Articles',
|
||||
'verbose_name': 'Article',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='articlecategory',
|
||||
options={
|
||||
'default_permissions': ('change',),
|
||||
'verbose_name_plural': "Catégories d'articles",
|
||||
'verbose_name': "Catégorie d'articles",
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='articlerule',
|
||||
options={
|
||||
'default_permissions': (),
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='checkout',
|
||||
options={
|
||||
'default_permissions': ('add', 'change'),
|
||||
'ordering': ['-valid_to'],
|
||||
'verbose_name_plural': 'Caisses',
|
||||
'verbose_name': 'Caisse',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='checkoutstatement',
|
||||
options={
|
||||
'default_permissions': ('add', 'change'),
|
||||
'verbose_name_plural': 'Relevés de caisses',
|
||||
'verbose_name': 'Relevé de caisse',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='checkouttransfer',
|
||||
options={
|
||||
'default_permissions': (),
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='inventory',
|
||||
options={
|
||||
'permissions': (
|
||||
(
|
||||
'order_to_inventory',
|
||||
"Générer un inventaire à partir d'une commande"
|
||||
),
|
||||
),
|
||||
'ordering': ['-at'],
|
||||
'verbose_name_plural': 'Inventaires',
|
||||
'default_permissions': ('add',),
|
||||
'verbose_name': 'Inventaire',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='inventoryarticle',
|
||||
options={
|
||||
'default_permissions': (),
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='operation',
|
||||
options={
|
||||
'permissions': (
|
||||
('perform_deposit', 'Effectuer une charge'),
|
||||
(
|
||||
'perform_negative_operations',
|
||||
'Enregistrer des commandes en négatif'
|
||||
),
|
||||
(
|
||||
'override_frozen_protection',
|
||||
"Forcer le gel d'un compte"
|
||||
),
|
||||
(
|
||||
'cancel_old_operations',
|
||||
'Annuler des commandes non récentes'
|
||||
),
|
||||
(
|
||||
'perform_commented_operations',
|
||||
'Enregistrer des commandes avec commentaires'
|
||||
),
|
||||
),
|
||||
'default_permissions': (),
|
||||
'verbose_name_plural': 'Opérations',
|
||||
'verbose_name': 'Opération',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='operationgroup',
|
||||
options={
|
||||
'default_permissions': (),
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='order',
|
||||
options={
|
||||
'default_permissions': ('add',),
|
||||
'ordering': ['-at'],
|
||||
'verbose_name_plural': 'Commandes',
|
||||
'verbose_name': 'Commande',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='orderarticle',
|
||||
options={
|
||||
'default_permissions': (),
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='supplier',
|
||||
options={
|
||||
'default_permissions': ('change',),
|
||||
'verbose_name_plural': 'Fournisseurs',
|
||||
'verbose_name': 'Fournisseur',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='supplierarticle',
|
||||
options={
|
||||
'default_permissions': (),
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='transfer',
|
||||
options={
|
||||
'default_permissions': ('add',),
|
||||
'verbose_name_plural': 'Transferts',
|
||||
'verbose_name': 'Transfert',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='transfergroup',
|
||||
options={
|
||||
'default_permissions': (),
|
||||
},
|
||||
),
|
||||
]
|
99
kfet/migrations/0063_update_permissions.py
Normal file
99
kfet/migrations/0063_update_permissions.py
Normal file
|
@ -0,0 +1,99 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
from django.db.models import Q
|
||||
|
||||
|
||||
def update_permissions(apps, schema_editor):
|
||||
Permission = apps.get_model('auth', 'Permission')
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
Account = apps.get_model('kfet', 'Account')
|
||||
|
||||
# If `kfet.is_team` permission exists, rename it.
|
||||
|
||||
Permission.objects.filter(
|
||||
content_type__app_label='kfet',
|
||||
content_type__model='account',
|
||||
codename='is_team',
|
||||
).update(name="Membre de l'équipe")
|
||||
|
||||
# If `kfet.view_negs` permission exists, move it as
|
||||
# `kfet.view_accountnegative`.
|
||||
|
||||
try:
|
||||
view_negs_p = Permission.objects.get(
|
||||
content_type__app_label='kfet',
|
||||
content_type__model='accountnegative',
|
||||
codename='view_negs',
|
||||
)
|
||||
except Permission.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
# Avoid failure due to unicity constraint if migrations were partially
|
||||
# applied.
|
||||
# Because `view_negs` still exists here, it should be safe to consider
|
||||
# that nothing uses `view_accountnegative` so that it can be deleted.
|
||||
Permission.objects.filter(
|
||||
content_type__app_label='kfet',
|
||||
content_type__model='accountnegative',
|
||||
codename='view_accountnegative',
|
||||
).delete()
|
||||
|
||||
view_negs_p.codename = 'view_accountnegative'
|
||||
view_negs_p.name = 'Can view Compte en négatif'
|
||||
view_negs_p.save()
|
||||
|
||||
# Delete unused permissions.
|
||||
|
||||
to_delete = {
|
||||
'account': ['delete_account'],
|
||||
'accountnegative': [
|
||||
'add_accountnegative', 'delete_accountnegative', 'view_negs'],
|
||||
'article': ['delete_article'],
|
||||
'articlecategory': ['add_articlecategory', 'delete_articlecategory'],
|
||||
'articlerule': '__all__',
|
||||
'checkout': ['delete_checkout'],
|
||||
'checkoutstatement': ['delete_checkoutstatement'],
|
||||
'checkouttransfer': '__all__',
|
||||
'inventory': ['change_inventory', 'delete_inventory'],
|
||||
'inventoryarticle': '__all__',
|
||||
'operation': ['add_operation', 'change_operation', 'delete_operation'],
|
||||
'operationgroup': '__all__',
|
||||
'order': ['change_order', 'delete_order'],
|
||||
'orderarticle': '__all__',
|
||||
'supplier': ['add_supplier', 'delete_supplier'],
|
||||
'supplierarticle': '__all__',
|
||||
'transfer': ['change_transfer', 'delete_transfer'],
|
||||
'transfergroup': '__all__',
|
||||
}
|
||||
|
||||
to_delete_q = Q()
|
||||
|
||||
for model_name, codenames in to_delete.items():
|
||||
if codenames == '__all__':
|
||||
to_delete_q |= Q(content_type__model=model_name)
|
||||
else:
|
||||
to_delete_q |= Q(
|
||||
content_type__model=model_name,
|
||||
codename__in=codenames,
|
||||
)
|
||||
|
||||
to_delete_q &= Q(content_type__app_label='kfet')
|
||||
|
||||
Permission.objects.filter(to_delete_q).delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
"""
|
||||
Data migration which performs permissions cleaning.
|
||||
"""
|
||||
dependencies = [
|
||||
('kfet', '0062_change_models_opts'),
|
||||
('auth', '0006_require_contenttypes_0002'),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(update_permissions),
|
||||
]
|
|
@ -15,9 +15,8 @@ from datetime import date
|
|||
import re
|
||||
|
||||
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 .utils import to_ukf
|
||||
|
||||
def choices_length(choices):
|
||||
|
@ -85,9 +84,11 @@ class Account(models.Model):
|
|||
blank = True, null = True, default = None)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Compte")
|
||||
verbose_name_plural = _("Comptes")
|
||||
default_permissions = ('add', 'change')
|
||||
permissions = (
|
||||
('is_team', 'Is part of the team'),
|
||||
('manage_perms', 'Gérer les permissions K-Fêt'),
|
||||
('is_team', "Membre de l'équipe"),
|
||||
('manage_addcosts', 'Gérer les majorations'),
|
||||
('edit_balance_account', "Modifier la balance d'un compte"),
|
||||
('change_account_password',
|
||||
|
@ -102,6 +103,11 @@ class Account(models.Model):
|
|||
def __str__(self):
|
||||
return '%s (%s)' % (self.trigramme, self.name)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('kfet.account.read', kwargs={
|
||||
'trigramme': self.trigramme,
|
||||
})
|
||||
|
||||
# Propriétés pour accéder aux attributs de cofprofile et user
|
||||
@property
|
||||
def user(self):
|
||||
|
@ -168,6 +174,7 @@ class Account(models.Model):
|
|||
return data
|
||||
|
||||
def perms_to_perform_operation(self, amount):
|
||||
from .config import kfet_config
|
||||
overdraft_duration_max = kfet_config.overdraft_duration
|
||||
overdraft_amount_max = kfet_config.overdraft_amount
|
||||
perms = set()
|
||||
|
@ -332,12 +339,13 @@ class AccountNegative(models.Model):
|
|||
comment = models.CharField("commentaire", max_length=255, blank=True)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('view_negs', 'Voir la liste des négatifs'),
|
||||
)
|
||||
verbose_name = _("Compte en négatif")
|
||||
verbose_name_plural = _("Comptes en négatif")
|
||||
default_permissions = ('view', 'change')
|
||||
|
||||
@property
|
||||
def until_default(self):
|
||||
from .config import kfet_config
|
||||
return self.start + kfet_config.overdraft_duration
|
||||
|
||||
|
||||
|
@ -357,7 +365,10 @@ class Checkout(models.Model):
|
|||
return reverse('kfet.checkout.read', kwargs={'pk': self.pk})
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Caisse")
|
||||
verbose_name_plural = _("Caisses")
|
||||
ordering = ['-valid_to']
|
||||
default_permissions = ('add', 'change')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -372,6 +383,10 @@ class CheckoutTransfer(models.Model):
|
|||
amount = models.DecimalField(
|
||||
max_digits = 6, decimal_places = 2)
|
||||
|
||||
class Meta:
|
||||
default_permissions = ()
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CheckoutStatement(models.Model):
|
||||
by = models.ForeignKey(
|
||||
|
@ -410,6 +425,11 @@ class CheckoutStatement(models.Model):
|
|||
"montant des chèques",
|
||||
default=0, max_digits=6, decimal_places=2)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Relevé de caisse")
|
||||
verbose_name_plural = _("Relevés de caisses")
|
||||
default_permissions = ('add', 'change')
|
||||
|
||||
def __str__(self):
|
||||
return '%s %s' % (self.checkout, self.at)
|
||||
|
||||
|
@ -450,6 +470,11 @@ class ArticleCategory(models.Model):
|
|||
"appliquée aux articles de "
|
||||
"cette catégorie.")
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Catégorie d'articles")
|
||||
verbose_name_plural = _("Catégories d'articles")
|
||||
default_permissions = ('change',)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
@ -486,6 +511,11 @@ class Article(models.Model):
|
|||
"capacité du contenant",
|
||||
blank = True, null = True, default = None)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Article")
|
||||
verbose_name_plural = _("Articles")
|
||||
default_permissions = ('add', 'change')
|
||||
|
||||
def __str__(self):
|
||||
return '%s - %s' % (self.category.name, self.name)
|
||||
|
||||
|
@ -505,6 +535,10 @@ class ArticleRule(models.Model):
|
|||
related_name = "rule_to")
|
||||
ratio = models.PositiveSmallIntegerField()
|
||||
|
||||
class Meta:
|
||||
default_permissions = ()
|
||||
|
||||
|
||||
class Inventory(models.Model):
|
||||
articles = models.ManyToManyField(
|
||||
Article,
|
||||
|
@ -521,7 +555,10 @@ class Inventory(models.Model):
|
|||
blank = True, null = True, default = None)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Inventaire")
|
||||
verbose_name_plural = _("Inventaires")
|
||||
ordering = ['-at']
|
||||
default_permissions = ('add',)
|
||||
permissions = (
|
||||
('order_to_inventory', "Générer un inventaire à partir d'une commande"),
|
||||
)
|
||||
|
@ -536,6 +573,9 @@ class InventoryArticle(models.Model):
|
|||
stock_new = models.IntegerField()
|
||||
stock_error = models.IntegerField(default = 0)
|
||||
|
||||
class Meta:
|
||||
default_permissions = ()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# S'il s'agit d'un inventaire provenant d'une livraison, il n'y a pas
|
||||
# d'erreur
|
||||
|
@ -557,6 +597,11 @@ class Supplier(models.Model):
|
|||
phone = models.CharField(_("téléphone"), max_length=20, blank=True)
|
||||
comment = models.TextField(_("commentaire"), blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Fournisseur")
|
||||
verbose_name_plural = _("Fournisseurs")
|
||||
default_permissions = ('change',)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
@ -577,6 +622,9 @@ class SupplierArticle(models.Model):
|
|||
max_digits = 7, decimal_places = 4,
|
||||
blank = True, null = True, default = None)
|
||||
|
||||
class Meta:
|
||||
default_permissions = ()
|
||||
|
||||
class Order(models.Model):
|
||||
supplier = models.ForeignKey(
|
||||
Supplier, on_delete = models.PROTECT,
|
||||
|
@ -590,7 +638,10 @@ class Order(models.Model):
|
|||
max_digits = 6, decimal_places = 2, default = 0)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Commande")
|
||||
verbose_name_plural = _("Commandes")
|
||||
ordering = ['-at']
|
||||
default_permissions = ('add',)
|
||||
|
||||
class OrderArticle(models.Model):
|
||||
order = models.ForeignKey(
|
||||
|
@ -600,6 +651,9 @@ class OrderArticle(models.Model):
|
|||
quantity_ordered = models.IntegerField()
|
||||
quantity_received = models.IntegerField(default = 0)
|
||||
|
||||
class Meta:
|
||||
default_permissions = ()
|
||||
|
||||
class TransferGroup(models.Model):
|
||||
at = models.DateTimeField(default=timezone.now)
|
||||
# Optional
|
||||
|
@ -611,6 +665,9 @@ class TransferGroup(models.Model):
|
|||
related_name = "+",
|
||||
blank = True, null = True, default = None)
|
||||
|
||||
class Meta:
|
||||
default_permissions = ()
|
||||
|
||||
|
||||
class Transfer(models.Model):
|
||||
group = models.ForeignKey(
|
||||
|
@ -631,6 +688,11 @@ class Transfer(models.Model):
|
|||
canceled_at = models.DateTimeField(
|
||||
null=True, blank=True, default=None)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Transfert")
|
||||
verbose_name_plural = _("Transferts")
|
||||
default_permissions = ('add',)
|
||||
|
||||
def __str__(self):
|
||||
return '{} -> {}: {}€'.format(self.from_acc, self.to_acc, self.amount)
|
||||
|
||||
|
@ -656,6 +718,9 @@ class OperationGroup(models.Model):
|
|||
related_name = "+",
|
||||
blank = True, null = True, default = None)
|
||||
|
||||
class Meta:
|
||||
default_permissions = ()
|
||||
|
||||
def __str__(self):
|
||||
return ', '.join(map(str, self.opes.all()))
|
||||
|
||||
|
@ -706,6 +771,9 @@ class Operation(models.Model):
|
|||
blank=True, null=True, default=None)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Opération")
|
||||
verbose_name_plural = _("Opérations")
|
||||
default_permissions = ()
|
||||
permissions = (
|
||||
('perform_deposit', 'Effectuer une charge'),
|
||||
('perform_negative_operations',
|
||||
|
|
52
kfet/static/kfet/css/base/forms.css
Normal file
52
kfet/static/kfet/css/base/forms.css
Normal file
|
@ -0,0 +1,52 @@
|
|||
.extra-form {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
|
||||
/* Checkbox select multiple field */
|
||||
|
||||
.checkbox-select-multiple > ul,
|
||||
.checkbox-select-multiple > ul > li > ul {
|
||||
padding-left: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.checkbox-select-multiple label {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
/* Permissions field */
|
||||
|
||||
.permissions-field > ul {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.permissions-field > ul > li {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.permissions-field > ul > li > ul {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
||||
margin-top: 10px;
|
||||
padding: 5px 10px 0;
|
||||
|
||||
border: 1px solid #CCC;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
|
||||
}
|
||||
|
||||
.permissions-field > ul > li > ul > li {
|
||||
float: left;
|
||||
flex: 0 1 100%;
|
||||
}
|
||||
|
||||
.permissions-field > ul > li > ul > li:not(:last-child) {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.permissions-field > ul > li > ul > li { flex: 0 1 auto; }
|
||||
}
|
|
@ -4,10 +4,12 @@
|
|||
/* Libs customizations */
|
||||
@import url("libs/jconfirm-kfet.css");
|
||||
@import url("libs/multiple-select-kfet.css");
|
||||
@import url("libs/formset-kfet.css");
|
||||
|
||||
/* Base */
|
||||
@import url("base/misc.css");
|
||||
@import url("base/buttons.css");
|
||||
@import url("base/forms.css");
|
||||
|
||||
/* Blocks */
|
||||
@import url("base/main.css");
|
||||
|
@ -35,6 +37,11 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.header small {
|
||||
color: #FFF;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.nopadding {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
@ -296,13 +303,6 @@ thead .tooltip {
|
|||
}
|
||||
|
||||
|
||||
/* Checkbox select multiple */
|
||||
|
||||
.checkbox-select-multiple label {
|
||||
font-weight: normal;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Statement creation */
|
||||
|
||||
.statement-create-summary table {
|
||||
|
|
36
kfet/static/kfet/css/libs/formset-kfet.css
Normal file
36
kfet/static/kfet/css/libs/formset-kfet.css
Normal file
|
@ -0,0 +1,36 @@
|
|||
[data-formset-form] {
|
||||
margin-bottom: 5px;
|
||||
padding: 10px;
|
||||
|
||||
border: 1px solid #CCC;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
|
||||
|
||||
transition: border 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
[data-formset-form] > :not(:last-child) {
|
||||
margin-right: 15px;
|
||||
}
|
||||
[data-formset-form] .form-actions {
|
||||
float: right;
|
||||
margin-left: 15px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
[data-formset-add] {
|
||||
position: relative;
|
||||
top: -5px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
[data-formset-form-deleted] {
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
#e9afb9, #e9afb9 2px, transparent 2px, transparent 12px
|
||||
);
|
||||
border: 1px solid #e9afb9;
|
||||
color: #999;
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
{% extends "kfet/base_col_2.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}Comptes{% endblock %}
|
||||
{% block header-title %}Comptes{% endblock %}
|
||||
|
@ -22,11 +23,11 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
{% if perms.kfet.manage_perms %}
|
||||
<a class="btn btn-primary" href="{% url 'kfet.account.group' %}">Permissions</a>
|
||||
{% if perms.kfetauth.view_group %}
|
||||
<a class="btn btn-primary" href="{% url 'kfet.group' %}">{% trans "Permissions" %}</a>
|
||||
{% endif %}
|
||||
|
||||
{% if perms.kfet.view_negs %}
|
||||
{% if perms.kfet.view_accountnegative %}
|
||||
<a class="btn btn-primary" href="{% url 'kfet.account.negative' %}">Négatifs</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
{% extends "kfet/base_col_2.html" %}
|
||||
|
||||
{% block title %}Groupes de comptes{% endblock %}
|
||||
{% block header-title %}Groupes de comptes{% endblock %}
|
||||
|
||||
{% block fixed %}
|
||||
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary" href="{% url 'kfet.account.group.create' %}">
|
||||
<span class="glyphicon glyphicon-plus"></span><span>Créer un groupe</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% for group in groups %}
|
||||
<section>
|
||||
<div class="heading">
|
||||
{{ group.name }}
|
||||
<div class="buttons">
|
||||
<a class="btn btn-default" href="{% url 'kfet.account.group.update' group.pk %}">
|
||||
<span class="glyphicon glyphicon-cog"></span><span class="hidden-xs">Éditer</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Comptes</h3>
|
||||
<div class="sub-block column-sm-2 column-md-3">
|
||||
<ul>
|
||||
{% for user in group.user_set.all %}
|
||||
<li>
|
||||
<a href="{% url "kfet.account.update" user.profile.account_kfet.trigramme %}">
|
||||
{{ user.profile.account_kfet }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<h3>Permissions</h3>
|
||||
<div class="column-sm-2 column-lg-3">
|
||||
{% regroup group.permissions.all by content_type as grouped_perms %}
|
||||
<ul class="list-unstyled">
|
||||
{% for perms_group in grouped_perms %}
|
||||
<li class="unbreakable">
|
||||
<b>{{ perms_group.grouper|title }}</b>
|
||||
<ul>
|
||||
{% for perm in perms_group.list %}
|
||||
<li>{{ perm.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,49 +0,0 @@
|
|||
{% 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 header-title %}Modification des permissions{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% 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 %}
|
|
@ -24,6 +24,7 @@
|
|||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/jquery-ui.min.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/jquery-confirm.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'js/jquery.formset.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/reconnecting-websocket.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/moment.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/moment-fr.js' %}"></script>
|
||||
|
|
19
kfet/templates/kfet/form_field_base_snippet.html
Normal file
19
kfet/templates/kfet/form_field_base_snippet.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% load widget_tweaks %}
|
||||
|
||||
{% with widget=field.field.widget %}
|
||||
|
||||
{% if field|widget_type == "checkboxselectmultiple" %}
|
||||
<div class="checkbox-select-multiple {{ widget.attrs.field_class }}">
|
||||
{{ field }}
|
||||
</div>
|
||||
{% elif field|widget_type == "checkboxinput" %}
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
{{ field }} {{ field.label }}
|
||||
</label>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ field|add_class:'form-control' }}
|
||||
{% endif %}
|
||||
|
||||
{% endwith %}
|
|
@ -1,26 +1,24 @@
|
|||
{% load widget_tweaks %}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
|
||||
<div class="col-sm-10">
|
||||
{% if field|widget_type == "checkboxselectmultiple" %}
|
||||
<ul class="list-unstyled checkbox-select-multiple">
|
||||
{% for choice in form.permissions %}
|
||||
<li class="col-sm-6 col-lg-4">
|
||||
<label for="{{ choice.id_for_label }}">
|
||||
{{ choice.tag }} {{ choice.choice_label }}
|
||||
</label>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
{{ field|add_class:'form-control' }}
|
||||
{% endif %}
|
||||
{% if not field.label %}
|
||||
{% elif field|widget_type == "checkboxinput" %}
|
||||
{# label is displayed along the checkbox #}
|
||||
{% else %}
|
||||
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">
|
||||
{{ field.label }}
|
||||
</label>
|
||||
{% endif %}
|
||||
|
||||
<div class="{% if not field.label %}col-sm-12{% elif field|widget_type == "checkboxinput" %}col-sm-10 col-sm-offset-2{% else %}col-sm-10{% endif %}">
|
||||
|
||||
{% include "kfet/form_field_base_snippet.html" with field=field %}
|
||||
|
||||
{% if field.errors %}
|
||||
<span class="help-block">{{field.errors}}</span>
|
||||
<span class="help-block">{{ field.errors }}</span>
|
||||
{% endif %}
|
||||
{% if field.help_text %}
|
||||
<span class="help-block">{{field.help_text}}</span>
|
||||
<span class="help-block">{{ field.help_text|safe }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<form action="" method="post" class="form-horizontal">
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{% include "kfet/form_snippet.html" %}
|
||||
{% if not authz %}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
{% for field in form %}
|
||||
{% include 'kfet/form_field_snippet.html' with field=field %}
|
||||
{% endfor %}
|
||||
<div class="form-horizontal">
|
||||
{% for field in form %}
|
||||
{% include 'kfet/form_field_snippet.html' with field=field %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
<div class="form-group">
|
||||
<div class="col-sm-6 col-sm-offset-3 text-center">
|
||||
<input type="submit" value="{{ value }}" class="btn btn-primary btn-lg">
|
||||
{% load i18n %}
|
||||
|
||||
{% trans "Enregistrer" as default_value %}
|
||||
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-6 col-sm-offset-3 text-center">
|
||||
<input type="submit" value="{% firstof value default_value %}" class="btn btn-primary btn-lg">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
12
kfet/templates/kfet/formset_form_actions.html
Normal file
12
kfet/templates/kfet/formset_form_actions.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% load i18n %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
<div class="form-group form-actions">
|
||||
{{ form.DELETE|add_class:"hide" }}
|
||||
<button type="button" class="btn btn-danger btn-sm" title="{% trans "Supprimer" %}" data-formset-delete-button>
|
||||
<span class="glyphicon glyphicon-trash"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-sm" title="{% trans "Restaurer" %}" data-formset-restore-button style="display: none">
|
||||
<span class="glyphicon glyphicon-plus"></span>
|
||||
</button>
|
||||
</div>
|
|
@ -1,5 +0,0 @@
|
|||
from django.template.defaulttags import register
|
||||
|
||||
@register.filter
|
||||
def get_item(dictionary, key):
|
||||
return dictionary.get(key)
|
8
kfet/templatetags/kfet_extras.py
Normal file
8
kfet/templatetags/kfet_extras.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from django.template.defaulttags import register
|
||||
|
||||
|
||||
@register.filter
|
||||
def get(o, key):
|
||||
if hasattr(o, 'get'):
|
||||
return o.get(key)
|
||||
return o[int(key)]
|
|
@ -12,6 +12,16 @@ class AccountTests(TestCase):
|
|||
self.account = Account(trigramme='000')
|
||||
self.account.save({'username': 'user'})
|
||||
|
||||
def test_get_absolute_url(self):
|
||||
self.assertEqual(
|
||||
self.account.get_absolute_url(), '/k-fet/accounts/000')
|
||||
|
||||
account_space = Account(trigramme=' ')
|
||||
account_space.save({'username': 'space'})
|
||||
|
||||
self.assertEqual(
|
||||
account_space.get_absolute_url(), '/k-fet/accounts/%20%20%20')
|
||||
|
||||
def test_password(self):
|
||||
self.account.change_pwd('anna')
|
||||
self.account.save()
|
||||
|
|
|
@ -3,7 +3,6 @@ from datetime import datetime, timedelta
|
|||
from decimal import Decimal
|
||||
from unittest import mock
|
||||
|
||||
from django.contrib.auth.models import Group
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import Client, TestCase
|
||||
from django.utils import timezone
|
||||
|
@ -15,7 +14,7 @@ from ..models import (
|
|||
SupplierArticle, Transfer, TransferGroup,
|
||||
)
|
||||
from .testcases import ViewTestCaseMixin
|
||||
from .utils import create_team, create_user, get_perms
|
||||
from .utils import create_team, create_user
|
||||
|
||||
|
||||
class AccountListViewTests(ViewTestCaseMixin, TestCase):
|
||||
|
@ -335,146 +334,6 @@ class AccountUpdateViewTests(ViewTestCaseMixin, TestCase):
|
|||
self.assertForbiddenKfet(r)
|
||||
|
||||
|
||||
class AccountGroupListViewTests(ViewTestCaseMixin, TestCase):
|
||||
url_name = 'kfet.account.group'
|
||||
url_expected = '/k-fet/accounts/groups'
|
||||
|
||||
auth_user = 'team1'
|
||||
auth_forbidden = [None, 'user', 'team']
|
||||
|
||||
def get_users_extra(self):
|
||||
return {
|
||||
'team1': create_team('team1', '101', perms=['kfet.manage_perms']),
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group1 = Group.objects.create(name='K-Fêt - Group1')
|
||||
self.group2 = Group.objects.create(name='K-Fêt - Group2')
|
||||
|
||||
def test_ok(self):
|
||||
r = self.client.get(self.url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
self.assertQuerysetEqual(
|
||||
r.context['groups'],
|
||||
map(repr, [self.group1, self.group2]),
|
||||
ordered=False,
|
||||
)
|
||||
|
||||
|
||||
class AccountGroupCreateViewTests(ViewTestCaseMixin, TestCase):
|
||||
url_name = 'kfet.account.group.create'
|
||||
url_expected = '/k-fet/accounts/groups/new'
|
||||
|
||||
http_methods = ['GET', 'POST']
|
||||
|
||||
auth_user = 'team1'
|
||||
auth_forbidden = [None, 'user', 'team']
|
||||
|
||||
def get_users_extra(self):
|
||||
return {
|
||||
'team1': create_team('team1', '101', perms=['kfet.manage_perms']),
|
||||
}
|
||||
|
||||
@property
|
||||
def post_data(self):
|
||||
return {
|
||||
'name': 'The Group',
|
||||
'permissions': [
|
||||
str(self.perms['kfet.is_team'].pk),
|
||||
str(self.perms['kfet.manage_perms'].pk),
|
||||
],
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.perms = get_perms(
|
||||
'kfet.is_team',
|
||||
'kfet.manage_perms',
|
||||
)
|
||||
|
||||
def test_get_ok(self):
|
||||
r = self.client.get(self.url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_post_ok(self):
|
||||
r = self.client.post(self.url, self.post_data)
|
||||
self.assertRedirects(r, reverse('kfet.account.group'))
|
||||
|
||||
group = Group.objects.get(name='K-Fêt The Group')
|
||||
|
||||
self.assertQuerysetEqual(
|
||||
group.permissions.all(),
|
||||
map(repr, [
|
||||
self.perms['kfet.is_team'],
|
||||
self.perms['kfet.manage_perms'],
|
||||
]),
|
||||
ordered=False,
|
||||
)
|
||||
|
||||
|
||||
class AccountGroupUpdateViewTests(ViewTestCaseMixin, TestCase):
|
||||
url_name = 'kfet.account.group.update'
|
||||
|
||||
http_methods = ['GET', 'POST']
|
||||
|
||||
auth_user = 'team1'
|
||||
auth_forbidden = [None, 'user', 'team']
|
||||
|
||||
@property
|
||||
def url_kwargs(self):
|
||||
return {'pk': self.group.pk}
|
||||
|
||||
@property
|
||||
def url_expected(self):
|
||||
return '/k-fet/accounts/groups/{}/edit'.format(self.group.pk)
|
||||
|
||||
def get_users_extra(self):
|
||||
return {
|
||||
'team1': create_team('team1', '101', perms=['kfet.manage_perms']),
|
||||
}
|
||||
|
||||
@property
|
||||
def post_data(self):
|
||||
return {
|
||||
'name': 'The Group',
|
||||
'permissions': [
|
||||
str(self.perms['kfet.is_team'].pk),
|
||||
str(self.perms['kfet.manage_perms'].pk),
|
||||
],
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.perms = get_perms(
|
||||
'kfet.is_team',
|
||||
'kfet.manage_perms',
|
||||
)
|
||||
self.group = Group.objects.create(name='K-Fêt - Group')
|
||||
self.group.permissions = self.perms.values()
|
||||
|
||||
def test_get_ok(self):
|
||||
r = self.client.get(self.url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_post_ok(self):
|
||||
r = self.client.post(self.url, self.post_data)
|
||||
self.assertRedirects(r, reverse('kfet.account.group'))
|
||||
|
||||
self.group.refresh_from_db()
|
||||
|
||||
self.assertEqual(self.group.name, 'K-Fêt The Group')
|
||||
self.assertQuerysetEqual(
|
||||
self.group.permissions.all(),
|
||||
map(repr, [
|
||||
self.perms['kfet.is_team'],
|
||||
self.perms['kfet.manage_perms'],
|
||||
]),
|
||||
ordered=False,
|
||||
)
|
||||
|
||||
|
||||
class AccountNegativeListViewTests(ViewTestCaseMixin, TestCase):
|
||||
url_name = 'kfet.account.negative'
|
||||
url_expected = '/k-fet/accounts/negatives'
|
||||
|
@ -484,7 +343,8 @@ class AccountNegativeListViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
def get_users_extra(self):
|
||||
return {
|
||||
'team1': create_team('team1', '101', perms=['kfet.view_negs']),
|
||||
'team1': create_team('team1', '101', perms=[
|
||||
'kfet.view_accountnegative']),
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
|
|
20
kfet/urls.py
20
kfet/urls.py
|
@ -8,8 +8,6 @@ from kfet.decorators import teamkfet_required
|
|||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^login/generic$', views.login_generic,
|
||||
name='kfet.login.generic'),
|
||||
url(r'^history$', views.history,
|
||||
name='kfet.history'),
|
||||
|
||||
|
@ -50,20 +48,8 @@ urlpatterns = [
|
|||
url(r'^accounts/(?P<trigramme>.{3})/edit$', views.account_update,
|
||||
name='kfet.account.update'),
|
||||
|
||||
# Account - Groups
|
||||
url(r'^accounts/groups$', views.account_group,
|
||||
name='kfet.account.group'),
|
||||
url(r'^accounts/groups/new$',
|
||||
permission_required('kfet.manage_perms')
|
||||
(views.AccountGroupCreate.as_view()),
|
||||
name='kfet.account.group.create'),
|
||||
url(r'^accounts/groups/(?P<pk>\d+)/edit$',
|
||||
permission_required('kfet.manage_perms')
|
||||
(views.AccountGroupUpdate.as_view()),
|
||||
name='kfet.account.group.update'),
|
||||
|
||||
url(r'^accounts/negatives$',
|
||||
permission_required('kfet.view_negs')
|
||||
permission_required('kfet.view_accountnegative')
|
||||
(views.AccountNegativeList.as_view()),
|
||||
name='kfet.account.negative'),
|
||||
|
||||
|
@ -240,6 +226,6 @@ urlpatterns = [
|
|||
]
|
||||
|
||||
urlpatterns += [
|
||||
# K-Fêt Open urls
|
||||
url('^open/', include('kfet.open.urls')),
|
||||
url(r'^', include('kfet.auth.urls')),
|
||||
url(r'^open/', include('kfet.open.urls')),
|
||||
]
|
||||
|
|
|
@ -7,11 +7,10 @@ from django.core.serializers.json import DjangoJSONEncoder
|
|||
from channels.channel import Group
|
||||
from channels.generic.websockets import JsonWebsocketConsumer
|
||||
|
||||
from .config import kfet_config
|
||||
|
||||
|
||||
def to_ukf(balance, is_cof=False):
|
||||
"""Convert euro to UKF."""
|
||||
from .config import kfet_config
|
||||
subvention = kfet_config.subvention_cof
|
||||
grant = (1 + subvention / 100) if is_cof else 1
|
||||
return math.floor(balance * 10 * grant)
|
||||
|
|
|
@ -24,7 +24,9 @@ from django.utils.decorators import method_decorator
|
|||
|
||||
from gestioncof.models import CofProfile
|
||||
|
||||
from kfet.config import kfet_config
|
||||
from .auth.forms import UserGroupForm
|
||||
|
||||
from kfet.config import KFetConfigForm, kfet_config
|
||||
from kfet.decorators import teamkfet_required
|
||||
from kfet.models import (
|
||||
Account, Checkout, Article, AccountNegative,
|
||||
|
@ -33,14 +35,14 @@ from kfet.models import (
|
|||
TransferGroup, Transfer, ArticleCategory)
|
||||
from kfet.forms import (
|
||||
AccountTriForm, AccountBalanceForm, AccountNoTriForm, UserForm, CofForm,
|
||||
UserRestrictTeamForm, UserGroupForm, AccountForm, CofRestrictForm,
|
||||
UserRestrictTeamForm, AccountForm, CofRestrictForm,
|
||||
AccountPwdForm, AccountNegativeForm, UserRestrictForm, AccountRestrictForm,
|
||||
CheckoutForm, CheckoutRestrictForm, CheckoutStatementCreateForm,
|
||||
CheckoutStatementUpdateForm, ArticleForm, ArticleRestrictForm,
|
||||
KPsulOperationGroupForm, KPsulAccountForm, KPsulCheckoutForm,
|
||||
KPsulOperationFormSet, AddcostForm, FilterHistoryForm,
|
||||
TransferFormSet, InventoryArticleForm, OrderArticleForm,
|
||||
OrderArticleToInventoryForm, CategoryForm, KFetConfigForm
|
||||
OrderArticleToInventoryForm, CategoryForm,
|
||||
)
|
||||
from collections import defaultdict
|
||||
from kfet import consumers
|
||||
|
@ -50,10 +52,6 @@ import heapq
|
|||
import statistics
|
||||
from kfet.statistic import ScaleMixin, last_stats_manifest, tot_ventes, WeekScale
|
||||
|
||||
from .auth.views import ( # noqa
|
||||
account_group, login_generic, AccountGroupCreate, AccountGroupUpdate,
|
||||
)
|
||||
|
||||
|
||||
def put_cleaned_data_in_dict(dict, form):
|
||||
for field in form.cleaned_data:
|
||||
|
|
|
@ -29,5 +29,9 @@ wagtailmenus==2.2.*
|
|||
# Remove this when we switch to Django 1.11
|
||||
djangorestframework==3.6.4
|
||||
|
||||
# This fork enables restore of forms.
|
||||
# Original project: https://bitbucket.org/tim_heap/django-formset-js
|
||||
git+https://bitbucket.org/georgema1982/django-formset-js.git#egg=django-formset-js
|
||||
|
||||
# Production tools
|
||||
wheel
|
||||
|
|
3
setup.cfg
Normal file
3
setup.cfg
Normal file
|
@ -0,0 +1,3 @@
|
|||
[flake8]
|
||||
# E731: lambda expressions
|
||||
ignore = E731
|
0
utils/__init__.py
Normal file
0
utils/__init__.py
Normal file
61
utils/forms.py
Normal file
61
utils/forms.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
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
141
utils/tests/test_forms.py
Normal file
141
utils/tests/test_forms.py
Normal file
|
@ -0,0 +1,141 @@
|
|||
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