Eu resolvi estudar um pouco mais de DRF depois do tutorial do Hugo Brilhante na Python Brasil 11.
Obs: se você não sabe Django sugiro que leia este tutorial antes.
Este tutorial é a parte 1/6 de uma série de posts sobre DRF.
Obs: Tem coisas que é melhor nem traduzir. ;)
- 0 - Quickstart
- 1 - Serialization
- 2 - Requests & Responses
- 3 - Class based views
- 4 - Authentication & permissions
- 5 - Relationships & hyperlinked APIs
- 6 - Viewsets & routers
Pra quem não sabe, para usar API Web usamos REST, no caso, Django Rest Framework, framework web do Django.
Nota: este tutorial não é exatamente igual ao do Hugo, é baseado nele. E baseado também em Tutorial 1: Serialization.
Então para criar a API, no meu caso, eu usei:
- Ambiente: .venv
- Projeto: myproject
- App: core
- Model: Person
- Fields: first_name, last_name, email, active (boolean), created
Configurando um novo ambiente
$ virtualenv -p python3 .venv
$ source .venv/bin/activate
$ mkdir drf;cd drf
$ pip install django==1.8.6 djangorestframework==3.3.1
$ pip install django-filter drf-nested-routers
$ pip freeze > requirements.txt
$ django-admin.py startproject myproject .
$ python manage.py startapp core
Veja o meu requirements.txt
Django==1.8.6
django-filter==0.11.0
djangorestframework==3.3.1
drf-nested-routers==0.10.0
Step-0 Projeto inicial
Abra o arquivo settings.py
e em INSTALLED_APPS
acrescente
INSTALLED_APPS=(...'rest_framework','core',)
Step-1 Serializer
models.py
: Criando o modelo Person
fromdjango.dbimportmodelsclassPerson(models.Model):first_name=models.CharField(max_length=30)last_name=models.CharField(max_length=30)email=models.EmailField(null=True,blank=True)active=models.BooleanField(default=True)created=models.DateTimeField(auto_now_add=True,auto_now=False)classMeta:ordering=['first_name']verbose_name=u'pessoa'verbose_name_plural=u'pessoas'def__str__(self):returnself.first_name+" "+self.last_namefull_name=property(__str__)
serializers.py
: Criando PersonSerializer
Precisamos proporcionar uma forma de serialização e desserialização das instâncias de person
em uma representação JSON.
$ cd core/
$ touch serializers.py
Edite
fromrest_frameworkimportserializersfromcore.modelsimportPersonclassPersonSerializer(serializers.Serializer):pk=serializers.IntegerField(read_only=True)first_name=serializers.CharField(max_length=30)last_name=serializers.CharField(max_length=30)email=serializers.EmailField()active=serializers.BooleanField(default=True)created=serializers.DateTimeField()defcreate(self,validated_data):""" Create and return a new `Person` instance, given the validated data. :param validated_data:"""returnPerson.objects.create(**validated_data)defupdate(self,instance,validated_data):""" Update and return an existing `Person` instance, given the validated data."""instance.first_name=validated_data.get('first_name',instance.first_name)instance.last_name=validated_data.get('last_name',instance.last_name)instance.email=validated_data.get('email',instance.email)instance.save()returninstance
A primeira parte da classe define os campos que serão serializados. Os métodos create()
e update()
criam e atualizam as instâncias, respectivamente, quando chamados.
Uma classe de serialização é similar a uma classe Form
do Django, e inclui validações similares para os campos, tais como required
, max_length
e default
.
Fazendo a migração
$ ./manage.py makemigrations core
$ ./manage.py migrate
Trabalhando com a serialização
Abra o shell
do Django.
$ ./manage.py shell
Primeiro vamos criar uma pessoa.
>>>fromcore.modelsimportPerson>>>fromcore.serializersimportPersonSerializer>>>fromrest_framework.renderersimportJSONRenderer>>>fromrest_framework.parsersimportJSONParser>>>person=Person(first_name='Paul',last_name='Van Dyke',email='paul@email.com')>>>person.save()>>>person=Person(first_name='Regis',last_name='Santos',email='regis@email.com')>>>person.save()
Agora que já temos alguns dados podemos ver a serialização da última instância.
>>>serializer=PersonSerializer(person)>>>serializer.data# {'pk': 2, 'first_name': 'Regis', 'created': '2015-11-15T03:20:25.084990Z', 'last_name': 'Santos', 'email': 'regis@email.com', 'active': True}
Neste ponto nós traduzimos a instância do modelo em tipos de dados nativos do Python. Para finalizar o processo de serialização nós vamos renderizar os dados em json
.
>>>content=JSONRenderer().render(serializer.data)>>>content# b'{"pk":2,"first_name":"Regis","last_name":"Santos","email":"regis@email.com","active":true,"created":"2015-11-15T03:20:25.084990Z"}'
A desserialização é similar.
>>>fromcore.modelsimportPerson>>>fromcore.serializersimportPersonSerializer>>>fromrest_framework.renderersimportJSONRenderer>>>fromrest_framework.parsersimportJSONParser>>>fromdjango.utils.siximportBytesIO>>>person=Person.objects.get(pk=1)>>>serializer=PersonSerializer(person)>>>content=JSONRenderer().render(serializer.data)>>>stream=BytesIO(content)>>>data=JSONParser().parse(stream)>>>serializer=PersonSerializer(data=data)>>>serializer.is_valid()# True>>>serializer.validated_data# OrderedDict([('first_name', 'Paul'), ('last_name', 'Van Dyke'), ('email', 'paul@email.com'), ('active', True)])
Step-2 ModelSerializer
Nossa classe PersonSerializer
está replicando um monte de informações que está contido no modelo Person
.
Da mesma forma que o Django fornece Form
e ModelForm
, REST framework inclui as classes Serializer
e ModelSerializer
.
Vamos refatorar nosso arquivo serializers.py
, que agora ficará assim:
fromrest_frameworkimportserializersfromcore.modelsimportPersonclassPersonSerializer(serializers.ModelSerializer):classMeta:model=Personfields=('pk','first_name','last_name','email','active','created')
Uma propriedade legal que a serialização tem é que você pode inspecionar todos os campos em uma instância serializer, imprimindo sua representação. Abra o shell
do Django.
$ ./manage.py shell
>>>fromcore.serializersimportPersonSerializer>>>serializer=PersonSerializer()>>>print(repr(serializer))# PersonSerializer():# pk = IntegerField(label='ID', read_only=True)# first_name = CharField(max_length=30)# last_name = CharField(max_length=30)# email = EmailField(allow_blank=True, allow_null=True, max_length=254, required=False)# active = BooleanField(required=False)# created = DateTimeField(read_only=True)
É importante lembrar que as classes ModelSerializer
não faz nenhuma mágica, são simplesmente um atalho para a criação das classes de serialização:
- Os campos são definidos automaticamente.
- Os métodos
create()
eupdate()
são implementados por padrão de uma forma simplificada.
views.py
: Criando views regulares usando nosso Serializer
Vamos criar uma view simples para visualizar os dados em json
.
Edite o arquivo views.py
fromdjango.httpimportHttpResponsefromdjango.views.decorators.csrfimportcsrf_exemptfromrest_framework.renderersimportJSONRendererfromrest_framework.parsersimportJSONParserfromcore.modelsimportPersonfromcore.serializersimportPersonSerializerclassJSONResponse(HttpResponse):""" An HttpResponse that renders its content into JSON."""def__init__(self,data,**kwargs):content=JSONRenderer().render(data)kwargs['content_type']='application/json'super(JSONResponse,self).__init__(content,**kwargs)
A raiz da nossa API será uma lista de todas as pessoas, ou podemos criar uma pessoa nova.
@csrf_exemptdefperson_list(request):""" List all persons, or create a new person."""ifrequest.method=='GET':persons=Person.objects.all()serializer=PersonSerializer(persons,many=True)returnJSONResponse(serializer.data)elifrequest.method=='POST':data=JSONParser().parse(request)serializer=PersonSerializer(data=data)ifserializer.is_valid():serializer.save()returnJSONResponse(serializer.data,status=201)returnJSONResponse(serializer.errors,status=400)
Note que nós queremos usar o POST mas não temos o CSRF Token, por isso usamos o @csrf_exempt
.
Também vamos precisar visualizar os detalhes de cada pessoa. Assim podemos recuperar, atualizar ou deletar cada registro.
@csrf_exemptdefperson_detail(request,pk):""" Retrieve, update or delete a person."""try:person=Person.objects.get(pk=pk)exceptPerson.DoesNotExist:returnHttpResponse(status=404)ifrequest.method=='GET':serializer=PersonSerializer(person)returnJSONResponse(serializer.data)elifrequest.method=='PUT':data=JSONParser().parse(request)serializer=PersonSerializer(person,data=data)ifserializer.is_valid():serializer.save()returnJSONResponse(serializer.data)returnJSONResponse(serializer.errors,status=400)elifrequest.method=='DELETE':person.delete()returnHttpResponse(status=204)
Agora, vamos criar as urls. Crie um novo arquivo core/urls.py
.
fromdjango.conf.urlsimporturlfromcoreimportviewsurlpatterns=[url(r'^persons/$',views.person_list),url(r'^persons/(?P<pk>[0-9]+)/$',views.person_detail),]
E acrescente a seguinte linha em myproject/urls.py
.
urlpatterns=[url(r'^',include('core.urls')),url(r'^admin/',include(admin.site.urls)),]
Instalando httpie
Podemos usar o curl, mas o httpieé mais amigável, e escrito em Python.
$ sudo pip install httpie
Vamos usar o httpie
digitando
$ http http://127.0.0.1:8000/persons/
HTTP/1.0 200 OK
Content-Type: application/json
Date: Sun, 15 Nov 2015 03:24:44 GMT
Server: WSGIServer/0.2 CPython/3.4.3
X-Frame-Options: SAMEORIGIN
[{"active": true,
"created": "2015-11-15T03:20:24.938378Z",
"email": "paul@email.com",
"first_name": "Paul",
"last_name": "Van Dyke",
"pk": 1
},
{"active": true,
"created": "2015-11-15T03:20:25.084990Z",
"email": "regis@email.com",
"first_name": "Regis",
"last_name": "Santos",
"pk": 2
}]
Veja os detalhes
$ http http://127.0.0.1:8000/persons/1/
Atenção: se você receber erro 301, muito provavelmente é porque você esqueceu da barra
/
no final da url.
Como seria em curl?
Assim
$ curl http://127.0.0.1:8000/persons/
[{"pk":1,"first_name":"Paul","last_name":"Van Dyke","email":"paul@email.com","active":true,"created":"2015-11-15T03:20:24.938378Z"},{"pk":2,"first_name":"Regis","last_name":"Santos","email":"regis@email.com","active":true,"created":"2015-11-15T03:20:25.084990Z"}]
GitHub: Se você quiser pode olhar meu GitHub, mas terá que ver os commits para ver os passos.