Esse tutorial tem como objetivo explicar o básico sobre Class Based Views
no Django. Por motivos de agilidade vou usar CBV
para me referir as
Class Based Views
.
Segundo a documentação do Django sobre CBV:
CBV's permitem você estruturar as suas views e reutilizar código aproveitando heranças e mixins
O Django já vem CBV's genéricas que atendem as necessidades da maioria das aplicações. Essas views genéricas são flexiveis o suficiente para você poder adaptá-las as suas necessidades.
Nesse tutorial eu vou falar brevemente sobre os 4 grupos de CBV's que existem no Django atualmente:
Antes de começarmos a falar sobre as CBV's vamos ver como apontar uma rota do Django para uma CBV:
from django.conf.urls import url
from django.views.generic import TemplateView
from meuapp.views import AboutView
urlpatterns = [
url(r'^about/', AboutView.as_view()),
]
Base Views
As classes listadas abaixo contém muito da funcionalidade necessária para criar views no Django. Essas classes são a base sob a qual as outras CBV's são construídas.
View
A classe genérica master. Todas as outras classes herdam dessa classe. O fluxo básico de execução dessa classe quando recebe uma requisição é:
dispatch()
http_method_not_allowed()
options()
A função dispatch()
verifica se a classe tem um método com o nome do verbo
HTTP usado na requisição. Caso não haja, um http.HttpResponseNotAllowed
é
retornado.
Essa classe sempre responde a requisições com o verbo OPTIONS
retornando
nesse caso uma lista com os verbos suportados. A não ser que o método
options()
seja sobrescrito.
Um exemplo de implementação:
from django.http import HttpResponse
from django.views.generic import View
class MyView(View):
def get(self, request, *args, **kwargs):
return HttpResponse('Hello, World!')
No exemplo acima a classe só responde a requisições do tipo GET
e
OPTIONS
, todas as outras requisições retornam
http.HttpResponseNotAllowed
.
Template View
Renderiza um template. O fluxo básico de execução dessa classe quando recebe uma requisição é:
dispatch()
http_method_not_allowed()
get_context_data()
Quando você precisa apenas renderizar uma página para o usuário essa com certeza
é a melhor CBV para o caso. Você pode editar o contexto que o template recebe
sobrescrevendo a função get_context_data()
Um exemplo de implementação:
from django.views.generic.base import TemplateView
from articles.models import Article
class HomePageView(TemplateView):
template_name = "home.html"
def get_context_data(self, **kwargs):
context = super(HomePageView, self).get_context_data(**kwargs)
context['latest_articles'] = Article.objects.all()[:5]
return context
No exemplo acima o template home.html será renderizado e vai receber como
contexto uma variável chamada lastest_articles
.
Uma coisa interessante é que o contexto da TemplateView
é populado pelo
ContextMixin
esse mixin pega automaticamente os argumentos da URL que serviu a View.
Considere por exemplo:
from django.conf.urls import patterns, url
from .views import HelloView
urlpatterns = patterns(
'',
url(r'^say_hello/(?P<name>[\w_-]+)/$', HelloView.as_view(), name='say_hello'),
)
No caso do exemplo acima o template renderizado pela HelloView
teria
em seu contexto a variável name
.
Redirect View
Redireciona o usuário para a url informada.
A URL a ser redirecionada pode conter parâmetros no estilo dicionário-de-strings.
Os parâmetros capturados na URL do RedirectView
serão repassados para a
URL que o usuário está sendo redirecionado.
O fluxo básico de execução dessa classe quando recebe uma requisição é:
dispatch()
http_method_not_allowed()
get_redirect_url()
Considere a seguinte configuração de URL's para o exemplo de implementação:
from django.conf.urls import url
from django.views.generic.base import RedirectView
from article.views import ArticleCounterRedirectView, ArticleDetail
urlpatterns = [
url(r'^counter/(?P<pk>[0-9]+)/$', ArticleCounterRedirectView.as_view(), name='article-counter'),
url(r'^details/(?P<pk>[0-9]+)/$', ArticleDetail.as_view(), name='article-detail'),
]
Exemplo de implementação:
from django.shortcuts import get_object_or_404
from django.views.generic.base import RedirectView
from articles.models import Article
class ArticleCounterRedirectView(RedirectView):
permanent = False
query_string = True
pattern_name = 'article-detail'
def get_redirect_url(self, *args, **kwargs):
article = get_object_or_404(Article, pk=kwargs['pk'])
article.update_counter()
return super(ArticleCounterRedirectView, self).get_redirect_url(*args, **kwargs)
Principais atributos:
url
: A URL destino no formato de Stringpattern_name
: O nome do padrão de URL. Umreverse
será aplicado usando os mesmosargs
ekwargs
passados para aRedirectView
permanent
: Se forTrue
retorna o status code como 301, caso contrário, retorna 302.query_string
: Se forTrue
a query_string será enviada para a URL de destino.
Display Views
As duas views abaixo foram desenvolvidas para exibir informações. Tipicamente essas views são as mais usadas na maioria dos projetos.
DetailView
Renderiza um template contendo em seu contexto um objeto obtido pelo parâmetro enviado na URL.
No fluxo de execução dessa view o objeto que está sendo utilizado está em
self.object
O fluxo básico de execução dessa classe quando recebe uma requisição é:
dispatch()
http_method_not_allowed()
get_template_names()
get_slug_field()
get_queryset()
get_object()
get_context_object_name()
get_context_data()
get()
render_to_response()
O fluxo parece grande e complexo mas na verdade é muito simples e facilmente customizável. Basicamente o que acontece é:
get_template_names()
retorna uma lista de templates que devem ser usados para
renderizar a resposta. Caso o primeiro template da lista não seja encontrado o
Django tenta o segundo e assim por diante.
Em seguida o get_slug_field()
entra em ação, essa função deve retornar o
nome do campo que será usado para fazer a busca pelo objeto. Por default o
Django procura pelo campo slug
.
Agora o get_queryset
deve retornar um queryset que será usado para buscar
um objeto. Aqui é um ótimo lugar para, por exemplo, aplicar um filtro para
exibir somente o Artigo cujo autor é o usuário logado. Considere o exemplo
abaixo:
def ArtigoView(DetailView):
model = Artigo
get_queryset(self):
return self.model.filter(user=request.user)
# ... o restante do código foi suprimido
IMPORTANTE: O get_queryset()
é chamado pela implementação default do
método get_object()
, se o get_object()
for sobrescrito a chamada ao
get_queryset()
pode não ser realizada.
O get_object()
então é o responsável por retornar o objeto que será enviado
para o template. Normalmente essa função não precisa ser sobrescrita.
Depois de obter o objeto que será enviado para o template é necessário saber
qual será o nome desse objeto no contexto do template, isso é feito pela função
get_context_object_name()
, por default o nome do objeto no template será o
nome do Model
, no exemplo acima seria artigo
Depois disso temos o get_context_data()
que já foi comentado acima e então
o get()
que obtém o objeto e coloca no contexto, e em seguida o
render_to_response
que renderiza o template.
IMPORTANTE: É importante notar que o Django oferece variáveis de
instância para facilitar a customização do comportamento da classe.
Por exemplo a troca do nome do objeto pode ser feita alterando a variável de
instância context_object_name
ao invés de sobrescrever a função
get_object_name()
.
Abaixo segue um exemplo, onde exibir os detalhes de um Artigo somente se o
usuário for o autor dele e vamos pegar esse Artigo pelo campo titulo
e
renderizar esse artigo no template detalhe_artigo.html
com o nome
meu_artigo
.
views.py
from django.views.generic.detail import DetailView
from django.utils import timezone
from articles.models import Article
class ArticleDetailView(DetailView):
slug_field = 'titulo'
model = Article
context_object_name = 'meu_artigo'
template_name = 'detalhe_artigo.html'
get_queryset(self):
return self.model.filter(user=self.request.user)
urls.py
from django.conf.urls import url
from article.views import ArticleDetailView
urlpatterns = [
url(r'^(?P<titulo>[-\w]+)/$', ArticleDetailView.as_view(), name='article-detail'),
]
detalhe_artigo.html
<h1>{{ meu_artigo.titulo }}</h1>
<p>{{ meu_artigo.conteudo }}</p>
<p>Reporter: {{ meu_artigo.user.name }}</p>
<p>Published: {{ meu_artigo.data_publicacao|date }}</p>
ListView
Uma página que representa uma lista de objetos.
Enquanto essa view está executando a variável self.object_list
vai conter
a lista de objetos que a view está utilizando.
O fluxo básico de execução dessa classe quando recebe uma requisição é:
dispatch()
http_method_not_allowed()
get_template_names()
get_queryset()
get_object()
get_context_object_name()
get_context_data()
get()
render_to_response()
Nada de novo aqui certo? Podemos exibir apenas uma lista de Artigos que estão
com status='publicado'
from django.views.generic.list import ListView
from django.utils import timezone
from articles.models import Artigo
class ArticleListView(ListView):
model = Artigo
def get_queryset(self, **kwargs):
return Artigo.objects.filter(status='publicado')
Outra opção seria:
from django.views.generic.list import ListView
from django.utils import timezone
from articles.models import Artigo
class ArticleListView(ListView):
model = Artigo
queryset = Artigo.objects.filter(status='publicado')
artigo_list.html
<h1>Articles</h1>
<ul>
{% for article in object_list %}
<li>{{ article.pub_date|date }} - {{ article.headline }}</li>
{% empty %}
<li>No articles yet.</li>
{% endfor %}
</ul>
DICA: Normalmente sobrescrevemos as funções quando o retorno depende dos parâmetros da requisição e utilizamos as variáveis de instância quando não há essa dependência.
O nome do template que é usado em ambas as views DetailView
e ListView
é determinado da seguinte forma:
- O valor da variável
template_name
na View (se definido) - O valor do campo
template_name_field
na instância do objeto que a view está usando. <app_label>/<model_name><template_name_suffix>.html
Editing Views
As views descritas abaixo contém o comportamento básico para edição de conteúdo.
FormView
Uma view que mostra um formulário. Se houver erro, mostra o formulário novamente contendo os erros de validação. Em caso de sucesso redireciona o usuário para uma nova URL.
forms.py
from django import forms
class ContactForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
def send_email(self):
# send email using the self.cleaned_data dictionary
pass
views.py
from myapp.forms import ContactForm
from django.views.generic.edit import FormView
class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm
success_url = '/thanks/'
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
form.send_email()
return super(ContactView, self).form_valid(form)
contact.html
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Send message" />
</form>
As funções mais importantes do FormView
são:
form_valid()
: Chamada quando o formulário é validado com sucessoform_invalid()
: Chamada quando o formuĺário contém errosget_sucess_url()
: Chamada quando o formulário é validado com sucesso e retorna a url para qual o usuário deve ser redirecionado.
Views para lidar com models
(ModelForms)
Grande parte do "poder" das CBV's vem quando precisamos trabalhar com models.
As views listadas abaixo: CreateView
, UpdateView
e DeleteView
foram
criadas para facilitar esse trabalho com os models, essas views podem gerar um
ModelForm
de maneira automática, desde que seja possível determinar qual
é o model que a view está utilizando.
A view vai tentar determinar o model a ser usado das seguintes formas:
- Se houver um atributo
model
na classe - Se o método
get_object()
retorna um objeto, a classe desse objeto será usada - Se houver um atributo
queryset
o model doqueryset
será utilizado
Você não precisa nem mesmo definir um success_url
as views CreateView
e
UpdateView
utilizam automaticamente a função get_absolute_url()
do model
se essa função existir.
Você também pode customizar o formulário usado na view se você precisar de algum
tratamento adicional, para fazer isso basta definir a classe de formulários a ser
usada no atributo form_class
:
from django.views.generic.edit import CreateView
from myapp.models import Author
from myapp.forms import AuthorForm
class AuthorCreate(CreateView):
model = Author
form_class = AuthorForm
CreateView, UpdateView e DeleteView
Uma view que exibe um form para criar, atualizar ou apagar um objeto. Caso existam erros no formulário, este é exibido novamente junto com as mensagens de erro.
Em caso de sucesso o objeto é salvo.
models.py
from django.core.urlresolvers import reverse
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse('author-detail', kwargs={'pk': self.pk})
views.py
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.core.urlresolvers import reverse_lazy
from myapp.models import Author
class AuthorCreate(CreateView):
model = Author
fields = ['name']
class AuthorUpdate(UpdateView):
model = Author
fields = ['name']
class AuthorDelete(DeleteView):
model = Author
success_url = reverse_lazy('author-list')
urls.py
from django.conf.urls import url
from myapp.views import AuthorCreate, AuthorUpdate, AuthorDelete
urlpatterns = [
# ...
url(r'author/add/$', AuthorCreate.as_view(), name='author_add'),
url(r'author/(?P<pk>[0-9]+)/$', AuthorUpdate.as_view(), name='author_update'),
url(r'author/(?P<pk>[0-9]+)/delete/$', AuthorDelete.as_view(), name='author_delete'),
]
O atributo fields
determina quais campos do model devem estar presentes no
formulário. É obrigatório especificar o atributo fields
ou então o atributo
form_class
, nunca os dois ao mesmo tempo, pois isso geraria uma exceção
ImproperlyConfigured
.
É importante notar também que a DeleteView
exibe as informações do objeto que
será deletado quando é acessada usando o verbo GET
, quando usado o verbo
POST
o objeto é efetivamente apagado.
DICA: O nome dos templates é determinado da seguinte forma:
CreateView
e UpdateView usam myapp/author_form.htmlDeleteView
usa myapp/author_confirm_delete.html
Date Views
Date-based generic views são views com a função de exibir páginas com dados filtrados por datas, por exemplo: posts em um blog, notícias, consultas ao médico, etc.
ArchiveIndexView
Uma página que exibe os "últimas" objetos inseridos, desconsiderando aqueles com uma
data futura a não ser que o atributo allow_future
seja definido como True
.
É importante notar que:
- O nome default do
context_object_name
élatest
. - O sufixo
_archive
no nome do template. - Além da lista de objetos o contexto também contem a variável
date_list
contendo todos os anos que tem objetos em ordem decrescente. Isso pode ser alterado para mês ou dia usando o atributodate_list_period
. Isso se aplica a todas as Data-based generic views.
Implementação simples:
urls.py
from django.conf.urls import url
from django.views.generic.dates import ArchiveIndexView
from myapp.models import Article
urlpatterns = [
url(r'^archive/$',
ArchiveIndexView.as_view(model=Article, date_field="pub_date"),
name="article_archive"),
]
YearArchiveView
Uma página para exibir um arquivo anual. Retorna todos os objetos de um determinado ano.
No contexto além da lista de objetos temos ainda:
date_list
: Um objeto QuerySet contendo todos os meses que tenham objetos naquele ano representados como objetos datetime.datetime em ordem crescente.year
: Um objeto datetime.datetime representando o ano atualnext_year
: Um objeto datetime.datetime representando o próximo anoprevious_year
: Um objeto datetime.datetime representando o ano anterior
Exemplo de implementação:
views.py
from django.views.generic.dates import YearArchiveView
from myapp.models import Article
class ArticleYearArchiveView(YearArchiveView):
queryset = Article.objects.all()
date_field = "pub_date"
make_object_list = True
allow_future = True
urls.py
from django.conf.urls import url
from myapp.views import ArticleYearArchiveView
urlpatterns = [
url(r'^(?P<year>[0-9]{4})/$',
ArticleYearArchiveView.as_view(),
name="article_year_archive"),
]
article_archive_year.html
<ul>
{% for date in date_list %}
<li>{{ date|date }}</li>
{% endfor %}
</ul>
MonthArchiveView
Uma página para exibir um arquivo mensal. Retorna todos os objetos de um determinado mês.
No contexto além da lista de objetos temos ainda:
date_list
: Um objeto QuerySet contendo todos os dias que tenham objetos naquele mês representados como objetos datetime.datetime em ordem crescente.month
: Um objeto datetime.datetime representando o mês atualnext_month
: Um objeto datetime.datetime representando o próximo mêsprevious_month
: Um objeto datetime.datetime representando o mês anterior
Exemplo de implementação:
views.py
from django.views.generic.dates import MonthArchiveView
from myapp.models import Article
class ArticleMonthArchiveView(MonthArchiveView):
queryset = Article.objects.all()
date_field = "pub_date"
allow_future = True
urls.py
from django.conf.urls import url
from myapp.views import ArticleMonthArchiveView
urlpatterns = [
# Example: /2012/aug/
url(r'^(?P<year>[0-9]{4})/(?P<month>[-\w]+)/$',
ArticleMonthArchiveView.as_view(),
name="archive_month"),
# Example: /2012/08/
url(r'^(?P<year>[0-9]{4})/(?P<month>[0-9]+)/$',
ArticleMonthArchiveView.as_view(month_format='%m'),
name="archive_month_numeric"),
]
article_archive_month.html
<ul>
{% for article in object_list %}
<li>{{ article.pub_date|date:"F j, Y" }}: {{ article.title }}</li>
{% endfor %}
</ul>
<p>
{% if previous_month %}
Previous Month: {{ previous_month|date:"F Y" }}
{% endif %}
{% if next_month %}
Next Month: {{ next_month|date:"F Y" }}
{% endif %}
</p>
WeekArchiveView
Uma página para exibir um arquivo semanal. Retorna todos os objetos de uma determinada semana.
No contexto além da lista de objetos temos ainda:
week
: Um objeto datetime.datetime representando a semana atualnext_week
: Um objeto datetime.datetime representando a próxima semanaprevious_week
: Um objeto datetime.datetime representando a semana anterior
Implementação simples:
views.py
from django.views.generic.dates import WeekArchiveView
from myapp.models import Article
class ArticleWeekArchiveView(WeekArchiveView):
queryset = Article.objects.all()
date_field = "pub_date"
week_format = "%W"
allow_future = True
urls.py
from django.conf.urls import url
from myapp.views import ArticleWeekArchiveView
urlpatterns = [
# Example: /2012/week/23/
url(r'^(?P<year>[0-9]{4})/week/(?P<week>[0-9]+)/$',
ArticleWeekArchiveView.as_view(),
name="archive_week"),
]
article_archive_week.html
<h1>Week {{ week|date:'W' }}</h1>
<ul>
{% for article in object_list %}
<li>{{ article.pub_date|date:"F j, Y" }}: {{ article.title }}</li>
{% endfor %}
</ul>
<p>
{% if previous_week %}
Previous Week: {{ previous_week|date:"F Y" }}
{% endif %}
{% if previous_week and next_week %}--{% endif %}
{% if next_week %}
Next week: {{ next_week|date:"F Y" }}
{% endif %}
</p>
DayArchiveView
Uma página para exibir um arquivo diário. Retorna todos os objetos de um determinado dia.
No contexto além da lista de objetos temos ainda:
day
: Um objeto datetime.datetime representando o dia atualnext_day
: Um objeto datetime.datetime representando o próximo diaprevious_day
: Um objeto datetime.datetime representando o dia anteriornext_month
: Um objeto datetime.datetime representando o primeiro dia do próximo mêsprevious_month
: Um objeto datetime.datetime representando o primeiro dia do mês anterior
Implementação simples:
views.py
from django.views.generic.dates import DayArchiveView
from myapp.models import Article
class ArticleDayArchiveView(DayArchiveView):
queryset = Article.objects.all()
date_field = "pub_date"
allow_future = True
urls.py
from django.conf.urls import url
from myapp.views import ArticleDayArchiveView
urlpatterns = [
# Example: /2012/nov/10/
url(r'^(?P<year>[0-9]{4})/(?P<month>[-\w]+)/(?P<day>[0-9]+)/$',
ArticleDayArchiveView.as_view(),
name="archive_day"),
]
article_archive_day.html
<h1>{{ day }}</h1>
<ul>
{% for article in object_list %}
<li>{{ article.pub_date|date:"F j, Y" }}: {{ article.title }}</li>
{% endfor %}
</ul>
<p>
{% if previous_day %}
Previous Day: {{ previous_day }}
{% endif %}
{% if previous_day and next_day %}--{% endif %}
{% if next_day %}
Next Day: {{ next_day }}
{% endif %}
</p>
TodayArchiveView
É a mesma coisa do DayArchiveView
mas não usa os parâmetros da URL para
determinar o ano/mês/dia.
O que muda é o urls.py, veja o exemplo abaixo:
from django.conf.urls import url
from myapp.views import ArticleTodayArchiveView
urlpatterns = [
url(r'^today/$',
ArticleTodayArchiveView.as_view(),
name="archive_today"),
]
DateDetailView
É a mesma coisa que a DetailView
com a diferença que a data é utilizada
junto com o pk/slug para determinar qual objeto deve ser obtido.
O que muda é o urls.py, veja o exemplo abaixo:
from django.conf.urls import url
from django.views.generic.dates import DateDetailView
urlpatterns = [
url(r'^(?P<year>[0-9]{4})/(?P<month>[-\w]+)/(?P<day>[0-9]+)/(?P<pk>[0-9]+)/$',
DateDetailView.as_view(model=Article, date_field="pub_date"),
name="archive_date_detail"),
]
Conclusão
Longe de tentar exaurir um assunto de tamanha complexidade e abrangência minha intenção com esse artigo foi mostrar o funcionamento básico das Class Based Views e quem sabe incentivar você a utilizar CBV's no seu próximo projeto.
Envie para mim qualquer dúvida, crítica ou sugestão que você tiver em qualquer uma das minhas redes sociais, posso demorar um pouco a responder mas eu respondo! :)
Ah, se você se interessou pelo assunto e quer se aprofundar mais eu aconselho começar pela Documentação oficial
Referências
- https://docs.djangoproject.com/en/1.8/topics/class-based-views/
- https://docs.djangoproject.com/en/1.8/ref/class-based-views/
- https://github.com/django/django/blob/master/django/views/generic/
- http://ccbv.co.uk/
"Class Based Views no Django" de "Raphael Passini Diniz" está licenciado com uma Licença Creative Commons - Atribuição-NãoComercial-SemDerivações 4.0 Internacional.