Escrevendo sua primeira app Django, parte 3

Este tutorial inicia-se onde o Tutorial 2 terminou. Vamos continuar a aplicação web de enquete e focaremos na criação da interface pública – “views”.

Visão Geral

Uma view é um “tipo” de página Web em sua aplicação Django que em geral serve a uma função específica e tem um template específico. Por exemplo, em uma aplicação de blog, você deve ter as seguintes views:

  • Página inicial do blog - exibe os artigos mais recentes.
  • Página de “detalhes” - página de vínculo estático permanente para um único artigo.
  • Página de arquivo por ano - exibe todos os meses com artigos para um determinado ano.
  • Página de arquivo por mês - exibe todos os dias com artigos para um determinado mês.
  • Página de arquivo por dia - exibe todos os artigos de um determinado dia.
  • Ação de comentários - controla o envio de comentários para um artigo.

Em nossa aplicação de enquetes, nós teremos as seguintes views:

  • Página de “índice” de enquetes - exibe as enquetes mais recente.
  • Question “detail” page – displays a question text, with no results but with a form to vote.
  • Página de “resultados” de perguntas - exibe os resultados de uma pergunta em particular.
  • Ação de voto - gerencia a votação para uma escolha particular em uma enquete em particular.

No Django, páginas web e outros conteúdos são entregues por views. Cada view é representada por uma simples função Python(ou metodos, no caso das class-based views). O django irá escolher uma view examinando a URL que foi requisitada (para ser preciso, a parte da URL depois do nome de dominio).

Agora em seu tempo pela internet você deve ter visto coisas lindas como “ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B”. Você ficará satisfeito em saber que o Django permite padrões de URL muito mais elegantes do que isso.

Um padrão de URL é simplesmente a forma geral de uma URL - por exemplo: /newsarchive/<year>/<month>/.

Para ir de uma URL para uma view, O Django usa oque é conhecido como ‘URLconfs’. Uma URLconf mapeia padrões de URL (descritas com expressões regulares) para views.

Este tutorial fornece instruções básicas na utilização de URLconfs, e você pode recorrer a django.urls para mais informações.

Escrevendo mais views

Agora vamos adicionar mais algumas views em polls/views.py. Estas views são um pouco diferentes, porque elas recebem um argumento:

polls/views.py
def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

Ligue estas novas views dentro do módulo polls.urls adicionando as seguintes chamadas url():

polls/urls.py
from django.conf.urls import url

from . import views

