Share
Embed
Transcript
83 slides
-
REST • Client - Server • Stateless • Caching •
Unified interface: • Resources identification • Resources manipulation • Auto-described message • Hypermedia • Layered architecture -
DRF Content negotiation / Parser Router Authentication / Permissions /
Throttling Deserialization Processing / Pagination / Filtering Serialization Renderer Middleware -
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 -
Authentication Permissions Throttling • Authentication: Who are you ? •
Permissions: Can you do this ? • Throttling: Did you abuse our ressources ? -
(De)serialization • Python base types <-> complex objects ‣ Representation
step ‣ "2015-06-03" <-> datetime.date(2015, 6, 3) • Sanitize the inputs: ‣ type checking ‣ escaping -
Serializers {"demo": 1} <class dict> "2015-06-03" <class str> <SomeObj> <class
SomeObj> 2015-06-03 <class date> Serializer Unsafe Safe -
Two levels of settings # global: settings.py REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.SessionAuthentication', ) } # Per class class ExampleView(APIView): authentication_classes = ( BasicAuthentication, SessionAuthentication ) -
Virtual environment $ virtualenv workshop_drf_env Using base prefix '/opt/local/Python.framework/Versions/3.4' New
python executable in workshop_drf_env/bin/python3.4 Also creating executable in workshop_drf_env/bin/python Installing setuptools, pip...done. $ cd workshop_drf_env/ $ source bin/activate (workshop_drf_env) $ -
Dependencies (workshop_drf_env)$ pip install Django djangorestframework django- filter Collecting Django==1.8.2
Downloading Django-1.8.2-py2.py3-none-any.whl (6.2MB) 100% |████████████████████████████████| 6.2MB 23.2MB/s Collecting djangorestframework==3.1.2 Downloading djangorestframework-3.1.2-py2.py3-none-any.whl (463kB) 100% |████████████████████████████████| 466kB 23.1MB/s … Installing collected packages: Django, djangorestframework, django- filter Successfully installed Django-1.8.2 django-filter-0.10.0 djangorestframework-3.1.2 (workshop_drf_env)$ -
Project creation (workshop_drf_env)$ django-admin startproject workshop_drf (workshop_drf_env)$ cd workshop_drf/ (workshop_drf_env)$
python manage.py startapp todo (workshop_drf_env)$ mv todo workshop_drf/ -
Layout workshop_drf + manage.py + workshop_drf + __init__.py + settings.py
+ urls.py + wsgi.py + todo + __init__.py + admin.py + models.py + tests.py + views.py + migrations + __init__.py -
settings.py INSTALLED_APPS = ( … # Project 'workshop_drf.todo', # 3rd
parties 'rest_framework', ) -
todo/models.py from django.db import models from django.conf import settings class
Category(models.Model): name = models.CharField(max_length=64) def __str__(self): return self.name class Meta: verbose_name_plural = "Categories" class Task(models.Model): name = models.CharField(max_length=64) owner = models.ForeignKey(settings.AUTH_USER_MODEL) categories = models.ManyToManyField( Category, related_name="tasks") done = models.BooleanField(default=False) def __str__(self): return self.name -
Migrations creation (workshop_drf_env)$ python manage.py check System check identified no
issues (0 silenced). (workshop_drf_env)$ python manage.py makemigrations Migrations for 'todo': 0001_initial.py: - Create model Category - Create model Task (workshop_drf_env)$ -
Database creation (workshop_drf_env)$ python manage.py migrate Operations to perform: Synchronize
unmigrated apps: messages, staticfiles, rest_framework Apply all migrations: admin, contenttypes, sessions, todo, auth Synchronizing apps without migrations: Creating tables... Running deferred SQL... Installing custom SQL... Running migrations: Rendering model states... DONE Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK […] Applying sessions.0001_initial... OK Applying todo.0001_initial... OK -
todo/admin.py from django.contrib import admin from . import models admin.site.register(models.Category)
admin.site.register(models.Task) -
$ python manage.py createsuperuser $ python manage.py runserver http://127.0.0.1/admin/ Create
some categories & tasks Play time -
todo/serializers.py from rest_framework import serializers from . import models class
CategorySerializer(serializers.ModelSerializer): class Meta: model = models.Category class TaskSerializer(serializers.ModelSerializer): class Meta: model = models.Task -
todo/views.py from rest_framework import viewsets from . import serializers, models
class CategoryViewSet(viewsets.ModelViewSet): queryset = models.Category.objects.all() serializer_class = serializers.CategorySerializer class TaskViewSet(viewsets.ModelViewSet): queryset = models.Task.objects.all() serializer_class = serializers.TaskSerializer -
todo/urls.py from django.conf.urls import url, include from rest_framework.routers import DefaultRouter
from workshop_drf.todo import views router = DefaultRouter() router.register(r'category', views.CategoryViewSet) router.register(r'task', views.TaskViewSet) urlpatterns = [ url(r'^admin/', include(admin.site.urls)), url(r'^', include(router.urls)), url(r'^api-auth/', include( 'rest_framework.urls', namespace=‘rest_framework’)), ] -
todo/urls.py from django.conf.urls import url, include from rest_framework.routers import DefaultRouter
from . import views router = DefaultRouter() router.register(r'category', views.CategoryViewSet) router.register(r'task', views.TaskViewSet) urlpatterns = [ url(r'^', include(router.urls)), url(r'^api-auth/', include( 'rest_framework.urls', namespace='rest_framework')) ] -
urls.py from django.conf.urls import include, url from django.contrib import admin
import workshop_drf.todo.urls urlpatterns = [ url(r'^admin/', include(admin.site.urls)), url(r'', include(workshop_drf.todo.urls)), ] -
Browse • Overview: ➡ http://localhost:8000/ • List / create categories:
➡ http://localhost:8000/category/ • Show / update / delete a category: ➡ http://localhost:8000/category/1/ • As JSon: ➡ http://localhost:8000/category/?format=json -
Tests ! from django.core.urlresolvers import reverse from rest_framework import status
from rest_framework.test import APITestCase from django.contrib.auth import User from . import models -
Tests !! class TestTask(APITestCase): def test_task_creation(self): user = User.objects.create(username="admin") category
= models.Category.objects.create(name="Django") url = reverse('task-list') data = { "name": "demo", "owner": user.id, "categories": [category.id], "done": False, } response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED, response.content) data['id'] = 1 self.assertEqual(response.data, data) self.assertEqual(category.tasks.count(), 1) -
Tests !! class TestTask(APITestCase): def test_task_creation(self): user = User.objects.create(username="admin") category
= models.Category.objects.create(name="Django") url = reverse('task-list') data = { "name": "demo", "owner": user.id, "categories": [category.id], "done": False, } response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED response.content) data['id'] = 1 self.assertEqual(response.data, data) self.assertEqual(category.tasks.count(), 1) -
Tests !! class TestTask(APITestCase): def test_task_creation(self): user = User.objects.create(username="admin") category
= models.Category.objects.create(name="Django") url = reverse('task-list') data = { "name": "demo", "owner": user.id, "categories": [category.id], "done": False, } response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED, response.content) data['id'] = 1 self.assertEqual(response.data, data) self.assertEqual(category.tasks.count(), 1) -
Tests !! class TestTask(APITestCase): def test_task_creation(self): user = User.objects.create(username="admin") category
= models.Category.objects.create(name="Django") url = reverse('task-list') data = { "name": "demo", "owner": user.id, "categories": [category.id], "done": False, } response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED, response.content) data['id'] = 1 self.assertEqual(response.data, data) self.assertEqual(category.tasks.count(), 1) -
Tests !! class TestTask(APITestCase): def test_task_creation(self): user = User.objects.create(username="admin") category
= models.Category.objects.create(name="Django") url = reverse('task-list') data = { "name": "demo", "owner": user.id, "categories": [category.id], "done": False, } response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED, response.content) data['id'] = 1 self.assertEqual(response.data, data) self.assertEqual(category.tasks.count(), 1) -
Tests !! class TestTask(APITestCase): def test_task_creation(self): user = User.objects.create(username="admin") category
= models.Category.objects.create(name="Django") url = reverse('task-list') data = { "name": "demo", "owner": user.id, "categories": [category.id], "done": False, } response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED, response.content) data['id'] = 1 self.assertEqual(response.data, data) self.assertEqual(category.tasks.count(), 1) -
Tests !! class TestTask(APITestCase): def test_task_creation(self): user = User.objects.create(username="admin") category
= models.Category.objects.create(name="Django") url = reverse('task-list') data = { "name": "demo", "owner": user.id, "categories": [category.id], "done": False, } response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED, response.content) data['id'] = 1 self.assertEqual(response.data, data) self.assertEqual(category.tasks.count(), 1) -
Running tests (workshop_drf_env)$ python manage.py test Creating test database for
alias 'default'... . ---------------------------------------------------------------- Ran 1 test in 0.053s OK Destroying test database for alias 'default'... (workshop_drf_env)$ -
todo/serializers.py 1/2 from rest_framework import serializers from django.contrib.auth.models import User
from . import models class CategorySerializer(serializers.ModelSerializer): class Meta: model = models.Category fields = ('id', 'name') -
todo/serializers.py 2/2 class TaskSerializer(serializers.ModelSerializer): owner = serializers.SlugRelatedField( slug_field='username', queryset=User.objects.all()) categories
= serializers.SlugRelatedField( slug_field='name', queryset=models.Category.objects.all(), many=True) class Meta: model = models.Task fields = ('id', 'name', 'owner', 'categories', 'done') -
Tests (workshop_drf_env)$ python manage.py test Creating test database for alias
'default'... F =================================================================== FAIL: test_task_creation (workshop_drf.todo.tests.TestTask) ------------------------------------------------------------------- Traceback (most recent call last): File "/Users/xordoquy/Documents/Devs/workshop_drf_djangoconeu2015/ workshop_drf/todo/tests.py", line 24, in test_task_creation response.status_code, status.HTTP_201_CREATED) AssertionError: 400 != 201 : b'{"categories":["Object with name=1 does not exist."],"owner":["Object with username=1 does not exist."]}' ------------------------------------------------------------------- Ran 1 test in 0.055s FAILED (failures=1) Destroying test database for alias 'default'... (workshop_drf_env)$ -
Tests 400 != 201 { "categories": ["Object with name=1 does
not exist."], "owner": ["Object with username=1 does not exist."] } -
Updated test class TestTask(APITestCase): def test_task_creation(self): user = User.objects.create(username="admin") category
= models.Category.objects.create(name="Django") url = reverse('task-list') data = { "name": "demo", "owner": user.username, "categories": [category.name], "done": False, } response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED, response.content) data['id'] = 1 self.assertEqual(response.data, data) self.assertEqual(category.tasks.count(), 1) -
class TaskSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField( 'task-detail', source='id', read_only=True) owner =
serializers.SlugRelatedField( slug_field='username', queryset=get_user_model().objects.all()) categories = serializers.SlugRelatedField( slug_field='name', queryset=models.Category.objects.all(), many=True) class Meta: model = models.Task fields = ('id', 'name', 'owner', 'categories', 'done', 'url') todo/serializers.py -
class TestTask(APITestCase): def test_task_creation(self): user = User.objects.create(username="admin") category = models.Category.objects.create(name="Django")
url = reverse('task-list') data = { "name": "demo", "owner": user.username, "categories": [category.name], "done": False, 'url': 'http://testserver/task/1/', } response = self.client.post(url, data, format='json') self.assertEqual( response.status_code, status.HTTP_201_CREATED, response.content) data['id'] = 1 self.assertEqual(response.data, data) self.assertEqual(category.tasks.count(), 1) todo/tests.py -
Updated test def test_task_creation(self): user = User.objects.create(username="admin") user.set_password("a") user.save() self.client.login(username='admin',
password='a') category = models.Category.objects.create(name="Django") […] self.assertEqual(category.tasks.count(), 1) task = models.Task.objects.get(id=1) self.assertEqual(task.owner_id, user.id) -
class TaskViewSet(viewsets.ModelViewSet): queryset = models.Task.objects.all() serializer_class = serializers.TaskSerializer def perform_create(self,
serializer): serializer.save(owner=self.request.user) todo/views.py -
class TaskSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField( 'task-detail', source='id', read_only=True) owner =
serializers.SlugRelatedField( slug_field='username', read_only=True) categories = serializers.SlugRelatedField( slug_field='name', queryset=models.Category.objects.all(), many=True) class Meta: model = models.Task fields = ('id', 'name', 'owner', 'categories', 'done', ‘url’) def create(self, validated_data): categories = validated_data.pop('categories') task = models.Task.objects.create(**validated_data) task.categories = categories return task todo/serializers.py -
class TaskSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField( 'task-detail', source='id', read_only=True) owner =
serializers.SlugRelatedField( slug_field='username', read_only=True, default=serializers.CurrentUserDefault()) categories = serializers.SlugRelatedField( slug_field='name', queryset=models.Category.objects.all(), many=True) class Meta: model = models.Task fields = ('id', 'name', 'owner', 'categories', 'done', ‘url’) todo/serializers.py -
Categories [{ "id": 4, "name": "Django", "tasks": [{ "id": 1,
"name": "Faire les slides", "owner": "admin", "categories": ["Django"], "done": false }, { "id": 3, "name": "Fix #546", "owner": "admin", "categories": ["Django"], "done": false }] }] -
todo/serializers.py class CategorySerializer(serializers.ModelSerializer): tasks = Task(many=True, read_only=True) class Meta: model
= models.Category fields = ('id', 'name', 'tasks') -
My tasks • All my tasks: /task/mine/ • My tasks
within a category: /category/<id>/mine/ -
from rest_framework.response import Response from rest_framework.decorators import detail_route, list_route class
TaskViewSet(viewsets.ModelViewSet): […] @list_route() def mine(self, request): queryset = self.filter_queryset( self.get_queryset().filter(owner=request.user)) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) todo/views.py -
todo/serializers.py class MyCategorySerializer(serializers.ModelSerializer): tasks = TaskSerializer(many=True, source='my_tasks') class Meta: model
= models.Category fields = ('id', 'name', 'tasks') -
todo/views.py class CategoryViewSet(viewsets.ModelViewSet): queryset = models.Category.objects.all() serializer_class = serializers.CategorySerializer @detail_route()
def mine(self, request, *args, **kwargs): category = self.get_object() category.my_tasks = category.tasks.filter( owner=request.user) serializer = serializers.MyCategory( category, context={‘request’: request} ) return Response(serializer.data) -
todo/views.py class CategoryViewSet(viewsets.ModelViewSet): queryset = models.Category.objects.all() serializer_class = serializers.CategorySerializer @detail_route()
def mine(self, request, *args, **kwargs): category = self.get_object() category.my_tasks = category.tasks.filter( owner=request.user) serializer = serializers.MyCategory( category, context={‘request’: request} ) return Response(serializer.data) -
todo/views.py class CategoryViewSet(viewsets.ModelViewSet): queryset = models.Category.objects.all() serializer_class = serializers.CategorySerializer @detail_route()
def mine(self, request, *args, **kwargs): category = self.get_object() category.my_tasks = category.tasks.filter( owner=request.user) serializer = serializers.MyCategory( category, context={‘request’: request} ) return Response(serializer.data) -
todo/views.py class CategoryViewSet(viewsets.ModelViewSet): queryset = models.Category.objects.all() serializer_class = serializers.CategorySerializer @detail_route()
def mine(self, request, *args, **kwargs): category = self.get_object() category.my_tasks = category.tasks.filter( owner=request.user) serializer = serializers.MyCategory( category, context={‘request’: request} ) return Response(serializer.data) -
todo/views.py class CategoryViewSet(viewsets.ModelViewSet): queryset = models.Category.objects.all() serializer_class = serializers.CategorySerializer @detail_route()
def mine(self, request, *args, **kwargs): category = self.get_object() category.my_tasks = category.tasks.filter( owner=request.user) serializer = serializers.MyCategory( category, context={‘request’: request} ) return Response(serializer.data) -
todo/filters.py import django_filters from . import models class TaskFilterSet(django_filters.FilterSet): class
Meta: model = models.Task fields = ['done', 'owner', 'categories'] -
todo/views.py from rest_framework import view sets from rest_framework.filters import DjangoFilterBackend
from . import serializers, models, filters class Task(viewsets.ModelViewSet): queryset = models.Task.objects.all() serializer_class = serializers.TaskSerializer filter_backends = (DjangoFilterBackend,) filter_class = filters.TaskFilter -
todo/filters.py import django_filters from django.contrib.auth import get_user_model from . import
models class TaskFilterSet(django_filters.FilterSet): owner = django_filters.ModelChoiceFilter( to_field_name="username", queryset=get_user_model().objects.all()) categories = django_filters.ModelMultipleChoiceFilter( to_field_name="name", queryset=models.Category.objects.all()) class Meta: model = models.Task fields = ['done', 'owner', 'categories'] -
todo/views.py from rest_framework import viewsets from rest_framework.filters import DjangoFilterBackend from
. import serializers, models, filters class TaskViewSet(viewsets.ModelViewSet): queryset = models.Task.objects.all() serializer_class = serializers.TaskSerializer filter_backends = (DjangoFilterBackend,) filter_class = filters.TaskFilter -
• Montrer les classes et le source DRF • Modifier
un get/post • Montrer les docstrings dans l’interface d’admin ! • Validators • Passer l’ensemble sous forme d’exercices