Criar um API simples - HelpingHandPT/COVID19-be GitHub Wiki

Criar um API simples

Nesta página vamos aplicar informação presente na página Django REST Framework para criar um API para Reserva de Voos.

O objetivo deste API é permitir que os passageiros encontrem voos e reservem os mesmos. Para isso precisamos de criar três modelos (Flight, Passenger e Reservation), com respetivos serializers e toda a informação necessária. Posteriormente iremos criar funcionalidades findFlights e bookFlight.

Setup do Software

Vamos assumir que os passos presentes na página Configurar e Correr o Projecto foram seguidos e já temos um ambiente virtual com Django.

Nota: os passos aqui presentes foram criados numa máquina com Windows.

Instalar MySQL e MySQL Workbench

  1. Fazer o download do MySQL Server.

Vai à página de downloads do MySQL. Faz o download do MySQL Community Server e há um botão Go to Download Page > para o instalar Windows (x86, 32 & 64 bits). Nessa página que aparece, veremos Windows (x86, 32-bit) MSI Installer. Clicamos no respetivo botão de Download seremos perguntados se queremos fazer Login ou Sign Up. Esse passo pode ser ignorado clicando na ligação presente no final da página que diz: No thanks, just start my download.

  1. Instalar MySQL Server

Clicando no ficheiro .msi basta seguir os passos. Após a licensa, o primeiro passos será escolher se queremos instalar a versão 32-bits ou 64-bits do MySQL Server.

Atenção que quando pedirem para escolher os produtos e funcionalidades que queremos instalar, optem por, pelo menos, MySQL Server, MySQL Workbench, MySQL Notifier, MySQL Connectors. Posteriormente, podemos ter de instalar requerimentos em falta clicando sobre um e depois no botão Execute (repetir para todos se forem mais do que um em falta).

Nota: Se não gostam do MySQL Workbench, sugiro o cliente HeidiSQL. A instalação é direta.

Instalar python mysqlclient

Este Python interface para MySQL permitirá que nos conectemos a uma base de dados MySQL. Esta instalação pode não ser necessária se o ficheiro requirements.txt instalou por nós. Caso contrário, uma vez dentro do ambiente virtual (se não entenderem esta referência, ler página Configurar e Correr o Projecto), a instalação do mysqlclient é direta, basta usar o seguinte comando

pip install mysqlclient

Instalar Django REST Framework

Django REST Framework é um kit de ferramentas poderoso e flexível para criar Web APIs. Esta instalação pode não ser necessária se o ficheiro requirements.txt instalou por nós. Caso contrário, uma vez dentro do ambiente virtual (se não entenderem esta referência, ler página Configurar e Correr o Projecto), a instalação do djangorestframework é direta, basta usar o seguinte comando

pip install djangorestframework

Instalar Postman

Para instalar a aplicação Postman, basta ir até aqui e selecionar a versão que precisamos com base no OS do dispositivo que estamos a utilizar. Ainda que seja um ambiente completo para desenvolvimento de APIs, nós vamos usar apenas para testar o nosso RESTful API.

Criar o projeto

  1. Criar um projeto. Chamei de wikiHelpingHand.

    (venv) PS C:\djangorest> django-admin startproject wikiHelpingHand
    
  2. Criar uma aplicação. Chamei de flightApp.

    (venv) PS C:\djangorest\wikiHelpingHand> python manage.py startapp flightApp
    
  3. Ir a C:/djangorest/wikiHelpingHand/wikiHelpingHand/settings.py para adicionar as APPS.

    INSTALLED_APPS = [
    	'django.contrib.admin',
    	'django.contrib.auth',
    	'django.contrib.contenttypes',
    	'django.contrib.sessions',
    	'django.contrib.messages',
    	'django.contrib.staticfiles',
    	'rest_framework',
    	'flightApp'
    ]
    
  4. No mesmo ficheiro, configuremos o acesso à BD.

    DATABASES = {
    	'default': {
    		'ENGINE': 'django.db.backends.mysql', 
    		'NAME': 'wikiHelpingHand',
    		'USER': 'root',
    		'PASSWORD': '', # No meu caso não coloquei password
    		'HOST': 'localhost',   # Ou um IP onde a BD está alojada
    		'PORT': '3306',
    	}
    }
    
  5. Criar uma tabela na BD de nome wikiHelpingHand.

Criar os modelos

Dentro de C:/djangorest/wikiHelpingHand/flightApp/models.py, criemos os modelos da seguinte forma

from django.db import models

# Create your models here.

class Flight(models.Model):
	flightNumber=models.CharField(max_length=10)
	operatingAirlines=models.CharField(max_length=20)
	departureCity=models.CharField(max_length=20)
	arrivalCity=models.CharField(max_length=20)
	dateOfDeparture=models.DateField()
	estimatedTimeOfDeparture=models.TimeField()

class Passenger(models.Model):
	firstName = models.CharField(max_length=20)
	lastName = models.CharField(max_length=20)
	middleName = models.CharField(max_length=20)
	email = models.CharField(max_length=20) # Podemos colocar um número maior aqui pois é um email.
	phone = models.CharField(max_length=10)

class Reservation(models.Model): # Este modelo tem relação com os anteriores.
	flight = models.OneToOneField(Flight,on_delete=models.CASCADE) # CASCADE significa que se um flight é apagado, essa reserva também deve ser apagada.
	passenger = models.OneToOneField(Passenger,on_delete=models.CASCADE)

Criar os serializers

Criar dentro da app C:/djangorest/wikiHelpingHand/flightApp/ um ficheiro de nome serializers.py. Dentro desse ficheiro, começamos por importar os serializers e os modelos. Criar uma class para cada serializer que extende ModelSerializer e, dentro da mesma, definimos uma class Meta.

from rest_framework import serializers
from flightApp.models import Flight,Passenger,Reservation

class FlightSerializer(serializers.ModelSerializer):
	class Meta:
		model = Flight
		fields='__all__' # Ao usar __all__ significa que queremos usar todos os campos do modelo
						 # Se quisermos só alguns podemos usar fields=['flightNumber','operatingAirlines','departureCity']

class PassengerSerializer(serializers.ModelSerializer):
	class Meta:
		model = Passenger
		fields='__all__'

class ReservationSerializer(serializers.ModelSerializer):
	class Meta:
		model = Reservation
		fields='__all__'

Criar ViewSets

Neste passo vamos criar as views dos endpoints REST para as operações CRUD. Então, dentro de C:/djangorest/wikiHelpingHand/flightApp/views.py, começaremos por importar os modelos, serializers e ViewSets.

from django.shortcuts import render
from flightApp.models import Flight,Passenger,Reservation
from flightApp.serializers import FlightSerializer,PassengerSerializer,ReservationSerializer
from rest_framework import viewsets

# Create your views here.
class FlightViewSet(viewsets.ModelViewSet):
	queryset=Flight.objects.all()
	serializer_class=FlightSerializer

class PassengerViewSet(viewsets.ModelViewSet):
	queryset=Passenger.objects.all()
	serializer_class=PassengerSerializer

class ReservationViewSet(viewsets.ModelViewSet):
	queryset=Reservation.objects.all()
	serializer_class=ReservationSerializer

Configurar o Router

Neste passo vamos configurar os urls.py. Dentro de C:/djangorest/wikiHelpingHand/wikiHelpingHand/urls.py. Começaremos por importar include, as views (da flightApp) e do DRF DefaultRouter.

from django.contrib import admin
from django.urls import path, include
from flightApp import views
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('flights', views.FlightViewSet)
router.register('passenger', views.PassengerViewSet)
router.register('reservations', views.ReservationViewSet)


urlpatterns = [
	path('admin/', admin.site.urls),
	path('',include(router.urls))
]

Migrações

Uma vez que já temos a BD wikiHelpingHand criada, para criar as tabelas (inclusive as do Django) basta executar os dois comandos a seguir

(venv) PS C:\djangorest\wikiHelpingHand> python manage.py makemigrations
Migrations for 'flightApp':
  flightApp\migrations\0001_initial.py
	- Create model Flight
	- Create model Passenger
	- Create model Reservation

e

(venv) PS C:\djangorest\wikiHelpingHand> python manage.py migrate       
System check identified some issues:

WARNINGS:
?: (mysql.W002) MySQL Strict Mode is not set for database connection 'default'
		HINT: MySQL's Strict Mode fixes many data integrity problems in MySQL, such as data truncation upon insertion, by escalating warnings into errors. It is strongly recommended you activate it. See: 
https://docs.djangoproject.com/en/3.0/ref/databases/#mysql-sql-mode
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, flightApp, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying flightApp.0001_initial... OK
  Applying sessions.0001_initial... OK

Primeira fase de testes

Iniciando o servidor

python manage.py runserver

podemos ver o API e testar as operações CRUD navegando até http://localhost:8000/

Implementar o endpoint findFlights

Em views.py criar a FBV find_flights que irá retornar todos os voos após indicarmos qual a departureCity, arrivalCity e dateOfDeparture.

from django.shortcuts import render
from flightApp.models import Flight,Passenger,Reservation
from flightApp.serializers import FlightSerializer,PassengerSerializer,ReservationSerializer
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.decorators import api_view

# Create your views here.
@api_view(['POST'])
def find_flights(request):
	#Filtrar voos todos
	flights=Flight.objects.filter(departureCity=request.data['departureCity'],arrivalCity=request.data['arrivalCity'],dateOfDeparture=request.data['dateOfDeparture'])
	#Criar um objeto serializer
	serializer=FlightSerializer(flights,many=True)
	return Response(serializer.data)

class FlightViewSet(viewsets.ModelViewSet):
	queryset=Flight.objects.all()
	serializer_class=FlightSerializer

class PassengerViewSet(viewsets.ModelViewSet):
	queryset=Passenger.objects.all()
	serializer_class=PassengerSerializer

class ReservationViewSet(viewsets.ModelViewSet):
	queryset=Reservation.objects.all()
	serializer_class=ReservationSerializer

Além disso, como é FBV nos urls.py temos de usar urlpatterns,

