Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
BDE
Note Kfet 2018
Commits
fd0f4908
Commit
fd0f4908
authored
Aug 02, 2018
by
Hamza Dely
Browse files
Merge branch 'api_activites'
parents
d876c7f3
851b6815
Changes
9
Hide whitespace changes
Inline
Side-by-side
README.md
View file @
fd0f4908
...
...
@@ -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)
...
...
activites/api.py
0 → 100644
View file @
fd0f4908
"""
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
)),
]
activites/migrations/0003_auto_20180719_1939.py
0 → 100644
View file @
fd0f4908
# -*- 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'
)]),
),
]
activites/migrations/0004_auto_20180719_1949.py
0 → 100644
View file @
fd0f4908
# -*- 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'
),
),
]
activites/serializers.py
0 → 100644
View file @
fd0f4908
"""
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'
]
activites/views.py
View file @
fd0f4908
...
...
@@ -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
)
comptes/api.py
View file @
fd0f4908
...
...
@@ -6,7 +6,7 @@ from rest_framework import routers
from
comptes.views
import
AdherentViewSet
,
AliasViewSet
router
=
routers
.
Simple
Router
()
router
=
routers
.
Default
Router
()
router
.
register
(
r
'aliases'
,
AliasViewSet
)
router
.
register
(
r
'adherents'
,
AdherentViewSet
)
...
...
note_kfet/api.py
View file @
fd0f4908
...
...
@@ -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
...
...
note_kfet/serializers/mixins.py
View file @
fd0f4908
...
...
@@ -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
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment