ZEP-17 : Elaboration de l'API des membres

a marqué ce sujet comme résolu.

Salut,

Je vais faire un mini compte rendu mais surtout un appel à de nouveaux contributeurs !

Je commence enfin à bien comprendre comment fonctionne Django Rest Framework et l'approche CBV alors j'avance bien plus vite. Et ça, c'est vraiment top !

Donc plusieurs choses faites aujourd'hui :

  1. Gestion du PUT supportée pour modifier un utilisateur : https://github.com/gustavi/zds-site/pull/12
  2. Centralisation des conditions de validation dans les formulaires et les serializers : https://github.com/gustavi/zds-site/pull/14
  3. Et j'ai déjà une version fonctionnelle de la gestion du POST en local (création d'un compte). Voici un exemple de sortie lorsque vous allez créer un compte par l'API :
1
2
3
4
5
6
{
    "id": 13,
    "username": "user6",
    "email": "email6@email.com",
    "password": "pbkdf2_sha256$12000$Yp4hTQ4cyVJl$7Ma4sFLNdkNVF8f+rZ6ONdhwmuraNSTKoXJFV/lMVaM="
}

Voilà pour le compte rendu. Maintenant, plus intéressant, mon appel à de nouveaux contributeurs. Ce n'est clairement pas une nécessité puisque je pourrais me charger des tâches restantes mais, actuellement, je suis le seul du projet réellement compétent dans l'élaboration de l'API. gustavi donne plutôt un avis technique sur mon code et Taguan a contribué sur l'approche CBV des vues du site et pas de l'API.

J'aimerais éviter le problème que nous rencontrons avec le support du Markdown sur le site dont l'expertise est détenue exclusivement par Kje, par exemple. Je compte me charger prochainement de la création d'un compte, de la pagination et des options dans le header des requêtes HTTP mais je recherche quelqu'un qui aimerait monter en compétence dans l'élaboration d'une API Django avec la confection de l'API staff sur le module des membres ! Si vous êtes intéressé, n'hésitez surtout pas. Malheureusement, gustavi dispose de très peu de temps (juste assez pour faire de la revue de code) et Taguan n'a pas de temps pour le moment.

Sur ce, bonne nuit parce qu'il est tard à l'heure où j'écris ces lignes. ^^

Salut à tous,

Je me heurte à un problème lié aux headers dans les requêtes HTTP. J'aimerais l'avis des connaisseurs dans ce domaine.

Aujourd'hui, j'ai terminé la création d'un compte et la pagination dans les listes. C'est super beau toussa toussa. Maintenant, je m'attaques aux headers dans les requêtes et j'aurais quelques questions :

  • Django est super bien foutu et permet d'utiliser les ETags avec une simple constante à mettre à True dans le fichier settings.py. Par contre, la documentation dit que la bande passante du client sera économisée au détriment des performances. Voulons-nous vraiment cela ?
  • CORS est supportée par Django Rest Framework (documentation) grâce au projet open-source django-cors-header. C'est tout aussi simple à intégrer que les ETags (ou presque) mais il demande de la configuration que je ne suis pas en mesure de donner. Retrouvez sur le README du projet toutes les configurations possibles.

Merci d'avance pour ceux qui prendront la peine de répondre à mes questions. A part ça, il reste plus grand chose à développer pour la ZEP. :)

+2 -0

Comme je te l'ai déjà dit des centaines de fois : bravo pour ton travail qui est génial ! J'ai pas eu le temps de me plonger dedans que tu as presque terminé :)

  • Si on met du cache ça ne pose pas de souci non ?
  • Je ne connais pas assez ce point pour donner un avis objectif.

Serait-il possible d'avoir une option pour désactiver l'API dans le settings.py ?

+2 -0
  • Django est super bien foutu et permet d'utiliser les ETags avec une simple constante à mettre à True dans le fichier settings.py. Par contre, la documentation dit que la bande passante du client sera économisée au détriment des performances. Voulons-nous vraiment cela ?

Andr0

Alors, je ne connais pas Django mais qu'appelles-tu "au détriment des performances" (le lien vers la doc est cassé) ? Sans pouvoir quantifier c'est difficile de répondre à "Voulous-nous vraiment cela" puisqu'il s'agit là d'une question de compromis entre confort client / confort serveur.

J'ai tendance à penser "Bah oui, évidemment que ça se fait au détriment des perfs" ça me semble logique, à chaque fois que tu vas renvoyer une réponse, tu vas calculer son ETag (et éventuellement le stocker) et à chaque fois que tu reçois une requête, tu vas analyser le header If-Modified-Since pour savoir si tu dois renvoyer une réponse ou non.

Nécessairement, ça entraîne un temps de traitement côté serveur.

Après, la question est : est-ce intéressant pour les clients ?

J'aurais tendance à répondre oui pour une simple raison : on n'a pas de websockets sur ZdS.