from django.contrib import admin
from django.urls import path, include
from flightApp import views
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('flights', views.FlightViewSet)
router.register('passenger', views.PassengerViewSet)
router.register('reservations', views.ReservationViewSet)


urlpatterns = [
	path('admin/', admin.site.urls),
	path('',include(router.urls)),
	path('findFlights/',views.find_flights) # Endpoint para a vista find_flights
]

Testar findFlights

Usando o Browsable API, começamos por criar vários dummy para com a mesma departureCity, arrivalCity e dateOfDeparture.

O Browsable API do DRF não permite testar estas vistas com uma lógica diferente de operações CRUD. Para tal, usamos o Postman.

Abrindo o Postman, vamos criar um novo request POST para http://localhost:8000/findFlights/

Posteriormente, clicamos em Body. Dentro do Body, x-www-form-urlencoded.

Aqui introduzimos como keys: departureCity, arrivalCity e dateOfDeparture ; como value os valores criados no início desta parte. No meu caso obtive Status 200 OK com uma lista de voos.

Implementar o endpoint bookFlight

Vamos criar uma FBV para reserva de voos (bookFlight). Aqui nós queremos introduzir no cliente firstName, lastName, middleName, email e phone. Adicionalmente, o cliente também deverá passar um flightId.

Como um voo pode ter mais do que uma reserva, precisamos de ir a models.py e alterar a relação de Reservations com Flight de OneToOneField para ForeingKey (uma para muitas).

from django.db import models

# Create your models here.

class Flight(models.Model):
	flightNumber=models.CharField(max_length=10)
	operatingAirlines=models.CharField(max_length=20)
	departureCity=models.CharField(max_length=20)
	arrivalCity=models.CharField(max_length=20)
	dateOfDeparture=models.DateField()
	estimatedTimeOfDeparture=models.TimeField()

class Passenger(models.Model):
	firstName = models.CharField(max_length=20)
	lastName = models.CharField(max_length=20)
	middleName = models.CharField(max_length=20)
	email = models.CharField(max_length=20)
	phone = models.CharField(max_length=10)

class Reservation(models.Model): # Este modelo tem relação com os anteriores.
	flight = models.ForeignKey(Flight,on_delete=models.CASCADE) # CASCADE significa que se um flight é apagado, essa reserva também deve ser apagada.
	passenger = models.OneToOneField(Passenger,on_delete=models.CASCADE)

Após esta alteração nos models.py precisamos de fazer migrações:

(venv) PS C:\djangorest\wikiHelpingHand> python manage.py makemigrations
Migrations for 'flightApp':
  flightApp\migrations\0002_auto_20200404_2334.py
	- Alter field flight on reservation

Em views.py

from django.shortcuts import render
from flightApp.models import Flight,Passenger,Reservation
from flightApp.serializers import FlightSerializer,PassengerSerializer,ReservationSerializer
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.decorators import api_view
from rest_framework import status

# Create your views here.
@api_view(['POST'])
def find_flights(request):
	#Filtrar voos todos
	flights=Flight.objects.filter(departureCity=request.data['departureCity'],arrivalCity=request.data['arrivalCity'],dateOfDeparture=request.data['dateOfDeparture'])
	#Criar um objeto serializer
	serializer=FlightSerializer(flights,many=True)
	return Response(serializer.data)

@api_view(['POST'])
def save_reservation(request):
	flight=Flight.objects.get(id=request.data['flightId'])
	passenger=Passenger()
	passenger.firstName=request.data['firstName']
	passenger.lastName=request.data['lastName']
	passenger.middleName=request.data['middleName']
	passenger.email=request.data['email']
	passenger.phone=request.data['phone']
            passenger.save()

	reservation=Reservation()
	reservation.flight = flight
	reservation.passenger = passenger
	reservation.save()
	return Response(status=status.HTTP_201_CREATED)

class FlightViewSet(viewsets.ModelViewSet):
	queryset=Flight.objects.all()
	serializer_class=FlightSerializer

class PassengerViewSet(viewsets.ModelViewSet):
	queryset=Passenger.objects.all()
	serializer_class=PassengerSerializer

class ReservationViewSet(viewsets.ModelViewSet):
	queryset=Reservation.objects.all()
	serializer_class=ReservationSerializer

Em urls.py

from django.contrib import admin
from django.urls import path, include
from flightApp import views
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('flights', views.FlightViewSet)
router.register('passenger', views.PassengerViewSet)
router.register('reservations', views.ReservationViewSet)


urlpatterns = [
	path('admin/', admin.site.urls),
	path('',include(router.urls)),
	path('findFlights/',views.find_flights),
	path('saveReservation/',views.save_reservation)
]

Testar bookFlight

Abrindo novamente o Postman, vamos criar um novo request POST para http://localhost:8000/saveReservation/

Posteriormente, clicamos em Body. Dentro do Body, x-www-form-urlencoded.

Aqui introduzimos como keys: flightId, firstName, lastName, middleName, email e phone; como value adicionamos um flightId existente criado anteriormente e o utilizador pode nem existir. No fim devemos obter um Status 201 Created.