diff --git a/api/__init__.py b/api/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/api/event/__init__.py b/api/event/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/api/event/fields.py b/api/event/fields.py
new file mode 100644
index 0000000..4df025c
--- /dev/null
+++ b/api/event/fields.py
@@ -0,0 +1,33 @@
+from rest_framework import serializers
+from rest_framework.reverse import reverse
+
+
+class EventHyperlinkedFieldMixin:
+
+    def get_url(self, obj, view_name, request, format):
+        url_kwargs = {'pk': obj.pk}
+        if getattr(obj, 'event', None):
+            url_kwargs['event_pk'] = obj.event.pk
+        return reverse(
+            view_name, kwargs=url_kwargs, request=request, format=format)
+
+    def get_object(self, view_name, view_args, view_kwargs):
+        lookup_kwargs = {
+            'pk': view_kwargs['pk'],
+            'event_id': view_kwargs.get('event_pk'),
+        }
+        return self.get_queryset().get(**lookup_kwargs)
+
+
+class EventHyperlinkedRelatedField(
+            EventHyperlinkedFieldMixin,
+            serializers.HyperlinkedRelatedField,
+        ):
+    pass
+
+
+class EventHyperlinkedIdentityField(
+            EventHyperlinkedFieldMixin,
+            serializers.HyperlinkedIdentityField
+        ):
+    pass
diff --git a/api/event/serializers.py b/api/event/serializers.py
index 44461e6..a7ff849 100644
--- a/api/event/serializers.py
+++ b/api/event/serializers.py
@@ -1,53 +1,30 @@
-from django.shortcuts import get_object_or_404
+from django.db import transaction
+from django.utils.decorators import method_decorator
+
 from rest_framework import serializers
+
 from event.models import Event, ActivityTag, Place, ActivityTemplate
 
+from .fields import EventHyperlinkedIdentityField
+
 
 # Event Serializer
-class EventSerializer(serializers.HyperlinkedModelSerializer):
+class EventSerializer(serializers.ModelSerializer):
+    # TODO: Change this to a nested serializer ~(url, full_name) of User
     created_by = serializers.ReadOnlyField(source='created_by.get_full_name')
-    created_at = serializers.ReadOnlyField()
 
     class Meta:
         model = Event
-        fields = ('url', 'id', 'title', 'slug', 'created_by', 'created_at',
-                  'description', 'beginning_date', 'ending_date')
-
-
-# Classes utilitaires
-class EventSpecificSerializerMixin():
-    """
-    Provide `update` and `create` methods  for nested view with an Event
-    For example for Models which extends EventSpecificMixin
-    the event id has to be provided in the `save` method
-    Works fine with view.EventSpecificViewSet
-    Also provides :
-    event = eventserializer(allow_null=true, read_only=true)
-    """
-    event = EventSerializer(allow_null=True, read_only=True)
-
-    def update(self, instance, validated_data):
-        """
-        Note : does NOT change the event value of the instance
-        """
-        validated_data.pop('event_pk')
-        [setattr(instance, key, value)
-         for key, value in validated_data.items()]
-        instance.save()
-        return instance
-
-    def create(self, validated_data):
-        ModelClass = self.Meta.model
-        event_pk = validated_data.pop('event_pk', None)
-        event = event_pk and get_object_or_404(Event, id=event_pk) or None
-        instance = ModelClass.objects.create(event=event, **validated_data)
-        return instance
+        fields = (
+            'url', 'id', 'title', 'slug', 'created_by', 'created_at',
+            'description', 'beginning_date', 'ending_date',
+        )
 
 
 # Serializers
 # TODO rajouter des permissions
-class PlaceSerializer(EventSpecificSerializerMixin,
-                      serializers.ModelSerializer):
+class PlaceSerializer(serializers.ModelSerializer):
+    serializer_url_field = EventHyperlinkedIdentityField
 
     class Meta:
         model = Place
