WIP: Les permissions du CMS peuvent être attribuées aux groupes K-Fêt (depuis le site K-Fêt) + quelques améliorations #565

Draft
delobell wants to merge 17 commits from aureplop/kfet-auth_cms into aureplop/kfet-auth
50 changed files with 1814 additions and 411 deletions

View file

@ -69,6 +69,7 @@ INSTALLED_APPS = [
'autocomplete_light',
'captcha',
'django_cas_ng',
'djangoformsetjs',
'bootstrapform',
'kfet',
'kfet.open',

View file

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

View file

@ -1,23 +1,74 @@
import re
from itertools import groupby
from operator import attrgetter
from django import forms
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 GroupsField(forms.ModelMultipleChoiceField):
def __init__(self, **kwargs):
kwargs.setdefault('queryset', Group.objects.all())
kwargs.setdefault('widget', widgets.CheckboxSelectMultiple)
kwargs.setdefault('widget', forms.CheckboxSelectMultiple)
super().__init__(**kwargs)
class BasePermissionsField(forms.ModelMultipleChoiceField):
def __init__(self, **kwargs):
kwargs.setdefault('widget', widgets.CheckboxSelectMultiple)
kwargs.setdefault('widget', forms.CheckboxSelectMultiple(attrs={
'field_class': 'permissions-field',
}))
super().__init__(**kwargs)
def label_from_instance(self, obj):
return obj.name
# 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):

View file

@ -4,13 +4,12 @@ from django.utils.translation import ugettext_lazy as _
from utils.forms import KeepUnselectableModelFormMixin
from .fields import GroupsField, CorePermissionsField
from .models import Group
class GroupForm(KeepUnselectableModelFormMixin, forms.ModelForm):
permissions = CorePermissionsField(label=_("Permissions"), required=False)
permissions = CorePermissionsField(label='', required=False)
keep_unselectable_fields = ['permissions']

View file

@ -21,26 +21,4 @@ class Migration(migrations.Migration):
('token', models.CharField(unique=True, max_length=50)),
],
),
migrations.CreateModel(
name='Group',
fields=[
('group_ptr', models.OneToOneField(parent_link=True, serialize=False, primary_key=True, auto_created=True, to='auth.Group')),
],
options={
'verbose_name': 'Groupe',
'verbose_name_plural': 'Groupes',
},
bases=('auth.group',),
),
migrations.CreateModel(
name='Permission',
fields=[
],
options={
'verbose_name': 'Permission',
'verbose_name_plural': 'Permissions',
'proxy': True,
},
bases=('auth.permission',),
),
]

View 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',),
),
]

View file

@ -22,7 +22,8 @@ class Migration(migrations.Migration):
"""
dependencies = [
('kfetauth', '0001_initial'),
('kfetauth', '0002_create_group_permission_models'),
('auth', '0006_require_contenttypes_0002'),
]
operations = [

View 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),
]

View file

@ -5,6 +5,8 @@ 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):
@ -20,6 +22,9 @@ class GenericTeamToken(models.Model):
objects = GenericTeamTokenManager()
class Meta:
default_permissions = ()
class Group(DjangoGroup):
@ -35,6 +40,7 @@ class Group(DjangoGroup):
class Meta:
verbose_name = _("Groupe")
verbose_name_plural = _("Groupes")
default_permissions = ('view', 'add', 'change')
KFET_CORE_APP_LABELS = ['kfet', 'kfetauth']
@ -48,6 +54,7 @@ class CorePermissionManager(models.Manager):
class Permission(DjangoPermission):
kfetcore = CorePermissionManager()
kfetcms = CmsPermissionManager()
class Meta:
proxy = True

View 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 %}

View 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 %}

View file

@ -10,15 +10,15 @@ from django.core.urlresolvers import reverse
from django.test import RequestFactory, TestCase
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 .forms import GroupForm, UserGroupForm
from .middleware import TemporaryAuthMiddleware
from .models import GenericTeamToken, Group, Permission
from .utils import get_kfet_generic_user
from .views import GenericLoginView
##
@ -85,6 +85,7 @@ class GroupFormTests(TestCase):
content_type=ot_ct, codename='cool')
def test_creation(self):
from .forms import GroupForm
data = {
'name': 'Another Group',
'permissions': [self.kf_perm1.pk],
@ -101,6 +102,7 @@ class GroupFormTests(TestCase):
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]
@ -139,6 +141,7 @@ class UserGroupFormTests(TestCase):
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
self.user.groups.add(self.ot_group)
@ -257,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
@ -348,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()
@ -364,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):
"""
@ -387,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
View 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'),
]

View file

@ -1,22 +1,23 @@
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 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, Group
User = get_user_model()
class GenericLoginView(View):
"""
@ -104,38 +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
.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 BaseAccountGroupFormViewMixin:
model = Group
form_class = GroupForm
template_name = 'kfet/account_group_form.html'
success_url = reverse_lazy('kfet.account.group')
_group_formview_extras = None
class AccountGroupFormViewMixin(
SuccessMessageMixin,
BaseAccountGroupFormViewMixin,
):
pass
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]
class AccountGroupCreate(AccountGroupFormViewMixin, CreateView):
success_message = 'Nouveau groupe : %(name)s'
@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,
})
class AccountGroupUpdate(AccountGroupFormViewMixin, UpdateView):
success_message = 'Groupe modifié : %(name)s'
@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
View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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': (),
},
),
]

View 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),
]

View file

@ -17,7 +17,6 @@ import re
from .auth import KFET_GENERIC_TRIGRAMME
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',

View 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; }
}

View file

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

View 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;
}

View file

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

View file

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

View file

@ -1,10 +0,0 @@
{% extends 'kfet/base_form.html' %}
{% block title %}Permissions - Édition{% endblock %}
{% block header-title %}Modification des permissions{% endblock %}
{% block main %}
{% include "kfet/form_full_snippet.html" with authz=perms.kfet.manage_perms submit_text="Enregistrer" %}
{% endblock %}

View file

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

View 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 %}

View file

@ -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 field %}
<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>
{% 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>

View file

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

View file

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

View file

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

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

View file

@ -1,5 +0,0 @@
from django.template.defaulttags import register
@register.filter
def get_item(dictionary, key):
return dictionary.get(key)

View 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)]

View file

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

View file

@ -7,8 +7,6 @@ from django.core.urlresolvers import reverse
from django.test import Client, TestCase
from django.utils import timezone
from kfet.auth.models import Group
from ..config import kfet_config
from ..models import (
Account, Article, ArticleCategory, Checkout, CheckoutStatement, Inventory,
@ -16,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):
@ -336,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='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, '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'
@ -485,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):

View file

@ -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')),
]

View file

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

View file

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

View file

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