Merge branch 'Qwann/Serializers' into Qwann/Serializers_event2

This commit is contained in:
Qwann 2017-07-26 15:51:59 +02:00
commit 2412c8344f
8 changed files with 110 additions and 36 deletions

View file

@ -9,14 +9,29 @@ from api.users.serializers import UserSerializer
User = get_user_model() User = get_user_model()
# Event Serializer
class EventSerializer(serializers.HyperlinkedModelSerializer):
created_by = serializers.ReadOnlyField(source='created_by.get_full_name')
creation_date = serializers.ReadOnlyField()
class Meta:
model = Event
fields = ('url', 'id', 'title', 'slug', 'created_by', 'creation_date',
'description', 'beginning_date', 'ending_date')
# Classes utilitaires # Classes utilitaires
class EventSpecificSerializer(serializers.ModelSerializer): class EventSpecificSerializer(serializers.ModelSerializer):
""" """
Provide `update` and `create` methods for nested view with an Event Provide `update` and `create` methods for nested view with an Event
For example for Models which exetends EventSpecificMixin For example for Models which extends EventSpecificMixin
the event id has to be provided in the `save` method the event id has to be provided in the `save` method
Works fine with view.EventSpecificViewSet 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): def update(self, instance, validated_data):
""" """
Note : does NOT change the event value of the instance Note : does NOT change the event value of the instance
@ -36,19 +51,8 @@ class EventSpecificSerializer(serializers.ModelSerializer):
# Serializers # Serializers
class EventSerializer(serializers.HyperlinkedModelSerializer):
created_by = serializers.ReadOnlyField(source='created_by.username')
class Meta:
model = Event
fields = ('url', 'id', 'title', 'slug', 'created_by', 'creation_date',
'description', 'beginning_date', 'ending_date')
# TODO rajouter des permissions # TODO rajouter des permissions
class PlaceSerializer(EventSpecificSerializer): class PlaceSerializer(EventSpecificSerializer):
event = EventSerializer(allow_null=True, read_only=True)
class Meta: class Meta:
model = Place model = Place
fields = ('url', 'id', 'name', 'description', 'event') fields = ('url', 'id', 'name', 'description', 'event')
@ -56,8 +60,6 @@ class PlaceSerializer(EventSpecificSerializer):
# TODO rajouter des permissions # TODO rajouter des permissions
class ActivityTagSerializer(EventSpecificSerializer): class ActivityTagSerializer(EventSpecificSerializer):
event = EventSerializer(allow_null=True, read_only=True)
class Meta: class Meta:
model = ActivityTag model = ActivityTag
fields = ('url', 'id', 'name', 'is_public', 'color', 'event') fields = ('url', 'id', 'name', 'is_public', 'color', 'event')
@ -74,23 +76,41 @@ class ActivityTemplateSerializer(serializers.ModelSerializer):
'min_perm', 'max_perm', 'description', 'remarks', 'tags',) 'min_perm', 'max_perm', 'description', 'remarks', 'tags',)
def update(self, instance, validated_data): 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') tags_data = validated_data.pop('tags')
validated_data.pop('event_pk') validated_data.pop('event_pk')
event = instance.event event = instance.event
[setattr(instance, key, value) [setattr(instance, key, value)
for key, value in validated_data.items()] for key, value in validated_data.items()]
instance.save() instance.save()
# 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 = [ActivityTag.objects.get_or_create(event=event, **tag_data)[0] tags = [ActivityTag.objects.get_or_create(event=event, **tag_data)[0]
for tag_data in tags_data] for tag_data in tags_data]
instance.tags = tags instance.tags.set(tags)
return instance return instance
def create(self, validated_data): def create(self, 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') tags_data = validated_data.pop('tags')
event_pk = validated_data.pop('event_pk') event_pk = validated_data.pop('event_pk')
event = event_pk and get_object_or_404(Event, id=event_pk) or None event = event_pk and get_object_or_404(Event, id=event_pk) or None
activity_template = ActivityTemplate.objects.create(event=event, activity_template = ActivityTemplate.objects.create(event=event,
**validated_data) **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
tags = [ActivityTag.objects.get_or_create(event=event, **tag_data)[0] tags = [ActivityTag.objects.get_or_create(event=event, **tag_data)[0]
for tag_data in tags_data] for tag_data in tags_data]
activity_template.tags = tags activity_template.tags = tags

View file

@ -15,11 +15,11 @@ class EventSpecificViewSet(ModelViewSet):
""" """
ViewSet that returns : ViewSet that returns :
* rootlevel objects if no Event is specified * rootlevel objects if no Event is specified
* OR objects related to the specidied event * OR objects related to the specified event
AND root level objects AND root level objects
if an event is specified it passes the event_pk if an event is specified it passes the event_pk
to the save method. Works fine with serializers.EventSpecificSerializer to the save method. Works fine with serializers.EventSpecificSerializer
Useful for models that exetends EventSpecificMixin Useful for models that extends EventSpecificMixin
""" """
def get_queryset(self): def get_queryset(self):
""" """

View file

@ -5,7 +5,7 @@ from rest_framework.test import APITestCase
from event.models import Event, Place, ActivityTag, ActivityTemplate from event.models import Event, Place, ActivityTag, ActivityTemplate
from api.event.serializers import ActivityTemplateSerializer, EventSerializer from api.event.serializers import ActivityTemplateSerializer, EventSerializer
from api.test_mixins import EventBasedModelMixin, EventSpecificMixin,\ from api.test_mixins import EventBasedModelTestMixin, EventSpecificTestMixin,\
ModelTestMixin ModelTestMixin
User = get_user_model() User = get_user_model()
@ -22,7 +22,7 @@ class EventTest(ModelTestMixin, APITestCase):
serializer = EventSerializer serializer = EventSerializer
class ActivityTemplateTest(EventBasedModelMixin, APITestCase): class ActivityTemplateTest(EventBasedModelTestMixin, APITestCase):
model = ActivityTemplate model = ActivityTemplate
base_name = 'event-activitytemplate' base_name = 'event-activitytemplate'
initial_count = 1 initial_count = 1
@ -44,7 +44,7 @@ class ActivityTemplateTest(EventBasedModelMixin, APITestCase):
self.assertEqual(instance.tags.count(), 2) self.assertEqual(instance.tags.count(), 2)
class ActivityTest(EventBasedModelMixin, APITestCase): class ActivityTest(EventBasedModelTestMixin, APITestCase):
model = ActivityTemplate model = ActivityTemplate
base_name = 'event-activity' base_name = 'event-activity'
initial_count = 1 initial_count = 1
@ -66,13 +66,13 @@ class ActivityTest(EventBasedModelMixin, APITestCase):
self.assertEqual(instance.tags.count(), 2) self.assertEqual(instance.tags.count(), 2)
class EventSpecficTagTest(EventSpecificMixin, APITestCase): class EventSpecficTagTest(EventSpecificTestMixin, APITestCase):
model = ActivityTag model = ActivityTag
root_base_name = 'activitytag' root_base_name = 'activitytag'
event_base_name = 'event-activitytag' event_base_name = 'event-activitytag'
class EventSpecficPlaceTest(EventSpecificMixin, APITestCase): class EventSpecficPlaceTest(EventSpecificTestMixin, APITestCase):
model = Place model = Place
root_base_name = 'place' root_base_name = 'place'
event_base_name = 'event-place' event_base_name = 'event-place'

View file

@ -14,6 +14,9 @@ User = get_user_model()
class DataBaseMixin(object): class DataBaseMixin(object):
"""
provides a datatabse for API tests
"""
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
# Users # Users
@ -61,23 +64,35 @@ class DataBaseMixin(object):
self.act1.tags.add(self.tag1) self.act1.tags.add(self.tag1)
class EventBasedModelMixin(DataBaseMixin): class EventBasedModelTestMixin(DataBaseMixin):
""" """
Note : need to define : `model`, `base_name`, `initial_count`, Note : need to define : `model`, `base_name`, `initial_count`,
`data_creation`, `instance_name`, `field_tested`, `serializer` `data_creation`, `instance_name`, `field_tested`, `serializer`
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 test_user_create_extra(self): def user_create_extra(self):
"""
extra test in creation by a permited user
"""
pass pass
def pre_update_extra(self, data): def pre_update_extra(self, data):
"""
extra modification for the data sent for update
"""
return data return data
def post_update_extra(self): def post_update_extra(self):
"""
extra test for updated model
"""
pass pass
def test_user_create(self): def test_user_create(self):
""" """
ensure we can create a new %model object using API ensure a permited user can create a new %model object using API
""" """
data = getattr(self, self.data_creation) data = getattr(self, self.data_creation)
@ -92,11 +107,11 @@ class EventBasedModelMixin(DataBaseMixin):
self.assertEqual(self.model.objects.get(id=self.initial_count+1).event, self.assertEqual(self.model.objects.get(id=self.initial_count+1).event,
self.event1) self.event1)
self.test_create_extra() self.user_create_extra()
def test_user_update(self): def test_user_update(self):
""" """
ensure we can update an %model object using API ensure a permited user can update a new %model object using API
""" """
instance = getattr(self, self.instance_name) instance = getattr(self, self.instance_name)
factory = APIRequestFactory() factory = APIRequestFactory()
@ -126,7 +141,7 @@ class EventBasedModelMixin(DataBaseMixin):
def test_user_delete(self): def test_user_delete(self):
""" """
ensure we can update an %model object using API ensure a permited user can delete a new %model object using API
""" """
instance = getattr(self, self.instance_name) instance = getattr(self, self.instance_name)
@ -145,11 +160,13 @@ class EventBasedModelMixin(DataBaseMixin):
# TODO rajouter la gestion des permissions dans le Mixin # TODO rajouter la gestion des permissions dans le Mixin
# TODO rajouter un test pour s'assurer que les personnes non # TODO rajouter un test pour s'assurer que les personnes non
# connectées ne peuvent pas create/update/delete # connectées ne peuvent pas create/update/delete
class EventSpecificMixin(object): class EventSpecificTestMixin(object):
""" """
Tests is the EventSpecifics querysets are rendered correctly Tests is the EventSpecifics querysets are rendered correctly
using the API using the API
Note : need to define : `model`, `root_base_name` and `event_base_name` Note : need to define : `model`, `root_base_name` and `event_base_name`
tests for models served by the API that inherit EventSpecificMixin
""" """
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
@ -180,6 +197,12 @@ class EventSpecificMixin(object):
self.place_event = Place.objects.create(**self.place_event_data) self.place_event = Place.objects.create(**self.place_event_data)
def test_lists(self): 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 event_id = self.event1.id
root_count = self.model.objects.filter(event=None).count() root_count = self.model.objects.filter(event=None).count()
event_count = (self.model.objects event_count = (self.model.objects
@ -208,10 +231,13 @@ class ModelTestMixin(DataBaseMixin):
""" """
Note : need to define : `model`, `base_name`, Note : need to define : `model`, `base_name`,
`instance_name`, `field_tested`, `serializer` `instance_name`, `field_tested`, `serializer`
generic mixin for testing creation/update/delete
of models served by the API
""" """
def test_user_create(self): def test_user_create(self):
""" """
ensure we can create a new %model object using API ensure a permited user can create a new %model object using API
""" """
data = getattr(self, self.data_creation) data = getattr(self, self.data_creation)
initial_count = self.model.objects.count() initial_count = self.model.objects.count()
@ -229,7 +255,7 @@ class ModelTestMixin(DataBaseMixin):
def test_user_update(self): def test_user_update(self):
""" """
ensure we can update an %model object using API ensure a permited user can update a new %model object using API
""" """
instance = getattr(self, self.instance_name) instance = getattr(self, self.instance_name)
factory = APIRequestFactory() factory = APIRequestFactory()
@ -253,7 +279,7 @@ class ModelTestMixin(DataBaseMixin):
def test_user_delete(self): def test_user_delete(self):
""" """
ensure we can update an %model object using API ensure a permited user can delete a new %model object using API
""" """
instance = getattr(self, self.instance_name) instance = getattr(self, self.instance_name)
initial_count = self.model.objects.count() initial_count = self.model.objects.count()

View file

@ -26,7 +26,7 @@ class SubscriptionTest(TestCase):
title='TestEvent', title='TestEvent',
slug='test', slug='test',
created_by=cls.root, created_by=cls.root,
creation_date=timezone.now(), created_at=timezone.now(),
description="Ceci est un test", description="Ceci est un test",
beginning_date=timezone.now() beginning_date=timezone.now()
+ timedelta(days=30), + timedelta(days=30),

View file

@ -0,0 +1,26 @@
# -*- 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'),
),
]

View file

@ -24,7 +24,7 @@ class Event(SubscriptionMixin, models.Model):
related_name="created_events", related_name="created_events",
editable=False, editable=False,
) )
creation_date = models.DateTimeField( created_at = models.DateTimeField(
_('date de création'), _('date de création'),
auto_now_add=True, auto_now_add=True,
) )
@ -167,6 +167,7 @@ class Activity(AbstractActivityTemplate):
ActivityTemplate, ActivityTemplate,
related_name="children", related_name="children",
blank=True, blank=True,
null=True,
) )
staff = models.ManyToManyField( staff = models.ManyToManyField(
User, User,
@ -178,7 +179,8 @@ class Activity(AbstractActivityTemplate):
end = models.DateTimeField(_("heure de fin")) end = models.DateTimeField(_("heure de fin"))
def get_herited(self, attrname): def get_herited(self, attrname):
inherited_fields = [f.name for f in ActivityTemplate._meta.get_fields()] inherited_fields = [f.name for f in
ActivityTemplate._meta.get_fields()]
m2m_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] if f.many_to_many]
attr = getattr(self, attrname) attr = getattr(self, attrname)

View file

@ -22,7 +22,7 @@ class ActivityInheritanceTest(TestCase):
title='La Nuit 2042', title='La Nuit 2042',
slug='nuit42', slug='nuit42',
created_by=cls.erkan, created_by=cls.erkan,
creation_date=timezone.now(), created_at=timezone.now(),
description="La nuit c'est lol", description="La nuit c'est lol",
beginning_date=timezone.now() beginning_date=timezone.now()
+ timedelta(days=30), + timedelta(days=30),