urlpatterns = [
    # ex: /polls/
    url(r'^$', views.index, name='index'),
    # ex: /polls/5/
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    # ex: /polls/5/results/
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    # ex: /polls/5/vote/
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

Dê uma olhada no seu navegador, em /polls/34/. Ele vai executar o método detail() e mostrar o ID que você informou na URL. Tente “/polls/34/results/” e “/polls/34/vote/” também – Eles vão mostrar a pagina resultadoss e a pagina de votação.

Quando alguém requisita uma página do seu site –vamos dizer, “/polls/34”, o Django irá carregar o módulo Python mysite.urls porque este está sendo apontado pela definição ROOT_URLCONF. Ele encontra a variável denominada urlpatterns e atravessa as expressões regulares seguindo a ordem. Depois de encontrar uma combinação no '^polls/', ele corta o texto que combina ("polls/") e envia a parte remanescente – "34/" – para o URLconf do ‘polls.urls’ para processamento posterior. Lá ele encontra a r'^(?P<question_id>[0-9]+)/$', resultando em uma chamada para a “view” detail() como aqui:

detail(request=<HttpRequest object>, question_id='34')

A parte question_id='34' vem de (?P<question_id>[0-9]+). Usando parênteses em torno de um padrão “captura” o texto casado por este padrão e envia ele como um argumento da função; ?P<question_id> define o nome que será usado para identificar o padrão casado; e [0-9]+ é a expressão regular para casar uma sequência de dígitos (ex., um número).

Como os padrões de URL são expressões regulares, realmente não há limites para o que você possa fazer com elas. E também não é necessário adicionar extensão na URL como .html - a menos que você queira, neste caso você pode fazer algo como:

url(r'^polls/latest\.html$', views.index),

Mas, não o faça isso, Isto é idiota.

Escreva views que façam algo

Cada view é responsável por fazer uma das duas coisas: retornar um objeto HttpResponse contendo o conteúdo para a página requisitada, ou levantar uma exceção como Http404. O resto é com você.

Sua view pode ler registros do banco de dados, ou não. Ela pode usar um sistema de templates como o do Django - ou outro sistema de templates Python de terceiros - ou não. Ele pode gerar um arquivo PDF, saída em um XML, criar um arquivo ZIP sob demanda, qualquer coisa que você quiser,usando qualquer biblioteca Python você quiser.

Tudo que o Django espera é que a view retorne um HttpResponse. Ou uma exceção.

Porque é conveniente, vamos usar a própria API de banco de dados do Django, a qual cobrimos em Tutorial 2. Aqui uma nova tentativa de view index(), a qual mostra as últimas 5 “poll questions” do sistema, separada por vírgulas, de acordo com sua data de publicação:

polls/views.py
from django.http import HttpResponse

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

# Leave the rest of the views (detail, results, vote) unchanged

Há um problema aqui, no entanto: o design da página esta codificado na view. Se você quiser mudar a forma de apresentação de sua página, você terá de editar este código diretamente em Python. Então vamos usar o sistema de templates do Django para separar o design do código Python:

Primeiro, crie um diretório chamado `` templates`` em seu diretório polls. O Django irá procurar templates lá.

A sua configuração de projeto TEMPLATES descreve como o Django vai carregar e renderizar templates. O arquivo de configuração padrão usa o backend DjangoTemplates do qual a opção APP_DIRS é configurada como True. Por convenção DjangoTemplates` procura por um subdiretório “templates” em cada uma das INSTALLED_APPS.

Dentro do diretório templates que acabou de criar, crie outro diretório polls, e dentro crie um arquivo chamado index.html. Em outras palavras, seu template deve estar em polls/templates/polls/index.html. Devido a forma como o carregador de templates app_directories funciona como descrito acima, você pode referenciar este template dentro do Django simplesmente como polls/index.html.

Namespacing de template

Agora nós podemos ser capazes de avnçar com a colocação dos nossos modelos diretamente em polls/templates (em vez de criar outro subdiretório poll), mas na verdade seria uma má ideia. Django irá escolher o primeiro template que encontra cujo nome corresponde, e se você tivesse um template com o mesmo nome em uma aplicação diferente, O Django e incapaz de distinguir entre eles. Precisamos ser capazes de apontar Django no caminho certo, e a maneira mais fácil de garantir isso é usar namespacing. Ou seja, colocando esses templates dentro de outro diretório nomeado para a aplicação em si.

Ponha o seguinte código neste template:

polls/templates/polls/index.html
{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

Agora vamos atualizar nossa view index em polls/views.py para usar o template:

polls/views.py
from django.http import HttpResponse
from django.template import loader

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = {
        'latest_question_list': latest_question_list,
    }
    return HttpResponse(template.render(context, request))

Esse código carrega o template chamado polls/index.html e passa um contexto para ele. O contexto é um dicionário mapeando nomes de variáveis ​​para objetos Python.

Carregue a página apontando seu navegador para “/polls/”, e você deve ver uma lista contendo a questão “What’s up” do Tutorial 2. O link aponta para página de detalhes das perguntas.

Um atalho: render()

É um estilo muito comum carregar um template, preenchê-lo com um contexto e retornar um objeto HttpResponse com o resultado do template renderizado. O Django fornece este atalho. Aqui esta toda a view index() reescrita:

polls/views.py
from django.shortcuts import render

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)

Note que uma vez que você tiver feito isto em todas as views, nós não vamos mais precisar importar loader e HttpResponse (você vai querer manter HttpResponse se você ainda tiver os métodos criados para detail, results, e vote).

A função render() recebe o nome do template como primeiro argumento e um dicionário opcional como segundo argumento. Ele retorna um objeto HttpResponse do template informado renderizado com o contexto determinado.

Levantando um erro 404

Agora, vamos abordar a view de detalhe de pergunta – a página que mostra as questões para uma enquete lançada. Aqui esta a view:

polls/views.py
from django.http import Http404
from django.shortcuts import render

from .models import Question
# ...
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

Um novo conceito aqui: A view levanta uma exceção Http404 se a enquete com ID requisitado não existir.

Nós iremos discutir o que você poderia colocar no template polls/detail.html mais tarde, mas se você gostaria de ter o exemplo acima funcionando rapidamente, um arquivo contendo:

polls/templates/polls/detail.html
{{ question }}

irá ajudar a começar por enquanto.

Um atalho: get_object_or_404()

É um estilo muito comum usar get() e levantar uma exceção Http404 se o objeto não existir. O Django fornece um atalho para isso. Aqui esta a view detail(), reescrita:

polls/views.py
from django.shortcuts import get_object_or_404, render

from .models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

A função get_object_or_404() recebe um modelo do Django como primeiro argumento e um número arbitrário de argumentos chave, que ele passa para a função do módulo get(). E levanta uma exceção Http404 se o objeto não existir.

Filosofia

Porquê usamos uma função auxiliar get_object_or_404() ao invés de automaticamente capturar as exceções ObjectDoesNotExist em alto nível ou fazer a API do modelo levantar Http404 ao invés de ObjectDoesNotExist?

Porque isso seria acoplar a camada de modelo com a camada de visão. Um dos principais objetivo do design do Django é manter o baixo acoplamento. Alguns acoplamento controlado é introduzido no módulo django.shortcuts.

Existe também a função get_list_or_404(), que trabalhada da mesma forma que get_object_or_404() – com a diferença de que ela usa filter() ao invés de get(). Ela levanta Http404 se a lista estiver vazia.

Use o sistema de template

De volta para a view detail() da nossa aplicação de enquentes. Dada a variável de contexto question, aqui está como o template polls/detail.htm deve ser:

polls/templates/polls/detail.html
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

O sistema de templates usa uma sintaxe separada por pontos para acessar os atributos da variável. No exemplo de {{ question.question_text }}, primeiro o Django procura por dicionário no objeto question. Se isto falhar, ele tenta procurar por um atributo – que funciona, neste caso. Se a procura do atributo também falhar, ele irá tentar uma chamada do tipo list-index.

A chamada do método acontece no laço {% for %}: poll.choice_set.all é interpretado como código Python poll.choice_set.all(), que retorna objetos Choice iteráveis que são suportado para ser usado na tag {% for %}.

Veja o guia de templates para maiores detalhes sobre templates.

Removendo URLs codificados nos templates

Lembre-se, quando escrevemos o link para uma pergunta no template polls/templates/index.html, o link foi parcialmente codificado como este:

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

O problema com esta abordagem, muita acoplada é que ele se torna um desafio para mudar URLs em projetos com um monte de templates. No entanto, uma vez que você definiu o argumento nome na url() no módulo polls.urls, você pode remover uma dependência de caminhos de URL específicos definidos em suas configurações de URL usando a tag de template ``{% url%}`:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

A maneira como isso funciona é, observando as definições de URL como especificado no módulo polls.urls. Você pode ver exatamente onde o nome de URL ‘detalhe’ é definido a seguir

...
# the 'name' value as called by the {% url %} template tag
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

Se você quiser alterar a URL das views de detalhe da enquete para outra coisa, talvez para algo como polls/specifics/12/ em vez de fazê-lo no template (ou templates) você mudaria em polls/urls.py:

...
# added the word 'specifics'
url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

Namespacing nomes de URL

Este projeto tutorial tem apenas um aplicação, polls. Em projetos reais Django, pode haver cinco, dez, vinte ou mais aplicações. Como o Django diferencia os nomes de URL entre eles? Por exemplo, a aplicação polls tem uma view detail, e assim pode um aplicativo no mesmo projeto que é para um blog. Como é que faz para que o Django saiba qual view da aplicação será criada para a url ao usar a tag de template`` {% url%} ``?

A resposta é adicionar namespaces a seu URLconf. No arquivo polls/urls.py, continue e adicione um app_name para configurar o namespace da aplicação.

polls/urls.py
from django.conf.urls import url

from . import views

app_name = 'polls'
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

Agora mudo seu template``polls/index.html`` de:

polls/templates/polls/index.html
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

para apontar para a view de detalhes d namespace:

polls/templates/polls/index.html
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

Quando você estiver confortável em escrever views, leia a parte 4 deste tutorial para aprender sobre processamento de formulários simples e sobre as views genéricas.

Back to Top