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:
Base views
View
TemplateView
RedirectView
Display views
DetailView
ListView
Editing views
Model based Views
CreateView, UpdateView, DeleteView
Date views
ArchiveView
YearView
MonthView
WeekView
DayView
TodayView
DateDetailView
Antes de começarmos a falar sobre as CBV’s vamos ver como apontar uma rota
do Django para uma CBV:
from django.views.generic import TemplateView
from meuapp.views import AboutViewurlpatterns = [
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.views.generic import Viewclass 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 .views import HelloViewurlpatterns = 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.views.generic.base import RedirectViewfrom article.views import ArticleCounterRedirectView, ArticleDetailurlpatterns = [
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.views.generic.base import RedirectViewfrom articles.models import Articleclass 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 String
pattern_name: O nome do padrão de URL. Um reverse será aplicado
usando os mesmos
args e kwargs passados para a RedirectView
permanent: Se for True retorna o status code como 301, caso contrário,
retorna 302.
query_string: Se for True 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:
model = Artigoget_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.utils import timezonefrom articles.models import Articleclass 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
<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.utils import timezonefrom articles.models import Artigoclass ArticleListView(ListView):
model = Artigo
def get_queryset(self, **kwargs):
return Artigo.objects.filter(status=‘publicado’)
Outra opção seria:
from django.utils import timezonefrom articles.models import Artigoclass ArticleListView(ListView):
model = Artigo
queryset = Artigo.objects.filter(status=‘publicado’)
artigo_list.html
<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 django.views.generic.edit import FormViewclass 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.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 sucesso
form_invalid(): Chamada quando o formuĺário contém erros
get_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 do queryset 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 myapp.models import Author
from myapp.forms import AuthorFormclass 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.db import modelsclass 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.core.urlresolvers import reverse_lazy
from myapp.models import Authorclass 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 myapp.views import AuthorCreate, AuthorUpdate, AuthorDeleteurlpatterns = [
# …
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.html
DeleteView 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 atributo
date_list_period. Isso se aplica a todas as Data-based generic views.
Implementação simples:
urls.py
from django.views.generic.dates import ArchiveIndexViewfrom myapp.models import Articleurlpatterns = [
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 atual
next_year: Um objeto datetime.datetime representando o próximo ano
previous_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
{% 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 atual
next_month: Um objeto datetime.datetime representando o próximo mês
previous_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
{% 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 atual
next_week: Um objeto datetime.datetime representando a próxima semana
previous_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 atual
next_day: Um objeto datetime.datetime representando o próximo dia
previous_day: Um objeto datetime.datetime representando o dia anterior
next_month: Um objeto datetime.datetime representando o primeiro dia do
próximo mês
previous_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.views.generic.dates import DateDetailViewurlpatterns = [
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/
Este artigo foi importado automaticamente por fazer parte do Planetário Dev. Quer fazer parte deste HUB de conteúdos? Faça parte do Planetário e veja as vantagens.
Não tem site ou blog? Seja um autor do site e ainda pode ser remunerado.