Commit fd0f4908 authored by Hamza Dely's avatar Hamza Dely

Merge branch 'api_activites'

parents d876c7f3 851b6815
......@@ -12,6 +12,7 @@ Les paquets peuvent être récupérés via APT ou PIP.
* python3-jinja2 (>= 2.9)
* python3-django-filters (>= 1.0)
* python3-djangorestframework (>= 3.4.0)
* drf-nested-routers (>= 0.90.2)
* python3-pil (>= 4.0.0)
* postgresql (>= 9.6)
* postgresql-plpython3 (>= 9.6)
......
"""
Vues de l'API de l'application « Comptes »
"""
from django.conf.urls import url, include
from rest_framework_nested import routers
from activites.views import ActiviteViewSet, InviteViewSet
router = routers.DefaultRouter()
router.register(r'activites', ActiviteViewSet)
activite_router = routers.NestedSimpleRouter(router, r'activites', lookup="activite")
activite_router.register(r'invites', InviteViewSet, base_name="activite-invites")
urlpatterns = [
url('^', include(router.urls)),
url('^', include(activite_router.urls)),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.10 on 2018-07-19 17:39
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('activites', '0002_auto_20170609_1940'),
]
operations = [
migrations.AddField(
model_name='invite',
name='naissance',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='date de naissance'),
preserve_default=False,
),
migrations.AlterUniqueTogether(
name='invite',
unique_together=set([('nom', 'prenom', 'naissance', 'activite')]),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.10 on 2018-07-19 17:49
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('activites', '0003_auto_20180719_1939'),
]
operations = [
migrations.AlterField(
model_name='invite',
name='naissance',
field=models.DateField(verbose_name='date de naissance'),
),
]
"""
Sérialiseurs de l'app « Activités »
"""
from rest_framework import serializers
from note_kfet.serializers import mixins
from activites.models import Activite, Invite
class ActiviteSerializer(mixins.DynamicFieldsMixin, serializers.ModelSerializer):
"""
Sérialiseur pour le modèle Activite
"""
class Meta:
model = Activite
fields = [
'id', 'intitule', 'description', 'debut',
'fin', 'liste_invitation', 'organisateur', 'valide_par',
]
read_only_fields = ['id']
default_empty = False
class InviteSerializer(serializers.ModelSerializer):
"""
Sérialiseur pour le modèle Invite
"""
class Meta:
model = Invite
fields = ['id', 'nom', 'prenom', 'naissance', 'invite_par', 'activite']
read_only_fields = ['id']
......@@ -18,6 +18,11 @@ from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMix
from django_filters.views import FilterView
from rest_framework import status
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.decorators import detail_route
from note_kfet.droits import D, Acl
from note_kfet.views.mixins import NoteMixin
......@@ -25,6 +30,11 @@ from comptes.models import Adherent
from activites.models import Activite, Invite
from activites.filters import ActiviteRechercheFilter
from activites.serializers import ActiviteSerializer, InviteSerializer
################################################################################
## Vues concernant le site Web ##
################################################################################
### Vues concernant les activités
......@@ -348,3 +358,270 @@ class ActiviteInviteRetraitView(PermissionRequiredMixin, LoginRequiredMixin, Red
kwargs.update(pk=invite.activite.id)
invite.delete()
return super().get_redirect_url(*args, **kwargs)
################################################################################
## Vues concernant l'API ##
################################################################################
class ActiviteViewSet(viewsets.GenericViewSet):
"""
Un ensemble de vues pour gérer les activités
"""
queryset = Activite.objects.all()
serializer_class = ActiviteSerializer
def get_queryset(self):
qs = super().get_queryset().filter()
if not self.request.user.has_perm("activites.activite_gerer", Acl.BASIQUE):
return qs.filter(Q(organisateur=self.request.user) | Q(valide_par__isnull=False))
else:
return qs
def list(self, request):
"""
Renvoie la liste des activités enregistrées.
Le paramètre 'show' peut prendre deux valeurs : 'normal' pour
afficher seulement les activités en cours ou à venir, ou 'all' pour
afficher toutes les activités. La valeur par défaut est 'normal'.
Les données doivent:
- être envoyées via un requête GET
- contenir dans la querystring le paramètre 'show'
"""
show = self.request.query_params.get('show', 'normal')
if show not in ['normal', 'all']:
return Response(
{"detail" : "Paramètre 'show' incorrect"},
status=status.HTTP_400_BAD_REQUEST,
)
lookup_args = {'fin__gte' : timezone.now()} if show == "normal" else {}
qs = super().get_queryset().filter(**lookup_args)
serializer = self.get_serializer(
qs,
many=True,
fields=['id', 'intitule', 'description', 'debut', 'fin', 'liste_invitation'],
)
return Response(serializer.data, status=status.HTTP_200_OK)
def retrieve(self, request, pk=None):
"""
Renvoie les détails sur une activité donnée.
Les données doivent:
- être envoyées via une requête GET
"""
fields = ['id', 'intitule', 'description', 'debut', 'fin', 'liste_invitation']
if self.request.user.has_perm("activites.activite_gerer", Acl.LIMITE):
fields += ['organisateur', 'valide_par']
serializer = self.get_serializer(self.get_object(), fields=fields)
return Response(serializer.data)
def create(self, request):
"""
Ajoute une nouvelle activité.
Les données doivent:
- être envoyées via une requête POST
-
"""
if not request.user.has_perm("activites.activite_ajouter", Acl.LIMITE):
return Response({}, status=status.HTTP_403_FORBIDDEN)
if ('organisateur' in request.data
and request.user.has_perm("activites.activite_ajouter", Acl.ETENDU)):
pass
elif 'organisateur' in request.data.dict():
return Response(
{
"detail" : "Vous ne pouvez pas indiquer d'organisateur",
},
status=status.HTTP_403_FORBIDDEN
)
else:
# L'organisateur n'a pas été spécifié, mais de toute façon l'utilisateur n'a
# pas le droit de le faire
request.data['organisateur'] = request.user
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid():
return Response({}, status=status.HTTP_400_BAD_REQUEST)
activite = serializer.save()
return Response({"id" : activite.id}, status=status.HTTP_201_CREATED)
def partial_update(self, request, pk=None):
"""
Modifie une activité donnée. Il n'est pas possible de valider ou dévalider
une proposition d'activité avec cette vue de l'API.
Les données doivent :
- être envoyées via une requête PATCH
"""
if not request.user.has_perm("activites.activite_modifier", Acl.LIMITE):
return Response({}, status=status.HTTP_403_FORBIDDEN)
if 'valide_par' in request.data:
return Response(
{"detail" : "Impossible de valider/dévalider une activité"}, # XXX: C'est pas très clair
status=status.HTTP_400_BAD_REQUEST,
)
fields = ['intitule', 'description', 'debut', 'fin', 'liste_invitation', 'organisateur']
activite = self.get_object()
serializer = self.get_serializer(activite, data=request.data, partial=True, fields=fields)
serializer.is_valid(raise_exception=True)
if ('organisateur' in serializer.validated_data
and not request.user.has_perm("activites.activite_modifier", Acl.ETENDU)):
return Response(
{"detail" : "Vous ne pouvez pas changer l'organisateur d'une activité"},
status=status.HTTP_403_FORBIDDEN,
)
serializer.save()
return Response({}, status=status.HTTP_200_OK)
@detail_route(methods=['patch'])
def status(self, request, pk=None):
"""
Valide ou dévalide une activité.
Les données doivent :
- être envoyées via une requête PATCH
- contenir un unique paramètre 'action' pouvant valoir 'valider' ou 'devalider'
"""
if not request.user.has_perm("activites.activite_gerer", Acl.ETENDU):
return Response({}, status=status.HTTP_403_FORBIDDEN)
action = request.data.get('action', None)
if action not in ['valider', 'devalider']:
return Response({"detail" : "Paramètre 'action' invalide"}, status=status.HTTP_400_BAD_REQUEST)
activite = self.get_object()
if activite.valide_par is None and action == 'devalider':
# L'activité est dévalidée, mais on demande de nouveau une dévalidation
return Response({"detail" : "L'activité est déjà dévalidée"}, status=status.HTTP_409_CONFLICT)
elif action == 'devalider':
# L'activité est validée, et on demande sa dévalidation
activite.valide_par = None
elif activite.valide_par is None:
# L'activité est dévalidée, et on demande sa validation
activite.valide_par = request.user
else:
# L'activité est validée, mais on demande de nouveau sa validation
return Response({"detail" : "L'activité est déjà validée"}, status=status.HTTP_409_CONFLICT)
activite.save()
return Response({}, status=status.HTTP_200_OK)
def destroy(self, request, pk=None):
"""
Supprime une activité.
Les données doivent :
- être envoyées via une requête DELETE
"""
if not request.user.has_perm("activites.activite_modifier", Acl.LIMITE):
return Response({}, status=status.HTTP_403_FORBIDDEN)
activite = self.get_object()
if (activite.organisateur != request.user
and not request.user.has_perm("activites.activite_modifier", Acl.ETENDU)):
return Response(
{
"detail" : "Vous n'avez pas l'accréditation necéssaire pour "
"supprimer cette activité"
},
status=status.HTTP_403_FORBIDDEN,
)
activite.delete()
return Response({}, status=status.HTTP_204_NO_CONTENT)
class InviteViewSet(viewsets.GenericViewSet):
"""
Un ensemble du vues pour gérer les invités
"""
serializer_class = InviteSerializer
def get_queryset(self):
return Invite.objects.filter(activite=self.kwargs['activite_pk'])
def list(self, request, activite_pk=None):
"""
Renvoie la liste des invités à une activité donnée.
Les données doivent :
- être envoyées via une requête GET
"""
if not request.user.has_perm("activites.activite_gerer", Acl.TOTAL):
return Response({}, status=status.HTTP_403_FORBIDDEN)
if not Activite.objects.get(pk=self.kwargs['activite_pk']).liste_invitation:
return Response(
{"detail" : "Cette activité n'a pas de liste d'invités"},
status=status.HTTP_404_NOT_FOUND,
)
serializer = self.get_serializer(self.get_queryset(), many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def retrieve(self, request, pk=None, activite_pk=None):
"""
Renvoie les informations concernant un invité donné.
Les données doivent :
- être envoyées via une requête GET
"""
if not request.user.has_perm("activites.activite_gerer", Acl.TOTAL):
return Response({}, status=status.HTTP_403_FORBIDDEN)
invite = self.get_object()
serializer = self.get_serializer(invite)
return Response(serializer.data, status=status.HTTP_200_OK)
def create(self, request, pk=None, activite_pk=None):
"""
Ajoute un invité à l'activité courante
Les données doivent :
- être envoyées via une requête POST
"""
if not request.user.has_perm("activites.invite_inviter", Acl.LIMITE):
return Response({}, status=status.HTTP_403_FORBIDDEN)
activite = Activite.objects.get(pk=activite_pk)
if not activite.liste_invitation:
# Pas de liste d'invités pour cette activité
return Response(
{"detail" : "Il n'y a pas de liste d'invités pour cette activité"},
status=status.HTTP_400_BAD_REQUEST,
)
if ('invite_par' in request.data
and not request.user.has_perm("activites.invite_inviter", Acl.TOTAL)):
return Response(
{"detail" : "Vous ne pouvez inviter une personne qu'en vôtre nom"},
status=status.HTTP_403_FORBIDDEN,
)
else:
request.data['invite_par'] = request.user.pk
request.data['activite'] = activite.pk
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({}, status=status.HTTP_201_CREATED)
def destroy(self, request, pk=None, activite_pk=None):
"""
Supprime un invité à une activité donnée.
Les données doivent :
- être envoyées via une requête DELETE
"""
if not request.user.has_perm("activites.invite_supprimer", Acl.LIMITE):
return Response({}, status=status.HTTP_403_FORBIDDEN)
invite = self.get_object()
if (invite.invite_par != request.user
and not request.user.has_perm("activites.invite_supprimer", Acl.TOTAL)):
return Response(
{"detail" : "Vous ne pouvez pas supprimer une invitation d'un autre adhérent"},
status=status.HTTP_403_FORBIDDEN,
)
invite.delete()
return Response({}, status=status.HTTP_204_NO_CONTENT)
......@@ -6,7 +6,7 @@ from rest_framework import routers
from comptes.views import AdherentViewSet, AliasViewSet
router = routers.SimpleRouter()
router = routers.DefaultRouter()
router.register(r'aliases', AliasViewSet)
router.register(r'adherents', AdherentViewSet)
......
......@@ -10,7 +10,7 @@ from rest_framework.reverse import reverse_lazy
from rest_framework.response import Response
# Dictionnaire contenant les apps disposant d'une API
APPS_API = ['comptes']
APPS_API = ['comptes', 'activites']
urlpatterns = [
url('^%s/' % app, include('%s.api' % app, namespace=app)) for app in APPS_API
......
......@@ -18,7 +18,12 @@ class DynamicFieldsMixin(object):
utilisés dans un sérialiseur.
"""
def __init__(self, *args, **kwargs):
fields = set(kwargs.pop('fields', []))
if getattr(self.__class__.Meta, 'default_empty', True):
default_fields = []
else:
default_fields = self.__class__.Meta.fields
fields = set(kwargs.pop('fields', default_fields))
super().__init__(*args, **kwargs)
for field in set(self.fields) - fields:
self.fields.pop(field)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment