Merge branch 'Aufinal/permissions' into 'master'

Permissions par évènement

See merge request cof-geek/GestionEvenementiel!15
This commit is contained in:
Martin Pepin 2018-05-03 14:34:36 +02:00
commit 3aab76613a
15 changed files with 302 additions and 94 deletions

View file

@ -1,8 +1,7 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-07-21 14:20
# Generated by Django 1.11.3 on 2017-08-17 12:21
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
@ -13,8 +12,6 @@ class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('auth', '0008_alter_user_username_max_length'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
@ -23,8 +20,6 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('object_id', models.PositiveIntegerField()),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')),
],
options={
'verbose_name': 'souscription en groupe',
@ -37,18 +32,9 @@ class Migration(migrations.Migration):
('object_id', models.PositiveIntegerField()),
('is_unsub', models.BooleanField(default=False, verbose_name='désinscription')),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'souscription utilisateur',
},
),
migrations.AlterUniqueTogether(
name='usersubscription',
unique_together=set([('user', 'content_type', 'object_id')]),
),
migrations.AlterUniqueTogether(
name='groupsubscription',
unique_together=set([('group', 'content_type', 'object_id')]),
),
]

View file

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-08-17 12:21
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('communication', '0001_initial'),
('auth', '0008_alter_user_username_max_length'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='usersubscription',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='groupsubscription',
name='content_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
),
migrations.AddField(
model_name='groupsubscription',
name='group',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group'),
),
migrations.AlterUniqueTogether(
name='usersubscription',
unique_together=set([('user', 'content_type', 'object_id')]),
),
migrations.AlterUniqueTogether(
name='groupsubscription',
unique_together=set([('group', 'content_type', 'object_id')]),
),
]

View file

@ -28,10 +28,8 @@ class SubscriptionTest(TestCase):
created_by=cls.root,
created_at=timezone.now(),
description="Ceci est un test",
beginning_date=timezone.now()
+ timedelta(days=30),
ending_date=timezone.now()
+ timedelta(days=31),
beginning_date=timezone.now() + timedelta(days=30),
ending_date=timezone.now() + timedelta(days=31),
)
cls.groupsub = GroupSubscription.objects.create(
content_object=cls.event,

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-08-12 12:47
# Generated by Django 1.11.3 on 2017-08-17 12:21
from __future__ import unicode_literals
from django.db import migrations, models
@ -11,7 +11,6 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('event', '0001_initial'),
]
operations = [
@ -34,8 +33,6 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.PositiveSmallIntegerField(verbose_name='quantité attribuée')),
('remarks', models.TextField(verbose_name="remarques concernant l'attribution")),
('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='event.Activity')),
('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.Equipment')),
],
options={
'verbose_name': 'attribution de matériel',
@ -57,14 +54,4 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'remarques sur le matériel',
},
),
migrations.AddField(
model_name='equipment',
name='activities',
field=models.ManyToManyField(related_name='equipment', through='equipment.EquipmentAttribution', to='event.Activity'),
),
migrations.AddField(
model_name='equipment',
name='event',
field=models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèle est spécifique à l'évènement en question.", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'),
),
]

View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-08-17 12:21
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('equipment', '0001_initial'),
('event', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='equipmentattribution',
name='activity',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='event.Activity'),
),
migrations.AddField(
model_name='equipmentattribution',
name='equipment',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.Equipment'),
),
migrations.AddField(
model_name='equipment',
name='activities',
field=models.ManyToManyField(related_name='equipment', through='equipment.EquipmentAttribution', to='event.Activity'),
),
migrations.AddField(
model_name='equipment',
name='event',
field=models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèle est spécifique à l'évènement en question.", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'),
),
]

View file

@ -55,6 +55,7 @@ INSTALLED_APPS = [
'rest_framework',
'bootstrapform',
'widget_tweaks',
'guardian',
'api',
'communication',
@ -136,6 +137,11 @@ CHANNEL_LAYERS = {
}
}
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'guardian.backends.ObjectPermissionBackend',
)
# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [

View file

@ -5,3 +5,6 @@ from django.utils.translation import ugettext_lazy as _
class EventConfig(AppConfig):
name = 'event'
verbose_name = _("Évènement")
def ready(self):
from . import signals

View file

