Salta el contingut

UD9: SERVEIS WEB I APLICACIONS WEB REACTIVES

INTRODUCCIÓ

Una API és un servei que proporciona accés a dades i mètodes que altres aplicacions poden utilitzar, mitjançant el protocol HTTP, la qual cosa permet que puga ser integrada en gran varietat d'aplicacions.

La variant d'API més popular per a serveis web actuals es denomina REpresentational State Transfer (REST), o també Servei REST o API REST. Consisteix en una sèrie de directrius dissenyades per a simplificar la comunicació entre client i servidor.

Request

La forma d'interactuar amb un servei REST és mitjançant un objecte anomenat petició, sol·licitud o request, que està format per:

  • Endpoint: es tracta d'una URL que caracteritza el tipus de servei (dada o acció) amb el qual estem interactuant, dins d'una API.

  • Mètode: especifica com interactuar amb l'endpoint. Els mètodes més usuals són:

    • GET: Consulta de dades
    • PUT: Modificació de dades
    • POST: Creació de dades
    • DELETE: Eliminació de dades
  • Dades: En aquells mètodes que necessiten canvi en les dades del sistema, caldrà incloure en el request les dades a crear/modificar.

  • Capçaleres: Conté els metadades addicionals necessaris, com tokens d'autenticació, tipus de contingut a retornar en la resposta, polítiques de cacheig...

Response

Quan es fa una request a un servei REST, obtenim una resposta (o response), que igual que en el cas del request, contindrà una capçalera i dades (quan corresponga). Les dades retornades, en forma de text, estaran normalment en format JSON (encara que també és possible utilitzar altres formats com XML).

AVALUACIÓ

Aquest document, junt amb el seu corresponent butlletí d'activitats (publicat addicionalment), cobreix els següents criteris d'avaluació:

RESULTATS D'APRENENTATGE CRITERIS D'AVALUACIÓ
RA7. Desenvolupa serveis web reutilitzables i accessibles mitjançant protocols web, verificant el seu funcionament. a) S'han reconegut les característiques pròpies i l'àmbit d'aplicació dels serveis web.
b) S'han reconegut els avantatges d'utilitzar serveis web per proporcionar accés a funcionalitats incorporades a la lògica de negoci d'una aplicació.
c) S'han identificat les tecnologies i els protocols implicats en el consum de serveis web.
d) S'han utilitzat els estàndards i arquitectures més difoses i implicades en el desenvolupament de serveis web.

LA LLIBRERIA REQUESTS DE PYTHON

La llibreria requests de python implementa els mètodes HTTP que ens permetran interactuar amb qualsevol API REST. El primer pas és instal·lar la llibreria requests mitjançant el comandament pip en un entorn virtual:

pip install requests
A continuació hem d'importar la llibreria en l'script que volem executar:
import requests
En els següents subapartats es mostren les operacions més bàsiques que es poden realitzar amb aquesta llibreria. Utilitzarem diversos REST API oberts per a fer proves:

  • api.open-notify.org: Utilitzarem aquest API REST per realitzar diverses peticions de consulta.
  • httpbin.org: Aquest API REST ens servirà per realitzar peticions de modificació/creació de dades, autenticació, entre altres.

Consulta de dades

Per fer una consulta de dades a un endpoint que ho permeta, utilitzem el mètode "get" de la llibreria requests, de la forma:

response = requests.get("http://api.open-notify.org/astros.json")
NOTA: Aquest API REST pretén proporcionar informació sobre les persones que es troben actualment a l'espai, no és una font oficial però ens servirà d'exemple.

Per visualitzar l'objecte retornat, fem un print de response:

Si volem veure els atributs de l'objecte response, podem utilitzar la funció dir():

Per accedir al contingut de la resposta, es poden utilitzar les següents opcions:

response.content() #resposta en brut (bytes)

response.text() #format text

response.json() #format json

Utilització de paràmetres de consulta

Si l'endpoint ho permet, es poden enviar paràmetres de consulta en la petició "get" al servidor, per tal d'acotar els resultats.

