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

To get from a URL to a view, Django uses what are known as ‘URLconfs’. A URLconf maps URL patterns to views.

This tutorial provides basic instruction in the use of URLconfs, and you can refer to Despachante de URL for more information.

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)

Wire these new views into the polls.urls module by adding the following path() calls:

polls/urls.py
from django.urls import path

from . import views

urlpatterns = [
    # ex: /polls/
    path('', views.index, name='index'),
    # ex: /polls/5/
    path('<int:question_id>/', views.detail, name='detail'),
    # ex: /polls/5/results/
    path('<int:question_id>/results/', views.results, name='results'),
    # ex: /polls/5/vote/
    path('<int:question_id>/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.

When somebody requests a page from your website – say, “/polls/34/”, Django will load the mysite.urls Python module because it’s pointed to by the ROOT_URLCONF setting. It finds the variable named urlpatterns and traverses the patterns in order. After finding the match at 'polls/', it strips off the matching text ("polls/") and sends the remaining text – "34/" – to the ‘polls.urls’ URLconf for further processing. There it matches '<int:question_id>/', resulting in a call to the detail() view like so:

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

The question_id=34 part comes from <int:question_id>. Using angle brackets “captures” part of the URL and sends it as a keyword argument to the view function. The :question_id> part of the string defines the name that will be used to identify the matched pattern, and the <int: part is a converter that determines what patterns should match this part of the URL path.

There’s no need to add URL cruft such as .html – unless you want to, in which case you can do something like this:

path('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>

The problem with this hardcoded, tightly-coupled approach is that it becomes challenging to change URLs on projects with a lot of templates. However, since you defined the name argument in the path() functions in the polls.urls module, you can remove a reliance on specific URL paths defined in your url configurations by using the {% url %} template tag:

<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
path('<int:question_id>/', 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'
path('specifics/<int:question_id>/', 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.urls import path

from . import views

app_name = 'polls'
urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
    path('<int:question_id>/results/', views.results, name='results'),
    path('<int:question_id>/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