@ -1,8 +1,7 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-08-12 12:47
# Generated by Django 1.11.3 on 2017-08-17 12:21
from __future__ import unicode_literals
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
@ -13,7 +12,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('auth', '0008_alter_user_username_max_length'),
]
operations = [
@ -74,15 +73,25 @@ class Migration(migrations.Migration):
('slug', models.SlugField(help_text="Seulement des lettres, des chiffres ou les caractères '_' ou '-'.", unique=True, verbose_name='identificateur')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='date de création')),
('description', models.TextField(verbose_name='description')),
('beginning_date', models.DateTimeField(verbose_name='date de début')),
('ending_date', models.DateTimeField(verbose_name='date de fin')),
('created_by', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_events', to=settings.AUTH_USER_MODEL, verbose_name='créé par')),
('beginning_date', models.DateTimeField(help_text="date publique de l'évènement", verbose_name='date de début')),
('ending_date', models.DateTimeField(help_text="date publique de l'évènement", verbose_name='date de fin')),
],
options={
'verbose_name': 'évènement',
'verbose_name_plural': 'évènements',
},
),
migrations.CreateModel(
name='EventGroup',
fields=[
('group_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='auth.Group')),
('event', models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèle est spécifique à l'évènement en question.", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement')),
],
options={
'abstract': False,
},
bases=('auth.group', models.Model),
),
migrations.CreateModel(
name='Place',
fields=[
@ -96,49 +105,4 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'lieux',
},
),
migrations.AddField(
model_name='activitytemplate',
name='event',
field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'),
),
migrations.AddField(
model_name='activitytemplate',
name='places',
field=models.ManyToManyField(blank=True, to='event.Place', verbose_name='lieux'),
),
migrations.AddField(
model_name='activitytemplate',
name='tags',
field=models.ManyToManyField(blank=True, to='event.ActivityTag', verbose_name='tags'),
),
migrations.AddField(
model_name='activitytag',
name='event',
field=models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèle est spécifique à l'évènement en question.", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'),
),
migrations.AddField(
model_name='activity',
name='event',
field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'),
),
migrations.AddField(
model_name='activity',
name='parent',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='children', to='event.ActivityTemplate', verbose_name='template'),
),
migrations.AddField(
model_name='activity',
name='places',
field=models.ManyToManyField(blank=True, to='event.Place', verbose_name='lieux'),
),
migrations.AddField(
model_name='activity',
name='staff',
field=models.ManyToManyField(blank=True, related_name='in_perm_activities', to=settings.AUTH_USER_MODEL, verbose_name='permanents'),
),
migrations.AddField(
model_name='activity',
name='tags',
field=models.ManyToManyField(blank=True, to='event.ActivityTag', verbose_name='tags'),
),
]

View file

@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-08-17 12:21
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('event', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='event',
name='created_by',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_events', to=settings.AUTH_USER_MODEL, verbose_name='créé par'),
),
migrations.AddField(
model_name='activitytemplate',
name='event',
field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'),
),
migrations.AddField(
model_name='activitytemplate',
name='places',
field=models.ManyToManyField(blank=True, to='event.Place', verbose_name='lieux'),
),
migrations.AddField(
model_name='activitytemplate',
name='tags',
field=models.ManyToManyField(blank=True, to='event.ActivityTag', verbose_name='tags'),
),
migrations.AddField(
model_name='activitytag',
name='event',
field=models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèle est spécifique à l'évènement en question.", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'),
),
migrations.AddField(
model_name='activity',
name='event',
field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'),
),
migrations.AddField(
model_name='activity',
name='parent',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='children', to='event.ActivityTemplate', verbose_name='template'),
),
migrations.AddField(
model_name='activity',
name='places',
field=models.ManyToManyField(blank=True, to='event.Place', verbose_name='lieux'),
),
migrations.AddField(
model_name='activity',
name='staff',
field=models.ManyToManyField(blank=True, related_name='in_perm_activities', to=settings.AUTH_USER_MODEL, verbose_name='permanents'),
),
migrations.AddField(
model_name='activity',
name='tags',
field=models.ManyToManyField(blank=True, to='event.ActivityTag', verbose_name='tags'),
),
]

View file

@ -1,4 +1,5 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.core.exceptions import FieldDoesNotExist, FieldError
from django.db import models
from django.utils.translation import ugettext_lazy as _
@ -65,11 +66,16 @@ class EventSpecificMixin(models.Model):
on_delete=models.CASCADE,
blank=True, null=True,
)
needs_event_permissions = True
class Meta:
abstract = True
class EventGroup(EventSpecificMixin, Group):
pass
class Place(EventSpecificMixin, models.Model):
name = models.CharField(
_("nom du lieu"),
@ -161,12 +167,14 @@ class AbstractActivityTemplate(SubscriptionMixin, models.Model):
verbose_name=_('lieux'),
blank=True,
)
needs_event_permissions = True
class Meta:
abstract = True
class ActivityTemplate(AbstractActivityTemplate):
class Meta:
verbose_name = _("template activité")
verbose_name_plural = _("templates activité")

51
event/signals.py Normal file
View file