@@ -55,8 +32,9 @@ class PlaceSerializer(EventSpecificSerializerMixin,
 
 
 # TODO rajouter des permissions
-class ActivityTagSerializer(EventSpecificSerializerMixin,
-                            serializers.ModelSerializer):
+class ActivityTagSerializer(serializers.ModelSerializer):
+    serializer_url_field = EventHyperlinkedIdentityField
+
     class Meta:
         model = ActivityTag
         fields = ('url', 'id', 'name', 'is_public', 'color', 'event')
@@ -64,36 +42,30 @@ class ActivityTagSerializer(EventSpecificSerializerMixin,
 
 # TODO rajouter des permissions
 class ActivityTemplateSerializer(serializers.ModelSerializer):
-    event = EventSerializer(read_only=True)
     tags = ActivityTagSerializer(many=True)
 
+    serializer_url_field = EventHyperlinkedIdentityField
+
     class Meta:
         model = ActivityTemplate
-        fields = ('id', 'title', 'event', 'is_public', 'has_perm',
-                  'min_perm', 'max_perm', 'description', 'remarks', 'tags',)
+        fields = (
+            'url', 'id', 'title', 'event', 'is_public', 'has_perm', 'min_perm',
+            'max_perm', 'description', 'remarks', 'tags',
+        )
 
-    def update(self, instance, validated_data):
-        """
-        @tags comportement attendu : si l'id existe déjà on ne change pas
-        les autres champs et si l'id n'existe pas on le créé
-        """
-        tags_data = validated_data.pop('tags')
-        validated_data.pop('event_pk')
-        event = instance.event
-        [setattr(instance, key, value)
-         for key, value in validated_data.items()]
-        instance.save()
+    def process_tags(self, instance, tags_data):
         # TODO: en fonction de si backbone envoie un `id` ou non lorsque le tag
         # n'existe pas encore il faudra faire un premier passage sur `tags` i
         # pour s'assurer que le get ne foire pas le get si, par exemple, le tag
         # été modifié entre temps dans la base de donnée (mais pas sur la
         # classe backbone
+        tags = []
         for tag_data in tags_data:
-            tag_data.pop('event', None)
-        tags = [ActivityTag.objects.get_or_create(event=event, **tag_data)[0]
-                for tag_data in tags_data]
-        instance.tags.set(tags)
-        return instance
+            tag, _ = ActivityTag.objects.get_or_create(**tag_data, defaults={
+                'event': instance.event,
+            })
+            tags.append(tag)
+        instance.tags.add(*tags)
 
     def create(self, validated_data):
         """
@@ -101,20 +73,18 @@ class ActivityTemplateSerializer(serializers.ModelSerializer):
         les autres champs et si l'id n'existe pas on le créé
         """
         tags_data = validated_data.pop('tags')
-        event_pk = validated_data.pop('event_pk')
-        event = event_pk and get_object_or_404(Event, id=event_pk) or None
-        activity_template = ActivityTemplate.objects.create(event=event,
-                                                            **validated_data)
-        # TODO: en fonction de si backbone envoie un `id` ou non lorsque le tag
-        # n'existe pas encore il faudra faire un premier passage sur `tags` i
-        # pour s'assurer que le get ne foire pas le get si, par exemple, le tag
-        # été modifié entre temps dans la base de donnée (mais pas sur la
-        # classe backbone
-        for tag_data in tags_data:
-            tag_data.pop('event', None)
-        tags = [ActivityTag.objects.get_or_create(event=event, **tag_data)[0]
-                for tag_data in tags_data]
-        activity_template.tags = tags
+        activity_template = super().create(validated_data)
+        self.process_tags(activity_template, tags_data)
+        return activity_template
+
+    def update(self, instance, validated_data):
+        """
+        @tags comportement attendu : si l'id existe déjà on ne change pas
+        les autres champs et si l'id n'existe pas on le créé
+        """
+        tags_data = validated_data.pop('tags')
+        activity_template = super().update(instance, validated_data)
+        self.process_tags(activity_template, tags_data)
         return activity_template
 
 
diff --git a/api/event/tests.py b/api/event/tests.py
index 147b185..ed748e1 100644
--- a/api/event/tests.py
+++ b/api/event/tests.py
@@ -1,56 +1,556 @@
+from datetime import datetime, timedelta
+
 from django.contrib.auth import get_user_model
+from django.utils import timezone
 
 from rest_framework.test import APITestCase
 
 from event.models import Event, Place, ActivityTag, ActivityTemplate
 
-from api.event.serializers import ActivityTemplateSerializer, EventSerializer
-from api.testcases import EventBasedModelTestMixin, EventSpecificTestMixin,\
-        ModelTestMixin
+from api.test.testcases import ModelAPITestCaseMixin
+from api.test.utils import json_format
 
 User = get_user_model()
 
 
-class EventTest(ModelTestMixin, APITestCase):
+class EventAPITests(ModelAPITestCaseMixin, APITestCase):
     model = Event
-    base_name = 'event'
-    tested_fields = {'title': "I'm a test", }
-    # Création
-    data_creation = 'event2_data'
-    # Update/Delete
-    instance_name = 'event1'
-    serializer = EventSerializer
+    url_label = 'event'
+
+    list_ordering = 'beginning_date'
+
+    @property
+    def user_auth_mapping(self):
+        return {
+            'create': self.user,
+        }
+
+    def get_expected_data(self, instance):
+        return json_format({
+            'url': (
+                'http://testserver/api/event/{pk}/'
+                .format(pk=instance.pk)
+            ),
+            'id': instance.id,
+            'title': instance.title,
+            'slug': instance.slug,
+            'description': instance.description,
+            'beginning_date': instance.beginning_date,
+            'ending_date': instance.ending_date,
+            'created_by': instance.created_by.get_full_name(),
+            'created_at': self.now,
+        })
+
+    @property
+    def instances_data(self):
+        return [
+            {
+                'title': "A first event",
+                'slug': 'first-event',
+                'description': "It's the first event.",
+                'beginning_date': self.now + timedelta(days=100),
+                'ending_date': self.now + timedelta(days=120),
+                'created_by': self.user,
+            },
+            {
+                'title': "Another event",
+                'slug': 'another-event',
+                'description': "It's another event.",
+                'beginning_date': self.now + timedelta(days=50),
+                'ending_date': self.now + timedelta(days=60),
+                'created_by': self.user,
+            },
+        ]
+
+    @property
+    def create_data(self):
+        return {
+            'title': "An Event",
+            'slug': 'event',
+            'description': "I am an event.",
+            'beginning_date': '2017-08-10 05:30:00',
+            'ending_date': '2017-10-21 17:16:20',
+        }
+
+    @property
+    def create_expected(self):
+        return {
+            **self.create_data,
+            'beginning_date': timezone.make_aware(
+                datetime(2017, 8, 10, 5, 30, 0)),
+            'ending_date': timezone.make_aware(
+                datetime(2017, 10, 21, 17, 16, 20)),
+            'id': 1,
+            'created_by': self.user,
+            'created_at': self.now,
+        }
+
+    @property
+    def update_data(self):
+        return {
+            'title': "An updated event.",
+            'slug': 'first-event',
+            'description': "It's the first updated event.",
+            'beginning_date': '2017-08-10 05:30:00',
+            'ending_date': '2017-10-21 17:16:20',
+            'created_by': 1,
+        }
+
+    @property
+    def update_expected(self):
+        return {
+            **self.update_data,
+            'beginning_date': timezone.make_aware(
+                datetime(2017, 8, 10, 5, 30, 0)),
+            'ending_date': timezone.make_aware(
+                datetime(2017, 10, 21, 17, 16, 20)),
+            'id': 1,
+            'created_by': self.user,
+            'created_at': self.now,
+        }
 
 
-class ActivityTemplateTest(EventBasedModelTestMixin, APITestCase):
+class ActivityTemplateAPITests(ModelAPITestCaseMixin, APITestCase):
     model = ActivityTemplate
-    base_name = 'event-activitytemplate'
-    initial_count = 1
-    # Creation
-    data_creation = 'act_temp2_data'
-    # Update/Delete
-    instance_name = 'act_temp1'
-    field_tested = 'title'
-    serializer = ActivityTemplateSerializer
+    url_label = 'activitytemplate'
 
-    def test_create_extra(self):
-        self.assertEqual(self.model.objects.get(id=1).tags.count(), 1)
+    list_ordering = 'title'
 
-    def pre_update_extra(self, data):
-        data['tags'].append(self.tag2_data)
+    def setUp(self):
+        super().setUp()
+
+        self.event = Event.objects.create(**{
+            'title': "event1",
+            'slug': "slug1",
+            'beginning_date': self.now + timedelta(days=30),
+            'description': "C'est trop cool !",
+            'ending_date': self.now + timedelta(days=31),
+            'created_by': self.user,
+        })
+
+        self.activity_tag = ActivityTag.objects.create(**{
+            'name': "tag2",
+            'is_public': False,
+            'color': "#222",
+        })
+
+    def get_url_model(self, *args, **kwargs):
+        kwargs['event_pk'] = 1
+        return super().get_url_model(*args, **kwargs)
+
+    def get_url_object(self, *args, **kwargs):
+        kwargs['event_pk'] = 1
+        return super().get_url_object(*args, **kwargs)
+
+    def get_expected_data(self, instance):
+        return json_format({
+            'url': (
+                'http://testserver/api/event/{event_pk}/template/{pk}/'
+                .format(event_pk=instance.event.pk, pk=instance.pk)
+            ),
+            'id': instance.id,
+            'title': instance.title,
+            'is_public': instance.is_public,
+            'remarks': instance.remarks,
+            'event': instance.event.pk,
+            'tags': [
+                {
+                    **(tag.event and {
+                        'url': (
+                            'http://testserver/api/event/{event_pk}/'
+                            'tag/{tag_pk}/'
+                            .format(event_pk=tag.event.pk, tag_pk=tag.pk)
+                        ),
+                        'event': tag.event.pk,
+                    } or {
+                        'url': (
+                            'http://testserver/api/tag/{tag_pk}/'
+                            .format(tag_pk=tag.pk)
+                        ),
+                        'event': None,
+                    }),
+                    'id': tag.id,
+                    'name': tag.name,
+                    'is_public': tag.is_public,
+                    'color': tag.color,
+                }
+                for tag in instance.tags.all()
+            ],
+            'has_perm': instance.has_perm,
+            'min_perm': instance.min_perm,
+            'max_perm': instance.max_perm,
+            'description': instance.description,
+        })
+
+    @property
+    def instances_data(self):
+        return [
+            {
+                'title': "act temp1",
+                'is_public': True,
+                'remarks': "test remark",
+                'event': self.event,
+            },
+            {
+                'title': "act temp2",
+                'is_public': False,
+                'remarks': "test remark",
+                'event': self.event,
+                'tags': [self.activity_tag]
+            },
+        ]
+
+    @property
+    def create_data(self):
+        return {
+            'title': "act temp2",
+            'is_public': False,
+            'remarks': "test remark",
+            'tags': [
+                {
+                    'name': "tag2",
+                    'is_public': False,
+                    'color': '#222',
+                },
+            ],
+        }
+
+    @property
+    def create_expected(self):
+        return {
+            **self.create_data,
+            'tags': [ActivityTag.objects.get(name='tag2')],
+        }
+
+    @property
+    def update_data(self):
+        return {
+            'title': "act temp3",
+            'is_public': False,
+            'remarks': "another test remark",
+            'tags': [
+                {
+                    'name': "tag2",
+                    'is_public': False,
+                    'color': '#222',
+                },
+                {
+                    'name': "body",
+                    'is_public': True,
+                    'color': '#555',
+                },
+            ],
+        }
+
+    @property
+    def update_expected(self):
+        tag_root = ActivityTag.objects.get(name='tag2')
+        self.assertIsNone(tag_root.event)
+
+        tag_bound = ActivityTag.objects.get(name='body')
+        self.assertEqual(tag_bound.event, self.event)
+
+        return {
+            'title': "act temp3",
+            'is_public': False,
+            'remarks': "another test remark",
+            'tags': [tag_root, tag_bound]
+        }
+
+
+class BaseActivityTagAPITests:
+    model = ActivityTag
+    url_label = 'activitytag'
+
+    list_ordering = ('is_public', 'name')
+
+    def setUp(self):
+        super().setUp()
+
+        self.event = Event.objects.create(**{
+            'title': "event1",
+            'slug': "slug1",
+            'beginning_date': self.now + timedelta(days=30),
+            'description': "C'est trop cool !",
+            'ending_date': self.now + timedelta(days=31),
+            'created_by': self.user,
+        })
+
+    def get_expected_data(self, instance):
+        return {
+            **(instance.event and {
+                'url': (
+                    'http://testserver/api/event/{event_pk}/tag/{pk}/'
+                    .format(event_pk=instance.event.pk, pk=instance.pk)
+                ),
+                'event': instance.event.pk,
+            } or {
+                'url': (
+                    'http://testserver/api/tag/{pk}/'
+                    .format(pk=instance.pk)
+                ),
+                'event': None,
+            }),
+            'id': instance.id,
+            'name': instance.name,
+            'is_public': instance.is_public,
+            'color': instance.color,
+        }
+
+    @property
+    def instances_data(self):
+        return [
+            {
+                'name': 'a tag',
+                'is_public': False,
+                'color': '#222',
+                'event': None,
+            },
+            {
+                'name': 'another tag',
+                'is_public': True,
+                'color': '#555',
+                'event': self.event,
+            }
+        ]
+
+    @property
+    def create_data(self):
+        return {
+            'name': 'plop tag',
+            'is_public': True,
+            'color': '#888999',
+        }
+
+    @property
+    def update_data(self):
+        return {
+            'name': 'this is the tag',
+            'is_public': True,
+            'color': '#333',
+        }
+
+
+class RootActivityTagAPITests(
+            BaseActivityTagAPITests,
+            ModelAPITestCaseMixin,
+            APITestCase
+        ):
+
+    @property
+    def list_expected(self):
+        return [ActivityTag.objects.get(name='a tag')]
+
+    @property
+    def create_expected(self):
+        return {
+            **self.create_data,
+            'event': None,
+        }
+
+    @property
+    def instance_data(self):
+        data = self.instances_data[0]
+        self.assertIsNone(
+            data['event'],
+            msg="This test should use a tag unbound to any event.",
+        )
         return data
 
-    def post_update_extra(self, instance):
-        self.assertEqual(instance.tags.count(), 2)
+    @property
+    def update_expected(self):
+        return {
+            **self.update_data,
+            'event': None,
+        }
 
 
-class EventSpecficTagTest(EventSpecificTestMixin, APITestCase):
-    model = ActivityTag
-    root_base_name = 'activitytag'
-    event_base_name = 'event-activitytag'
+class EventActivityTagAPITests(
+            BaseActivityTagAPITests,
+            ModelAPITestCaseMixin,
+            APITestCase
+        ):
+
+    def get_url_model(self, *args, **kwargs):
+        kwargs['event_pk'] = 1
+        return super().get_url_model(*args, **kwargs)
+
+    def get_url_object(self, *args, **kwargs):
+        kwargs['event_pk'] = 1
+        return super().get_url_object(*args, **kwargs)
+
+    @property
+    def list_expected(self):
+        return [
+            ActivityTag.objects.get(name='a tag'),
+            ActivityTag.objects.get(name='another tag'),
+        ]
+
+    @property
+    def create_expected(self):
+        return {
+            **self.create_data,
+            'event': self.event,
+        }
+
+    @property
+    def instance_data(self):
+        data = self.instances_data[1]
+        self.assertIsNotNone(
+            data['event'],
+            msg="This test should use an event-bound tag.",
+        )
+        return data
+
+    @property
+    def update_expected(self):
+        return {
+            **self.update_data,
+            'event': self.event,
+        }
 
 
-class EventSpecficPlaceTest(EventSpecificTestMixin, APITestCase):
+class BasePlaceAPITests:
     model = Place
-    root_base_name = 'place'
-    event_base_name = 'event-place'
+    url_label = 'place'
+
+    list_ordering = 'name'
+
+    def setUp(self):
+        super().setUp()
+
+        self.event = Event.objects.create(**{
+            'title': "event1",
+            'slug': "slug1",
+            'beginning_date': self.now + timedelta(days=30),
+            'description': "C'est trop cool !",
+            'ending_date': self.now + timedelta(days=31),
+            'created_by': self.user,
+        })
+
+    def get_expected_data(self, instance):
+        return {
+            **(instance.event and {
+                'url': (
+                    'http://testserver/api/event/{event_pk}/place/{pk}/'
+                    .format(event_pk=instance.event.pk, pk=instance.pk)
+                ),
+                'event': instance.event.pk,
+            } or {
+                'url': (
+                    'http://testserver/api/place/{pk}/'
+                    .format(pk=instance.pk)
+                ),
+                'event': None,
+            }),
+            'id': instance.id,
+            'name': instance.name,
+            'description': instance.description,
+        }
+
+    @property
+    def instances_data(self):
+        return [
+            {
+                'name': 'a place',
+                'event': None,
+                'description': 'a description',
+            },
+            {
+                'name': 'another place',
+                'event': self.event,
+            }
+        ]
+
+    @property
+    def create_data(self):
+        return {
+            'name': 'plop place',
+            'description': 'the couro is a chill place',
+        }
+
+    @property
+    def update_data(self):
+        return {
+            'name': 'this is the place',
+            'description': 'to be',
+        }
+
+
+class RootPlaceAPITests(
+            BasePlaceAPITests,
+            ModelAPITestCaseMixin,
+            APITestCase
+        ):
+
+    @property
+    def list_expected(self):
+        return [Place.objects.get(name='a place')]
+
+    @property
+    def create_expected(self):
+        return {
+            **self.create_data,
+            'event': None,
+        }
+
+    @property
+    def instance_data(self):
+        data = self.instances_data[0]
+        self.assertIsNone(
+            data['event'],
+            msg="This test should use a place unbound to any event.",
+        )
+        return data
+
+    @property
+    def update_expected(self):
+        return {
+            **self.update_data,
+            'event': None,
+        }
+
+
+class EventPlaceTagAPITests(
+            BasePlaceAPITests,
+            ModelAPITestCaseMixin,
+            APITestCase
+        ):
+
+    def get_url_model(self, *args, **kwargs):
+        kwargs['event_pk'] = 1
+        return super().get_url_model(*args, **kwargs)
+
+    def get_url_object(self, *args, **kwargs):
+        kwargs['event_pk'] = 1
+        return super().get_url_object(*args, **kwargs)
+
+    @property
+    def list_expected(self):
+        return [
+            Place.objects.get(name='a place'),
+            Place.objects.get(name='another place'),
+        ]
+
+    @property
+    def create_expected(self):
+        return {
+            **self.create_data,
+            'event': self.event,
+        }
+
+    @property
+    def instance_data(self):
+        data = self.instances_data[1]
+        self.assertIsNotNone(
+            data['event'],
+            msg="This test should use an event-bound place.",
+        )
+        return data
+
+    @property
+    def update_expected(self):
+        return {
+            **self.update_data,
+            'event': self.event,
+        }
diff --git a/api/event/views.py b/api/event/views.py
index b360583..3497f3a 100644
--- a/api/event/views.py
+++ b/api/event/views.py
@@ -1,17 +1,54 @@
 from django.contrib.auth import get_user_model
 from django.db.models import Q
+from django.shortcuts import get_object_or_404
+from django.utils.functional import cached_property
 
 from rest_framework.viewsets import ModelViewSet
+from rest_framework.filters import OrderingFilter
 
-from api.event.serializers import EventSerializer, PlaceSerializer,\
-    ActivityTagSerializer, ActivityTemplateSerializer, ActivitySerializer
-from event.models import Event, Place, ActivityTag, ActivityTemplate, Activity
+from event.models import Activity, ActivityTag, ActivityTemplate, Event, Place
+
+from .serializers import (
+    ActivitySerializer, ActivityTagSerializer, ActivityTemplateSerializer,
+    EventSerializer, PlaceSerializer,
+)
 
 User = get_user_model()
 
 
 # classes utilitaires
-class EventSpecificViewSet(ModelViewSet):
+
+class EventUrlViewSetMixin:
+    """
+    ViewSet mixin to handle the evenk_pk from url.
+    """
+
+    @cached_property
+    def event(self):
+        event_pk = self.kwargs.get('event_pk')
+        if event_pk:
+            return get_object_or_404(Event, pk=event_pk)
+        return None
+
+
+class EventModelViewSetMixin:
+
+    def perform_create(self, serializer):
+        serializer.save(event=self.event)
+
+    def perform_update(self, serializer):
+        serializer.save(event=self.event)
+
+
+class EventModelViewSet(
+            EventModelViewSetMixin,
+            EventUrlViewSetMixin,
+            ModelViewSet,
+        ):
+    pass
+
+
+class EventSpecificModelViewSet(EventModelViewSet):
     """
     ViewSet that returns :
     * rootlevel objects if no Event is specified
@@ -21,81 +58,64 @@ class EventSpecificViewSet(ModelViewSet):
     to the save method. Works fine with serializers.EventSpecificSerializer
     Useful for models that extends EventSpecificMixin
     """
+
     def get_queryset(self):
         """
         Warning : You may want to override this method
         and not call with super
         """
-        event_pk = self.kwargs.get('event_pk')
         queryset = super().get_queryset()
-        if event_pk:
-            return queryset.filter(Q(event=event_pk) | Q(event=None))
-        return queryset.filter(event=None)
-
-    def perform_create(self, serializer):
-        event_pk = self.kwargs.get('event_pk')
-        serializer.save(event_pk=event_pk)
-
-    def perform_update(self, serializer):
-        event_pk = self.kwargs.get('event_pk')
-        serializer.save(event_pk=event_pk)
+        filters = Q(event=None)
+        if self.event:
+            filters |= Q(event=self.event)
+        return queryset.filter(filters)
 
 
 # ViewSets
 class EventViewSet(ModelViewSet):
-    """
-    This viewset automatically provides `list`, `create`, `retrieve`,
-    `update` and `destroy` actions.
-
-    """
     queryset = Event.objects.all()
     serializer_class = EventSerializer
 
+    filter_backends = (OrderingFilter,)
+    ordering_fields = ('title', 'creation_date', 'beginning_date',
+                       'ending_date', )
+    ordering = ('beginning_date', )
+
     def perform_create(self, serializer):
         serializer.save(created_by=self.request.user)
 
 
-class PlaceViewSet(EventSpecificViewSet):
+class PlaceViewSet(EventSpecificModelViewSet):
     queryset = Place.objects.all()
     serializer_class = PlaceSerializer
 
+    filter_backends = (OrderingFilter,)
+    ordering_fields = ('name', )
+    ordering = ('name', )
 
-class ActivityTagViewSet(EventSpecificViewSet):
+
+class ActivityTagViewSet(EventSpecificModelViewSet):
     queryset = ActivityTag.objects.all()
     serializer_class = ActivityTagSerializer
 
+    filter_backends = (OrderingFilter,)
+    ordering_fields = ('is_public', 'name', )
+    ordering = ('is_public', 'name', )
 
-class ActivityTemplateViewSet(ModelViewSet):
-    """
-    This viewset automatically provides `list`, `create`, `retrieve`,
-    `update` and `destroy` actions.
 
-    """
+class ActivityTemplateViewSet(EventModelViewSet):
     queryset = ActivityTemplate.objects.all()
     serializer_class = ActivityTemplateSerializer
 
-    def perform_create(self, serializer):
-        event_pk = self.kwargs.get('event_pk')
-        serializer.save(event_pk=event_pk)
-
-    def perform_update(self, serializer):
-        event_pk = self.kwargs.get('event_pk')
-        serializer.save(event_pk=event_pk)
+    filter_backends = (OrderingFilter,)
+    ordering_fields = ('title', )
+    ordering = ('title', )
 
 
-class ActivityViewSet(ModelViewSet):
-    """
-    This viewset automatically provides `list`, `create`, `retrieve`,
-    `update` and `destroy` actions.
-
-    """
+class ActivityViewSet(EventModelViewSet):
     queryset = Activity.objects.all()
     serializer_class = ActivitySerializer
 
-    def perform_create(self, serializer):
-        event_pk = self.kwargs.get('event_pk')
-        serializer.save(event_pk=event_pk)
-
-    def perform_update(self, serializer):
-        event_pk = self.kwargs.get('event_pk')
-        serializer.save(event_pk=event_pk)
+    filter_backends = (OrderingFilter,)
+    ordering_fields = ('title', )
+    ordering = ('title', )
diff --git a/api/test/testcases.py b/api/test/testcases.py
new file mode 100644
index 0000000..fb879e4
--- /dev/null
+++ b/api/test/testcases.py
@@ -0,0 +1,694 @@
+from unittest import mock
+
+from django.contrib.auth import get_user_model
+from django.utils import timezone
+from django.urls import reverse
+
+from rest_framework import status
+
+User = get_user_model()
+
+
+class GenericAPITestCaseMixin:
+    """
+    Base mixin for testing one or more operations on a model.
+
+    The specifics of each operation is not implemented here.
+    Each operation has its own TestCase, which relies on methods/attributes
+    defined here (some or all).
+
+    The "real" mixins for each operation are:
+    [List,Create,Retrieve,Update,Destroy]APITestCaseMixin.
+
+    Example:
+
+        E.g. for a creation test, your testcase should be something like:
+
+        class MyModelCreateAPITestCase(
+                CreateAPITestCaseMixin,
+                GenericAPITestCaseMixin,
+                APITestCase,
+                ):
+            ...
+
+    Attributes:
+        # General
+        model (models.Model): The model class under test.
+        url_label (str): Use to get the API urls of the model under test:
+            - The model url is:`reverse('{url_label}-list).
+                This is used by `[List,Create]APITestCaseMixin`.
+                The url can also be customized by defining `get_url_model`
+                method.
+            - The url of a model object is: `reverse('{label}-detail')`.
+                This is used by `[Retrieve,Update,Destroy]APITestCaseMixin`.
+                The url can also be customized by defining `get_url_object`
+                method.
+
+        # Authentication
+        user_auth (User instance): If given, this user is authenticated for
+            each request sent to the API. Default to `None`.
+            See section 'Authenticate requests'.
+        user_auth_mapping (dict of str: User instance): Associates a user to
+            authenticate for each type of API request.
+            See section 'Authenticate requests'.
+
+    Authenticate requests:
+        Each real testcase call `get_user` method with a label for the
+        operation under test (e.g. UpdateAPITestCaseMixin will call
+        `get_user('update')`. If it returns a user, this latter is
+        authenticated with `force_authenticate` method of the test client.
+
+        The default behavior, which can be customized by replacing `get_user`
+        method, is to return `user_auth` attribute/property if not None. Else,
+        `user_auth_mapping[operation_type_label]` or None if key is missing.
+
+        A basic user is created during setUp. It can be found as `user`
+        attribute.
+
+    """
+    model = None
+    url_label = ''
+
+    user_auth = None
+    user_auth_mapping = {}
+
+    def setUp(self):
+        """
+        Prepare a test execution.
+
+        Don't forget to call super().setUp() if you define this in subclass.
+        """
+        # Dict comparison can be large.
+        self.maxDiff = 2000
+
+        # This will be returned by django.utils.timezone.now thanks to the mock
+        # in test methods.
+        # This can be useful to check auto_now and auto_now_add fields.
+        self.now = timezone.now()
+
+        # A very basic user.
+        self.user = User.objects.create_user(
+            username='user', password='user',
+            first_name='first', last_name='last',
+        )
+
+    def get_user(self, action):
+        """
+        Returns a user to authenticate for requests of type 'action'.
+
+        Property `user_auth` has precedence over the property
+        `user_auth_mapping`.
+
+        Args:
+            action (str): Operation label. See LCRUD TestCases for reserved
+                labels, before setting your own if you create a new common
+                operation TestCase.
+
+        Returns:
+            User instance | None:
+                If None, no user should be authenticated for this request.
+                Else, the returned user should be authenticated.
+        """
+        if self.user_auth is not None:
+            return self.user_auth
+        return self.user_auth_mapping.get(action)
+
+    def get_url_model(self, *args, **kwargs):
+        """
+        Returns the API url for the model.
+
+        Default to a reverse on `{url_label}-list`.
+
+        Used by `[List,Create]APITestCaseMixin`.
+
+        """
+        return reverse(
+            '{label}-list'.format(label=self.url_label),
+            args=args, kwargs=kwargs,
+        )
+
+    def get_url_object(self, *args, **kwargs):
+        """
+        Returns the API url for a particular object of model.
+
+        Default to a reverse on `{url_label}-detail`.
+
+        Used by `[Retrieve,Update,Destroy]APITestCaseMixin`.
+
+        """
+        return reverse(
+            '{label}-detail'.format(label=self.url_label),
+            args=args, kwargs=kwargs,
+        )
+
+    def db_create(self, data):
+        """
+        Create a model instance in DB from data given as argument.
+
+        `data` can define M2M fields.
+
+        Operations:
+            - Create object from non-M2M fields.
+            - Link the M2M fields with `set` on the created object.
+
+        Returns:
+            The created object.
+        """
+        fields, m2m_fields = {}, {}
+        for name, value in data.items():
+            if self.model._meta.get_field(name).many_to_many:
+                m2m_fields[name] = value
+            else:
+                fields[name] = value
+
+        instance = self.model.objects.create(**fields)
+        for name, value in m2m_fields.items():
+            getattr(instance, name).set(value)
+
+        return instance
+
+    def get_expected_data(self, instance):
+        """
+        Returns the data the API should reply for the object `instance`.
+
+        This should be the same result of the serializer used for the sent
+        request.
+
+        Must be defined by subclasses, except for DestroyAPITestCaseMixin.
+
+        Args:
+            instance (a `model` object)
+
+        Returns:
+            JSON-decoded data of this instance.
+
+        """
+        raise NotImplementedError(
+            "Subclass must implement 'get_expected_data(instance)' method."
+        )
+
+    def assertInstanceEqual(self, instance, expected):
+        """
+        Checks if instance verifies expected.
+
+        For each key/value pair of expected, it verifies that instance.key is
+        equal to value. `assertEqual` is used, except for M2M fields where
+        `assertQuerysetEqual(ordered=True, ...)` is used.
+
+        Args:
+            instance (`model` object): E.g. obtained from DB after a create
+                operation.
+            expected (dict): Expected data of instance.
+
+        """
+        for key, exp_value in expected.items():
+            field = self.model._meta.get_field(key)
+            value = getattr(instance, key)
+            if field.many_to_many:
+                self.assertQuerysetEqual(
+                    value.all(), map(repr, exp_value),
+                    ordered=False,
+                )
+            else:
+                self.assertEqual(value, exp_value)
+
+    @property
+    def instances_data(self):
+        """
+        Instances data of the model which will be created if necessary.
+
+        For example, ListAPITestCaseMixin uses these before sending the list
+        request.
+
+        The first is also used, if `instance_data` is not re-defined, for RUD
+        operations.
+
+        """
+        raise NotImplementedError(
+            "Property or attribute 'instances_data' must be declared by "
+            "subclass."
+        )
+
+    @property
+    def instance_data(self):
+        """
+        Data of a single instance of model.
+
+        For example, this data is used to create the instance of model to apply
+        RUD operations.
+
+        Default to the first item of the `instances_data` property.
+
+        """
+        return self.instances_data[0]
+
+
+class ListAPITestCaseMixin:
+    """
+    Mixin to test the "List" API request of a model.
+
+    Has hooks to check the returned objects and their ordering.
+
+    Authentication:
+        This operation use the label 'list'.
+        See `GenericAPITestCaseMixin`#`Authenticate requests` for further
+        informations.
+
+    Attributes:
+        list_ordering (list of str | str): The ordering of objects that should
+            be respected by the API response.
+            Same format as the ordering Meta attribute of models, e.g.
+            '-created_at' or ['-event', 'created_at'].
+            Ordering on '__' fields are currently not supported.
+
+    Todo:
+        * Allow '__' in ordering.
+        * Support pages. Currently we expect returned objects are on a single
+            page.
+
+    """
+    list_ordering = []
+
+    @property
+    def list_expected(self):
+        """
+        Model instances the API should returned.
+
+        Default to all objects of models.
+        Eventually sorted according to `list_ordering` attribute.
+
+        """
+        qs = self.model.objects.all()
+        if self.list_ordering:
+            qs = qs.order_by(self.list_ordering)
+        return qs
+
+    @property
+    def list_expected_count(self):
+        """
+        Number of objects the API should returned.
+
+        Default to the length of expected returned objects.
+        """
+        return len(self.list_expected)
+
+    @property
+    def list_expected_ordering(self):
+        """
+        Use this to get expected ordering, instead of relying directly on the
+        `list_ordering`, if you write subclasses.
+        """
+        if isinstance(self.list_ordering, (tuple, list)):
+            return self.list_ordering
+        return [self.list_ordering]
+
+    def assertOrdered(self, results, ordering):
+        """
+        Check `results` are sorted according to `ordering`.
+
+        Attributes:
+            results (list of model instances)
+            ordering (list of fields as str)
+
+        """
+        _ordering = [
+            (f[1:], True) if f.startswith('-') else (f, False)
+            for f in ordering
+        ]
+
+        it = iter(results)
+
+        previous = next(it)
+
+        for current in it:
+            self._assertOrdered(previous, current, _ordering)
+            previous = current
+
+    def _assertOrdered(self, d1, d2, ordering):
+        if not ordering:
+            return True
+
+        field, desc = ordering[0]
+
+        v1, v2 = d1[field], d2[field]
+
+        if v1 == v2:
+            self._assertOrdered(v1, v2, ordering[1:])
+        elif desc:
+            self.assertGreater(v1, v2, msg=(
+                "Ordering on '%s' (DESC) is not respected for the following "
+                "objects.\n- First: %r.\n- Second: %r."
+                % (field, d1, d2)
+            ))
+        else:
+            self.assertLess(v1, v2, msg=(
+                "Ordering on '%s' (ASC) is not respected for the following "
+                "objects:\n- First: %r.\n- Second: %r."
+                % (field, d1, d2)
+            ))
+
+    @mock.patch('django.utils.timezone.now')
+    def test_list(self, mock_now):
+        """
+        The test.
+
+        Preparation:
+            - Create instances from `instances_data` property.
+            - Optionally, authenticate client.
+
+        Execution:
+            - HTTP GET request on model url.
+
+        Check:
+            - Response status code: 200.
+            - The base response: count (pages not supported).
+            - Results (serialized instances) are formatted as expected and
+                their ordering.
+
+        """
+        mock_now.return_value = self.now
+
+        # Setup database.
+
+        for data in self.instances_data:
+            self.db_create(data)
+
+        # Call API to get instances list.
+        user = self.get_user('list')
+        if user:
+            self.client.force_authenticate(user)
+
+        r = self.client.get(self.get_url_model())
+
+        # Check API response.
+        self.assertEqual(r.status_code, status.HTTP_200_OK)
+
+        base_response = {
+            'count': self.list_expected_count,
+            'previous': None,
+            'next': None,
+        }
+        self.assertDictContainsSubset(base_response, r.data)
+
+        results = r.data['results']
+
+        # Check API response ordering.
+        self.assertOrdered(results, self.list_expected_ordering)
+
+        # Check API response data.
+        for result, instance in zip(results, self.list_expected):
+            self.assertDictEqual(result, self.get_expected_data(instance))
+
+
+class CreateAPITestCaseMixin:
+    """
+    Mixin to test the "Create" API request of a model.
+
+    Authentication:
+        This operation use the label 'create'.
+        See `GenericAPITestCaseMixin`#`Authenticate requests` for further
+        informations.
+
+    Note:
+        The request is sent assuming the payload is JSON.
+
+    """
+
+    @property
+    def create_data(self):
+        """
+        Payload of the create request sent to the API.
+        """
+        raise NotImplementedError(
+            "Subclass must define a 'create_data' attribute/property. This is "
+            "the payload of the POST request to the model url."
+        )
+
+    @property
+    def create_expected(self):
+        """
+        Data of model instance which should be created.
+        """
+        raise NotImplementedError(
+            "Subclass must define a create_expected attribute/property. It is "
+            "a dict-like object whose value should be equal to the key "
+            "attribute of the created instance."
+        )
+
+    @mock.patch('django.utils.timezone.now')
+    def test_create(self, mock_now):
+        """
+        The test.
+
+        Preparation:
+            - Optionally, authenticate client.
+
+        Execution:
+            - HTTP POST request on model url. Payload from `create_data`.
+
+        Check:
+            - Response status code: 201.
+            - Instance has been created in DB.
+            - Instance created is as expected (check with `create_expected`).
+            - Instance is correctly serialized (in response' data).
+
+        """
+        mock_now.return_value = self.now
+
+        # Call API to create an instance of model.
+        user = self.get_user('create')
+        if user:
+            self.client.force_authenticate(user)
+
+        r = self.client.post(self.get_url_model(), self.create_data)
+
+        # Check database.
+        instances = self.model.objects.all()
+        self.assertEqual(len(instances), 1)
+
+        instance = instances[0]
+        self.assertInstanceEqual(instance, self.create_expected)
+
+        # Check API response.
+        self.assertEqual(r.status_code, status.HTTP_201_CREATED)
+        self.assertDictEqual(r.data, self.get_expected_data(instance))
+
+
+class RetrieveAPITestCaseMixin:
+    """
+    Mixin to test the "Retrieve" API request of a model object.
+
+    Authentication:
+        This operation use the label 'retrieve'.
+        See `GenericAPITestCaseMixin`#`Authenticate requests` for further
+        informations.
+
+    """
+
+    @mock.patch('django.utils.timezone.now')
+    def test_retrieve(self, mock_now):
+        """
+        The test.
+
+        Preparation:
+            - Create a model instance from `instance_data` property.
+            - Optionally, authenticate client.
+
+        Execution:
+            - Get url of the object with its pk.
+            - HTTP GET request on this url.
+
+        Check:
+            - Response status code: 200.
+            - Instance is correctly serialized (in response' data).
+
+        """
+        mock_now.return_value = self.now
+
+        # Setup database.
+        data = self.instance_data
+        instance = self.db_create(data)
+
+        # Call API to retrieve the event data.
+        user = self.get_user('retrieve')
+        if user:
+            self.client.force_authenticate(user)
+
+        r = self.client.get(self.get_url_object(pk=1))
+
+        # Check API response.
+        self.assertEqual(r.status_code, status.HTTP_200_OK)
+        self.assertDictEqual(r.data, self.get_expected_data(instance))
+
+
+class UpdateAPITestCaseMixin:
+    """
+    Mixin to test the "Update" API request of a model.
+
+    Authentication:
+        This operation use the label 'update'.
+        See `GenericAPITestCaseMixin`#`Authenticate requests` for further
+        informations.
+
+    Notes:
+        * A single test using partial update / PATCH HTTP method is written.
+        * The request is sent assuming the payload is JSON.
+
+    Todo:
+        * Add test for update / PUT HTTP method.
+
+    """
+
+    @property
+    def update_data(self):
+        """
+        Payload of the update request sent to the API.
+        """
+        raise NotImplementedError(
+            "Subclass must define a update_data attribute/property. This is "
+            "the payload of the PUT request to the instance url."
+        )
+
+    @property
+    def update_expected(self):
+        """
+        Data of model instance which should be updated.
+        """
+        raise NotImplementedError(
+            "Subclass must define a update_expected attribute/property. It is "
+            "a dict-like object whose value should be equal to the key "
+            "attribute of the updated instance."
+        )
+
+    @mock.patch('django.utils.timezone.now')
+    def test_update(self, mock_now):
+        """
+        The test.
+
+        Preparation:
+            - Create a model instance from `instance_data` property.
+            - Optionally, authenticate client.
+
+        Execution:
+            - Get url of the object with its pk.
+            - HTTP PATCH request on this url. Payload from `update_data`.
+
+        Check:
+            - Response status code: 200.
+            - No instance has been created or deleted in DB.
+            - Updated instance is as expected (check with `update_expected`).
+            - Instance is correctly serialized (in response' data).
+
+        """
+        mock_now.return_value = self.now
+
+        # Setup database.
+        data = self.instance_data
+        instance = self.db_create(data)
+
+        # Call API to update the event.
+        user = self.get_user('update')
+        if user:
+            self.client.force_authenticate(user)
+
+        r = self.client.patch(self.get_url_object(pk=1), self.update_data)
+
+        # Check database.
+        instances = self.model.objects.all()
+        self.assertEqual(len(instances), 1)
+
+        instance.refresh_from_db()
+        self.assertInstanceEqual(instance, self.update_expected)
+
+        # Check API response.
+        self.assertEqual(r.status_code, status.HTTP_200_OK)
+        self.assertDictEqual(r.data, self.get_expected_data(instance))
+
+
+class DestroyAPITestCaseMixin:
+    """
+    Mixin to test the "Destroy" API request of a model.
+
+    Authentication:
+        This operation use the label 'destroy'.
+        See `GenericAPITestCaseMixin`#`Authenticate requests` for further
+        informations.
+
+    """
+
+    @mock.patch('django.utils.timezone.now')
+    def test_destroy(self, mock_now):
+        """
+        The test.
+
+        Preparation:
+            - Create a model instance from `instance_data` property.
+            - Optionally, authenticate client.
+
+        Execution:
+            - Get url of the object with its pk.
+            - HTTP DELETE request on this url.
+
+        Check:
+            - Response status code: 204.
+            - Instance is no longer present in DB.
+
+        """
+        mock_now.return_value = self.now
+
+        # Setup database.
+        data = self.instance_data
+        instance = self.db_create(data)
+
+        # Call API to update the event.
+        user = self.get_user('destroy')
+        if user:
+            self.client.force_authenticate(user)
+
+        r = self.client.delete(self.get_url_object(pk=1))
+
+        # Check database.
+        instances = self.model.objects.all()
+        self.assertEqual(len(instances), 0)
+
+        with self.assertRaises(self.model.DoesNotExist):
+            instance.refresh_from_db()
+
+        # Check API response.
+        self.assertEqual(r.status_code, status.HTTP_204_NO_CONTENT)
+        self.assertIsNone(r.data)
+
+
+class ModelAPITestCaseMixin(
+        ListAPITestCaseMixin, CreateAPITestCaseMixin, RetrieveAPITestCaseMixin,
+        UpdateAPITestCaseMixin, DestroyAPITestCaseMixin,
+        GenericAPITestCaseMixin,
+        ):
+    """
+    Tests all LCRUD operations on a model.
+
+    See general docs in GenericAPITestCaseMixin.
+    See docs for a specific operation in [Operation]APITestCaseMixin.
+
+    Example:
+
+        class EventAPITestCase(ModelAPITestCaseMixin, APITestCase):
+            model = Event
+            ...
+
+            @property
+            def create_data(self):
+                ...
+
+            @property
+            def create_expected(self):
+                ...
+
+            ...
+
+        If you want to run only a few operations, consider using
+        specific-mixins individually. You can still have something as
+        `EventAPITestCaseMixin` to provide common atributes/properties/methods.
+
+    """
+    pass
diff --git a/api/test/utils.py b/api/test/utils.py
new file mode 100644
index 0000000..d6778a3
--- /dev/null
+++ b/api/test/utils.py
@@ -0,0 +1,19 @@
+import datetime
+
+
+def _json_format(value):
+    if isinstance(value, datetime.datetime):
+        return value.isoformat().replace('+00:00', 'Z')
+    return value
+
+
+def json_format(to_format):
+    """
+    Returns value formatted like json output of the API.
+
+    Supported type of value:
+    * datetime
+    """
+    if type(to_format) == dict:
+        return {key: _json_format(value) for key, value in to_format.items()}
+    return _json_format(to_format)
diff --git a/api/testcases.py b/api/testcases.py
deleted file mode 100644
index d91f3cb..0000000
--- a/api/testcases.py
+++ /dev/null
@@ -1,292 +0,0 @@
-from datetime import timedelta
-from django.contrib.auth import get_user_model
-from django.db.models import Q
-from django.utils import timezone
-from django.urls import reverse
-
-from rest_framework.test import APIRequestFactory
-from rest_framework import status
-
-from event.models import Event, Place, ActivityTag, ActivityTemplate
-
-
-User = get_user_model()
-
-
-class DataBaseMixin(object):
-    """
-    provides a datatabse for API tests
-    """
-    @classmethod
-    def setUpTestData(cls):
-        # Users
-        cls.user1_data = {'username': "user1", 'password': "pass1"}
-        cls.user1 = User.objects.create_user(**cls.user1_data)
-
-    def setUp(self):
-        # Events
-        self.event1_data = {'title': "event1", 'slug': "slug1",
-                            'beginning_date': timezone.now()
-                            + timedelta(days=30),
-                            "description": "C'est trop cool !",
-                            'ending_date': timezone.now()+timedelta(days=31),
-                            'created_by': self.user1, }
-        self.event2_data = {"title": "test event", "slug": "test-event",
-                            "description": "C'est trop cool !",
-                            "beginning_date": "2017-07-18T18:05:00Z",
-                            "ending_date": "2017-07-19T18:05:00Z", }
-        self.event1 = Event.objects.create(**self.event1_data)
-
-        # ActivityTags
-        self.tag1_data = {"name": "tag1", "is_public": False, "color": "#111"}
-        self.tag2_data = {"name": "tag2", "is_public": False, "color": "#222"}
-        self.tag3_data = {"name": "tag3", "is_public": False, "color": "#333"}
-        self.tag1 = ActivityTag.objects.create(**self.tag1_data)
-
-        self.act_temp1_data = {'title': "act temp1", 'is_public': True,
-                               'remarks': "test remark", 'event': self.event1}
-        self.act_temp2_data = {'title': "act temp2", 'is_public': False,
-                               'remarks': "test remark",
-                               'tags': [self.tag2_data, ]}
-        self.act_temp1 = ActivityTemplate.objects.create(**self.act_temp1_data)
-        self.act_temp1.tags.add(self.tag1)
-
-
-class EventBasedModelTestMixin(DataBaseMixin):
-    """
-    Note : need to define :
-    `model`: the model served by the API
-    `base_name`: the base_name used in the URL
-    `initial_count`: (will disappear) inital count in the db
-    `data_creation`: name in db used to create new instance
-    `instance_name`: existing instance name in the db
-        used for update/delete
-    `field_tested`: name of field tested in the update test
-    `serializer`: serialiser used for the API
-
-    tests for models served by the API that are related to an event
-    and whose API urls are nested under ../event/<event_id>/%model
-    """
-    def user_create_extra(self):
-        """
-        extra test in creation by a permited user
-        """
-        pass
-
-    def pre_update_extra(self, data):
-        """
-        extra modification for the data sent for update
-        """
-        return data
-
-    def post_update_extra(self):
-        """
-        extra test for updated model
-        """
-        pass
-
-    def test_user_create(self):
-        """
-        ensure a permited user can create a new %model object using API
-        """
-        data = getattr(self, self.data_creation)
-
-        event_id = self.event1.id
-        url = reverse('{base_name}-list'.format(base_name=self.base_name),
-                      kwargs={'event_pk': event_id})
-        self.client.force_authenticate(user=self.user1)
-        response = self.client.post(url, data,
-                                    format='json')
-        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-        self.assertEqual(self.model.objects.count(), self.initial_count + 1)
-        self.assertEqual(self.model.objects.get(id=self.initial_count+1).event,
-                         self.event1)
-
-        self.user_create_extra()
-
-    def test_user_update(self):
-        """
-        ensure a permited user can update a new %model object using API
-        """
-        instance = getattr(self, self.instance_name)
-        factory = APIRequestFactory()
-
-        instance_id = instance.id
-        event_id = self.event1.id
-        url = reverse('{base_name}-list'.format(base_name=self.base_name),
-                      kwargs={'event_pk': event_id})
-        url = "%s%d/" % (url, instance_id)
-
-        request = factory.get(url)
-        data = self.serializer(instance, context={'request': request}).data
-
-        newvalue = "I'm a test"
-        data[self.field_tested] = newvalue
-
-        data = self.pre_update_extra(data)
-
-        self.client.force_authenticate(user=self.user1)
-        response = self.client.patch(url, data, format='json')
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-
-        instance.refresh_from_db()
-        self.assertEqual(getattr(instance, self.field_tested), newvalue)
-
-        self.post_update_extra(instance)
-
-    def test_user_delete(self):
-        """
-        ensure a permited user can delete a new %model object using API
-        """
-        instance = getattr(self, self.instance_name)
-
-        instance_id = instance.id
-        event_id = self.event1.id
-        url = reverse('{base_name}-list'.format(base_name=self.base_name),
-                      kwargs={'event_pk': event_id})
-        url = "%s%d/" % (url, instance_id)
-
-        self.client.force_authenticate(user=self.user1)
-        response = self.client.delete(url)
-        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(self.model.objects.count(), self.initial_count - 1)
-
-
-# TODO rajouter la gestion des permissions dans le Mixin
-# TODO rajouter un test pour s'assurer que les personnes non
-# connectées ne peuvent pas create/update/delete
-class EventSpecificTestMixin(object):
-    """
-    Tests is the EventSpecifics querysets are rendered correctly
-    using the API
-    Note : need to define :
-    `model`: the concerned model serve by the API
-    `root_base_name`: the base_name used in the root-level urls
-    `event_base_name`: the base_name used in the event-level urls
-
-    tests for  models served by the API that inherit EventSpecificMixin
-    """
-    @classmethod
-    def setUpTestData(cls):
-        # Users
-        cls.user1_data = {'username': "user1", 'password': "pass1"}
-        cls.user1 = User.objects.create_user(**cls.user1_data)
-        # Events
-        cls.event1_data = {'title': "event1", 'slug': "slug1",
-                           'beginning_date': timezone.now()
-                           + timedelta(days=30),
-                           'ending_date': timezone.now()+timedelta(days=31),
-                           'created_by': cls.user1, }
-        cls.event1 = Event.objects.create(**cls.event1_data)
-
-    def setUp(self):
-        # Tag
-        self.tag_root_data = {"name": "tag2", "is_public": False,
-                              "color": "#222"}
-        self.tag_event_data = {"name": "tag3", "is_public": False,
-                               "color": "#333", 'event': self.event1}
-        self.tag_root = ActivityTag.objects.create(**self.tag_root_data)
-        self.tag_event = ActivityTag.objects.create(**self.tag_event_data)
-
-        # Places
-        self.place_root_data = {'name': "place1", 'event': None, }
-        self.place_event_data = {'name': "place2", 'event': self.event1, }
-        self.place_root = Place.objects.create(**self.place_root_data)
-        self.place_event = Place.objects.create(**self.place_event_data)
-
-    def test_lists(self):
-        """
-        ensure that only root-level models are served under
-        api/%root_base_name/
-        and that root-level and event-level models are served under
-        api/event/<event_id>/%event_base_name/
-        """
-        event_id = self.event1.id
-        root_count = self.model.objects.filter(event=None).count()
-        event_count = (self.model.objects
-                       .filter(Q(event=self.event1) | Q(event=None)).count())
-
-        self.client.force_authenticate(user=self.user1)
-
-        url = reverse('{base}-list'.format(base=self.root_base_name))
-        response = self.client.get(url, format='json')
-        self.assertEqual(response.json()['count'], root_count)
-
-        event_id = self.event1.id
-        url = reverse('{base}-list'.format(base=self.event_base_name),
-                      kwargs={'event_pk': event_id})
-        response = self.client.get(url, format='json')
-        self.assertEqual(response.json()['count'], event_count)
-
-
-# TODO rajouter la gestion des permissions dans le Mixin
-# TODO rajouter un test pour s'assurer que les personnes non
-# connectées ne peuvent pas create/update/delete
-# TODO? essayer de factoriser avec EventBasedMixin ?
-# FIXME not working, peut être que le problème vient
-# du fait que les dates sont mal envoyées dans le data ? A voir.
-class ModelTestMixin(DataBaseMixin):
-    """
-    Note : need to define : `model`, `base_name`,
-    `instance_name`, `field_tested`, `serializer`
-
-    generic mixin for testing creation/update/delete
-    of models served by the API
-    """
-    def test_user_create(self):
-        """
-        ensure a permited user can create a new %model object using API
-        """
-        data = getattr(self, self.data_creation)
-        initial_count = self.model.objects.count()
-
-        url = reverse('{base_name}-list'.format(base_name=self.base_name))
-        self.client.force_authenticate(user=self.user1)
-        response = self.client.post(url, data)
-
-        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-        self.assertEqual(self.model.objects.count(), initial_count + 1)
-
-        instance = self.model.objects.get(id=initial_count+1)
-        for field in self.tested_fields.keys():
-            self.assertEqual(getattr(instance, field), data[field])
-
-    def test_user_update(self):
-        """
-        ensure a permited user can update a new %model object using API
-        """
-        instance = getattr(self, self.instance_name)
-        factory = APIRequestFactory()
-
-        instance_id = instance.id
-        url = reverse('{base_name}-detail'.format(base_name=self.base_name),
-                      kwargs={'pk': instance_id})
-
-        request = factory.get(url)
-        data = self.serializer(instance, context={'request': request}).data
-        for field, value in self.tested_fields.items():
-            data[field] = value
-
-        self.client.force_authenticate(user=self.user1)
-        response = self.client.put(url, data, format='json')
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-
-        instance.refresh_from_db()
-        for field, value in self.tested_fields.items():
-            self.assertEqual(getattr(instance, field), value)
-
-    def test_user_delete(self):
-        """
-        ensure a permited user can delete a new %model object using API
-        """
-        instance = getattr(self, self.instance_name)
-        initial_count = self.model.objects.count()
-
-        instance_id = instance.id
-        url = reverse('{base_name}-detail'.format(base_name=self.base_name),
-                      kwargs={'pk': instance_id})
-
-        self.client.force_authenticate(user=self.user1)
-        response = self.client.delete(url)
-        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(self.model.objects.count(), initial_count - 1)
diff --git a/api/urls.py b/api/urls.py
index 0409143..1a704a0 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -1,27 +1,26 @@
 from django.conf.urls import url, include
+
 from rest_framework_nested.routers import SimpleRouter, NestedSimpleRouter
-from api.event.views import EventViewSet, PlaceViewSet, ActivityTagViewSet,\
-        ActivityTemplateViewSet
 
-# Create a router and register our viewsets with it.
+from api.event import views
+
+
 router = SimpleRouter()
-router.register(r'event', EventViewSet, 'event')
-router.register(r'place', PlaceViewSet, 'place')
-router.register(r'activitytag', ActivityTagViewSet, 'activitytag')
+router.register(r'event', views.EventViewSet)
+router.register(r'place', views.PlaceViewSet)
+router.register(r'tag', views.ActivityTagViewSet)
 
-# Register nested router and register someviewsets vith it
+
+# Views behind /event/<event_pk>/...
 event_router = NestedSimpleRouter(router, r'event', lookup='event')
-event_router.register(r'place', PlaceViewSet, base_name='event-place')
-event_router.register(r'tag', ActivityTagViewSet, base_name='event-activitytag')
-event_router.register(r'activitytemplate', ActivityTemplateViewSet,
-                      base_name='event-activitytemplate')
+event_router.register(r'place', views.PlaceViewSet)
+event_router.register(r'tag', views.ActivityTagViewSet)
+event_router.register(r'template', views.ActivityTemplateViewSet)
 
 
-# The API URLs are now determined automatically by the router.
-# Additionally, we include the login URLs for the browsable API.
+# API URLconf: routers + auth for browsable API.
 urlpatterns = [
     url(r'^', include(router.urls)),
     url(r'^', include(event_router.urls)),
-    url(r'^api-auth/', include('rest_framework.urls',
-                               namespace='rest_framework'))
+    url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
 ]
diff --git a/api/users/__init__.py b/api/users/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/equipment/migrations/0001_initial.py b/equipment/migrations/0001_initial.py
index 00e5083..5e042ff 100644
--- a/equipment/migrations/0001_initial.py
+++ b/equipment/migrations/0001_initial.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Generated by Django 1.11.3 on 2017-07-21 14:20
+# Generated by Django 1.11.4 on 2017-08-12 12:47
 from __future__ import unicode_literals
 
 from django.db import migrations, models
@@ -65,6 +65,6 @@ class Migration(migrations.Migration):
         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'),
+            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'),
         ),
     ]
