Les API o Application Programming Interface ens permeten accedir de forma controlada a les dades d'una aplicació web, típicament a través del format JSON, tot i que inicialment es fes servir XML.
Es basen en l'arquitectura software REST. Aquesta estableix estàndards per a HTTP que faciliten la interoperativitat, en particular:
Referències teoria i documentació:
Abans que res aclarir que per fer crides AJAX no cal tenir instal·lat Django API REST.
Aquestes crides AJAX es poden resoldre amb una simple JsonResponse.
Per exemple, seguint el tutorial de Django «polls», podem implementar una view que ens retorni les dades de les Question
en format JSON d'aquesta manera. Per distingir de les views HTML farem un arxiu api.py
que sabem que retornarà objectes JSON:
from django.http import JsonResponse from polls.models import * #... def get_questions(request): jsonData = list( Question.objects.all().values() ) return JsonResponse({ "status": "OK", "questions": jsonData, }, safe=False)
Observa bé què cal per poder serialitzar els objectes del model:
jsonData = list( Question.objects.all().values() )
.values()
list( … )
Recorda que abans caldrà que implementis un endpoint, per exemple a /get_questions
dins de urls.py
:
from django.urls import path from . import views, api urlpatterns = [ path('', views.index, name='index'), #... path('get_questions', api.get_questions, name='get_questions'), ]
I podrem obtenir les dades des del browser o bé amb una crida per la shell:
$ curl localhost:8000/polls/get_questions {"questions": [{"id": 1, "question_text": "Qu\u00e8 vols per sopar?", "pub_date": "2022-02-17T09:42:57Z"}, {"id": 2, "question_text": "Qui guanyar\u00e0 la lliga?", "pub_date": "2022-02-17T09:43:13Z"}]}
Si voleu un pretty print del JSON a la línia de comandes podeu fer:
$ curl localhost:8000/polls/get_questions | python3 -m json.tool
I per acabar-ho d'adobar, afegim colors amb pygmentize -l json
:
$ curl localhost:8000/polls/get_questions | python3 -m json.tool | pygmentize -l json
El browser sempre és un tiro segur molt còmode:
Clar que d'aquesta manera el nostre «endpoint» quedarà exposat a qualsevol persona que vulgui accedir a la nostra BD. Si volem fer que només les persones loguejades puguin accedir a les dades de l'aplicació, podem protegir la nostra view molt simplement amb un decorator, en concret el @login_required decorator:
from django.contrib.auth.decorators import login_required # ... @login_required def get_questions(request): qs = Question.objects.all().values() return JsonResponse({ "status": "OK", "questions":list(qs) }, safe=False)
Comprova que via shell i la comanda curl
ja no puc accedir. En canvi, amb el navegador, si em logo abans sí que podré accedir a les dades.
Existeixen altres decorators que ens faciliten fer un «tunning» més fi de les nostres views. En particular pots mirar el decorator user_passes_test per assegurar-te que l'usuari logat compleix amb certes condicions, com pertànyer a cert grup d'usuaris amb determinats privilegis.
El mètode de crear views amb JsonResponse vist abans ens permetrà crear APIs accessibles per la pròpia web a través d'AJAX, però no ens facilitarà accedir des d'aplicacions externes com aplicacions mòbils o altres webs.
La Django REST framework ens permetrà obrir les dades a altres aplicacions i dispositius amb seguretat.
Pots seguir el quickstart per agafar una idea ràpida de com utilitzar el REST Framework.
Exercici:
User
, Question
i Choice
.curl
per a llistar els diferents tipus objectes (llista complerta i detalls d'un en particular).
Una part important per a tractar una API és la autenticació (comprovació de identitat) i autorització (quins permisos té aquell usuari identificat).
Django REST Framework authentication reference.
Els dos models d'autenticació més importants són:
Només recomanada per testing, i no per producció, ja que estem enviant sempre la contrasenya per validar-nos. Per defecte ja ve activada per a la configuració estàndard del REST Framework.
El típic exemple de request seria aquest:
curl -u admin:admin123 http://127.0.0.1:8000/api/users/
De vegades ens pot interessar una alternativa enviant les dades codificades en base64 en els headers:
curl -H 'Authorization:Basic YWRtaW46YWRtaW4xMjM=' http://127.0.0.1:8000/api/users/
Per obtenir el hash en base64 es pot fer:
echo -n "admin:admin123" | openssl base64
O en la versió amb indentació o «pretty print» (això és una implementació particular de Django, en altres no funciona):
curl -H 'Accept: application/json; indent=4' -u admin:admin123 http://127.0.0.1:8000/api/users/
Exercici:
curl -i
per visualitzar els headers amb la info).
Aquest és el mètode estàndard per a producció. La idea és que no enviem la contrasenya contínuament, sinó que enviem un token. Per obtenir aquest sí que caldrà emprar la contrasenya, però només un cop mentre que en BasicAuthentication s'envia sempre augmentant les probabilitats de que algú pugui intervenir la contrasenya.
El TokenAuthentication estarà igualment exposat a robatori de sessió o session hijacking, però al menys no comprometem la contrasenya que és una dada sensible. Per tenir un grau mínim de seguretat i no estar exposat a robatoris de sessió és imprescindible emprar sempre HTTPS.
Seguim doc oficial: crear endpooint o URL per obtenir Token.
from rest_framework.authtoken import views as apiviews urlpatterns += [ path('api-token-auth/', apiviews.obtain_auth_token) ]
Assegura't de tenir a settings.py
:
INSTALLED_APPS = [ #... 'rest_framework', 'rest_framework.authtoken', ] REST_FRAMEWORK = { #... 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.TokenAuthentication', ] }
Per aconseguir el Token:
curl -X POST -d 'username=admin&password=admin123' http://127.0.0.1:8000/api-token-auth/
Per utilitzar el Token:
curl http://127.0.0.1:8000/api/example/ -H 'Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b'
Exercici amb objectes aniuats:
Question
i Choice
.QuestionSerializer
per tal que tingui aniuat («nested») les Choice
, de manera que quan demanem una (o vàries) Question
ens surti també la info de les seves Choice
internes.
Si volem fer les nostres pròpies URLs per a la API i volem afegir-hi algun dels mètdodes d'autenticació del REST framework, ho podem fer així:
from rest_framework.authentication import TokenAuthentication, BasicAuthentication from rest_framework.permissions import IsAuthenticated from rest_framework.decorators import api_view, authentication_classes, permission_classes from django.http import JsonResponse @api_view(['GET']) @authentication_classes([TokenAuthentication, BasicAuthentication]) @permission_classes([IsAuthenticated]) def pin_request(request): return JsonResponse({ "usuari":request.user.username, "PIN":3142 }) urlpatterns += [ path('api/pin_request', pin_request ) ]