@ -0,0 +1,51 @@
from django.dispatch import receiver
from django.db.models.signals import post_save, post_migrate
from django.apps import apps
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Permission
from event.models import Event, EventGroup
from guardian.shortcuts import assign_perm
@receiver(post_save, sender=Event)
def create_groups_for_event(sender, **kwargs):
event, created = kwargs["instance"], kwargs["created"]
if created:
orgas = EventGroup.objects.create(
name="{}_orgas".format(event.slug),
event=event
)
for perm in Permission.objects.filter(
content_type=ContentType.objects.get_for_model(Event),
codename__contains="event_"):
assign_perm(perm.codename, orgas, event)
EventGroup.objects.create(
name="{}_participants".format(event.slug),
event=event,
)
@receiver(post_migrate)
def create_event_permissions(sender, **kwargs):
def event_specific_permissions():
opes = ['Add', 'Change', 'Delete']
models = [model.__name__.lower() for model in apps.get_models()
if getattr(model, 'needs_event_permissions', False)]
return [
('event_{}_{}'.format(op.lower(), model),
'{} {} for event'.format(op, model))
for op in opes
for model in models
]
content_type = ContentType.objects.get_for_model(Event)
for (code, verbose) in event_specific_permissions():
Permission.objects.get_or_create(
name=verbose,
content_type=content_type,
codename=code
)

View file

@ -1,10 +1,13 @@
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Permission
from django.core.exceptions import ValidationError
from django.test import TestCase
from datetime import timedelta
from django.utils import timezone
from .models import Event, ActivityTemplate, Activity, Place, \
from .models import Event, EventGroup, ActivityTemplate, Activity, Place, \
ActivityTag
from guardian.shortcuts import assign_perm
User = get_user_model()
@ -24,10 +27,8 @@ class ActivityInheritanceTest(TestCase):
created_by=cls.erkan,
created_at=timezone.now(),
description="La nuit c'est lol",
beginning_date=timezone.now()
+ timedelta(days=30),
ending_date=timezone.now()
+ timedelta(days=31),
beginning_date=timezone.now() + timedelta(days=30),
ending_date=timezone.now() + timedelta(days=31),
)
cls.loge = Place.objects.create(name="Loge 45")
cls.aqua = Place.objects.create(name="Aquarium")
@ -47,8 +48,7 @@ class ActivityInheritanceTest(TestCase):
self.real_act = Activity.objects.create(
parent=self.template_act,
event=self.event,
beginning=timezone.now()
+ timedelta(days=30),
beginning=timezone.now() + timedelta(days=30),
end=timezone.now()
+ timedelta(days=30)
+ timedelta(hours=2),
@ -155,3 +155,50 @@ class ActivityTagColorTest(TestCase):
)
with self.assertRaises(ValidationError):
self.tag.full_clean()
class EventPermissionTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.user_perm = User.objects.create(username="userperm")
cls.user_noperm = User.objects.create(username="usernoperm")
cls.user_groupperm = User.objects.create(username="usergroupperm")
cls.user_groupnoperm = User.objects.create(username="usergroupnoperm")
cls.root = User.objects.create_superuser(
username="root",
email="toto@toto.io",
password="toto"
)
cls.event = Event.objects.create(
title="Hackathon",
slug="django",
created_by=cls.root,
description="Le code c'est cool",
beginning_date=timezone.now(),
ending_date=timezone.now() + timedelta(days=1),
)
def test_event_groups(self):
groups = EventGroup.objects.filter(
event=self.event
)
self.assertEqual(groups.count(), 2)
def test_individual_perms(self):
assign_perm("event_add_place", self.user_perm, self.event)
self.assertTrue(self.user_perm.has_perm("event_add_place", self.event))
self.assertFalse(self.user_noperm.has_perm("event_add_place",
self.event))
def test_group_perms(self):
orgas = EventGroup.objects.get(
name="{}_orgas".format(self.event.slug),
)
self.user_groupperm.groups.add(orgas)
for perm in Permission.objects.filter(
content_type=ContentType.objects.get_for_model(Event),
codename__contains="event_"):
self.assertTrue(self.user_groupperm.has_perm(perm.codename,
self.event))
self.assertFalse(self.user_groupnoperm.has_perm(perm.codename,
self.event))

View file

@ -9,6 +9,7 @@ djangorestframework==3.6.3
drf-nested-routers==0.90.0
django-notifications
django-contrib-comments
django-guardian
# Production specific
daphne

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-07-23 14:14
# Generated by Django 1.11.3 on 2017-08-17 12:21
from __future__ import unicode_literals
import django.contrib.auth.models

View file

@ -15,3 +15,6 @@ class User(AbstractUser):
class Meta:
verbose_name = _("utilisateur")
verbose_name_plural = _("utilisateurs")