Você está na página 1de 41

Class Based Views no Django

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:

View

ListView

YearView

MonthView

WeekView

DayView

TodayView

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 é:

1. dispatch()

2. http_method_not_allowed()

3. 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 é:

1. dispatch()

2. http_method_not_allowed()

3. 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 é:

1. dispatch()

2. http_method_not_allowed()

3. 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 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 é:

1. dispatch()

2. http_method_not_allowed()

3. get_template_names()

4. get_slug_field()

5. get_queryset()

6. get_object()

7. get_context_object_name()

8. get_context_data()

9. get()

10.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 é:

1. dispatch()

2. http_method_not_allowed()

3. get_template_names()

4. get_queryset()

5. get_object()

6. get_context_object_name()

7. get_context_data()

8. get()

9. 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 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 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.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.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 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

<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 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

<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 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.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! :)

Criando novos comandos no django-admin

Veja aqui como criar o seu próprio comando para ser usado com o django-admin ou manage.py do Django.

O django-admin ou manage.py já tem um bocado de comandos interessantes, os mais utilizados são:

startproject - cria novos projetos.

startapp - cria novas apps.

makemigrations - cria novas migrações baseadas nas mudanças detectadas nos modelos Django.

migrate - sincroniza o banco de dados com as novas migrações.

createsuperuser - cria novos usuários.

test - roda os testes da aplicação.

loaddata - carrega dados iniciais a partir de um json, por exemplo, python manage.py loaddata fixtures.json

shell - inicializa um interpretador Python interativo.

dbshell - acessa o banco de dados através da linha de comando, ou seja, você pode executar comandos sql do banco, por exemplo, diretamente no terminal.

inspectdb - retorna todos os modelos Django que geraram as tabelas do banco de dados.

runserver - roda o servidor local do projeto Django.

Mas de repente você precisa criar um comando personalizado conforme a sua necessidade. A palavra chave é BaseCommand ou Writing custom django-admin commands.

Começando do começo

Importante: estamos usando Django 1.8 e Python 3.

Criando o projeto

Eu usei este Makefile para criar o projeto.

wget --output-document=Makefile https://goo.gl/UMTpZ1 make setup

Ele vai criar um virtualenv e pedir pra você executar os seguintes comandos:

source venv/bin/activate cd djangoproject make install

Pronto! Agora nós já temos um projetinho Django funcionando. Note que o nome da app é core.

Criando as pastas

Para criarmos um novo comando precisamos das seguintes pastas:

core ├── management

│ │ ├── commands

├──

init

py

├──

├── novocomando.py

init

py

No nosso caso, teremos 3 novos comandos, então digite, estando na pasta djangoproject

mkdir -p core/management/commands

touch core/management/ init

touch core/management/commands/{ init

py,hello.py,initdata.py,search.py}

py

Sintaxe do novo comando

Importante: estamos usando Django 1.8 e Python 3.

O Django 1.8 usa o argparse como parser de argumentos do command, mais informações em module-argparse.

from django.core.management.base import BaseCommand, CommandError from optparse import make_option

class Command(BaseCommand):

help = 'Texto de ajuda aqui.' option_list = BaseCommand.option_list + ( make_option('--awards', '-a', action="store_true", help='Ajuda da opção aqui.'),

)

def handle(self, **options):

self.stdout.write('Hello world.') if options['awards']:

self.stdout.write('Awards')

Entendeu? Basicamente o handle é a função que executa o comando principal, no caso o self.stdout.write('Hello world.'), ou seja, se você digitar o comando a seguir ele imprime a mensagem na tela.

$ python manage.py hello Hello World

--awards é um argumento opcional, você também pode digitar -a.

$ python manage.py hello -a Hello World

Awards

action="store_true" significa que ele armazena um valor verdadeiro.

Obs: A partir do Django 1.8 os comandos de argumentos opcionais são baseados em **options.

Veja uma outra forma de escrever

from django.core.management.base import BaseCommand, CommandError

class Command(BaseCommand):

def add_arguments(self, parser):

# Argumento nomeado (opcional) parser.add_argument('--awards', '-a', action='store_true', help='Ajuda da opção aqui.')

def handle(self, *args, **options):

self.stdout.write('Hello world.') if options['awards']:

self.stdout.write('Awards')

A diferença é que aqui usamos parser.add_argument ao invés de make_option.

hello.py

from django.core.management.base import BaseCommand, CommandError # minimalista class Command(BaseCommand):

help = 'Print hello world'

def handle(self, **options):

self.stdout.write('Hello World')

Uso

$ python manage.py hello

initdata.py

Objetivo: Obter alguns filmes de uma api e salvar os dados no banco. api: omdbapi.com models.py

from django.db import models

class Movie(models.Model):

title = models.CharField(u'título', max_length=100) year = models.PositiveIntegerField('ano', null=True, blank=True) released = models.CharField(u'lançamento', max_length=100, default='', blank=True) director = models.CharField('diretor', max_length=100, default='', blank=True) actors = models.CharField('atores', max_length=100, default='', blank=True) poster = models.URLField('poster', null=True, blank=True) imdbRating = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True) imdbID = models.CharField(max_length=50, default='', blank=True)