Du coup, le moindre client qui veut rafraîchir des données en temps réel (pas nécessaire pour l'instant pour les membres, mais sans doute plus tard pour les MPs, les forums, …) va devoir faire du polling.

Et qui dit polling, dit lourde consommation : en termes de batterie (mais ça on ne va pas pouvoir y faire grand chose…) mais surtout en bande passante.

En plus de cela, il y a aussi le confort d'implémentation du client à prendre en compte. Si on luis permet de mettre en cache plein de données de son côté (local storage), ce qui serait quand même un minimum, alors on doit également lui permettre de poller et ne pas se fader un contenu (potentiellement énorme hein : la liste des messages d'un membre…) à parser, analyser, pour… … des prunes, puisque rien n'a changé depuis la dernière fois qu'il l'a demandée (y'a 30 secondes par exemple).

Quand on passe sur un mécanisme "full-websocket" dans lequel un client souscrit explicitement et le serveur lui pousse les modifications, alors le ETag a forcément un intérêt moindre. Mais on n'en est vraiment pas là. C'est une étape supplémentaire (et j'y réfléchis d'ailleurs très sérieusement une fois que l'API sera en place) à très long terme.

Comme le dit très justement gustavi, il faut tirer partie du cache sur cette fonctionnalité. Mais pour une première implémentation, je pense qu'on peut passer par un calcul (et une analyse) systématique de l'ETag à chaque requête. Parce que qui dit cache dit mécanique d'invalidation et ça peut lourdement engendrer des bugs (cache pas invalidé => un topic jamais récupéré… Aïe). Donc à voir par la suite.

NB : la meilleure façon de gérer ça que j'ai trouvée c'est une annotation (i.e. decorator) sur les managers business qui dit grosso modo "Quand cette méthode est invoquée, invalide le cache qui a la clef bidule". C'est lourd à implémenter (vraiment) mais ça donne un résultat très élégant.

+3 -0

Inutile de m'expliquer le fonctionnement de l'ETag ou pourquoi il est intéressant de l'implémenter. Je l'ai bien compris. D'ailleurs, l'implémentation proposée par Django correspond parfaitement à ce que nous avons définis ensemble lors de la rédaction de cette ZEP. Par contre, nous n'avons aucun pouvoir sur son fonctionnement une fois rajoutée dans le fichier settings.py. En fait, il agit en middleware et automatiquement sur l'entièreté du site (pas seulement sur l'API). La fonctionnalité n'impact donc pas uniquement le travail de cette ZEP mais tout le site.

Voici ce que dit la documentation (rien de plus) :

USE_ETAGS

Default: False

A boolean that specifies whether to output the “Etag” header. This saves bandwidth but slows down performance. This is used by the CommonMiddleware (see Middleware) and in theCache Framework (see Django’s cache framework).

PS : Etrange pour le lien, il est bon pourtant. Le voici à nu : https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-USE_ETAGS

Bah alors j'ai pas compris ta question, désolé.

En fait, pour notre API, je pense que l'ETag est nécessaire mais placer la constante à True dans le settings.py applique l'ETag a tout le site et pas seulement à l'API. C'est donc un choix à faire qui va au-delà de cette ZEP puisque je pourrais appliquer un système de cache non voulu par les autres contributeurs.

Effectivement, je m'étais mal exprimé. Pour ma défense, il était tard. :-°

Ah ouais effectivement j'avais pas du tout compris ça, my bad.

Là ça sort vraiment de mon scope, j'arrive pas à mesurer ce que ça apporte pour des pages générées côté serveur, et surtout… si ça peut entraîner des effets de bord indésirables. J'imagine que non, mais j'ai du mal à visualiser comment ça va se passer pour admettons, la page des MPs (qui ne bouge pas beaucoup), Django fait un rendering de la page, calcule son ETag, compare avec le If-Modified-Since et renvoie soit une 304 soit la nouvelle page ?

Là ouais, c'est clair que ça introduit pas mal de processing quand même.

:\

+0 -0

De même pour moi, voici les headers pour afficher la home :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
Remote Address:127.0.0.1:8000
Request URL:http://localhost:8000/
Request Method:GET
Status Code:200 OK

Request Headers
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate, sdch
Accept-Language:fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4
Cache-Control:max-age=0
Connection:keep-alive
Cookie:hasconsent=true; djdt=hide; sessionid=lkyka3tgskg89ahe4cy4ez09llmro7db; csrftoken=wvi2PlrlAMd1T7bA9BPrxilQsGyij73P
Host:localhost:8000
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36

Response Headers
Content-Type:text/html; charset=utf-8
Date:Tue, 30 Dec 2014 23:09:01 GMT
ETag:"e70692deb03cd6e270393ef87687304c"
Server:WSGIServer/0.1 Python/2.7.6
Set-Cookie:csrftoken=wvi2PlrlAMd1T7bA9BPrxilQsGyij73P; expires=Tue, 29-Dec-2015 23:09:01 GMT; Max-Age=31449600; Path=/
Vary:Cookie

On constate bien que le serveur a renvoyé un ETag par contre, il n'est jamais envoyé par le navigateur.

Bien, j'ai fais quelques recherches suite à cette remarque de ta part Javier :

NB : la meilleure façon de gérer ça que j'ai trouvée c'est une annotation (i.e. decorator) sur les managers business qui dit grosso modo "Quand cette méthode est invoquée, invalide le cache qui a la clef bidule". C'est lourd à implémenter (vraiment) mais ça donne un résultat très élégant.

Javier

Je n'ai pas trouvé une solution identique mais j'ai trouvé ce projet : DRF extensions qui, comme son nom l'indique, est fait pour fonctionner avec DRF. L'une des fonctionnalités que propose cette librairie est de décorer les méthodes de l'API voulue pour utiliser les ETags. Par exemple, pour utiliser les ETags sur l'API qui renvoie la liste des membres, nous aurons le code suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class MemberListAPI(ListCreateAPIView):
    """
    Displays the list of registered users or create one.
    """

    queryset = Profile.objects.all_members_ordered_by_date_joined()

    @etag()
    def get(self, request, *args, **kwargs):
        self.serializer_class = UserSerializer
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.serializer_class = UserCreateSerializer
        return self.create(request, *args, **kwargs)

Personnellement, je trouve cette solution plus élégante et n'impact plus tout le site. :)

Je viens demander un avis à la communauté.

Je suis sur l'élaboration de l'API des membres pour le staff (tout ce qui est lié aux sanctions). Actuellement, sur le site, tout est géré dans une seule vue et un paramètre contenu dans la requête définit s'il faut appliquer (ou retirer) la sanction sur un membre.

Au départ, j'étais partie sur une requête par sanction. J'aurais eu quelque chose semblable à :

1
2
3
4
5
http://www.zestedesavoir.com/api/membres/1/lecture-seule/
http://www.zestedesavoir.com/api/membres/1/non-lecture-seule/
http://www.zestedesavoir.com/api/membres/1/ban/
http://www.zestedesavoir.com/api/membres/1/non-ban/
...

ou une seule URL par sanction et un paramètre dans la requête qui indique si on retire ou ajoute la sanction sur le membre donné.

1
2
3
http://www.zestedesavoir.com/api/membres/1/lecture-seule/ 
http://www.zestedesavoir.com/api/membres/1/ban/
...

Puis je me suis arrêté, j'y ai réfléchis et je me suis demandé si l'url http://www.zestedesavoir.com/api/membres/1/sanction/ avec des paramètres dans la requête ne serait pas une meilleure idée.

Il y a des avantages et des inconvénients pour chaque solution mais j'aimerais savoir ce que vous en pensez pour faire un choix approuvé par le plus de monde.

Je pense que c'est mieux avec l'url /sanction.

Je ne sais pas comment fonctionne les sanctions mais s'il y a une durée à la sanction (lecture seule pendant une semaine par exemple) on pourrai imaginer un membre du staff bannir un utilisateur pour une semaine et lui mettre en même temps une lecture seule pour un mois. L'avantage avec /sanction c'est que l'on peu faire les deux en une seule requête.

Dans le même ordre d'idée on peu retirer un ban et le transformer en une lecture seule en une seule requête :)

+0 -0

Le type de sanction étant obligatoire, en général on préfèrera utiliser les urls ainsi :

1
2
3
4
5
6
http://www.zestedesavoir.com/api/membres/1/sanction/lecture-seule/
http://www.zestedesavoir.com/api/membres/1/sanction/lecture-seule/?period=10
http://www.zestedesavoir.com/api/membres/1/sanction/non-lecture-seule/
http://www.zestedesavoir.com/api/membres/1/sanction/ban/
http://www.zestedesavoir.com/api/membres/1/sanction/ban/?period=10
http://www.zestedesavoir.com/api/membres/1/sanction/non-ban/

Un paramètre ne doit jamais être obligatoire dans l'url d'une API

Itou, sauf que je virerais les URLs suivantes :

1
2
http://www.zestedesavoir.com/api/membres/1/sanction/non-lecture-seule/
http://www.zestedesavoir.com/api/membres/1/sanction/non-ban/

Autant utiliser une requête POST ou DELETE non ?

.... 3 minutes plus tard ....

Mmmf, après rapide lecture du code, je comprends pourquoi vous avez mis ces urls là vu la manière dont c'est implémenté. Cela dit, je continue à trouver ça plus logique d'utiliser DELETE ou POST quand il s'agit d'annuler une sanction.

Itou, sauf que je virerais les URLs suivantes :

1
2
http://www.zestedesavoir.com/api/membres/1/sanction/non-lecture-seule/
http://www.zestedesavoir.com/api/membres/1/sanction/non-ban/

Autant utiliser une requête POST ou DELETE non ?

Taguan

Tu as tout a fait raison. Ici un DELETE est tout à fait approprié à la levée de la sanction.

Mais plus généralement ça m'embête un peu qu'on implémente des trucs qui n'ont pas été spécifiées/validées dans la ZEP. Mais peut être ai-je manqué quelque chose.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte