CMS permissions can be managed from group views.
These permissions concern pages, images, documents and access to the wagtail admin site. Only appropriate elements can be selected: only the kfet root page and its descendants, same for the kfet root collection (for images and documents), and kfet snippets (MemberTeam). Add django-formset-js as dependency to help manipulate formsets. K-Fêt groups created from "devdata" commands get suitable permissions for the CMS.
This commit is contained in:
parent
82582866b4
commit
07f1a53532
19 changed files with 687 additions and 8 deletions
|
@ -68,6 +68,7 @@ INSTALLED_APPS = [
|
||||||
'autocomplete_light',
|
'autocomplete_light',
|
||||||
'captcha',
|
'captcha',
|
||||||
'django_cas_ng',
|
'django_cas_ng',
|
||||||
|
'djangoformsetjs',
|
||||||
'bootstrapform',
|
'bootstrapform',
|
||||||
'kfet',
|
'kfet',
|
||||||
'kfet.open',
|
'kfet.open',
|
||||||
|
|
|
@ -5,6 +5,8 @@ from django.db import models
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from kfet.cms.models import CmsPermissionManager
|
||||||
|
|
||||||
|
|
||||||
class GenericTeamTokenManager(models.Manager):
|
class GenericTeamTokenManager(models.Manager):
|
||||||
|
|
||||||
|
@ -52,6 +54,7 @@ class CorePermissionManager(models.Manager):
|
||||||
|
|
||||||
class Permission(DjangoPermission):
|
class Permission(DjangoPermission):
|
||||||
kfetcore = CorePermissionManager()
|
kfetcore = CorePermissionManager()
|
||||||
|
kfetcms = CmsPermissionManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
|
@ -11,6 +11,8 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from django.views.decorators.http import require_http_methods
|
from django.views.decorators.http import require_http_methods
|
||||||
|
|
||||||
|
from kfet.cms.views import get_kfetcms_group_formview_extra
|
||||||
|
|
||||||
from .forms import GroupForm
|
from .forms import GroupForm
|
||||||
from .models import GenericTeamToken, Group
|
from .models import GenericTeamToken, Group
|
||||||
|
|
||||||
|
@ -125,6 +127,8 @@ def get_group_formview_extras():
|
||||||
_group_formview_extras = []
|
_group_formview_extras = []
|
||||||
|
|
||||||
# Register additional group forms below.
|
# Register additional group forms below.
|
||||||
|
_group_formview_extras.append(
|
||||||
|
get_kfetcms_group_formview_extra())
|
||||||
|
|
||||||
return [extra.copy() for extra in _group_formview_extras]
|
return [extra.copy() for extra in _group_formview_extras]
|
||||||
|
|
||||||
|
|
194
kfet/cms/forms.py
Normal file
194
kfet/cms/forms.py
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
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='',
|
||||||
|
queryset=Permission.kfetcms.all(),
|
||||||
|
)
|
||||||
|
|
||||||
|
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 import call_command
|
||||||
from django.core.management.base import BaseCommand
|
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):
|
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)
|
# Par défaut, il s'agit d'une copie du site K-Fêt (17-05)
|
||||||
|
|
||||||
call_command('loaddata', options['file'])
|
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.blocks import SnippetChooserBlock
|
||||||
from wagtail.wagtailsnippets.models import register_snippet
|
from wagtail.wagtailsnippets.models import register_snippet
|
||||||
|
|
||||||
from kfet.cms.context_processors import get_articles
|
from .utils import get_page_model_names
|
||||||
|
|
||||||
|
|
||||||
@register_snippet
|
@register_snippet
|
||||||
|
@ -60,6 +60,7 @@ class MenuBlock(blocks.StaticBlock):
|
||||||
template = 'kfetcms/block_menu.html'
|
template = 'kfetcms/block_menu.html'
|
||||||
|
|
||||||
def get_context(self, *args, **kwargs):
|
def get_context(self, *args, **kwargs):
|
||||||
|
from .context_processors import get_articles
|
||||||
context = super().get_context(*args, **kwargs)
|
context = super().get_context(*args, **kwargs)
|
||||||
context.update(get_articles())
|
context.update(get_articles())
|
||||||
return context
|
return context
|
||||||
|
@ -172,3 +173,18 @@ class KFetPage(Page):
|
||||||
page.seo_title = page.title
|
page.seo_title = page.title
|
||||||
|
|
||||||
return context
|
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)
|
||||||
|
]
|
73
kfet/cms/views.py
Normal file
73
kfet/cms/views.py
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
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 .forms import (
|
||||||
|
CmsGroupForm, SnippetsCmsGroupForm, prepare_page_permissions_formset,
|
||||||
|
prepare_collection_member_permissions_formset,
|
||||||
|
)
|
||||||
|
from .utils import get_kfet_root_collection, get_kfet_root_page
|
||||||
|
|
||||||
|
|
||||||
|
def get_kfetcms_group_formview_extra():
|
||||||
|
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
|
|
@ -4,6 +4,7 @@
|
||||||
/* Libs customizations */
|
/* Libs customizations */
|
||||||
@import url("libs/jconfirm-kfet.css");
|
@import url("libs/jconfirm-kfet.css");
|
||||||
@import url("libs/multiple-select-kfet.css");
|
@import url("libs/multiple-select-kfet.css");
|
||||||
|
@import url("libs/formset-kfet.css");
|
||||||
|
|
||||||
/* Base */
|
/* Base */
|
||||||
@import url("base/misc.css");
|
@import url("base/misc.css");
|
||||||
|
|
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;
|
||||||
|
}
|
|
@ -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 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-ui.min.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/jquery-confirm.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/reconnecting-websocket.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/moment.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>
|
<script type="text/javascript" src="{% static 'kfet/js/moment-fr.js' %}"></script>
|
||||||
|
|
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)]
|
|
@ -26,5 +26,9 @@ python-dateutil
|
||||||
wagtail==1.10.*
|
wagtail==1.10.*
|
||||||
wagtailmenus==2.2.*
|
wagtailmenus==2.2.*
|
||||||
|
|
||||||
|
# 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
|
# Production tools
|
||||||
wheel
|
wheel
|
||||||
|
|
Loading…
Reference in a new issue