Neste tutorial vamos criar um aplicativo simples com AngularJS que vai consumir um Web Service criado com Django Rest Framework.
Para economizar tempo, você pode fazer o download do projeto completo aqui: https://bitbucket.org/mateuspadua/djangorestframework-angularjs/src e assim acompanhar a explicação abaixo:
Para iniciar este tutorial vamos criar nosso ambiente virtual utilizando o virtualenvwrapper.
Instalar virtualenvwrapper no Ubuntu.
Instalar virtualenvwrapper no Windows.
1 2 3 4 5 |
$ mkvirtualenv rest_angular_env $ pip install django $ pip install djangorestframework $ django-admin.py startproject rest_angular $ cd rest_angular |
Agora vamos criar nossa app:
1 |
$ ./manage.py startapp comentarios |
Edite o arquivo comentarios/models.py. Assim temos uma classe para representar cada comentário no banco de dados.
1 2 3 4 5 |
from django.db import models class Comentario(models.Model): titulo = models.CharField("Título", max_length=255) comentario = models.TextField("Comentário") |
No nosso arquivo settings.py adicione a app rest_framework e nossa app comentarios para INSTALLED_APPS
1 2 3 4 5 |
INSTALLED_APPS = ( ... 'comentarios', 'rest_framework', ) |
Para configurar nosso banco de dados adicione as linhas abaixo no arquivo settings.py. Obs: Para este tutorial vamos usar o sqlite3 apenas para fins de aprendizagem.
1 2 3 4 5 6 |
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } |
Vamos criar o banco e suas tabelas digitando na console o comando abaixo:
1 |
$ ./manage.py syncdb |
Crie um arquivo com o nome de urls.py(comentarios/urls.py) dentro da app comentarios, com o conteúdo abaixo:
1 2 3 4 5 6 7 8 9 10 11 |
from django.conf.urls import patterns, url from rest_framework.urlpatterns import format_suffix_patterns from comentarios import views urlpatterns = patterns('', url(r'^comentarios/$', views.ComentarioList.as_view(), name="comentarios"), url(r'^comentarios/(?P<pk>[0-9]+)/$', views.ComentarioDetail.as_view(), name="comentarios-detail"), url(r'^$', views.ComentarioView.as_view()), ) urlpatterns = format_suffix_patterns(urlpatterns) |
Com isso precisamos atualizar nosso arquivo urls.py que está dentro da pasta core. Seu conteúdo ficará assim:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from django.conf.urls import patterns, include, url from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', # Examples: # url(r'^$', 'core.views.home', name='home'), # url(r'^blog/', include('blog.urls')), url(r'^admin/', include(admin.site.urls)), url(r'^', include('comentarios.urls')), ) |
Dentro da app comentarios crie um arquivo chamado serializers.py com o conteúdo abaixo:
1 2 3 4 5 6 7 |
from rest_framework import serializers from comentarios.models import Comentario class CometarioSerializer(serializers.ModelSerializer): class Meta: model = Comentario fields = ('id', 'titulo', 'comentario') |
a classe CometarioSerializer é responsável por representar a formatação dos nossos dados, ou seja como ele será construído para ser enviado como resposta quando requisitado. Nesta caso aqui, cada item de uma lista JSON terá três propriedades, um id, um titulo e o comentario
Vamos editar agora o arquivo views.py dentro da app comentários
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
from comentarios.models import Comentario from comentarios.serializers import ComentarioSerializer from rest_framework import mixins from rest_framework import generics class ComentarioList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView): queryset = Comentario.objects.all() serializer_class = ComentarioSerializer def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) class ComentarioDetail(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView): queryset = Comentario.objects.all() serializer_class = ComentarioSerializer def get(self, request, *args, **kwargs): print "teste" return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs) |
Esta parte é muito importante, vamos falar um pouco sobre os verbos HTTP:
Crédito para o site http://www.caelum.com.br/apostila-vraptor-hibernate/rest/#11-3-o-triangulo-do-rest de onde retirei a explicação dos verbos abaixo. Os quatro primeiros são os mais utilizados:
- GET - recupera informações sobre o recurso identificado pela URI. Ex: listar produtos, visualizar o produto 45. Uma requisição GET não deve modificar nenhum recurso do seu sistema, ou seja, não deve ter nenhum efeito colateral, você apenas recupera informações do sistema.
- POST - adiciona informações usando o recurso da URI passada. Ex: adicionar um produto. Pode adicionar informações a um recurso ou criar um novo recurso.
- PUT - adiciona (ou modifica) um recurso na URI passada. Ex: atualizar um produto. A diferença fundamental entre um PUT e um POST é que no POST a URI significa o lugar que vai tratar a informação, e no PUT significa o lugar em que a informação será armazenada.
- DELETE - remove o recurso representado pela URI passada. Ex: remover um produto.
- HEAD, OPTIONS e TRACE - recuperam metadados da URI passada. Respectivamente o Header, quais métodos são possíveis e informações de debug.
Em cada requisição HTTP um desses verbos é enviado. Você pode conferir isso utilizando o Firebug do Firefox ou Ferramentas do desenvolvedor do Chrome, como mostrado aqui:
Toda URL em um serviço RESTFULL deve conter apenas substantivos e nunca verbos, isso porque o verbo será representando pelo verbo HTTP enviado, por exemplo:
A URL meusite/clientes/adicionar não é legal, pois além de representar o recurso (/clientes) ela também representa a ação com o verbo /adicionar.
Portanto para ser uma URL válida, esta deveria conter apenas meusite/clientes. E ao chamar esta URL passaríamos o verbo POST e isso indicaria que estamos adicionando alguma informação ao nosso sistema.
Agora repare que a classe ComentarioList implementa dois métodos:
1 2 3 4 5 |
def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) |
Isso é exatamente o que parece, :). O método get serve para listar os comentários e o post para adicionar comentários. Obs: se você tentar fazer uma requisição AJAX para a View ComentariosList com a url 127.0.0.1:8000/comentarios com o verbo DELETE por exemplo, isto não será permitido pois o método DELETE não foi implementado nesta View.
Encontrei também um slide muito interessante sobre o assunto neste link, http://pt.slideshare.net/santosluis87/rest-the-right-way
Agora podemos testar nossa API rodando:
1 |
$ ./manage.py runserver |
E digite no seu browser: http://127.0.0.1:8000/comentarios/
Finalmente vamos utilizar o AngularJS, para isso, crie uma uma pasta chamada templates no root do projeto e crie um arquivo chamado comentarios.html dentro dela e cole o conteúdo abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<!doctype html> <html ng-app="comentariosApp"> <head> <link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.4.2/pure-min.css"> <link rel="stylesheet" href="{{ STATIC_URL }}css/common.css"> <script> globals = { comentariosUrl: '{% url "comentarios" %}' } </script> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.13/angular.min.js" charset="utf-8"></script> <script src="{{ STATIC_URL }}js/core.js"></script> </head> <body> <div ng-controller="comentariosCtrl"> <label>Name:</label> <input type="text" ng-model="yourName" placeholder="Seu nome aqui"> <span>Hello {$yourName$}!</span><br><br> <hr> <div ng-repeat="c in data.comentarios track by c.id"> <h2>{$c.titulo$}</h2> <p>{$c.comentario$}</p> <a href="#" ng-click="removerComentario(c)">remover</a> <hr> </div> <div class="pure-alert pure-alert-success" ng-show="data.success"> <b>Atualizado!</b> <br><br> </div> <br> <form class="pure-form pure-form-aligned" ng-submit="updateComentarios()"> <label>Titulo:</label> <input type="text" name="titulo" ng-model="data.titulo" placeholder="Digite o título do comentário"> <br><br> <label>Comentario:</label> <textarea name="comentario" ng-model="data.comentario" placeholder="Digite seu comentario"></textarea> <input type="submit" value="Adicionar comentário"> </form> <hr> </div> </body> </html> |
Crie também um arquivo com o nome de core.js dentro de core/static/js. Obs: repare que as pastas static e js não existem, portanto as crie antes. Cole então o conteúdo abaixo no arquivo core.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
var profileEditApp = angular.module('comentariosApp', []); profileEditApp.config(['$httpProvider', '$interpolateProvider', function($httpProvider, $interpolateProvider) { /* for compatibility with django teplate engine */ $interpolateProvider.startSymbol('{$'); $interpolateProvider.endSymbol('$}'); /* csrf */ $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken'; $httpProvider.defaults.xsrfCookieName = 'csrftoken'; }]); profileEditApp.controller('comentariosCtrl', function ($scope, $http) { $scope.data = {success: false, teste:"pinzi"}; function getComentarios(){ $http({method: 'GET', url: globals.comentariosUrl}). success(function(data, status, headers, config) { $scope.data.comentarios = data; }) } getComentarios() $scope.removerComentario = function(comentario){ $http({method: 'DELETE', url: globals.comentariosUrl + comentario.id + "/", data: { id: comentario.id }}). success(function(data, status, headers, config) { var index = $scope.data.comentarios.indexOf(comentario); if (index != -1) { $scope.data.comentarios.splice(index, 1); } }). error(function(data, status, headers, config) { }); } $scope.updateComentarios = function() { $http({method: 'POST', url: globals.comentariosUrl, data: { titulo: $scope.data['titulo'], comentario: $scope.data['comentario'] }}). success(function(data, status, headers, config) { $scope.data['success'] = true; getComentarios(); }). error(function(data, status, headers, config) { $scope.data['success'] = false; }); } }); |
Aqui iniciamos nossa APP com AngularJS, repare nas funções:
- getComentarios que utiliza o método GET para recuparar os comentários
- removerComentario que utiliza o método DELETE para apagar um comentário
- updateComentarios que utiliza o método POST para adicionar um comentário
Para ter algum estilo no nosso tutorial crie um arquivo com o nome de common.css dentro de core/static/css. Obs: repare que a pasta css não existe, portanto crie-a antes. Cole então o conteúdo abaixo no arquivo common.css:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
body{ padding: 20px; } label{ width: 100px; display: inline-block; } a{ margin-bottom: 30px; display: inline-block; } |
No arquivo settings.py cole o conteúdo abaixo no início deste arquivo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) APP_DIR = os.path.dirname(__file__) # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.6/howto/static-files/ STATIC_ROOT = os.path.join(BASE_DIR, 'static') # URL prefix for static files. # Example: "http://example.com/static/", "http://static.example.com/" STATIC_URL = '/static/' # Additional locations of static files STATICFILES_DIRS = ( os.path.join(APP_DIR, 'static'), # Put strings here, like "/home/html/static" or "C:/www/django/static". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. ) # List of finder classes that know how to find static files in # various locations. STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', #'django.contrib.staticfiles.finders.DefaultStorageFinder', 'compressor.finders.CompressorFinder', ) TEMPLATE_DIRS = ( os.path.join(BASE_DIR, 'templates'), # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. ) |
Inicie o server novamente caso não esteja iniciado:
1 |
$ ./manage.py runserver |
E então digite em seu browser 127.0.0.1:8000
Com isso é só testar nossa aplicação e bons estudos.
Você pode fazer o download do projeto completo aqui: https://bitbucket.org/mateuspadua/djangorestframework-angularjs/src
Mais sobre AngularJS:
http://javascriptbrasil.com/2013/10/23/aprenda-angularjs-com-estes-5-exemplos-praticos/
http://tableless.com.br/criando-uma-aplicacao-simples-com-angularjs/
http://pt.wikipedia.org/wiki/AngularJS
http://pt.slideshare.net/WilsonMendes/angularjs-um-framework-para-facilitar-sua-vida
Tutorial oficial do Django Rest Framewrok:
http://www.django-rest-framework.org/#tutorial
Alguns links interessantes sobre REST:
http://blog.tucaz.net/2009/11/22/verbos-http-get-e-post-voce-sabe-mesmo-a-diferenca/
http://pt.wikipedia.org/wiki/REST
http://www.infoq.com/br/news/2009/08/CRUDREST