diff --git a/evenementiel/settings/common.py b/evenementiel/settings/common.py
index 8baa801..6d41864 100644
--- a/evenementiel/settings/common.py
+++ b/evenementiel/settings/common.py
@@ -78,6 +78,7 @@ REST_FRAMEWORK = {
         'rest_framework.permissions.AllowAny',
     ],
     'PAGE_SIZE': 10,
+    'TEST_REQUEST_DEFAULT_FORMAT': 'json',
 }
 
 ROOT_URLCONF = 'evenementiel.urls'
diff --git a/event/migrations/0001_initial.py b/event/migrations/0001_initial.py
index 79dfb71..2c8ea07 100644
--- a/event/migrations/0001_initial.py
+++ b/event/migrations/0001_initial.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Generated by Django 1.11.3 on 2017-07-21 14:20
+# Generated by Django 1.11.4 on 2017-08-12 12:47
 from __future__ import unicode_literals
 
 from django.conf import settings
@@ -22,12 +22,12 @@ class Migration(migrations.Migration):
             fields=[
                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                 ('title', models.CharField(blank=True, max_length=200, null=True, verbose_name="nom de l'activité")),
-                ('is_public', models.NullBooleanField()),
-                ('has_perm', models.NullBooleanField()),
+                ('is_public', models.NullBooleanField(verbose_name='est public')),
+                ('has_perm', models.NullBooleanField(verbose_name='inscription de permanents')),
                 ('min_perm', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='nombre minimum de permanents')),
                 ('max_perm', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='nombre maximum de permanents')),
