Clean serializers and viewsets
Event-based urls - Add viewset mixin 'EventUrlViewSetMixin' to get the event from the 'event_pk' url kwarg of a view. - Add url serializer fields for object which can be accessed with a root-level and/or event-specific url ('EventHyperlinked*Field). Update viewsets and serializers to use these + clean inheritance viewsets.
This commit is contained in:
parent
07d4c3ead1
commit
fc4930a49e
4 changed files with 157 additions and 135 deletions
33
api/event/fields.py
Normal file
33
api/event/fields.py
Normal file
|
@ -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
|
|
@ -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 rest_framework import serializers
|
||||||
|
|
||||||
from event.models import Event, ActivityTag, Place, ActivityTemplate
|
from event.models import Event, ActivityTag, Place, ActivityTemplate
|
||||||
|
|
||||||
|
from .fields import EventHyperlinkedIdentityField
|
||||||
|
|
||||||
|
|
||||||
# Event Serializer
|
# 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_by = serializers.ReadOnlyField(source='created_by.get_full_name')
|
||||||
created_at = serializers.ReadOnlyField()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Event
|
model = Event
|
||||||
fields = ('url', 'id', 'title', 'slug', 'created_by', 'created_at',
|
fields = (
|
||||||
'description', 'beginning_date', 'ending_date')
|
'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
|
|
||||||
|
|
||||||
|
|
||||||
# Serializers
|
# Serializers
|
||||||
# TODO rajouter des permissions
|
# TODO rajouter des permissions
|
||||||
class PlaceSerializer(EventSpecificSerializerMixin,
|
class PlaceSerializer(serializers.ModelSerializer):
|
||||||
serializers.ModelSerializer):
|
serializer_url_field = EventHyperlinkedIdentityField
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Place
|
model = Place
|
||||||
|
@ -55,8 +32,9 @@ class PlaceSerializer(EventSpecificSerializerMixin,
|
||||||
|
|
||||||
|
|
||||||
# TODO rajouter des permissions
|
# TODO rajouter des permissions
|
||||||
class ActivityTagSerializer(EventSpecificSerializerMixin,
|
class ActivityTagSerializer(serializers.ModelSerializer):
|
||||||
serializers.ModelSerializer):
|
serializer_url_field = EventHyperlinkedIdentityField
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ActivityTag
|
model = ActivityTag
|
||||||
fields = ('url', 'id', 'name', 'is_public', 'color', 'event')
|
fields = ('url', 'id', 'name', 'is_public', 'color', 'event')
|
||||||
|
@ -64,36 +42,30 @@ class ActivityTagSerializer(EventSpecificSerializerMixin,
|
||||||
|
|
||||||
# TODO rajouter des permissions
|
# TODO rajouter des permissions
|
||||||
class ActivityTemplateSerializer(serializers.ModelSerializer):
|
class ActivityTemplateSerializer(serializers.ModelSerializer):
|
||||||
event = EventSerializer(read_only=True)
|
|
||||||
tags = ActivityTagSerializer(many=True)
|
tags = ActivityTagSerializer(many=True)
|
||||||
|
|
||||||
|
serializer_url_field = EventHyperlinkedIdentityField
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ActivityTemplate
|
model = ActivityTemplate
|
||||||
fields = ('id', 'title', 'event', 'is_public', 'has_perm',
|
fields = (
|
||||||
'min_perm', 'max_perm', 'description', 'remarks', 'tags',)
|
'url', 'id', 'title', 'event', 'is_public', 'has_perm', 'min_perm',
|
||||||
|
'max_perm', 'description', 'remarks', 'tags',
|
||||||
|
)
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def process_tags(self, instance, tags_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()
|
|
||||||
# TODO: en fonction de si backbone envoie un `id` ou non lorsque le tag
|
# 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
|
# 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
|
# 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
|
# été modifié entre temps dans la base de donnée (mais pas sur la
|
||||||
# classe backbone
|
# classe backbone
|
||||||
|
tags = []
|
||||||
for tag_data in tags_data:
|
for tag_data in tags_data:
|
||||||
tag_data.pop('event', None)
|
tag, _ = ActivityTag.objects.get_or_create(**tag_data, defaults={
|
||||||
tags = [ActivityTag.objects.get_or_create(event=event, **tag_data)[0]
|
'event': instance.event,
|
||||||
for tag_data in tags_data]
|
})
|
||||||
instance.tags.set(tags)
|
tags.append(tag)
|
||||||
return instance
|
instance.tags.add(*tags)
|
||||||
|
|
||||||
def create(self, validated_data):
|
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éé
|
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')
|
activity_template = super().create(validated_data)
|
||||||
event = event_pk and get_object_or_404(Event, id=event_pk) or None
|
self.process_tags(activity_template, tags_data)
|
||||||
activity_template = ActivityTemplate.objects.create(event=event,
|
return activity_template
|
||||||
**validated_data)
|
|
||||||
# TODO: en fonction de si backbone envoie un `id` ou non lorsque le tag
|
def update(self, instance, validated_data):
|
||||||
# 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
|
@tags comportement attendu : si l'id existe déjà on ne change pas
|
||||||
# été modifié entre temps dans la base de donnée (mais pas sur la
|
les autres champs et si l'id n'existe pas on le créé
|
||||||
# classe backbone
|
"""
|
||||||
for tag_data in tags_data:
|
tags_data = validated_data.pop('tags')
|
||||||
tag_data.pop('event', None)
|
activity_template = super().update(instance, validated_data)
|
||||||
tags = [ActivityTag.objects.get_or_create(event=event, **tag_data)[0]
|
self.process_tags(activity_template, tags_data)
|
||||||
for tag_data in tags_data]
|
|
||||||
activity_template.tags = tags
|
|
||||||
return activity_template
|
return activity_template
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,54 @@
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.db.models import Q
|
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.viewsets import ModelViewSet
|
||||||
|
from rest_framework.filters import OrderingFilter
|
||||||
|
|
||||||
from api.event.serializers import EventSerializer, PlaceSerializer,\
|
from event.models import Activity, ActivityTag, ActivityTemplate, Event, Place
|
||||||
ActivityTagSerializer, ActivityTemplateSerializer, ActivitySerializer
|
|
||||||
from event.models import Event, Place, ActivityTag, ActivityTemplate, Activity
|
from .serializers import (
|
||||||
|
ActivitySerializer, ActivityTagSerializer, ActivityTemplateSerializer,
|
||||||
|
EventSerializer, PlaceSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
# classes utilitaires
|
# 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 :
|
ViewSet that returns :
|
||||||
* rootlevel objects if no Event is specified
|
* rootlevel objects if no Event is specified
|
||||||
|
@ -21,81 +58,64 @@ class EventSpecificViewSet(ModelViewSet):
|
||||||
to the save method. Works fine with serializers.EventSpecificSerializer
|
to the save method. Works fine with serializers.EventSpecificSerializer
|
||||||
Useful for models that extends EventSpecificMixin
|
Useful for models that extends EventSpecificMixin
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""
|
"""
|
||||||
Warning : You may want to override this method
|
Warning : You may want to override this method
|
||||||
and not call with super
|
and not call with super
|
||||||
"""
|
"""
|
||||||
event_pk = self.kwargs.get('event_pk')
|
|
||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset()
|
||||||
if event_pk:
|
filters = Q(event=None)
|
||||||
return queryset.filter(Q(event=event_pk) | Q(event=None))
|
if self.event:
|
||||||
return queryset.filter(event=None)
|
filters |= Q(event=self.event)
|
||||||
|
return queryset.filter(filters)
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
# ViewSets
|
# ViewSets
|
||||||
class EventViewSet(ModelViewSet):
|
class EventViewSet(ModelViewSet):
|
||||||
"""
|
|
||||||
This viewset automatically provides `list`, `create`, `retrieve`,
|
|
||||||
`update` and `destroy` actions.
|
|
||||||
|
|
||||||
"""
|
|
||||||
queryset = Event.objects.all()
|
queryset = Event.objects.all()
|
||||||
serializer_class = EventSerializer
|
serializer_class = EventSerializer
|
||||||
|
|
||||||
|
filter_backends = (OrderingFilter,)
|
||||||
|
ordering_fields = ('title', 'creation_date', 'beginning_date',
|
||||||
|
'ending_date', )
|
||||||
|
ordering = ('beginning_date', )
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
serializer.save(created_by=self.request.user)
|
serializer.save(created_by=self.request.user)
|
||||||
|
|
||||||
|
|
||||||
class PlaceViewSet(EventSpecificViewSet):
|
class PlaceViewSet(EventSpecificModelViewSet):
|
||||||
queryset = Place.objects.all()
|
queryset = Place.objects.all()
|
||||||
serializer_class = PlaceSerializer
|
serializer_class = PlaceSerializer
|
||||||
|
|
||||||
|
filter_backends = (OrderingFilter,)
|
||||||
|
ordering_fields = ('name', )
|
||||||
|
ordering = ('name', )
|
||||||
|
|
||||||
class ActivityTagViewSet(EventSpecificViewSet):
|
|
||||||
|
class ActivityTagViewSet(EventSpecificModelViewSet):
|
||||||
queryset = ActivityTag.objects.all()
|
queryset = ActivityTag.objects.all()
|
||||||
serializer_class = ActivityTagSerializer
|
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()
|
queryset = ActivityTemplate.objects.all()
|
||||||
serializer_class = ActivityTemplateSerializer
|
serializer_class = ActivityTemplateSerializer
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
filter_backends = (OrderingFilter,)
|
||||||
event_pk = self.kwargs.get('event_pk')
|
ordering_fields = ('title', )
|
||||||
serializer.save(event_pk=event_pk)
|
ordering = ('title', )
|
||||||
|
|
||||||
def perform_update(self, serializer):
|
|
||||||
event_pk = self.kwargs.get('event_pk')
|
|
||||||
serializer.save(event_pk=event_pk)
|
|
||||||
|
|
||||||
|
|
||||||
class ActivityViewSet(ModelViewSet):
|
class ActivityViewSet(EventModelViewSet):
|
||||||
"""
|
|
||||||
This viewset automatically provides `list`, `create`, `retrieve`,
|
|
||||||
`update` and `destroy` actions.
|
|
||||||
|
|
||||||
"""
|
|
||||||
queryset = Activity.objects.all()
|
queryset = Activity.objects.all()
|
||||||
serializer_class = ActivitySerializer
|
serializer_class = ActivitySerializer
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
filter_backends = (OrderingFilter,)
|
||||||
event_pk = self.kwargs.get('event_pk')
|
ordering_fields = ('title', )
|
||||||
serializer.save(event_pk=event_pk)
|
ordering = ('title', )
|
||||||
|
|
||||||
def perform_update(self, serializer):
|
|
||||||
event_pk = self.kwargs.get('event_pk')
|
|
||||||
serializer.save(event_pk=event_pk)
|
|
||||||
|
|
29
api/urls.py
29
api/urls.py
|
@ -1,27 +1,26 @@
|
||||||
from django.conf.urls import url, include
|
from django.conf.urls import url, include
|
||||||
|
|
||||||
from rest_framework_nested.routers import SimpleRouter, NestedSimpleRouter
|
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 = SimpleRouter()
|
||||||
router.register(r'event', EventViewSet, 'event')
|
router.register(r'event', views.EventViewSet)
|
||||||
router.register(r'place', PlaceViewSet, 'place')
|
router.register(r'place', views.PlaceViewSet)
|
||||||
router.register(r'activitytag', ActivityTagViewSet, 'activitytag')
|
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 = NestedSimpleRouter(router, r'event', lookup='event')
|
||||||
event_router.register(r'place', PlaceViewSet, base_name='event-place')
|
event_router.register(r'place', views.PlaceViewSet)
|
||||||
event_router.register(r'tag', ActivityTagViewSet, base_name='event-activitytag')
|
event_router.register(r'tag', views.ActivityTagViewSet)
|
||||||
event_router.register(r'activitytemplate', ActivityTemplateViewSet,
|
event_router.register(r'template', views.ActivityTemplateViewSet)
|
||||||
base_name='event-activitytemplate')
|
|
||||||
|
|
||||||
|
|
||||||
# The API URLs are now determined automatically by the router.
|
# API URLconf: routers + auth for browsable API.
|
||||||
# Additionally, we include the login URLs for the browsable API.
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^', include(router.urls)),
|
url(r'^', include(router.urls)),
|
||||||
url(r'^', include(event_router.urls)),
|
url(r'^', include(event_router.urls)),
|
||||||
url(r'^api-auth/', include('rest_framework.urls',
|
url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||||
namespace='rest_framework'))
|
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in a new issue