Upgrade to Pro — share decks privately, control downloads, hide ads and more …

pycon.fr 2018 - Django REST framework workshop

xordoquy
October 06, 2018

pycon.fr 2018 - Django REST framework workshop

xordoquy

October 06, 2018
Tweet

More Decks by xordoquy

Other Decks in Programming

Transcript

  1. DRF Content negotiation / Parser Router Authentication / Permissions /

    Throttling Deserialization Processing / Pagination / Filtering Serialization Renderer Middleware
  2. Parsers / Renderers b'{"demo": 1}’ <class bytes> b'"2015-06-03"' <class bytes>

    {"demo": 1} <class dict> "2015-06-03" <class str> JSONParser Binary world Python land JSONRenderer
  3. Identification Permissions Throttling • Identification: Qui êtes-vous ? • Permissions:

    Pouvez-vous le faire ? • Throttling: Abusez-vous des ressources ?
  4. (dé)sérialisation • Types de base Python <-> objets complexes •

    Etape de représentation • "2015-06-03" <-> datetime.date(2015, 6, 3) • Validation des entrées: • Vérification de typage • Sécurisation (injections diverses)
  5. Serializers {"demo": 1} <class dict> "2015-06-03" <class str> <SomeObj> <class

    SomeObj> 2015-06-03 <class date> Serializer Unsafe Safe
  6. settings.py INSTALLED_APPS = [ … 'rest_framework', 'teaching', 'corsheaders', ] …

    MIDDLEWARE = [ … 'corsheaders.middleware.CorsMiddleware', ] … CORS_ORIGIN_ALLOW_ALL = True
  7. teaching/serializers.py from rest_framework import serializers from . import models class

    CourseMaterialSerializer(serializers.ModelSerializer): class Meta: model = models.CourseMaterial fields = ['title', 'description', 'url', 'author']
  8. teaching/api_views.py from rest_framework import viewsets from . import serializers, models

    class CourseMaterialViewSet(viewsets.ModelViewSet): queryset = models.CourseMaterial.objects.all() serializer_class = serializers.CourseMaterialSerializer
  9. teaching/urls.py from rest_framework import routers from . import views, api_views

    router = routers.DefaultRouter() router.register( r'course_material', api_views.CourseMaterialViewSet, ) urlpatterns = [ … ] + router.urls
  10. Navigation • Page de garde: ➡ http://localhost:8000/teaching/ • Liste /

    creation: ➡ http://localhost:8000/teaching/course_material/ • Voir / mettre à jour / supprimer: ➡ http://localhost:8000/teaching/course_material/1/ • En JSon: ➡ …/teaching/course_material/?format=json ➡ …/teaching/course_material.json
  11. Création http --json POST http://127.0.0.1:8000/teaching/course_material/\ author=1 description="blabla" title="bla" HTTP/1.1 201

    Created Allow: GET, POST, HEAD, OPTIONS Content-Length: 60 Content-Type: application/json Date: Fri, 05 Oct 2018 23:05:39 GMT Location: None Server: WSGIServer/0.2 CPython/3.6.6 Vary: Accept, Cookie X-Frame-Options: SAMEORIGIN { "author": 1, "description": "blabla", "title": "bla", "url": null }
  12. teaching/tests.py class TestTask(APITestCase): def test_task_creation(self): author = models.Author.objects.create( name="hodor", email="[email protected]",

    ) course_material = models.CourseMaterial.objects.create( title="Hodoooor", author=author, ) data = { 'title': "Hodoooor", 'description': None, 'author': 1, 'url': None, } url = reverse('teaching:coursematerial-list') response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED, response.content) self.assertEqual(response.data, data)
  13. teaching/tests.py class TestTask(APITestCase): def test_task_creation(self): author = models.Author.objects.create( name="hodor", email="[email protected]",

    ) course_material = models.CourseMaterial.objects.create( title="Hodoooor", author=author, ) data = { 'title': "Hodoooor", 'description': None, 'author': 1, 'url': None, } url = reverse('teaching:coursematerial-list') response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED, response.content) self.assertEqual(response.data, data)
  14. teaching/tests.py class TestTask(APITestCase): def test_task_creation(self): author = models.Author.objects.create( name="hodor", email="[email protected]",

    ) course_material = models.CourseMaterial.objects.create( title="Hodoooor", author=author, ) data = { 'title': "Hodoooor", 'description': None, 'author': 1, 'url': None, } url = reverse('teaching:coursematerial-list') response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED, response.content) self.assertEqual(response.data, data)
  15. teaching/tests.py class TestTask(APITestCase): def test_task_creation(self): author = models.Author.objects.create( name="hodor", email="[email protected]",

    ) course_material = models.CourseMaterial.objects.create( title="Hodoooor", author=author, ) data = { 'title': "Hodoooor", 'description': None, 'author': 1, 'url': None, } url = reverse('teaching:coursematerial-list') response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED, response.content) self.assertEqual(response.data, data)
  16. teaching/tests.py class TestTask(APITestCase): def test_task_creation(self): author = models.Author.objects.create( name="hodor", email="[email protected]",

    ) course_material = models.CourseMaterial.objects.create( title="Hodoooor", author=author, ) data = { 'title': "Hodoooor", 'description': None, 'author': 1, 'url': None, } url = reverse('teaching:coursematerial-list') response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED, response.content) self.assertEqual(response.data, data)
  17. teaching/tests.py class TestTask(APITestCase): def test_task_creation(self): author = models.Author.objects.create( name="hodor", email="[email protected]",

    ) course_material = models.CourseMaterial.objects.create( title="Hodoooor", author=author, ) data = { 'title': "Hodoooor", 'description': None, 'author': 1, 'url': None, } url = reverse('teaching:coursematerial-list') response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED, response.content) self.assertEqual(response.data, data)
  18. Lancer le test (tp-atelier-django)[monprojet]$ python manage.py test Creating test database

    for alias 'default'... System check identified no issues (0 silenced). . ---------------------------------------------------------------- Ran 1 test in 0.015s OK Destroying test database for alias 'default'... (tp-atelier-django)[monprojet]$
  19. teaching/tests.py from django.urls import reverse from rest_framework import status from

    rest_framework.test import APITestCase from . import models
  20. teaching/tests.py class TestTask(APITestCase): def test_task_creation(self): data = { 'title': "Hodoooor",

    'description': None, 'author': 1, 'url': None, } author = models.Author.objects.create( name="hodor", email="[email protected]", ) course_material = models.CourseMaterial.objects.create( title="Hodoooor", author=author, ) url = reverse('teaching:coursematerial-list') response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED, response.content) self.assertEqual(response.data, data)
  21. Lancer le test (tp-atelier-django)[monprojet]$ python manage.py test Creating test database

    for alias 'default'... System check identified no issues (0 silenced). . ---------------------------------------------------------------- Ran 1 test in 0.015s OK Destroying test database for alias 'default'... (tp-atelier-django)[monprojet]$
  22. Author illisible http --json POST http://127.0.0.1:8000/teaching/course_material/\ author=1 description="blabla" title="bla" HTTP/1.1

    201 Created Allow: GET, POST, HEAD, OPTIONS Content-Length: 60 Content-Type: application/json Date: Fri, 05 Oct 2018 23:05:39 GMT Location: None Server: WSGIServer/0.2 CPython/3.6.6 Vary: Accept, Cookie X-Frame-Options: SAMEORIGIN { "author": 1, "description": "blabla", "title": "bla", "url": null }
  23. Author amélioré http --json POST http://127.0.0.1:8000/teaching/course_material/\ author=1 description="blabla" title="bla" HTTP/1.1

    201 Created Allow: GET, POST, HEAD, OPTIONS Content-Length: 60 Content-Type: application/json Date: Fri, 05 Oct 2018 23:05:39 GMT Location: None Server: WSGIServer/0.2 CPython/3.6.6 Vary: Accept, Cookie X-Frame-Options: SAMEORIGIN { "author": "A. Christie", "description": "blabla", "title": "bla", "url": null }
  24. Tests (tp-atelier-django)[monprojet]$ python manage.py test Creating test database for alias

    'default'... System check identified no issues (0 silenced). F ================================================================= FAIL: test_task_creation (teaching.tests.TestTask) ----------------------------------------------------------------- Traceback (most recent call last): File "/Users/xordoquy/Documents/Devs/tp-atelier-django/monprojet/ teaching/tests.py", line 26, in test_task_creation response.content) AssertionError: 400 != 201 : b'{"author":["L\'object avec name=1 n\'existe pas."]}' ----------------------------------------------------------------- Ran 1 test in 0.016s FAILED (failures=1) Destroying test database for alias 'default'... (tp-atelier-django)[monprojet]$
  25. teaching/tests.py class TestTask(APITestCase): def test_task_creation(self): author = models.Author.objects.create( name="hodor", email="[email protected]",

    ) course_material = models.CourseMaterial.objects.create( title="Hodoooor", author=author, ) data = { 'title': course_material.title, 'description': None, 'author': author.name, 'url': None, } url = reverse('teaching:coursematerial-list') response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED, response.content) self.assertEqual(response.data, data)
  26. teaching/serializers.py from rest_framework import serializers from . import models class

    CourseMaterialSerializer(serializers.ModelSerializer): self_url = serializers.HyperlinkedIdentityField( 'coursematerial-detail', source='id', read_only=True) class Meta: model = models.CourseMaterial fields = [ 'title', 'description', 'url', 'author', 'self_url', ]
  27. Exemple { "author": { "email": "[email protected]", "id": 3, "name": "aha"

    }, "description": "Description", "title": "titre !", "url": null }
  28. serializers.py class AuthorSerializer(serializers.ModelSerializer): id = serializers.IntegerField(required=False) class Meta: model =

    models.Author fields = ['id', 'name', 'email'] class CourseMaterialSerializer(serializers.ModelSerializer): author = AuthorSerializer(read_only=True) class Meta: model = models.CourseMaterial fields = ['title', 'description', 'url', 'author']
  29. Résultat http --json http://127.0.0.1:8000/teaching/course_material/1/ -s colorful HTTP/1.1 200 OK …

    { "author": { "email": "[email protected]", "id": 1, "name": "A. Christie" }, "description": "Le crime de l'Orient-Express", "title": "Résoudre un mystère", "url": null }
  30. serializers.py class AuthorSerializer(serializers.ModelSerializer): id = serializers.IntegerField(required=False) class Meta: model =

    models.Author fields = ['id', 'name', ‘email'] class CourseMaterialSerializer(serializers.ModelSerializer): author = AuthorSerializer(read_only=True) class Meta: model = models.CourseMaterial fields = ['title', 'description', 'url', 'author']
  31. serializers.py class CourseMaterialSerializer(serializers.ModelSerializer): … def create(self, validated_data): author = self.create_or_update_author(

    validated_data.pop('author') ) course_material = models.CourseMaterial.objects.create( author=author, **validated_data, ) return course_material
  32. serializers.py class CourseMaterialSerializer(serializers.ModelSerializer): … def update(self, instance, validated_data): author =

    self.create_or_update_author( validated_data.pop('author') ) instance.title = validated_data['title'] instance.description = validated_data['description'] instance.email = validated_data['email'] instance.author = author instance.save() return instance
  33. serializers.py class CourseMaterialSerializer(serializers.ModelSerializer): … def create_or_update_author(self, author_data): try: author =

    models.Author.objects.get( id=author_data.get('id'), ) author.name = author_data['name'] author.email = author_data['email'] except models.Author.DoesNotExist: author = models.Author(**author_data) author.save() return author
  34. Résultat: http --json POST http://127.0.0.1:8000/teaching/course_material/\ author:='{"email": "[email protected]", "name": "aha"}' \

    description="blabla2" title="bla2" HTTP/1.1 201 Created … { "author": { "email": "[email protected]", "id": 3, "name": "aha" }, "description": "blabla2", "title": "bla2", "url": null }
  35. teaching/api_views.py from rest_framework import viewsets from rest_framework.decorators import action from

    rest_framework.response import Response from . import serializers, models class CourseMaterialViewSet(viewsets.ModelViewSet): queryset = models.CourseMaterial.objects.all() serializer_class = serializers.CourseMaterialSerializer @action(methods=['get'], detail=False) def authors(self, request, *args, **kwargs): authors = models.Author.objects.all() serializer = serializers.AuthorSerializer( instance=authors, many=True, ) return Response(serializer.data)
  36. Résultat http --json http://127.0.0.1:8000/teaching/course_material/authors/ HTTP/1.1 200 OK Allow: GET, HEAD,

    OPTIONS Content-Length: 139 Content-Type: application/json Date: Sat, 06 Oct 2018 00:47:21 GMT Server: WSGIServer/0.2 CPython/3.6.6 Vary: Accept, Cookie X-Frame-Options: SAMEORIGIN [ { "email": "[email protected]", "id": 1, "name": "A. Christie" } ]