-                ('description', models.TextField(blank=True, help_text='Public, Visible par tout le monde.', null=True, verbose_name='description')),
-                ('remarks', models.TextField(blank=True, help_text='Visible uniquement par les organisateurs', null=True, verbose_name='remarques')),
+                ('description', models.TextField(blank=True, help_text="Visible par tout le monde si l'événément est public.", null=True, verbose_name='description')),
+                ('remarks', models.TextField(blank=True, help_text='Visible uniquement par les organisateurs.', null=True, verbose_name='remarques')),
                 ('beginning', models.DateTimeField(verbose_name='heure de début')),
                 ('end', models.DateTimeField(verbose_name='heure de fin')),
             ],
@@ -41,8 +41,8 @@ class Migration(migrations.Migration):
             fields=[
                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                 ('name', models.CharField(max_length=200, verbose_name='nom du tag')),
-                ('is_public', models.BooleanField(help_text="Sert à faire une distinction dans l'affichage selon que cela soit destiné au public ou à l'équipe organisatrice")),
-                ('color', models.CharField(help_text='Rentrer une couleur en hexadécimal', max_length=7, validators=[django.core.validators.RegexValidator(message="La chaîne de caractère rentrée n'est pas une couleur en hexadécimal.", regex='^#(?:[0-9a-fA-F]{3}){1,2}$')], verbose_name='couleur')),
+                ('is_public', models.BooleanField(help_text="Sert à faire une distinction dans l'affichage selon que le tag soit destiné au public ou à l'organisation.", verbose_name='est public')),
+                ('color', models.CharField(help_text='Rentrer une couleur en hexadécimal (#XXX ou #XXXXXX).', max_length=7, validators=[django.core.validators.RegexValidator(message="La chaîne de caractère rentrée n'est pas une couleur en hexadécimal.", regex='^#(?:[0-9a-fA-F]{3}){1,2}$')], verbose_name='couleur')),
             ],
             options={
                 'verbose_name': 'tag',
@@ -54,12 +54,12 @@ class Migration(migrations.Migration):
             fields=[
                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                 ('title', models.CharField(blank=True, max_length=200, null=True, verbose_name="nom de l'activité")),
-                ('is_public', models.NullBooleanField()),
-                ('has_perm', models.NullBooleanField()),
+                ('is_public', models.NullBooleanField(verbose_name='est public')),
+                ('has_perm', models.NullBooleanField(verbose_name='inscription de permanents')),
                 ('min_perm', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='nombre minimum de permanents')),
                 ('max_perm', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='nombre maximum de permanents')),
-                ('description', models.TextField(blank=True, help_text='Public, Visible par tout le monde.', null=True, verbose_name='description')),
-                ('remarks', models.TextField(blank=True, help_text='Visible uniquement par les organisateurs', null=True, verbose_name='remarques')),
+                ('description', models.TextField(blank=True, help_text="Visible par tout le monde si l'événément est public.", null=True, verbose_name='description')),
+                ('remarks', models.TextField(blank=True, help_text='Visible uniquement par les organisateurs.', null=True, verbose_name='remarques')),
             ],
             options={
                 'verbose_name': 'template activité',
@@ -71,12 +71,12 @@ class Migration(migrations.Migration):
             fields=[
                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                 ('title', models.CharField(max_length=200, verbose_name="nom de l'évènement")),
-                ('slug', models.SlugField(help_text="Seulement des lettres, des chiffres oules caractères '_' ou '-'.", unique=True, verbose_name='identificateur')),
-                ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='date de création')),
+                ('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, on_delete=django.db.models.deletion.CASCADE, related_name='created_events', to=settings.AUTH_USER_MODEL)),
+                ('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')),
             ],
             options={
                 'verbose_name': 'évènement',
@@ -89,7 +89,7 @@ class Migration(migrations.Migration):
                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                 ('name', models.CharField(max_length=200, verbose_name='nom du lieu')),
                 ('description', models.TextField(blank=True)),
-                ('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')),
+                ('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={
                 'verbose_name': 'lieu',
@@ -99,46 +99,46 @@ class Migration(migrations.Migration):
         migrations.AddField(
             model_name='activitytemplate',
             name='event',
-            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='event.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='place',
-            field=models.ManyToManyField(blank=True, to='event.Place'),
+            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'),
+            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'),
+            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(on_delete=django.db.models.deletion.CASCADE, to='event.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(on_delete=django.db.models.deletion.CASCADE, related_name='children', to='event.ActivityTemplate'),
+            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='place',
-            field=models.ManyToManyField(blank=True, to='event.Place'),
+            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),
+            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'),
+            field=models.ManyToManyField(blank=True, to='event.ActivityTag', verbose_name='tags'),
         ),
     ]
