Django REST Framework est une boite à outils puissante et flexible qui vous facilite la création d'application web API .
Je dois avouer que ce projet a complétement changé ma manière de travailler sur des projets Django . Tout est centralisé et combiné avec les outils AngularJS / Grunt / CoffeeScript , vous augmentez considérablement votre productivité.
Nous verrons comment utiliser ce projet dans un tutoriel de création d'une eboutique.
Une API (Application Programming Interface, en français "interface de programmation d'application") est un ensemble de fonctions et de protocoles qui permet à différents logiciels de communiquer entre eux. En d'autres termes, une API est un moyen pour un programme de demander à un autre programme de faire quelque chose pour lui.
Voici quelques exemples de ce que vous pourriez faire avec une API dans votre projet :
Accéder à des données stockées sur un serveur distant (par exemple, les données d'un site web ou d'une application mobile) et les utiliser dans votre propre programme. Envoyer des requêtes à un service en ligne (comme une base de données ou un moteur de recherche) et recevoir une réponse sous forme de données structurées (par exemple, un fichier JSON ou XML). Appeler des fonctions d'un autre programme (par exemple, pour utiliser ses algorithmes de traitement de données ou de reconnaissance de la parole) dans votre propre code. En gros, une API vous permet de "brancher" votre programme sur d'autres programmes ou services, et de les utiliser comme si c'étaient des parties de votre propre programme. Cela peut vous faire gagner du temps et de l'effort en vous évitant de réinventer la roue, et en vous permettant de profiter de fonctionnalités déjà existantes et éprouvées.
Installation de Django REST Framework
Comme il s'agit d'une librarie python , nous pouvons passer par pip pour l'installation:
pip install djangorestframework pip install markdown pip install django-filter
Création de notre app avec Django
Nous pouvons ensuite créer notre projet (qu'importe l'emplacement)
django-admin startproject eboutique django-admin startapp erp
Adoptons notre connexion à la base de données
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'eboutique', 'USER': 'user', 'PASSWORD': 'MOTDEPASSE', 'HOST': '127.0.0.1', 'PORT': '', } }
Et renseignons nos applications
INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'erp' )
Puis créeons nos modèles
class Product(models.Model): date_add = models.DateTimeField(auto_now_add=True) name = models.CharField(max_length=255) code = models.CharField(max_length=100, null=True) price = models.FloatField() supplier = models.ForeignKey('Supplier', null=True) image = models.ImageField(upload_to='product') def __unicode__(self): return "{0}".format(self.code, ) class ProductItem(models.Model): product = models.ForeignKey('Product', related_name="product_item") code = models.CharField(max_length=255) ean13 = models.CharField(max_length=255) def __unicode__(self): return "{0}".format(self.code, ) class Supplier(models.Model): name = models.CharField(max_length=255) def __unicode__(self): return "{0}".format(self.name, )
Création du projet Django Rest Framework
Maintenant nous allons créer des fichiers que nous implémenterons directement dans le module eboutique . Vous pouvez évidemment créer un module consacré à l' API pour mieux organiser votre projet.
# eboutique/views.py from rest_framework import viewsets from erp.models import * from eboutique.serializers import * class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer class SupplierViewSet(viewsets.ModelViewSet): queryset = Supplier.objects.all() serializer_class = SupplierSerializer
# eboutique/serializers.py from rest_framework import serializers from erp.models import * class ProductSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Product fields = ('date_add', 'name', 'code', 'price') class SupplierSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Supplier fields = ('url', 'name')
# eboutique/urls.py from django.conf.urls import patterns, include, url from django.contrib import admin from rest_framework import routers from eboutique.views import * router = routers.DefaultRouter() router.register(r'product', ProductViewSet) router.register(r'supplier', SupplierViewSet) urlpatterns = patterns('', # Examples: # url(r'^$', 'eboutique.views.home', name='home'), # url(r'^blog/', include('blog.urls')), url(r'^admin/', include(admin.site.urls)), url(r'^api/', include(router.urls)), # url(r'^api/', include('rest_framework.urls', namespace='rest_framework')) )
Créeons maintenant le schéma de notre projet:
python manage.py migrate
Et lançons le serveur web
python manage.py runserver 8888
Vous devriez obtenir le résultat suivant via l'url http://localhost:8888/api
Si vous cliquez sur le lien permettant d'éditer les produits, vous pourrez ajouter un item:
Il peut être intéressant dans votre environnement de dev de créer un petit script qui initialise tout votre projet à votre guise. Un exemple de script est disponible ici:
Authentification et permission
Alors oui c'est magique en deux trois mouvements nous avons crée une application web API REST . Mais ce n'est pas encore parfait: il manque encore l'aspect sécurité c'est à dire la permissions d'acceder à certaines ressources (ou non).
Pour cela nous allons ajouter la configuation REST_FRAMEWORK dans notre fichier settings.py
:
# eboutique/settings.py REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', ), 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', ), 'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',), 'PAGE_SIZE': 100, 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', }
Si nous voulons maintenant accéder à la liste des produits: http://127.0.0.1:8888/api/product/
, nous recevons le message suivant:
{"detail":"Informations d'authentification non fournies."}
On nous refuse l'accès parce que je ne suis pas identifié!
Pour vous créer un système de login, je vous conseille de lire ce chapitre: Login Django
Personnaliser les vues
Dans l'exemple ci-dessous, nous verrons comment modifier la queryset et l'optimiser avec prefetch_related . Nous verrons également comment modifier l'objet de sortie et y ajouter au dernier moment des manipulations de données.
class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer filter_class = ProductFilter def get_serializer_class(self): if self.request.method == 'GET': return ReadProductSerializer else: return self.serializer_class def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) if 'search' in request.QUERY_PARAMS: search = request.QUERY_PARAMS['search'] q = Q() for term in search.split(' '): if term "": q = q & Q(name__icontains=term) q = q | Q(name__icontains=search) # On filtre avec l'objet Q queryset = queryset.filter(q) if search == "": queryset = queryset.none() queryset = queryset.prefetch_related("product_item") queryset = queryset.prefetch_related("product_item__product_item_stock") queryset = queryset.prefetch_related("product_item__product_item_stock__warehouse") queryset = queryset.prefetch_related("product_item__attribute") page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) def get_paginated_response(self, data): for product in data: product['product_item']["test"] = 1 assert self.paginator is not None return self.paginator.get_paginated_response(data)
Personnaliser les serializers
Vous pouvez ajouter l'option depth
pour afficher les détails d'un item directement dans l'objet parent sérializé
class ReadProductSerializer(serializers.ModelSerializer): product_item = ReadProductItemForProductSerializer(many=True, required=False) class Meta: model = Product fields = ('name', 'code', 'final_price', 'product_item', 'date_add') depth = 2
Vous pouvez également intérgir lors de l'envoi d'une requète CRUD telle que POST
ou UPDATE
# TicketItem class TicketItemSerializer(serializers.ModelSerializer): class Meta: model = TicketItem fields = ('id', 'name', 'quantity', 'remise', 'price_unit', 'product_item') extra_kwargs = {"id": {"required": False, "read_only": False}} # Ticket class TicketSerializer(ReadTicketSerializer): ticket_item = TicketItemSerializer(many=True, required=False) class Meta: model = Ticket fields = ('id', 'date_add', 'customer', 'price_total', 'ticket_item') def create(self, validated_data): model = getattr(self.Meta, 'model') ticket_item_data = validated_data.pop('ticket_item') instance = model.objects.create(**validated_data) for item in ticket_item_data: TicketItem.objects.create(ticket=instance, **item) return instance def update(self, instance, validated_data): ticket_item_data = validated_data.pop('ticket_item') ids = [dict(item)['id'] for item in ticket_item_data] for i in instance.ticket_item.all(): if i.id not in ids: i.delete() for item in ticket_item_data: TicketItem.objects.filter(id=dict(item)['id']).update(**item) return instance
AngularJS et DRF
Nous avons vu précedemment comment retourner des données très facilement au niveau du développement backend. Pour les développeur frontend c'est encore plus simple avec le module resource de AngularJS .
Intégrons le module ngResource
à notre projet:
app = angular.module 'app', ['ngRoute', 'ngResource', 'ngSanitize']
Configurons notre API:
app.service 'Entity', ($resource) -> @product = () -> return $resource '/api/products/:id/' , {id: '@id'}, { query: { method: 'GET', isArray: false }, search: { method: 'GET', isArray: false }, save: { method: 'POST', headers: { 'Content-Type': 'application/json' }, }, show: { method: 'GET' }, update: { method: 'PUT', params: {id: '@id'} }, delete: { method: 'DELETE', params: {id: '@id'} } } @productitems = () -> return $resource '/api/productitems/:id/' , {id: '@id'}, { query: { method: 'GET', isArray: false }, search: { method: 'GET', isArray: false }, save: { method: 'POST', headers: { 'Content-Type': 'application/json' }, }, show: { method: 'GET' }, update: { method: 'PUT', params: {id: '@id'} }, delete: { method: 'DELETE', params: {id: '@id'} } } true
Notre controlleur AngularJS
class DashboardController @$inject: ['$scope', 'Entity'] constructor: (@scope, Entity) -> @Entity = Entity @scope.products = [] @scope.add_product = @add_product @scope.upd_product = @upd_product @scope.del_product = @del_product @get_products() true add_product : () => item = new (@Entity.product())() item.name = "Iphone 6" item.price = 699.99 item.$save().then (r) => console.log @scope.products @scope.products.push r true upd_product : ($index) => product = @scope.products[$index] item = @Entity.product().get({'id': product.id}, (item) => item.name = product.name item.$update() true ) true get_products: () => @Entity.product().query({search: "test"}).$promise.then (r) => @scope.products = r.results true true del_product: ($index) => product = @scope.products[$index] @Entity.product().delete({'id': product.id}).$promise.then (r) => @scope.products.splice($index, 1) true true app.controller 'DashboardController', DashboardController
Et enfin notre template:
<ul> <li ng-repeat="product in products"> <input ng-model="product.name" ng-change="upd_product($index)" /> <button ng-click="del_product($index)">X</button> </li> </ul> <button ng-click="add_product()">Ajouter un produit</button>