class Meta:

ordering = ['title'] verbose_name = 'filme' verbose_name_plural = 'filmes'

def

str

(self):

return self.title

Não se esqueça de fazer

python manage.py makemigrations python manage.py migrate

admin.py

Vamos visualizar pelo admin.

from django.contrib import admin from core.models import Movie

admin.site.register(Movie)

Instale o requests

pip install requests

initdata.py

O código a seguir é longo, mas basicamente temos

print_red(name) função que imprime um texto em vermelho (opcional)

get_html(year) função que lê os dados da api usando requests, e depois escolhe um filme randomicamente a partir de 2 letras

} então

get_movie(year) se o dicionário conter {'Response': 'True',

retorna um dicionário do filme localizado

save() salva os dados no banco

handle(movies, year) este é o comando principal. Busca os filmes várias vezes, conforme definido pela variável movies, e salva os n filmes.

# -*- coding: utf-8 -*- #

import random import string import requests from django.core.management.base import BaseCommand, CommandError from django.core.exceptions import ValidationError from optparse import make_option from core.models import Movie

class Command(BaseCommand):

help = """Faz o crawler numa api de filmes e retorna os dados. Uso: python manage.py initdata ou: python manage.py initdata -m 20 ou: python manage.py initdata -m 20 -y 2015""" option_list = BaseCommand.option_list + ( make_option('--movies', '-m', dest='movies',

default=10,

help='Define a quantidade de filmes a ser inserido.'), make_option('--year', '-y', dest='year', action='store',

default=None, help='Define o ano de lançamento do filme.'),

)

def print_red(self, name):

"""imprime em vermelho""" print("\033[91m {}\033[00m".format(name))

def get_html(self, year):

"""

Le os dados na api http://www.omdbapi.com/ de forma aleatoria

e escolhe um filme buscando por 2 letras

"""

# Escolhe duas letras aleatoriamente

letters = ''.join(random.choice(string.ascii_lowercase) for _ in

range(2))

#

Se não for definido o ano, então escolhe um randomicamente

if year is None:

year = str(random.randint(1950, 2015)) url = 'http://www.omdbapi.com/? t={letters}*&y={year}&plot=short&r=json'.format(letters=letters, year=str(year)) return requests.get(url).json()

def get_movie(self, year, **kwargs):

""" Retorna um dicionário do filme """

movie = self.get_html(year)

j

= 1

# contador

# Faz a validação de Response. Se a resposta for falsa, então busca

outro filme. while movie['Response'] == 'False' and j < 100:

movie = self.get_html(year) self.print_red('Tentanto %d vezes\n' % j)

j += 1

return movie

def save(self, **kwargs):

"""SALVA os dados"""

try:

Movie.objects.create(**kwargs) except ValidationError as e:

self.print_red(e.messages) self.print_red('O objeto não foi salvo.\n')

def handle(self, movies, year, **options):

""" se "movies" não for nulo, transforma em inteiro """

self.verbosity = int(options.get('verbosity'))

if movies is not None:

movies = int(movies)

# busca os filmes n vezes, a partir da variavel "movies"

for i in range(movies):

#

verifica as validações

m

= self.get_movie(year)

if m['imdbRating'] == "N/A":

m['imdbRating'] = 0.0

# Transforma "year" em inteiro if "–" in m['Year']:

m['Year'] = year

data = { "title": m['Title'], "year": m['Year'], "released": m['Released'], "director": m['Director'], "actors": m['Actors'], "poster": m['Poster'], "imdbRating": m['imdbRating'], "imdbID": m['imdbID'],

}

self.save(**data)

if self.verbosity > 0:

self.stdout.write('\n {0} {1} {2}'.format(i + 1, data['year'],

data['title'])) if self.verbosity > 0:

self.stdout.write('\nForam salvos %d filmes' % movies)

Uso

Usage: python manage.py initdata [options]

Faz o crawler numa api de filmes e retorna os dados. Uso: python manage.py initdata ou: python manage.py initdata -m 20 ou: python manage.py initdata -m 20 -y 2015

search.py

Objetivo: Localizar o filme pelo título ou ano de lançamento.

from django.core.management.base import BaseCommand, CommandError from optparse import make_option from core.models import Movie

class Command(BaseCommand):

help = """Localiza um filme pelo título ou ano de lançamento. Uso: python manage.py search -t 'Ted 2' ou: python manage.py search -y 2015 ou: python manage.py search -t 'a' -y 2015"""

option_list = BaseCommand.option_list + ( make_option('--title', '-t', dest='title', default=None, help='Localiza um filme pelo título.'), make_option('--year', '-y', dest='year', default=None, help='Localiza um filme pelo ano de lançamento.'),

)

def handle(self, title=None, year=None, **options):

""" dicionário de filtros """ self.verbosity = int(options.get('verbosity'))

filters = { 'title

istartswith': title,

'year': year

}

filter_by = {key: value for key, value in filters.items() if value is not None} queryset = Movie.objects.filter(**filter_by)

if self.verbosity > 0:

for movie in queryset:

self.stdout.write("{0} {1}".format(movie.year, movie.title)) self.stdout.write('\n{0} filmes localizados.'.format(queryset.count()))

Uso

Usage: python manage.py search [options]

Localiza um filme pelo título ou ano de lançamento. Uso: python manage.py search -t 'Ted 2' ou: python manage.py search -y 2015 ou: python manage.py search -t 'a' -y 2015

Aqui tem um exemplo legal que eu usei como ideia pra fazer este post.

Introdução a Classes e Métodos em Python (básico)

Eu não sou a melhor pessoa do mundo para explicar este assunto, mas eu escrevi este post para introduzir um tópico sobre manipulação de banco de dados em SQLite3 com Python, porém mais informações sobre classes e métodos podem ser encontradas nos links abaixo. Veja os exemplos em

PS: Considere a sintaxe para Python 3.

Segundo a documentação do Python e o video Python para Zumbis, uma classe associa dados (atributos) e operações (métodos) numa só estrutura. Um objeto é uma variável cujo tipo é uma classe, ou seja, um objeto é uma instância de uma classe.

Na sua sintaxe mais elementar definimos uma classe conforme abaixo:

class NomeDaClasse(object):

pass

E um método (função) como:

def metodo(args):

pass

onde args são argumentos opcionais (parâmetros de entrada). A função metodo pode retornar um valor de saída:

def metodo(args):

return args

Juntando os dois temos:

class NomeDaClasse(object):

atributo1 = None

def metodo(self, args):

pass

A primeira pergunta que você vai ter é o porque do self em metodo. A resposta curta é, todo

metodo criado dentro de uma classe deve definir como primeiro parametro o self. Para a resposta longa, por favor, leia a excelente explicação que o Pedro Werneck fez:

A segunda pergunta é: para que serve o pass?

A resposta é que, em Python, ao contrario de várias outras liguagens de programação, os blocos de

código NÃO são definidos com os caracteres { e }, mas sim com indentação e o caractere :. Devido a esse fato, python necessitava de algo para explicitar quando se quer definir um bloco vazio. O pass foi criado exatamente para explicitar essa situação.

Um exemplo de uma função vazia feita em linguagem C e a mesma função vazia feita em Python:

void metodo(int num){

}

def metodo(num):

pass

Voltando a explicação sobre a classe: O atributo1 é um atributo com valor inicial None (nada). Poderia ser atributo1 = 0, por exemplo.

Importante: Note que para nome de classes usamos PalavrasComeçandoPorMaiúscula (isso tambem é conhecido como "CamelCase") e para nome de métodos (funções) usamos minúsculas_separadas_por_underscore. Esta é uma convenção adotada pelos Pythonistas segundo o Guia de Estilo PEP 8 - Style Guide for Python Code escrito por Guido Van Rossum.

Exemplo 1 - Calculadora simples

Existem pelo menos duas formas diferentes de trabalhar com os parâmetros de entrada. Neste exemplo, definiremos o parâmetro apenas uma vez com um método especial do Python chamado

init

Segundo João Reis, este método é chamado quando um objeto de uma classe é

instânciado. Este método é útil para fazer qualquer inicialização que você queira com seu objeto, ou seja, ele é o método "Inicializador" da instancia.

#calculadora.py class Calculadora(object):

def

init (self, self.a = a self.b = b

a, b):

def soma(self):

return self.a + self.b

def subtrai(self):

return self.a - self.b

def multiplica(self):

return self.a * self.b

def divide(self):

return self.a / self.b

Note que definimos dois parâmetros a e b (dentro do parênteses). E o self.a é um novo campo.

Poderíamos definir

def

init

(self,

param1, param2):

self.a = param1 self.b = param2

para não confundir, mas usualmente usamos o mesmo nome tanto no parâmetro quanto no novo campo.

Como dito antes, definimos os valores iniciais apenas uma vez e depois apenas usamos os métodos para calcular os valores.

Podemos rodar o Python no modo modo interativo pelo terminal e importar a classe (veja este video).

$ python3

>>> from calculadora import Calculadora >>> c = Calculadora(128,2) >>> print('Soma:', c.soma()) >>> print('Subtração:', c.subtrai()) >>> print('Multiplicação:', c.multiplica()) >>> print('Divisão:', c.divide())

c = Calculadora(128,2) é uma instância da classe com dois valores iniciais.

O resultado é:

>>> Soma: 130 >>> Subtração: 126 >>> Multiplicação: 256 >>> Divisão: 64.0

Podemos redefinir os valores iniciais da seguinte forma:

>>> c.a = 12 >>> c.b = 42 >>> print c.soma()

Resultado:

>>> 54

Importante: apesar de não fazer parte do escopo deste artigo, mas vejam este video Operadores aritméticos e divisão no Python 2 e Python 3, explicando sobre a diferença no resultado da divisão nas duas versões do Python.

Vejam também este artigo sobre ponto flutuante: Floating Point Arithmetic Issues and Limitations.

Exemplo 2 - Calculadora

Agora faremos uma classe sem valor inicial e com dois parâmetros para todos os métodos.

#calculadora2.py

class Calculadora(object):

def soma(self, a, b):

return a + b

def subtrai(self, a, b):

return a - b

def multiplica(self, a, b):

return a * b

def divide(self, a, b):

return a / b

Usando o terminal no modo interativo façamos:

$ python3 >>> from calculadora2 import Calculadora >>> c = Calculadora() >>> print('Soma:', c.soma(2,3)) >>> print('Subtração:', c.subtrai(2,10)) >>> print('Multiplicação:', c.multiplica(3,3)) >>> print('Divisão:', c.divide(128,2))

A vantagem de colocar os parâmetros em cada método, é que podemos calcular qualquer valor sem ter que instanciar uma nova classe para cada valor diferente.

Exemplo 3 - Classe Pedido

Agora veremos um exemplo que mais se aproxima do que iremos fazer em banco de dados, mas aqui iremos apenas instanciar os objetos e armazená-los em memória numa lista.

Veremos o código na íntegra e depois os comentários.

#user.py class User(object):

seq = 0 objects = []

def

init

(self, nome, idade):

self.id = None self.nome = nome self.idade = idade

def save(self):

self

self.id = self

class

seq

+= 1

class

seq

 

self

class

objects.append(self)

def

str

(self):

return self.nome

def

repr

(self):

return '<{}: {} - {} - {}>\n'.format(self self.nome, self.idade)

@classmethod def all(cls):

return cls.objects

if

':

u1 = User('Regis', 35)

u1.save()

u2 = User('Fabio', 20)

u2.save()

print(User.all())

name

== '

main

class

name

, self.id,

Podemos rodar o Python no modo modo interativo pelo terminal e importar a classe (veja este video).

$ python3 >>> from user import User >>> u1 = User('Regis', 35) >>> u1.save() >>> u2 = User('Fabio',20) >>> u2.save() >>> print(User.all())

Agora os comentários:

Definindo a classe

class User(object):

Define um atributo que servirá como contador inicial e um atributo objects (tupla vazia) que é uma lista de instâncias de User que foram salvos (que chamaram o método save).

seq = 0 objects = []

Atribui um valor inicial aos atributos no momento da chamada do construtor.

def

init

(self, nome, idade):

Inicializando os atributos, id começa com None, pois a instância foi criada mas ainda não foi salva.

self.id = None self.nome = nome self.idade = idade

Método para salvar os dados ele incrementa o atributo de classe que conta quantas instâncias foram salvas e adiciona a instância na lista de objects.

def save(self):

self

seq. Aqui poderia ser usado User.seq, porém caso User fosse herdado, o seq seria o de User e não da classe filha.

class

acessa a classe que criou a instância, assim é possível acessar o atributo de

self

self.id = self

class

seq

+= 1

class

seq

Da mesma forma que acessamos seq, acessamos objects e é feito um append com a instância.

self

class

objects.append(self)

Retorna uma representação do objeto como str, usado em conversões para string. Exemplo:

str(my_user), print my_user.

def

str

(self):

return self.nome

Retorna uma representação do objeto usada para outros objetos. Exemplo: quando é convertida uma lista de user para string.

def

repr

(self):

self

class

name

é a forma de acessar o nome da classe que gerou a instância.

return '<{}: {} - {} - {}>\n'.format(self self.nome, self.idade)

class

name

, self.id,

Class method usado para acessar todas as instâncias salvas (que chamaram o método save). Aqui usamos um @classmethod, pois faz mais sentido ser um método de classe do que de instância, pois estamos retornando informações da classe e não de uma instância isolada.

@classmethod def all(cls):

return cls.objects

Demonstração do uso da classe.

if

name

== '

main

':

u1 = User('Regis', 35) u2 = User('Fabio',20) print(User.all())

Note que nesse print a lista está vazia.

u1.save()

u2.save()

print(User.all())

Após chamar o save para as duas instâncias elas são guardadas e o método User.all() retorna essa lista.

Guia rápido de comandos SQLite3

É sempre bom ter tudo que você precisa de forma rápida e simples.

Escrevi este post para um mini tutorial de SQLite3. Através do terminal:

Criando uma tabela

1. Criando um banco de dados.

$ sqlite3 Clientes.db

2. A Ajuda.

sqlite> .help

3. Criando a tabela clientes.

sqlite> CREATE TABLE clientes(

> id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,

> Nome VARCHAR(100) NOT NULL,

> CPF VARCHAR(11) NOT NULL,

> Email VARCHAR(20) NOT NULL,

> Fone VARCHAR(20),

> UF VARCHAR(2) NOT NULL

> );

Nota: Se usamos AUTOINCREMENT não precisamos do NOT NULL.

sqlite> CREATE TABLE clientes(

>

id INTEGER PRIMARY KEY AUTOINCREMENT,

>

4.

Visualizando o código SQL que criou a tabela.

sqlite> .schema clientes

5. Visualizando todas as tabelas existentes.

sqlite> .table

6. Saindo do SQLite3.

sqlite> .exit

CRUD

Abra um editor de texto e salve um arquivo com o nome inserirdados.sql.

$ gedit inserirdados.sql

E digite a inserção de alguns dados.

INSERT INTO clientes VALUES(1, 'Regis', '00000000000', 'rg@email.com', '1100000000', 'SP'); INSERT INTO clientes VALUES(2, 'Abigail', '11111111111', 'abigail@email.com', '1112345678', 'RJ'); INSERT INTO clientes VALUES(3, 'Benedito', '22222222222', 'benedito@email.com', '1187654321', 'SP'); INSERT INTO clientes VALUES(4, 'Zacarias', '33333333333', 'zacarias@email.com', '1199999999', 'RJ');

Nota: No caso do INSERT INTO não precisamos numerar, basta trocar o número do id por NULL, exemplo:

INSERT INTO clientes VALUES(NULL, 'Carlos', '99999999999', 'carlos@email.com', '118888-8888', 'SP');

7. Importe estes comandos no sqlite.

$ sqlite3 Clientes.db < inserirdados.sql

8. Abra o SQLite3 novamente, e visualize os dados.

$ sqlite3 Clientes.db

sqlite> SELECT * FROM clientes;

9. Você pode exibir o nome das colunas digitando

sqlite> .header on

10.Para escrever o resultado num arquivo externo digite

sqlite> .output resultado.txt

sqlite> SELECT * FROM clientes; sqlite> .exit

$ cat resultado.txt

11.Adicionando uma nova coluna na tabela clientes.

sqlite> ALTER TABLE clientes ADD COLUMN bloqueado BOOLEAN;

No SQLite3 os valores para boolean são 0 (falso) e 1 (verdadeiro).

12.Visualizando as colunas da tabela clientes.

sqlite> PRAGMA table_info(clientes);

13.Alterando os valores do campo bloqueado.

sqlite> UPDATE clientes SET bloqueado=0; -- comentario: Atualiza todos os registros para Falso. sqlite> UPDATE clientes SET bloqueado=1 WHERE id=1; -- Atualiza apenas o registro com id=1 para Verdadeiro. sqlite> UPDATE clientes SET bloqueado=1 WHERE UF='RJ'; -- Atualiza para Verdadeiro todos os registros com UF='RJ'.

Faça um SELECT novamente para ver o resultado.

14.Deletando registros.

sqlite> DELETE FROM clientes WHERE id=4;

Cuidado: se você não usar o WHERE e escolher um id você pode deletar todos os registros da tabela.

15.Você pode exibir os dados na forma de coluna.

sqlite> .mode column

Backup

$ sqlite3 Clientes.db .dump > clientes.sql

$ cat clientes.sql

PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE clientes( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, Nome VARCHAR(100) NOT NULL, CPF VARCHAR(11) NOT NULL, Email VARCHAR(20) NOT NULL, Fone VARCHAR(20), UF VARCHAR(2) NOT NULL ); INSERT INTO "clientes"

VALUES(1,'Regis','00000000000','rg@email.com','1100000000','SP');

INSERT INTO "clientes"

VALUES(2,'Abigail','11111111111','abigail@email.com','1112345678','RJ');

INSERT INTO "clientes"

VALUES(3,'Benedito','22222222222','benedito@email.com','1187654321','SP');

INSERT INTO "clientes"

VALUES(4,'Zacarias','33333333333','zacarias@email.com','1199999999','RJ');

COMMIT;

Pronto, se corromper o seu banco de dados, você pode recuperá-lo:

$ mv Clientes.db Clientes.db.old

$ sqlite3 Clientes_recuperado.db < clientes.sql

$ sqlite3 Clientes_recuperado.db 'SELECT * FROM clientes;'

Faça um SELECT novamente para ver o resultado do novo banco de dados.

Relacionando tabelas

Todos devem saber que num banco de dados relacional a chave estrangeira ou FOREIGN KEY tem um papel importante no relacionamento entre duas tabelas. Veremos aqui como relacionar duas tabelas.

Primeiros façamos um backup do nosso bd.

$ sqlite3 Clientes.db .dump > clientes.sql

Apenas para relembrar, vamos ver qual é a nossa tabela

$ sqlite3 Clientes.db

sqlite> .tables clientes sqlite> .header on sqlite> .mode column

E quais são seus registros.

sqlite> SELECT * FROM clientes;

id

bloqueado ---------- ---------- ----------- ------------ ---------- ----------

----------

Nome

CPF

Email

Fone

UF

1 Regis

00000000000 rg@email.com 1100000000 SP

1

2 Abigail

11111111111 abigail@emai 1112345678 RJ

1

3 Benedito

22222222222 benedito@ema 1187654321 SP

0

Então vamos criar duas novas tabelas: cidades e clientes_novo.

sqlite> CREATE TABLE cidades(

> id INTEGER PRIMARY KEY AUTOINCREMENT,

> cidade TEXT,

> uf VARCHAR(2)

> );

> CREATE TABLE clientes_novo(

> id INTEGER PRIMARY KEY AUTOINCREMENT,

> Nome VARCHAR(100) NOT NULL,

> CPF VARCHAR(11) NOT NULL,

> Email VARCHAR(20) NOT NULL,

> Fone VARCHAR(20),

> bloqueado BOOLEAN,

> cidade_id INTEGER,

> FOREIGN KEY (cidade_id) REFERENCES cidades(id)

> );

Segundo Sqlite Drop Column, não tem como "deletar" uma coluna, então precisamos criar uma nova tabela clientes_novo com os campos que precisamos e copiar os dados da primeira tabela para esta.

sqlite> INSERT INTO clientes_novo (id, Nome, CPF, Email, Fone, bloqueado)

> SELECT id, Nome, CPF, Email, Fone, bloqueado FROM clientes;

Veja que selecionamos os campos da tabela clientes e a inserimos em clientes_novo. Note que não copiamos o campo UF porque agora ele é da tabela cidades.

Agora podemos deletar a tabela "antiga".

sqlite> DROP TABLE clientes;

E renomear a nova tabela.

sqlite> ALTER TABLE clientes_novo RENAME TO clientes;

Veja o resultado da nova tabela.

sqlite> SELECT * FROM clientes;

id

Nome

CPF

Email

Fone

bloqueado

cidade_id

---------- ---------- ----------- ------------ ---------- ----------

----------

1

Regis

00000000000 rg@email.com 1100000000 1

2

Abigail

11111111111 abigail@emai 1112345678 1

3

Benedito

22222222222 benedito@ema 1187654321 0

Agora você terá que popular as cidades e definir a cidade_id em cada cliente. Lembrando que a chave é AUTOINCREMENT, então use NULL.

sqlite> INSERT INTO cidades VALUES (NULL,'Campinas','SP'); sqlite> INSERT INTO cidades VALUES (NULL,'Sao Paulo','SP'); sqlite> INSERT INTO cidades VALUES (NULL,'Rio de Janeiro','RJ');

Veja os registros da tabela cidades.

sqlite> SELECT * FROM cidades;

id

---------- ---------- ----------

cidade

uf

1 Campinas

SP

2 Sao Paulo

SP

3 Rio de Jan

RJ

Agora precisamos atualizar a cidade_id de cada cliente.

sqlite> UPDATE clientes SET cidade_id = 3 WHERE id = 1; sqlite> UPDATE clientes SET cidade_id = 1 WHERE id = 2; sqlite> UPDATE clientes SET cidade_id = 2 WHERE id = 3;

Resultado.

sqlite> SELECT * FROM clientes;

id

cidade_id ---------- ---------- ----------- ------------ ---------- ----------

Nome

CPF

Email

Fone

bloqueado

----------

1

Regis

00000000000 rg@email.com 1100000000 1

3

2

Abigail

11111111111 abigail@emai 1112345678 1

1

3

Benedito

22222222222 benedito@ema 1187654321 0

2

Façamos um INNER JOIN para visualizar todos os dados, inclusive a cidade e o uf.

sqlite> SELECT * FROM clientes INNER JOIN cidades ON clientes.cidade_id = cidades.id;

id

Nome

CPF

Email

Fone

bloqueado

cidade_id

cidade

uf

---------- ---------- ----------- ------------ ---------- ---------- ---------- -------------- --

1

Regis

00000000000 rg@email.com 1100000000 1

3

Rio de Janeiro

RJ

 

2

Abigail

11111111111 abigail@emai 1112345678 1

1

Campinas

SP

3

Benedito

22222222222 benedito@ema 1187654321 0

2

Sao Paulo

SP

ntrodução

# Se quer saber primeiro sobre class based generic views e esta série de artigos que estou fazendo, basta ler este trecho do primeiro artigo da série.

Vamos imaginar que você precisa serializar a Response de uma view tua para utilizar os dados e trabalhá-los com Ajax, e/ou para criar uma API, ou seja lá qual for o motivo, se você usa as class based generic views do Django fica mais fácil e elegante. Neste artigo veremos um exemplo simples de como fazê-lo.

Reforçando novamente que tomo por base o código do repositório que criei no github para os exemplos.

O arquivo views.py , ou em nosso caso list_and_detail.py , tem uma view que lista os objetos de uma "lista de reprodução", como abaixo:

from django.views.generic importListView from app_exemplo.models importListaDeReproducao

class ListasDeReproducao(ListView):

model = ListaDeReproducao

Com o exemplo acima teremos uma lista de objetos de

em text/html do modo convencional, em que precisamos renderizar um template e tudo mais.

Para aproveitarmos possíveis comportamentos, personalizações e/ou mesmo as definições dos

atributos já contidos em

método

ListaDeReproducao

, porém o retorno será

ListaDeReproducao

vamos estender esta view e fazer a modificação no

para termos um response application/json.

get(self, request, *args, **kwargs)

Em views.json (json.py) eu tenho isto:

# coding: utf-8 from django.http import HttpResponse from django.core import serializers from list_and_detail import ListasDeReproducao

class ListasDeReproducaoJson(ListasDeReproducao):

context_object_name = 'listas_de_reproducao'

def get(self, request, *args, **kwargs):

data = serializers.serialize("json", self.get_queryset()) return HttpResponse(data, content_type='application/json')

O conteúdo desta resposta é o retorno de self.get_queryset() já serializado no formato JSON. Algo como:

[

{

 

"pk":1,

"model":"app_exemplo.listadereproducao", "fields":{ "modificado":"2010-03-13 12:12:45", "audios":[

1,

2,

3

], "criado":"2010-01-26 15:25:26", "titulo":"Animiais", "slug":"Animais", "descricao":"Alguns efeitos sonoros de animais."

}

},

{

 

"pk":2,

"model":"app_exemplo.listadereproducao", "fields":{ "modificado":"2010-05-30 08:16:53", "audios":[

4,

5,

6,

7

], "criado":"2010-01-05 14:12:28", "titulo":"Clicks", "slug":"clicks", "descricao":"Efeitos sonoros de CLICKs."

}

},

//

Para um exemplo isto serve, mas talvez você realmente queira personalizar isto antes de transformá- lo em Json. Por exemplo se você quer serializar um queryset de usuários, dependendo de como sua query foi composta, pode trazer o campo 'senha' e, talvez, você queira omití-lo deste retorno. Contudo creio que não haverá grandes problemas para fazer isto, uma vez que com

self.get_queryset()

você consegue manipular bem estes resultados, antes de serializá-los.

Bem, este foi um exemplo simples, em um outro artigo creio que farei algo como retornar o Json e trabalhar ele com Jquery.

Se você quisesse, ao invés de Json, retornar um XML, bastaria modificar duas simples coisas, neste simples exemplo, é claro:

# serializers.serialize("xml", self.get_queryset()) return HttpResponse(data, content_type='application/xml')

Twitter Bootstrap contains an exhaustive set of CSS classes, components, and jQuery plugins that are responsive across a broad array of devices. When combined with the Django web framework, creating responsive, data-driven websites becomes quick and easy for Python developers.

The modal dialog box is one of the components Bootstrap provides. Since Django’s class-based views makes it easy to define templates and forms with complex validation rules, using Django to generate the contents of these modal dialog boxes has become a common task.

Many different methods exist for accomplishing this task, but these solutions often require duplicating template

Many different methods exist for accomplishing this task, but these solutions often require duplicating template code, don’t address rendering the same view as both a standalone page and the contents of a modal, or don’t account for redirects when submitting forms.

recently found myself trying to render a Django view as both a modal and a standalone page and had the following requirements:

I

minimize code repetition

update the modal with any form errors

close the modal on successful submission

I was able to accomplish this task with a few lines of Python, a few lines of JavaScript, and a minor template change.

Server-Side Changes

The server-side changes consisted of creating an AjaxTemplateMixin to render a different template for AJAX requests and making a small change to an existing template.

An AjaxTemplateMixin

1

class AjaxTemplateMixin(object):

2

3

def dispatch(self, request, *args, **kwargs):

4

if not hasattr(self, 'ajax_template_name'):

5

split = self.template_name.split('.html')

6

split[-1] = '_inner'

7

split.append('.html')

8

self.ajax_template_name = ''.join(split)

9

if request.is_ajax():

10

self.template_name = self.ajax_template_name

11

return super(AjaxTemplateMixin, self).dispatch(request, *args,

**kwargs)

The first step required writing a mixin to add an ajax_template_name attribute Django’s class-based views. If this attribute is not explicitly defined, it will default to adding _inner to the end of the template_name attribute. For example, take the following FormView class:

1 class TestFormView(SuccessMessageMixin, AjaxTemplateMixin, FormView):

2 template_name = 'test_app/test_form.html'

3 form_class = TestForm

4 success_url = reverse_lazy('home')

5 success_message = "Way to go!"

In this example, the ajax_template_name defaults to test_app/test_form_inner.html. If the request is AJAX, then the view renders this template. Otherwise, the view renders the test_app/test_form.html template.

Create the AJAX Template

Now that the view will render ajax_template_name for AJAX requests we have to create it. This template could be unique, but more than likely it will be the same as template_name but without extending the base template containing the site’s header, navigation, and footer. This could be as simple as changing test_app/test_form.html from:

1

{% extends 'test_app/home.html' %}

2

4

{% load crispy_forms_tags %}

5 <div class="row">

6 <form class="form-horizontal" action="{% url 'test-form' %}" method="post">

7

{% crispy form %}

8

<input type="submit" class="btn btn-submit col-md-offset-2">

9

</form>

10

</div>

11

{% endblock content %}

to:

1

{% extends 'test_app/home.html' %}

2

3

{% block content %}

4

{% include 'test_app/test_form_inner.html' %}

5

{% endblock content %}

and creating test_app/test_form_inner.html containing:

1 {% load crispy_forms_tags %}

2 <div class="row">

3 <form class="form-horizontal" action="{% url 'test-form' %}"

method="post">

4 {% crispy form %}

5 <input type="submit" class="btn btn-submit col-md-offset-2">

6 </form>

7 </div>

All we’ve done here is moved the HTML within the content block to its own template. The example template uses django-crispy-forms to generate the form markup using Bootstrap CSS classes but this is not a requirement.

Front-end Changes

At this point, rendering your view is easy, unless it contains a form.

Rendering a View in a Modal the Easy Way

Given the following modal in your HTML:

1 <div class="modal fade" id="form-modal" tabindex="-1" role="dialog" aria- labelledby="myModalLabel" aria-hidden="true">

2 <div class="modal-dialog">

3 <div class="modal-content">

4 <div class="modal-header">

5 <button type="button" class="close" data-dismiss="modal" aria- hidden="true">&times;</button>

6

<h4 class="modal-title">Modal title</h4>

7

</div>

8

<div id="form-modal-body" class="modal-body">

9

10

</div>

11

<div class="modal-footer">

12

<button type="button" class="btn btn-default" data-

dismiss="modal">Close</button>

13

</div>

14

</div>

15

</div>

rendering a Django view in it can be as simple as adding:

1 <a data-toggle="modal" href="{% url 'test-form' %}" data-target="#form- modal">Click me</a>

to your template. Behind the scenes, Bootstrap is using the data attributes to call jQuery’s .load() method to make an AJAX call to the test-form url and replace the HTML within #form-modal. However, there are a couple problems with this:

Using data attributes replaces the entire contents of the modal, so your template will need to contain the .modal-dialog, .modal-content and .modal-body DIVs to render properly.

jQuery’s .load() is only called once the first time the modal is opened.

Any redirects that occur, such as from submitting a form, will redirect the entire page.

If none of this matters to you, then great, you’re done! Otherwise, keep reading.

Rendering a View in a Modal the Slightly Harder Way

The following JavaScript solves the problems above:

1 var formAjaxSubmit = function(form, modal) {

2

$(form).submit(function (e) {

3

e.preventDefault();

4

$.ajax({

5

type: $(this).attr('method'),

6

url: $(this).attr('action'),

7

data: $(this).serialize(),

8

success: function (xhr, ajaxOptions, thrownError) {

9

if ( $(xhr).find('.has-error').length > 0 ) {

10

$(modal).find('.modal-body').html(xhr);

11

formAjaxSubmit(form, modal);

12

} else {

13

$(modal).modal('toggle');

14

}

15

},

16

error: function (xhr, ajaxOptions, thrownError) {

17

// handle response errors here

18

}

19

});

20

});

21

}

22

$('#comment-button').click(function() {

23

$('#form-modal-body').load('/test-form/', function () {

24

$('#form-modal').modal('toggle');

25

formAjaxSubmit('#form-modal-body form', '#form-modal');

26

});

27

}

This code binds to the click event on #comment-button and loads the /test-form/ HTML asynchronously into the body of the modal. Since this is an AJAX call, the test_form/test_form_inner.html template will be rendered and the form will be displayed without any site navigation or footer.

Additionally, this code also calls formAjaxSubmit(). This function binds to the form’s submit event. By calling preventDefault(), the callback function prevents the form from performing

its default submit action. Instead, the form’s content is serialized and sent via an AJAX call using the form’s defined action and method.

If the server sends back a successful response, the success function is called. The xhr parameter contains the HTML received from the server. Note that a successful response from the server does not mean that the form validated successfully. Therefore, xhr is checked to see if it contains any field errors by looking for the has_error Bootstrap class in its contents. If any errors are found, the modal’s body is updated with the form and its errors. Otherwise, the modal is closed.

class Signup(CreateView):

model = User fields = ['first_name', 'last_name', 'email', 'password']

def get_form(self, form_class):

form = super(Signup, self).get_form(form_class) form.fields['password'].widget = forms.PasswordInput() return form

from django import forms

class SignupForm(forms.ModelForm):

class Meta:

model = User fields = ['first_name', 'last_name', 'email', 'password'] widgets = { 'password': forms.PasswordInput()

}

class Signup(CreateView):

form_class = SignupForm model = User

Summary In conclusion, we were able to define our form and view in Django and

Summary

In conclusion, we were able to define our form and view in Django and render it correctly in a

Bootstrap modal by adding one short Django mixin, making a small change to an existing template, and adding a few lines of JavaScript.