diff --git a/event/migrations/0002_auto_20170723_1419.py b/event/migrations/0002_auto_20170723_1419.py
deleted file mode 100644
index 954dc74..0000000
--- a/event/migrations/0002_auto_20170723_1419.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11.3 on 2017-07-23 14:19
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('event', '0001_initial'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='activity',
-            name='parent',
-            field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='event.ActivityTemplate'),
-        ),
-    ]
diff --git a/event/migrations/0003_auto_20170726_1116.py b/event/migrations/0003_auto_20170726_1116.py
deleted file mode 100644
index e776702..0000000
--- a/event/migrations/0003_auto_20170726_1116.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11.3 on 2017-07-26 11:16
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('event', '0002_auto_20170723_1419'),
-    ]
-
-    operations = [
-        migrations.RenameField(
-            model_name='event',
-            old_name='creation_date',
-            new_name='created_at',
-        ),
-        migrations.AlterField(
-            model_name='activity',
-            name='parent',
-            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='event.ActivityTemplate'),
-        ),
-    ]
diff --git a/event/models.py b/event/models.py
index eebe9b3..7560ae1 100644
--- a/event/models.py
+++ b/event/models.py
@@ -1,36 +1,41 @@
 from django.contrib.auth import get_user_model
-from django.utils.translation import ugettext_lazy as _
-from django.core.validators import RegexValidator
-from django.core.exceptions import FieldError
+from django.core.exceptions import FieldDoesNotExist, FieldError
 from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
 from communication.models import SubscriptionMixin
 