Això es pot fer mitjançant l'argument "params" del mètode get. Aquest argument ha de ser un diccionari amb el/s paràmetre/s a enviar al servidor. A continuació es mostra un exemple de consulta, per saber quan l'Estació Espacial Internacional passarà per un punt determinat:

gh_session = requests.Session() gh_session.auth = (username, token)

Creació de dades

Mitjançant httpbin.org és possible realitzar peticions tipus "post", encara que les dades no s'emmagatzemen realment en una base de dades. Un exemple és el següent, on es passa al paràmetre "data" un diccionari amb les dades a crear:

response = requests.post('https://httpbin.org/post', data = {'valor':'nou'})
El resultat es pot veure en la següent captura:

Modificació de dades

De manera similar al mètode post, es pot fer una crida mitjançant put, per a la modificació de dades:

response = requests.put('https://httpbin.org/put', data = {'valor':'modificat'})

Accés a les capçaleres

Per obtenir els metadades continguts en la capçalera de la resposta s'utilitza l'atribut headers de l'objecte response. Per exemple, per obtenir la data i hora en què es va produir la resposta:

print(response.headers["date"])

Autenticació

Per interactuar amb serveis REST que necessiten una autenticació prèvia es poden utilitzar diversos mètodes, depenent del tipus d'autenticació que requerisca l'endpoint. A continuació veurem l'autenticació Basic i la basada en OAuth.

Autenticació Basic

És el mètode més simple i consisteix a enviar un nom d'usuari i una contrasenya, equivalent a introduir usuari i contrasenya en un lloc web. En l'exemple següent, es tracta d'obtenir dades del compte de l'usuari "user", i per a això cal enviar en la pròpia consulta els paràmetres d'autenticació per evitar un error de consulta no autoritzada:

