====== APIs amb Django ====== 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'[[https://es.wikipedia.org/wiki/Transferencia_de_Estado_Representacional|arquitectura software REST]]. Aquesta estableix estàndards per a HTTP que faciliten la interoperativitat, en particular: * **Mètodes** equivalents al CRUD de BD: **GET, POST, PUT, DELETE** (però poden definir-se més). * [[https://restfulapi.net/http-status-codes/|HTTP Status code]]: per definir l'estat de la connexió Referències teoria i documentació: * [[https://cacauet.org/wiki/index.php/Web_Services|Teoria web services]] * [[https://cacauet.org/wiki/index.php/Web_Services:_exemple_Escacs#Especificaci.C3.B3|Exemple API escacs]] * [[https://cacauet.org/wiki/index.php/Serveis_web_amb_Laravel#Especificacions_d.27una_API|Exemple API vota karaoke]] {{tag> #FpInfor #DawMp07 #DawMp07Uf4 #DawMp07Uf04 django web api framework rest }} \\ ===== APIs simples amb JsonResponse ===== 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 [[https://docs.djangoproject.com/en/4.0/ref/request-response/#jsonresponse-objects|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() ) - Tenim un queryset de Questions - Seleccionem només els valors amb ''.values()'' - Transformem en una llista amb ''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: {{ django_get_questions_api.png?500 }} \\ ==== Protegint els endpoints ==== 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 [[https://docs.djangoproject.com/en/4.0/topics/auth/default/#the-login-required-decorator|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. == Decorator @user_passes_test == Existeixen altres //decorators// que ens faciliten fer un "tunning" més fi de les nostres //views//. En particular pots mirar [[https://docs.djangoproject.com/es/3.2/topics/auth/default/#django.contrib.auth.decorators.user_passes_test|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. \\ ====== Treballant APIs amb Django REST framework ====== 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 [[https://www.django-rest-framework.org/|Django REST framework]] ens permetrà obrir les dades a altres aplicacions i dispositius amb seguretat. Pots seguir el [[https://www.django-rest-framework.org/tutorial/quickstart/|quickstart]] per agafar una idea ràpida de com utilitzar el REST Framework. Exercici: - Aplica el quickstart del Django REST Framework al tutorial "polls" oficial de Django. - Crea serializers per als objectes ''User'', ''Question'' i ''Choice''. - Realitza requests des de la línia de comandes amb ''curl'' per a llistar els diferents tipus objectes (llista complerta i detalls d'un en particular). \\ ===== Autenticació i autorització ===== Una part important per a tractar una API és la autenticació (comprovació de identitat) i autorització (quins permisos té aquell usuari identificat). [[https://www.django-rest-framework.org/api-guide/authentication/|Django REST Framework authentication reference]]. Els dos models d'autenticació més importants són: * BasicAuthentication * TokenAuthentication \\ ==== BasicAuthentication ==== **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: - Cerca com ha de ser una query per a enregistrar un nou usuari. - Comprova que si no ens autentiquem no ens deixa enregistrar-lo. * Quin //status code// ens dóna amb l'error d'autenticació? (Utilitza la comanda amb el flag ''curl -i'' per visualitzar els //headers// amb la info). - Comprova què passa si intentem insertar un User amb un username preexistent. * Quin //status code// ens dóna amb l'error? \\ ==== TokenAuthentication ==== 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 sessio web|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: [[https://www.django-rest-framework.org/api-guide/authentication/#by-exposing-an-api-endpoint|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' \\ ===== Objectes aniuats ===== Exercici amb objectes aniuats: - Crea els serializers per al tutorial oficial Django "polls", per als objectes ''Question'' i ''Choice''. - Configura el ''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. * Utilitza la [[https://www.django-rest-framework.org/api-guide/relations/#nested-relationships|documentació de "nested relationships"]]. \\ ===== URLs personalitzades amb TokenAuthentication del REST framework ===== 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 ) ] \\