V článku si ukážeme ako pomocou Djanga respektíve Django Rest frameworku naprogramujeme jednoduché REST API s autentifikáciou a autorizáciou. Predpokladá sa, že programátor ovláda Django framework a tým pádom sa nemusíme zdržiavať vysvetlovaním základných nastavení projektu. Ako modelovú situáciu použijeme klasiku v podobe vytvorenia todo listu.
Závislosti
V prvom kroku si nainštalujeme Django REST framework, ktorý je nadstavbou Djanga a správa sa ako modul v projekte.
INSTALLED_APPS = ( ... 'rest_framework', )
Model TODO listu bude vyzerať takto:
from django.db import models class Todo(models.Model): task = models.CharField(max_length=50) description = models.CharField(max_length=255, default=“") priority = models.SmallIntegerField(default=1)
Spustíme vytvorenie migracie a samotnú migráciu.
python manage.py makemigrations python manage.py migrate
Serializácia modelov
Vytvoríme si serializer, ktorý má na starosti serializovať a deserializovať inštanciu našeho modelu do JSON tvaru. Vytváranie serializera je niečo podobné, ako práca s Django formulárom. Uložíme si súbor serializers.py. Nepoužijeme defaultný serializers.Serializer pretože by sme museli každú položku modulu nastavovať typom
description = serializers.CharField( required=False, allow_blank=True, max_length=255 )
Miesto toho využijeme serializers.ModelSerializer. Modelový serializer definuje položky podľa modelu a my si tak uľahčíme robotu.
class SnippetSerializer(serializers.ModelSerializer): class Meta: model = Todo fields = ('id', ’task', ‘description', ‘priority',)
Do našeho modulu Todo pridáme urls.py súbor
from django.urls import path from todo import views urlpatterns = [ path(’todo/',views.TodoList.as_view()), ]
a taktiež do hlavného urls.py v roote aplikácie link na naše API
from django.conf import settings from django.conf.urls.static import static from django.urls import include from django.urls import path urlpatterns = [ ... path('api/', include(’todo.urls')), ...
Django Rest Framework umožňuje dvoma spôsobmi určiť, že view djanga je API views.
- 1. pre view, ktoré je definované ako funkcia použijeme @api_view
- 2. pre view, ktoré je definované ako classa použijeme APIView
Každý používa iný spôsob. Ja preferujem base-classed view. Umožňuje mi tak dodržiavať princíp DRY (dont repeat yourself) . Tu je náš kód vo views.py
from todo.models import Todo from todo.serializers import TodoSerializer from django.http import Http404 from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status class TodoList(APIView): """ List all todos, or create a new todo. """ def get(self, request, format=None): todos = Todo.objects.all() serializer = TodoSerializer(todos, many=True) return Response(serializer.data) def post(self, request, format=None): serializer = TodoSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Uvedeným spôsobom máme pripravené API, ktoré pri GET requeste zobrazí zoznam všetkých
TODO úloh, ktoré sú v databáze. Pri validnom POST requeste vieme vložiť TODO úloh do
databázy.
Na Djangu a jeho modulov je krásne, že obsahujú veľa “sugar” vecí, ktoré vývoj
zjednodušujú. Kedže v jednoduchosti je krása, naše API vylepšíme použitím generických
class-based view.
from todo.models import Todo from todo.serializers import TodoSerializer from rest_framework import generics class TodoList(generics.ListCreateAPIView): queryset = Todo.objects.all() serializer_class = TodoSerializer
Pár riadkov kódu a dosiahli sme rovnaký efekt, ako pri dedení view z triedy APIView.
Generická trieda LIstCreateAPIView obsahuje v svojom vnútri metódy na ukladanie
modelového objektu a tiež ponúka vylistovanie už vložených objektov. Pred pripravených
generických view je v module rest_framework.generics veľa. Stačí si len vybrať. Ak by
sme chceli API len s vylistovaním objektov, stačilo by dediť od ListAPIView.
Vlastná funkcionalita ukladania dát sa zabezpečí prepísaním metódy create vo views.
… class TodoList(generics.ListCreateAPIView): queryset = Todo.objects.all() serializer_class = TodoSerializer create(self, validation_data): # pristup k datam print(validated_data[’task’]) # samotne ulozenie dat, ktore vrati model obj = Todo.objects.create(**validated_data) obj.save(foo=validated_data['task']) return obj
Vytvorené API umožňuje využívať funkcionalitu všetkým aj prihláseným aj
neprihláseným používateľom. Ukážeme si, ako implementovať autentifikáciu a autorizáciu.
Do modelu TODO pridáme nasledovné
… owner = models.ForeignKey('auth.User', related_name=‘todos', on_delete=models.CASCADE) …
Taktiež si vytvoríme serializer na prácu s modelom User, ktorý si prevezmeme z už hotového riešenia modulu django.contrib.auth.model
from django.contrib.auth.models import User class UserSerializer(serializers.ModelSerializer): todos = serializers.PrimaryKeyRelatedField(many=True, queryset=Todo.objects.all()) class Meta: model = User fields = ('id', 'username', ’todos')
Pridaním owner atribútu sme dosiahli reverzný vzťah modelových objekto User vs Todo. Dôsledkom toho je, že naše TODO obsahuje kľuč na vlastníka a vlastník zase reláciu na všetky vytvorené objekty Todo.
Práca s Userom
do views.py
from django.contrib.auth.models import User class UserList(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer class UserDetail(generics.RetrieveAPIView): queryset = User.objects.all() serializer_class = UserSerializer
do urls.py
url(r'^users/$', views.UserList.as_view()), url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
Pre dosiahnutie automatického uloženia vlastníka vytvoreného objektu. Todo je potrebné
prepísať metódu perform_create v views.py a tiež pridať do serializera atribút owner.
serializers.py
... owner = serializers.ReadOnlyField(source='owner.username’) ...
views.py
... def perform_create(self, serializer): serializer.save(owner=self.request.user)
Pri ukladaní Todo modelu sa nám z requestu prenesie prihlásený používateľ a nastaví sa
ako vlastník vytváraného objektu. V serializeri sme definovali ReadOnlyField, čo znamená,
že tento atribút nevieme priamo v API prepísať. Automaticky sa nastaví až v procese ukladania
v metóde perform_create.
Máme prepojený Todo model s Userom a chceme aby vytvorené Todo bolo možné editovať len
používateľom, ktorý ho vytvoril. Framework ponúka rôzne druhy autorizačných tried.
My použijeme IsAuthenticatedOrReadOnly a tiež si vytvoríme jednu vlastnú. Prvá nám zabezpečí,
aby vytvorene objekty videli len prihlásený používateľ. Tú druhú si zostrojíme tak, aby objekty mohol editovať len vlastník vytvoreného objektu.
from rest_framework import permissions ... permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
Ak by sme teraz skúsili vytvoriť objekt, nepodarí sa nám to. Je potrebné vytvoriť podstránku, ktorá umožní prihlásenie používateľa. Existuje však hotové riešenie a to v podobe frameworkovej internej url adresy
urlpatterns += [ url(r'^api-auth/', include('rest_framework.urls')), ]
Vlastné riešenie autorizácie
from rest_framework import permissions class IsOwnerOrReadOnly(permissions.BasePermission): """ Custom permission to only allow owners of an object to edit it. """ def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request, # so we'll always allow GET, HEAD or OPTIONS requests. if request.method in permissions.SAFE_METHODS: return True # Write permissions are only allowed to the owner of the snippet. return obj.owner == request.user
a upravíme naše views.py
from todos.permissions import IsOwnerOrReadOnly ... permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,) ...
Otestovanie
Dotaz bez autorizácie
http POST http://127.0.0.1:8000/todos/ task="nejaka uloha" { "detail": "Authentication credentials were not provided." }
Autentifikácia našeho requestu môže vyzerať aj takto
http -a admin:password123 POST http://127.0.0.1:8000/todos/ task="nejaka uloha" { "id": 1, “task": "nejaka uloha", “description": "", “priority": 1, }
Záver
Na jednoduchom príklade sme si ukázali použitie RESTového rozhrania v prostredí Django. Príklad ilustruje len základnú prácu s API. Do budúca plánujem uverejniť článok spojenia REST rozhrania s asynchrónnym task queue frameworkom Celary.
Softvérový vývojár a podnikateľ zameriavajúci sa na platformu Java, Python a Javascript. Mám dlhoročné profesionálne skúsenosti s vývojom Java/JavaEE aplikácií. Som zakladateľ firmy Morione, ktorá sa venuje návrhom a realizáciou efektívnych webových aplikácií. Podporujem a vyvíjam viaceré startupy. Vo voľnom čase cestujem po svete, behám po horách, píšem blogy a tvorím kreatívne videá.