+from .validators import ColorValidator
+
 User = get_user_model()
 
 
 class Event(SubscriptionMixin, models.Model):
     title = models.CharField(
-                    _("nom de l'évènement"),
-                    max_length=200,
-                )
+        _("nom de l'évènement"),
+        max_length=200,
+    )
     slug = models.SlugField(
-                    _('identificateur'),
-                    unique=True,
-                    help_text=_("Seulement des lettres, des chiffres ou"
-                                "les caractères '_' ou '-'."),
-                )
+        _("identificateur"),
+        unique=True,
+        help_text=_(
+            "Seulement des lettres, des chiffres ou les caractères '_' ou '-'."
+        ),
+    )
     created_by = models.ForeignKey(
-                    User,
-                    related_name="created_events",
-                    editable=False,
-                )
+        User,
+        verbose_name=_("créé par"),
+        on_delete=models.SET_NULL,
+        related_name="created_events",
+        editable=False, null=True,
+    )
     created_at = models.DateTimeField(
-                    _('date de création'),
-                    auto_now_add=True,
-                )
-    description = models.TextField(_('description'))
-    beginning_date = models.DateTimeField(_('date de début'))
-    ending_date = models.DateTimeField(_('date de fin'))
+        _("date de création"),
+        auto_now_add=True,
+    )
+    description = models.TextField(_("description"))
+    beginning_date = models.DateTimeField(_("date de début"))
+    ending_date = models.DateTimeField(_("date de fin"))
 
     class Meta:
         verbose_name = _("évènement")