from requests.auth import HTTPBasicAuth
response = requests.get(
    'https://api.github.com/user', 
    auth=HTTPBasicAuth('user', 'password')

O també, es pot fer mitjançant una tupla:

requests.get('https://api.github.com/user', auth=('user', 'password'))
NOTA: En el cas de l'API de Github, el valor de password es refereix al token personal d'accés (aquest punt es desenvoluparà en les activitats).

Autenticació Digest

Un altre mètode comú d'autenticació és mitjançant Digest, de la forma:

from requests.auth import HTTPDigestAuth
url = 'https://httpbin.org/digest-auth/auth/user/pass'
response = requests.get(url, auth=HTTPDigestAuth('user', 'pass'))

Autenticació mitjançant sessió

La llibreria requests proporciona un objecte Session que permet conservar determinats paràmetres per a peticions HTTP diferents, així com cookies. Això permet simular una sessió com si fora un navegador web.

Un exemple de codi pot ser el següent, en el qual recuperem els repositoris d'un determinat usuari que s'autentica prèviament:

username = 'user'
token = 'XXX_token'
repos_url = 'https://api.github.com/user/repos'
gh_session = requests.Session()
gh_session.auth = (username, token)
print(gh_session.get(repos_url).text)

Autenticació OAuth

Hi ha dos estàndards OAuth: OAuth1 i OAuth2. En aquest enllaç pots trobar les diferències fonamentals entre els dos mètodes.

Per a OAuth2, un exemple d'autenticació mitjançant Google es detalla en aquesta pàgina. Segons aquestes instruccions, el funcionament bàsic seria:

  1. A mode d'exemple, es mostra a continuació una API d'accés a un projecte de Firebase:

  1. Després, l'aplicació client (la que estem desenvolupant) sol·licita un token d'accés del servidor d'autorització de Google, extrau un token de la resposta i envia el token a l'API de Google a la qual es vol accedir. El scope, que s'envia com a paràmetre en aquesta sol·licitud d'accés, determina el conjunt de recursos i operacions (dins de l'aplicació) als quals es pot accedir mitjançant el token. És possible sol·licitar un scope, però que s'atorgue un de diferent, després cal comprovar el scope rebut.

  2. En l'accés a una API, s'adjunta el token d'accés en la capçalera de la sol·licitud HTTP. Els tokens d'accés són vàlids només per al conjunt de les operacions i els recursos descrits en el scope de la sol·licitud de token. Per exemple, si s'emet un token d'accés per a l'API de Google Calendar, no atorga accés a l'API de Contactes de Google. No obstant això, pots enviar aquest token d'accés a l'API de Google Calendar diverses vegades per a operacions similars.

  3. Els tokens atorgats tenen un temps de vida determinat, després del qual és necessari sol·licitar-ne un de nou. Mitjançant l'anomenat token d'actualització, que és un token proporcionat en el pas 2, diferent del token d'accés, s'aconsegueix obtenir nous tokens d'accés una vegada caducats mitjançant una sol·licitud específica de refresc.

NOTA: Hi ha diferents variants en el flux d'autenticació d'OAuth2, depenent del tipus d'aplicació que estem desenvolupant. En aquest enllaç es poden consultar diferents fluxos d'autenticació per a la llibreria requests. En aquest altre enllaç pots consultar, de manera gràfica, els diferents fluxos d'autenticació (en aquest cas amb Google).

Es pot experimentar amb l'espai OAuth 2 de Google. Per exemple, per al cas d'AI Platform Training & Prediction API (per a l'entrenament de models de ML), existeix un servei web per explorar tots els endpoints i les seues especificacions (Discovery Document), sota la següent URL:

https://ml.googleapis.com/$discovery/rest?version=v1

Encara que no és necessari expedir un token d'accés per explorar aquest servei (es pot consultar amb un simple navegador), anem a realitzar els passos que s'haurien de donar per invocar qualsevol altre servei que necessitara un token. A continuació es descriuen els passos realitzats en l'espai OAuth 2:

PAS 1: Seleccionem la primera entrada del servei "AI Platform Training & Prediction API v1" i polsem en "Authorize APIs" (sota la secció "Select & authorize APIs"). Utilitza un compte de prova que tingues de Google:

PAS 2: Després de polsar "Authorize APIs" es desplega el següent pas (Exchange authorization code for tokens), on ja ve informat el camp "Authorization code" (és el codi que ens envia en primera instància la plataforma de Google), i a continuació ja podem polsar el botó "Exchange authorization code for tokens":

Després de polsar aquest botó, s'informen els camps "Refresh token" i "Access token", i s'indica el temps després del qual caducarà l'Access token (en segons). Si es polsa en el botó "Refresh access token", s'obtindrà un nou "Access token".

PAS 3: En aquest pas, ja estem preparats per accedir a l'endpoint per al qual hem sol·licitat el token d'accés. Per tant, introduïm la URL en el camp "Request URI" (no necessitem introduir el token d'accés en la capçalera segons la nota que s'indica baix). Polsem en "Send the request", i obtenim la resposta en la part dreta del panell:

En la part dreta es pot apreciar que el token d'accés s'ha enviat com a part del request, i que s'ha obtingut un codi 200 satisfactori en la resposta. El cos del missatge es pot veure en format JSON:

Gestió d'errors

En les crides a API REST és necessari implementar mecanismes per contemplar les situacions en què es produïsca algun tipus d'imprevist. Això proporciona robustesa a les aplicacions que desenvolupem.

En primer lloc, és necessari conéixer els codis de resposta que existeixen, i el seu significat. Podràs trobar una llista completa en aquest enllaç.

A continuació, tenim diverses possibilitats per capturar els possibles errors. Podem realitzar algun tipus d'acció amb un bloc condicional:

response = requests.get("http://api.open-notify.org/astros.json")
if (response.status_code == 200):
    print("La sol·licitud s'ha realitzat amb èxit.")
    # Ací inserir codi quan la sol·licitud és exitosa
elif (response.status_code == 404):
    print("No s'ha trobat cap resultat.")
    # Ací inserir codi quan la sol·licitud NO és exitosa
També podem utilitzar un bloc try/except:
try:
    response = requests.get('http://api.open-notify.org/astros.json')
  response.raise_for_status() # Llençarà una excepció si s'ha produït un error que serà capturat pel bloc "except"
    # Codi addicional, executat si no s'ha produït cap error
except requests.exceptions.HTTPError as error:
    print(error)
    # Codi a executar si s'ha produït un error

Descàrrega d'HTML

Una altra de les utilitats de la llibreria requests és poder obtenir el codi HTML d'una pàgina web. Això és especialment important per al cas de web scraping.

Per a això, n'hi haurà prou amb realitzar una petició de tipus GET a la URL el codi HTML de la qual volem descarregar, de la forma:

DJANGO REST FRAMEWORK

En els apartats anteriors hem vist com consumir serveis REST mitjançant la llibreria requests de Python.

En aquesta segona part del document anem a aprendre com crear serveis REST mitjançant el framework Django per poder desacoblar la capa backend de la capa client (frontend). Aquests serveis REST podran ser consumits posteriorment per aplicacions web client, apps mòbils, aplicacions d'escriptori o fins i tot altres sistemes backend. Anem a continuar utilitzant, a mode il·lustratiu, l'exemple del portfolio.

En els següents subapartats anem a instal·lar l'app de Django que ens permet programar serveis REST (Django REST Framework), desgranarem les diferents parts en què es compon un servei REST (serialitzadors, vistes i URLs), així com l'eina de Django per poder provar-los de manera visual. Finalment, establirem la forma de documentar l'API REST perquè els desenvolupadors del costat client puguen utilitzar-la adequadament.

Tota la documentació referent a Django REST Framework la podràs trobar en aquest enllaç.

D'una banda, tenim un tutorial introductori en el qual podem revisar el més important del framework, sota el submenú Tutorial:

I la documentació exhaustiva la trobem sota API Guide:

Existeixen també nombrosos recursos a Internet, i videotutorials que expliquen en profunditat la utilització d'aquest framework.

Instal·lació

Per poder utilitzar Django REST Framework (DRF a partir d'ara), hem d'instal·lar l'app corresponent (juntament amb les seues dependències) mitjançant pip, segons es detalla en aquest enllaç. També es detalla en aquesta breu guia com configurar les URLs de DRF per provar els serveis REST mitjançant l'API navegable.

Assegura't d'incloure el següent en urls.py:

Organització de fitxers

Durant el desenvolupament dels serveis web anem a crear, com a mínim, dos tipus d'objectes: serialitzadors i vistes. Per poder organitzar aquests nous objectes convenientment i les nostres apps puguen ser exportables, anem a crear un nou directori "api" dins de cada app per a la qual desenvolupem serveis REST. Servisca el següent com a exemple:

Dins de la nova carpeta "api" crearem un nou fitxer init.py, i com a mínim hi haurà dos fitxers serializers.py i views.py (per als serialitzadors i vistes, respectivament).

Serialitzadors

Els serialitzadors són equivalents, en el model vista-controlador, als formularis d'entrada de dades en el sistema. Funcionen en dos sentits:

  • Transformen tipus de dades complexos de Python, moltes vegades extrets dels models, en fitxers de text pla en format JSON o XML (entre altres). A aquest pas se li diu serialització.
  • Conversió de dades JSON o XML a tipus complexos de dades en Python, prèvia validació de les dades (com un formulari). A aquest pas se li diu deserialització.

En el nostre projecte de portfolio, necessitem serialitzadors per a:

  • Llistat de projectes
  • Formulari de projecte

Normalment, utilitzarem un parell serialitzador-vista per a les llistes, i un altre parell per a les operacions CRUD d'un objecte. En el primer cas, consultarem una llista (paginada o no) i necessitarem menys camps; en el segon cas, necessitarem recuperar tots (o quasi tots) els camps d'un objecte, i implementar més validacions per a la consistència de les dades.

Tota la informació sobre serialitzadors la trobaràs en aquests enllaços:

Categories

Atés que el model de categories conté un sol camp (el nom) podem crear un serialitzador que ens servisca tant per llistar categories, com per accedir al seu detall. Anem a començar amb aquest cas, que és el més senzill.

Definim un sol serialitzador de categories de la següent manera (en serializers.py):

Podríem haver inclòs només el camp "nom", però és convenient incloure l'"id" per si el necessitem en el frontend per realitzar qualsevol tipus d'operació amb una categoria, per a la qual cosa necessitaríem utilitzar el seu id concret.

Llistat de projectes

Una primera versió d'aquest serialitzador seria la següent (inserim el següent codi en serializers.py de l'app portfolioapp):

Pràcticament estem utilitzant tots els camps del model Projecte, amb la qual cosa anem a introduir el següent canvi: el camp "descripcio" serà retallat perquè incloga els 30 primers caràcters (al ser una llista, necessitem menys detall). Per a això, fem les següents modificacions:

Hem realitzat els següents canvis:

  • Comentem el camp 'descripcio' i afegim el camp 'descripcio_short'.
  • Com 'descripcio_short' no existeix en el model, creem un atribut addicional 'descripcio_short' a partir de la classe SerializerMethodField, i l'incloem en la llista 'fields'.
  • Finalment, creem un mètode get_descripcio_short que rep com a paràmetre l'objecte que estem tractant (l'objecte projecte), i retorna els 30 primers caràcters de la seua descripció.

NOTA: Quan definim un SerializerMethodField, el mètode corresponent que deriva el seu valor tindrà el nom get_[NOM DEL CAMP], i rep com a paràmetre l'objecte associat al model definit en Meta.

En aquests moments encara no podem provar l'eixida del serialitzador, però si avancem en el temps i donem per suposat que ja tenim la vista corresponent programada (i la seua URL), obtindríem la següent eixida:

Hem fet un gran avanç. Hem aconseguit generar una llista de projectes, amb els seus corresponents camps, i un d'ells és una versió retallada de la descripció del projecte.

Però existeix un aspecte que necessitem canviar: el camp "categories" només ens retorna l'ID de les categories associades al projecte. L'ideal seria que ens retornara tant l'ID com la seua descripció, i així podríem visualitzar (en el costat client) la llista amb el nom de les categories.

Per poder fer això, necessitem serialitzar els objectes Categoria dins de la llista de projectes. Per tant, es tracta d'un serialitzador anidat.

Per crear aquest serialitzador anidat, necessitem utilitzar el serialitzador de categories que hem definit anteriorment.

Amb aquest serialitzador, podrem serialitzar a la nostra conveniència les categories de cada projecte, però com? La resposta és: definint un nou camp en ProyectoListSerializer de tipus SerializerMethodField (com descripcio_short) però el valor del qual estiga serialitzat al seu torn per CategoriaSerializer. Modifica ProyectoListSerializer amb el següent codi:

Ara observem l'eixida:

Ara sí podrem utilitzar aquest array JSON i utilitzar-lo des de codi JavaScript per representar correctament la llista de projectes junt amb les seues categories.

Llavors, què hem fet exactament amb el mètode get_categorias_serialized? Hem utilitzat el serialitzador CategoriaSerializer per transformar les categories d'un projecte (que al final és una llista dels IDs de les categories associades) a una llista de diccionaris JSON amb el parell id/nom.

Això ens porta a la següent pregunta: podríem utilitzar un serialitzador en qualsevol part d'un codi Python per serialitzar un determinat objecte? La resposta és que sí, com s'explica en aquest videotutorial. Enfoca aquest vídeo establint el paral·lelisme entre formularis de Django, i serialitzadors (is_valid, validate ...).

Detall de projecte

Ara volem crear un serialitzador que ens servisca per poder modificar tots els camps d'un projecte. Ja hem desenvolupat anteriorment el formulari de projecte mitjançant el model vista-controlador:

Per tant hem d'incloure tots aquests camps en el serialitzador:

NOTA: encara que incloguem l'id d'un objecte en el serialitzador, no podrem modificar-lo des del frontend. La columna "id" o "pk" sempre és de lectura.

Vistes i Mixins

Les vistes en els serveis REST continuen complint el mateix objectiu que les vistes en el model vista-controlador: implementen la lògica de negoci i actuen sobre les dades introduïdes mitjançant el serialitzador corresponent (formularis, en el cas del model vista-controlador).

Cal destacar també el següent:

  • Les vistes poden o no tindre associat un serialitzador (de la mateixa manera que les vistes del model vista-controlador, que no necessàriament han de tindre un formulari associat), depenent del seu propòsit. La missió de les vistes és rebre un request, i retornar un response, i moltes vegades la lògica de negoci que implementen no requereix manejar cap model per retornar informació (es pot retornar una confirmació en finalitzar un procés).
  • Igual que en el model vista-controlador, existeixen vistes basades en funció i vistes basades en classe, amb els mateixos avantatges i desavantatges.
  • La tendència és desenvolupar els serialitzadors perquè siguen el més simples possible, i deixar el pes de la lògica de negoci a les vistes. Per exemple, es poden establir relacions entre models en un serialitzador per actualitzar diferents models al mateix temps, però les bones pràctiques aconsellen que siga la vista que recull les dades, qui s'encarregue de fer això.

Primer revisarem l'aspecte de les vistes FBV, en aquest enllaç, per a continuació revisar l'exemple més bàsic d'una vista basada en la classe APIView, en aquest enllaç.

A continuació revisem el que són les vistes genèriques de DRF, en aquest enllaç, fent especial èmfasi en els Mixins (ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin).

Finalment, revisem els viewsets (fent especial èmfasi en el GenericViewSet).

Després d'aquesta revisió, concloem el següent:

  • Quan fem operacions CRUD sobre un model, utilitzarem la vista GenericViewSet en combinació amb els mixins de model, segons les operacions que necessitem fer sobre aquest model.
  • Per implementar lògiques de negoci que no siguen particulars sobre un determinat model (per exemple, no involucren cap model o diversos d'ells), utilitzarem la vista APIView.

Llista de categories

Anem a implementar la nostra primera vista, que serà la llista de categories. El serialitzador ja el tenim, ara falta triar la vista més adequada.

Podríem pensar en utilitzar una vista que herete de ModelViewSet, ja que implementaria totes les operacions CRUD sobre un determinat objecte (llistat, creació, actualització, ...) i ens serviria per a tot el relacionat amb aquest objecte. Però en aquest cas estaríem possibilitant realitzar més operacions que les realment necessàries. Pensem en el desplegable de categories de la part pública del portofolio: només hem de llistar les categories mitjançant un dropdown, no anem a realitzar cap operació més, per tant no hauríem d'exposar, mitjançant aquesta vista, més del necessari (creació, actualització, etc.).

Per tant, necessitem una vista que només es dedique a mostrar el nom de les categories, res més.

Dit això, utilitzarem la vista GenericViewSet en combinació amb el mixin ListModelMixin per recuperar la llista de categories. En api/views.py:

Ara necessitem configurar la URL corresponent per poder comprovar el resultat. Per a això, introduïm el següent codi en urls.py:

Més informació sobre l'objecte router, la trobaràs en aquest enllaç.

Has d'incloure la següent línia en urls.py, en urlpatterns:

path('api/', include(router.urls)),
Per comprovar el resultat, consultem la URL http://localhost/api/categoria_list/ :

Enhorabona, has configurat el teu primer servei REST!

Manteniment de categories

En aquest apartat anem a implementar el manteniment de categories de dues formes: mitjançant la classe ModelViewSet i mitjançant la combinació de mixins amb GenericViewSet.

Cal fer notar, en primer lloc, que qualsevol vista que implique alteració de les dades en la BBDD hauria d'implementar mecanismes de seguretat perquè només els usuaris autoritzats puguen realitzar determinades operacions (excepte la de contacte, que és anònima).

Salvant el punt de l'autenticació, que abordarem en la següent unitat, anem a implementar una vista CRUD sobre l'objecte de categories. Per a això, introduïm el següent codi en api/views.py:

I la seua corresponent URL:

Ara, si consultem la URL http://localhost/api/categoria_crud/ veurem la llista de categories, i al final de la pàgina veurem un xicotet formulari per introduir noves categories:

En la pestanya "Raw data" podríem introduir les dades en format JSON per crear una nova categoria, després de polsar el botó POST. Això ens evita utilitzar eines addicionals, com Postman.

Creem una nova tecnologia a l'aula, i tornem a visualitzar la llista sencera de categories. Comprovem que s'ha creat correctament. Però, com podríem actualitzar-la o eliminar-la?

Per a això prenem l'ID de la nova categoria, i annexem aquest ID a la URL anterior, de la forma:

Veiem que en la URL indica l'ID 11 (és només un exemple d'un ID existent), i a més ara en el formulari el botó diu PUT (modificació) en lloc de POST (creació), i s'habilita el botó DELETE.

Per tant, hem creat un servei REST que implementa totes les operacions CRUD sobre l'objecte Categoria.

Com podríem restringir determinades operacions CRUD sobre aquest model? Per exemple, volem poder crear, recuperar i actualitzar categories, però no esborrar-les. Per a això, utilitzaríem la vista GenericViewSet en combinació amb els mixins adequats, com es mostra en el següent codi (insereix-lo en api/views.py):

Creem la corresponent URL:

Ara, accedim al detall d'una categoria i comprovem que no apareix el botó DELETE, i veiem les possibles operacions segons la directiva Allow:

Llista de projectes

Com a activitat de classe, implementem la vista que llista tots els projectes, i que utilitza el serialitzador ProyectoListSerializer.

Manteniment de projectes

Per al servei web de manteniment de projectes (consulta, creació, actualització, esborrat) utilitzarem el serialitzador ProyectoDetailSerializer que ja hem programat anteriorment. Inserim la següent vista en api/views.py:

Configurem la URL:

I provem amb un ID de projecte existent:

Si anem al final de la pàgina veurem un formulari per poder realitzar les operacions CRUD amb el projecte i provar el servei web:

També podem modificar directament el JSON mitjançant la pestanya Raw data:

Mètodes de les vistes

Tal com vam veure en les CBV del model vista-controlador, existeixen determinats mètodes que podem sobreescriure per modificar el comportament de les vistes estàndard.

En aquest enllaç es citen els possibles mètodes, que revisem en la classe. Anirem utilitzant alguns d'aquests mètodes en els següents apartats.

Validacions

En aquest apartat veurem com podríem, per exemple, comprovar que la data de creació pertany al mateix any que l'enviat en el camp "year". Aquesta comprovació la realitzarem en la creació i en l'actualització del projecte.

Per a això, sobreescriurem els mètodes perform_create i perform_update de la vista ProyectoCRUDViewSet, de la següent manera:

Hem de fer la importació prèviament de ValidationError:

NOTA: Com es veu, podem crear un mètode (el podem anomenar segons la nostra conveniència) que reaprofite molt de codi que es repeteix en els dos mètodes, i invocar-lo des de perform_create i perform_update. Ho realitzem com a exercici de classe.

Anem a provar que salta l'error. Introduïm un any diferent al de la data de creació:

En polsar el botó PUT, obtenim el següent error:

Aquesta serà la resposta HTTP que es retornarà a la part front, amb codi 400, i amb un JSON en el body que continga el diccionari amb els errors trobats. Aquests errors hauran de ser processats per la part client i representats convenientment.

NOTA: també disposem del mètode perform_destroy, per al cas del mixin DestroyModelMixin, si fora necessari.

Paràmetres

Imaginem que pretenem filtrar els projectes posteriors a un determinat any. Això ho podríem fer enviant un paràmetre a la vista ProyectoListViewSet que continga l'any. Aquest paràmetre serà recollit en el mètode get_queryset, el qual retornarà un queryset filtrant pel número d'any. Vegem com queda la lògica d'aquest mètode:

I el resultat, amb el paràmetre en la URL:

Com a activitat en classe, realitzarem el filtrat per una determinada categoria.

Ordenació

Per establir l'ordenació d'una llista d'objectes podríem utilitzar paràmetres per enviar per URL el tipus d'ordenació que necessitem. Però DRF ens proporciona un backend d'ordenació que ens permet fer això amb la mínima lògica.

Per establir l'ordenació per defecte d'un queryset pel camp "fecha_creacion", n'hi ha prou amb establir els atributs filter_backends i ordering en la vista corresponent, de la manera:

D'aquesta manera, consultem el resultat d'aquest endpoint i podem comprovar que s'ha ordenat per data de creació.

Si volguérem poder ordenar per diversos camps, de manera ascendent o descendent, hem d'utilitzar l'atribut "ordering_fields" de la manera:

Ara, en consultar l'endpoint amb l'API navegable de DRF, apareixerà un nou botó anomenat "Filtres", que ens permetrà establir l'ordenació que vulguem:

En polsar sobre alguna de les opcions, veurem que la URL es modifica convenientment per acomodar un nou paràmetre o paràmetres d'ordenació:

Cerca

Igual que en el cas de l'ordenació, podríem utilitzar paràmetres en el mètode get_queryset per poder establir una cerca per determinats camps. Però DRF també ens proporciona un mecanisme per poder buscar per camps amb el mínim codi, utilitzant el SearchFilter backend. Per a això, modifiquem de nou la vista i agreguem el SearchFilter backend a la tupla de filter_backends, i definim l'atribut search_fields, de la manera:

En aquest cas estem permetent la cerca per títol i descripció, i a més pel nom d'una categoria associada al projecte. En l'API navegable, veiem que en polsar sobre el botó Filtres ens apareix un nou camp Search. En introduir un valor en aquest camp, s'afegeix un nou paràmetre "search" a la URL:

I la URL:

Paginació

Una altra de les funcionalitats que hem anat desenvolupant al llarg dels projectes del curs ha estat la paginació de les llistes. En aquest aspecte, DRF també ens proporciona un mètode senzill per paginar les nostres llistes, basant-nos en aquest enllaç.

Com s'explica en la documentació, existeixen diversos mètodes per establir la mida de la paginació. Ens interessa tindre diverses mides de paginació per poder utilitzar-les segons ens convinga. Per a això, creem un nou fitxer pagination.py en un nou directori "api" dins de l'app "common" amb tres tipus de paginació:

  • LargeResultsSetPagination: mida 50 per pàgina
  • StandardResultsSetPagination: mida 20 per pàgina
  • ShortResultsSetPagination: mida 5 per pàgina

Creem les classes corresponents com a activitat de classe, dins de l'app portfolioapp.

Ara utilitzem una d'aquestes classes dins de la vista CategoriaListViewSet:

I comprovem l'eixida paginada en l'API navegable:

Vistes sense serialitzador

És possible que necessitem programar una vista que només siga el punt d'entrada per executar una part de la nostra lògica de negoci, sense necessitat de rebre o retornar dades. Per exemple: un procés en la nostra aplicació avaluapp que s'encarregue de calcular les notes finals de l'alumnat a partir de les notes parcials dels criteris d'avaluació, en cadascuna de les unitats.

En aquest cas, no necessitem rebre cap dada ni retornar-la, simplement hem de rebre la petició d'un usuari autoritzat, i retornar una resposta de confirmació quan s'haja executat correctament.

En aquest cas no necessitem serialitzar ni deserialitzar estructures de dades (les operacions es farien directament en la base de dades). Per tant, aquest seria un clar exemple d'una vista que implementa una lògica de negoci, però no utilitza les dades rebudes per executar-la.

En aquest apartat anem a realitzar un exemple bàsic de vista sense serialitzador, amb la finalitat de convertir tots els noms de les categories perquè la primera lletra siga majúscula i les altres minúscules. Aquesta operació la podríem realitzar d'altres formes més efectives, però servisca com a exemple il·lustratiu.

Anem a realitzar la mateixa vista mitjançant CBV i FBV, amb el mètode GET i sense paràmetres.

Per al cas de CBV utilitzem la vista APIView, que és la més senzilla de les CBV de DRF. La vista seria de la següent manera:

Ara creem la URL, però no la podem registrar amb el router com les altres vistes basades en vistes genèriques, hem de configurar-la en la llista urlpatterns, com les configurades en el model vista-controlador, de la manera:

Ara anem a programar una vista basada en funció que faça el mateix. Prèviament hem d'importar el decorador api_view:

from rest_framework.decorators import api_view

I programem la vista de la següent manera:

Finalment, configurem la URL en la llista urlpatterns:

API navegable de DRF: test i documentació

DRF ens proporciona de sèrie una eina per poder provar i documentar els serveis web programats, anomenada "browsable API", que hem anat utilitzant al llarg d'aquest tutorial per verificar els resultats.

En aquest enllaç trobaràs tot el referent a la documentació de serveis REST en DRF, tant el que concerneix a mòduls externs amb els quals poder documentar les nostres APIs com l'API navegable.