@@ -45,12 +50,14 @@ class EventSpecificMixin(models.Model):
     or not (depending on whether the event field is null)"""
 
     event = models.ForeignKey(
-        'event.Event',
+        Event,
         verbose_name=_("évènement"),
-        help_text=_("Si spécifié, l'instance du modèle "
-                    "est spécifique à l'évènement en question"),
-        blank=True,
-        null=True
+        help_text=_(
+            "Si spécifié, l'instance du modèle est spécifique à l'évènement "
+            "en question."
+        ),
+        on_delete=models.CASCADE,
+        blank=True, null=True,
     )
 
     class Meta:
@@ -59,9 +66,9 @@ class EventSpecificMixin(models.Model):
 
 class Place(EventSpecificMixin, models.Model):
     name = models.CharField(
-                    _("nom du lieu"),
-                    max_length=200,
-                )
+        _("nom du lieu"),
+        max_length=200,
+    )
     description = models.TextField(blank=True)
 
     class Meta:
@@ -74,26 +81,22 @@ class Place(EventSpecificMixin, models.Model):
 
 class ActivityTag(EventSpecificMixin, models.Model):
     name = models.CharField(
-                    _("nom du tag"),
-                    max_length=200,
-                )
+        _("nom du tag"),
+        max_length=200,
+    )
     is_public = models.BooleanField(
-                    help_text=_("Sert à faire une distinction dans"
-                                " l'affichage selon que cela soit"
-                                " destiné au public ou à l'équipe"
-                                " organisatrice"),
-                )
-    color_regex = RegexValidator(
-                regex=r'^#(?:[0-9a-fA-F]{3}){1,2}$',
-                message=_("La chaîne de caractère rentrée n'est pas"
-                          " une couleur en hexadécimal."),
-                )
+        _("est public"),
+        help_text=_(
+            "Sert à faire une distinction dans l'affichage selon que le tag "
+            "soit destiné au public ou à l'organisation."
+        ),
+    )
     color = models.CharField(
-                    _('couleur'),
-                    max_length=7,
-                    validators=[color_regex],
-                    help_text=_("Rentrer une couleur en hexadécimal"),
-                )
+        _('couleur'),
+        max_length=7,
+        validators=[ColorValidator],
+        help_text=_("Rentrer une couleur en hexadécimal (#XXX ou #XXXXXX)."),
+    )
 
     class Meta:
         verbose_name = _("tag")
@@ -105,49 +108,53 @@ class ActivityTag(EventSpecificMixin, models.Model):
 
 class AbstractActivityTemplate(SubscriptionMixin, models.Model):
     title = models.CharField(
-                    _("nom de l'activité"),
-                    max_length=200,
-                    blank=True,
-                    null=True,
-                )
+        _("nom de l'activité"),
+        max_length=200,
+        blank=True, null=True,
+    )
     # FIXME: voir comment on traite l'héritage de `event`
-    event = models.ForeignKey(Event)
+    event = models.ForeignKey(
+        Event,
+        verbose_name=_("évènement"),
+        on_delete=models.CASCADE,
+        editable=False,
+    )
     is_public = models.NullBooleanField(
-                    blank=True,
-                )
+        _("est public"),
+        blank=True,
+    )
     has_perm = models.NullBooleanField(
-                    blank=True,
-                )
+        _("inscription de permanents"),
+        blank=True,
+    )
     min_perm = models.PositiveSmallIntegerField(
-                    _('nombre minimum de permanents'),
-                    blank=True,
-                    null=True,
-                )
+        _('nombre minimum de permanents'),
+        blank=True, null=True,
+    )
     max_perm = models.PositiveSmallIntegerField(
-                    _('nombre maximum de permanents'),
-                    blank=True,
-                    null=True,
-                )
+        _('nombre maximum de permanents'),
+        blank=True, null=True,
+    )
     description = models.TextField(
-                    _('description'),
-                    help_text=_("Public, Visible par tout le monde."),
-                    blank=True,
-                    null=True,
-                )
+        _('description'),
+        help_text=_("Visible par tout le monde si l'événément est public."),
+        blank=True, null=True,
+    )
     remarks = models.TextField(
-                    _('remarques'),
-                    help_text=_("Visible uniquement par les organisateurs"),
-                    blank=True,
-                    null=True,
-                )
+        _('remarques'),
+        help_text=_("Visible uniquement par les organisateurs."),
+        blank=True, null=True,
+    )
     tags = models.ManyToManyField(
-                    ActivityTag,
-                    blank=True,
-                )
-    place = models.ManyToManyField(
-                    Place,
-                    blank=True,
-                )
+        ActivityTag,
+        verbose_name=_('tags'),
+        blank=True,
+    )
+    places = models.ManyToManyField(
+        Place,
+        verbose_name=_('lieux'),
+        blank=True,
+    )
 
     class Meta:
         abstract = True
@@ -164,40 +171,42 @@ class ActivityTemplate(AbstractActivityTemplate):
 
 class Activity(AbstractActivityTemplate):
     parent = models.ForeignKey(
-                    ActivityTemplate,
-                    related_name="children",
-                    blank=True,
-                    null=True,
-                )
+        ActivityTemplate,
+        verbose_name=_("template"),
+        on_delete=models.PROTECT,
+        related_name="children",
+        blank=True, null=True,
+    )
     staff = models.ManyToManyField(
-                    User,
-                    related_name="in_perm_activities",
-                    blank=True,
-                )
+        User,
+        verbose_name=_("permanents"),
+        related_name="in_perm_activities",
+        blank=True,
+    )
 
     beginning = models.DateTimeField(_("heure de début"))
     end = models.DateTimeField(_("heure de fin"))
 
     def get_herited(self, attrname):
-        inherited_fields = [f.name for f in
-                            ActivityTemplate._meta.get_fields()]
-        m2m_fields = [f.name for f in ActivityTemplate._meta.get_fields()
-                      if f.many_to_many]
-        attr = getattr(self, attrname)
-        if attrname not in inherited_fields:
+        try:
+            tpl_field = ActivityTemplate._meta.get_field(attrname)
+        except FieldDoesNotExist:
             raise FieldError(
-                    _("%(attrname)s n'est pas un champ héritable"),
-                    params={'attrname': attrname},
-                )
-        elif attrname in m2m_fields:
-            if attr.exists():
-                return attr
+                "%(attrname)s field can't be herited.",
+                params={'attrname': attrname},
+            )
+
+        value = getattr(self, attrname)
+
+        if tpl_field.many_to_many:
+            if value.exists():
+                return value
             else:
                 return getattr(self.parent, attrname)
-        elif attr is None:
+        elif value is None:
             return getattr(self.parent, attrname)
         else:
-            return attr
+            return value
 
     class Meta:
         verbose_name = _("activité")
diff --git a/event/tests.py b/event/tests.py
index 36dee90..29c840f 100644
--- a/event/tests.py
+++ b/event/tests.py
@@ -97,14 +97,14 @@ class ActivityInheritanceTest(TestCase):
         self.assertEqual(self.real_act.get_herited('max_perm'), 1)
 
     def test_inherits_place(self):
-        self.template_act.place.add(self.loge)
+        self.template_act.places.add(self.loge)
         self.assertEqual(
-                self.real_act.get_herited('place').get(),
+                self.real_act.get_herited('places').get(),
                 self.loge
             )
-        self.real_act.place.add(self.aqua)
+        self.real_act.places.add(self.aqua)
         self.assertEqual(
-                self.real_act.get_herited('place').get(),
+                self.real_act.get_herited('places').get(),
                 self.aqua
             )
 
diff --git a/event/validators.py b/event/validators.py
new file mode 100644
index 0000000..46c2d19
--- /dev/null
+++ b/event/validators.py
@@ -0,0 +1,10 @@
+from django.core.validators import RegexValidator
+from django.utils.translation import ugettext_lazy as _
+
+
+ColorValidator = RegexValidator(
+    regex=r'^#(?:[0-9a-fA-F]{3}){1,2}$',
+    message=_(
+        "La chaîne de caractère rentrée n'est pas une couleur en hexadécimal."
+    ),
+)
diff --git a/users/migrations/__init__.py b/users/migrations/__init__.py
new file mode 100644
index 0000000..e69de29