A pasta de arquivos estáticos do projeto pode ser criada na pasta onde ficam as configu- rações do projeto, onde também está o arquivo settings.py. O mais importante é definir a variável STATICFILES_DIRS.
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'umas_e_ostras/static'), )
Foi usado o método os.path.join para unir a variável BASE_DIR e o nome da pasta “umas_e_ ostras/static”. Essa técnica é importante, pois quando esse projeto for implantado em um servidor, não será necessário alterar essa configuração. BASE_DIR terá o caminho do projeto e será concatenado com o nome da subpasta onde os arquivos estáticos serão armazenados. Os templates da aplicação reservas precisam ser alterados para utilizar o template do projeto. A primeira linha deve ter o comando para estender o template base. O conteúdo precisa ser gerado dentro de um bloco, chamado de conteúdo.
{% extends 'base.html' %}
... Qualquer html preexistente que não faz parte do conteúdo. {% block conteudo %}
... código preexistente nos templates ... {% endblock %}
Isso precisa ser aplicado a todos os templates do projeto. Em nosso exemplo, são os tem- plates da aplicação reservas, já que o projeto só tem uma aplicação.
reservas/templates/reservas/index.html
O template index.html da aplicação reservas serve como exemplo para explicar o funciona- mento da capacidade de estender templates.
{% extends 'base.html' %} {% load staticfiles %}
<link rel="stylesheet" type="text/css" href="{% static 'reservas/style.css' %}" /> {% block conteudo %}
{% if ultimos_clientes %} <ul>
Py th on e D ja ng o F un da m en to s <li>
<a href="{% url 'reservas:detalhe' cliente.id %}"> {{ cliente.nome }} </a> </li> {% endfor %} </ul> {% else %}
<p>Nenhum cliente registrado.</p> {% endif %}
{% endblock %}
O trecho antigo mantido fora do bloco conteudo não causa mais efeito algum, pois não está contido em nenhum bloco e é ignorado pelo sistema de templates.
{% load staticfiles %}
<link rel="stylesheet" type="text/css" href="{% static ‘reservas/style.css’ %}" />
Só aquilo que está no bloco conteudo será apresentado ao acessar a view que utiliza esse template.
reservas/templates/reservas/lista.html
O template lista.html teve a adição do cabeçalho com o comando para estender o template básico padrão (base.html) e a redefinição do bloco conteudo. O código anterior do template ficou dentro do bloco conteudo.
{% extends "base.html" %} {% block conteudo %}
<h1>{{ cliente.nome }}</h1> <ul>
{% for reserva in cliente.reservas_confirmadas %}
<li>Data da reserva: {{ reserva.data_reserva }} -- Data do evento: {{ reserva.data_evento }} -- Quantidade de pessoas: {{ reserva.pessoas }} </li>
{% endfor %} </ul>
<a href="{% url 'reservas:detalhe' cliente.id %}"> Confirmar mais reservas?
</a> {% endblock %}
reservas/templates/reservas/detalhe.html
Da mesma forma o template detalhe.html teve apenas a adição do comando para estender o template base.html e a redefinição do bloco conteudo.
{% extends "base.html" %} {% block conteudo %}
Ca pí tu lo 9 - A rq ui vo s e st át ic os , v ie w s g en ér ic as e t es te s {% if error_message %}
<p><strong>{{ error_message }}</strong></p> {% endif %}
<form action="{% url 'reservas:confirma' cliente.id %}" method="post">
{% csrf_token %}
{% for reserva in cliente.reservas_nao_confirmadas %} <input type="checkbox" name="confirmacao"...
<label for="confirmacao{{ forloop.counter }}">... {% endfor %}
<input type="submit" value="Atualizar" /> </form>
{% endblock %}
Importante perceber que no exemplo anterior parte do código foi omitido por ser repetido. Se esse código for colado na aplicação, não funcionará corretamente.
umas_e_ostras/urls.py
q
1 O projeto precisa de uma view para o novo template “templates/index.html”. 1 A view genérica TemplateView pode ser usada.
A última alteração necessária para customizar a aparência do projeto é a criação da view index do site, que renderizará o template index.html.
Existe uma view genérica básica que pode ser usada para renderizar o template index. É chamada de TemplateView e serve para renderizar templates como esse, que não pre- cisam de um model.
# umas_e_ostras/urls.py
from django.conf.urls import include, url from django.contrib import admin
from django.views.generic import TemplateView urlpatterns = [ url('^$', TemplateView.as_view(template_name='index.html'), name='index'), url(r'^reservas/', include('reservas.urls', namespace='reservas')), url(r'^admin/', admin.site.urls), ]
Basta adicionar a definição da URL para que o template seja renderizado. Não é necessário nem mesmo escrever uma classe e fazer herança para que o funcionamento seja obtido.
url('^$', TemplateView.as_view(template_name='index.html'), name=’index’),
Para ver o site funcionando, basta iniciar o servidor de desenvolvimento e acessar a URL indicada no console: http://localhost:8000/.
Py th on e D ja ng o F un da m en to s
Formulários com django.forms.Form
q
1 Módulo django.forms possui ferramentas para manipular formulários.
1 Contempla a definição dos campos, geração do HTML e captura dos dados recebidos via POST, entre outras coisas.
1 Classe django.forms.Form fornece os mecanismos necessários para o funcionamento. O framework Django também oferece facilidades para criar nossos próprios formulários. No módulo django.forms, estão disponíveis diversas classes e funções para auxiliar a criação de formulários. Vale lembrar que em aplicações do Django o conceito de formulário é um pouco mais amplo. Além do formulário HTML, existe também o objeto form (que gera o formulário HTML ou os dados estruturados retornados quando o formulário é submetido, ou ainda, o conjunto dessas partes funcionando do começo ao fim). Todo esse mecanismo é fornecido pela classe Form do módulo django.forms.
Para explicar seu uso, será criado um formulário de contato. Esse formulário precisará de uma URL para acesso e outra URL para a página de sucesso. Precisará também de uma view para tratar o formulário e outra view para renderizar a página de sucesso. O template templates/base.html precisa ser alterado para adicionar o link de acesso ao formulário de contato. Um template para o formulário de contato e um template para a página de sucesso precisam ser criados. E a classe com o formulário de contato que herda de django.forms. Form precisa ser criada. Vejamos a seguir essas alterações com as respectivas explicações.
Novo arquivo forms.py
Para definir um novo formulário, é necessário implementar uma classe que herde da classe django.forms.Form. Nessa classe são definidos os campos e tipos de cada campo do formulário.
# reservas/forms.py from django import forms class FormContato(forms.Form): email = forms.EmailField() assunto = forms.CharField(label='Assunto', max_length=150) comentarios = forms.CharField(label='Comentários', widget=forms.Textarea)
O exemplo define uma classe para um formulário de contato com os campos: e-mail, assunto e comentários. Os campos aceitam parâmetros que customizam o comportamento do formulário. O parâmetro label define a etiqueta que aparece ao lado do campo no formu- lário. O parâmetro max_length aplica um limite ao tamanho do campo. O parâmetro widget modifica o componente que será usado para coletar a informação no formulário. Nesse exemplo foi usado um componente de texto com várias linhas, o forms.Textarea.
Ca pí tu lo 9 - A rq ui vo s e st át ic os , v ie w s g en ér ic as e t es te s
Template base.html
No template base.html foi adicionado um link no menu de navegação para a URL do formu- lário de contato.
<!DOCTYPE html> <html>
<head>
{% load staticfiles %}
<meta http-equiv="Content-type" content="test/html; charset=UTF-8"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/ jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/ jquery-ui.min.js"></script>
<link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}" /> </head> <body> {% block header %} <header></header> {% endblock %} {% block navigation %} <nav>
<a href="{% url 'index' %}">
<div id="inicio" class="navButtons"></div> </a>
<a href="{% url 'reservas:index' %}">
<div id="reservas" class="navButtons"></div> </a>
<div id="cardapio" class="navButtons"></div> <a href="{% url 'reservas:contato' %}">
<div id="contato" class="navButtons"></div> </a> </nav> {% endblock %} <section id="body"> <div class="conteudo"> {% block conteudo %} {% endblock %} </div> </section> </body> </html>
Como os templates usados no projeto estendem esse template base.html, nenhuma outra modificação é necessária.
Py th on e D ja ng o F un da m en to s
Template contato.html
Para apresentar o formulário, utilizamos o template contato.html. Esse template precisa definir uma tag HTML <form> para indicar qual a ação será executada quando o botão Enviar for pressionado.
{% extends "base.html" %} {% block conteudo %}
<form action="{% url 'reservas:contato' %}" method="post" class="elegant-aero"> {% csrf_token %}
{{ form.as_ul }}
<input type="submit" value="Enviar" /> </form>
{% endblock %}
Utilizamos a macro da linguagem de template do Django {% url %} para definir a ação. Além disso é definida uma classe CSS, elegant-aero, para customizar a aparência do formulário. O estilo elegant-aero foi definido no arquivo estático umas_e_ostras/static/css/style.css.
Template obrigado.html
Após o envio do e-mail com sucesso é apresentada uma página de agradecimento. O tem- plate obrigado.html é usado para exibir essa mensagem.
{% extends "base.html" %} {% block conteudo %}
<p>Recebemos sua mensagem. Obrigado pelo contato.</p> <p>Entraremos em contato em breve.</p>
{% endblock %}
Alteração no arquivo urls.py
As URLs de contato e agradecimento precisam ser mapeadas para as views que exibirão o formulário, processar os dados por ele enviados e apresentar a mensagem de agradecimento.
# reservas/urls.py
from django.conf.urls import url from.import views
urlpatterns = [ # ex: /reservas/
url(r'^$', views.index, name='index'), # ex: /reservas/1/ url(r'^(?P<cliente_id>[0-9]+)/$', views.detalhe, name='detalhe'), # ex: /reservas/1/lista/ url(r'^(?P<cliente_id>[0-9]+)/lista/$', views.reservas, name='reservas'), # ex: /reservas/1/confirma/ url(r'^(?P<cliente_id>[0-9]+)/confirma/$', views.confirma, name='confirma'),
Ca pí tu lo 9 - A rq ui vo s e st át ic os , v ie w s g en ér ic as e t es te s # ex: /reservas/contato/ url(r'^contato/$', views.contato, name='contato'), # ex: /reservas/obrigado/ url(r'^obrigado/$', views.obrigado, name='obrigado'), ]
A URL /reservas/contato/ é associada à view views.contato que exibirá o formulário e também receberá os dados para enviar o e-mail. A URL /reservas/obrigado/ é associada à view views.obrigado que exibirá a mensagem de agradecimento.
Alterações no arquivo views.py
Falta então criar as views contato e obrigado, que vão exibir o formulário em branco, pro- cessar os dados, enviar o e-mail e exibir a mensagem de agradecimento.
# reservas/views.py
from django.http import HttpResponse from django.http import HttpResponseRedirect from django.shortcuts import render
from django.shortcuts import get_object_or_404 from django.core.urlresolvers import reverse from .models import Cliente, Reserva from .forms import FormContato def index(request):
ordem = request.GET.get('ordem', 'registrado_em') direcao = request.GET.get('direcao', 'desc')
campos = (field.name for field in Cliente._meta.fields) if ordem not in campos:
ordem = 'registrado_em' if direcao == 'desc': direcao = '-' else:
direcao = ''
ordenacao = '{}{}'.format(direcao, ordem) ultimos_clientes = Cliente.objects.order_by( ordenacao)[:5]
context = {'ultimos_clientes': ultimos_clientes} return render(request, 'reservas/index.html', context) def detalhe(request, cliente_id):
cliente = get_object_or_404(Cliente, pk=cliente_id) return render(request, 'reservas/detalhe.html', {'cliente': cliente})
Py th on e D ja ng o F un da m en to s
def reservas(request, cliente_id):
cliente = get_object_or_404(Cliente, pk=cliente_id) return render(request, 'reservas/lista.html', {'cliente': cliente})
def confirma(request, cliente_id):
cliente = get_object_or_404(Cliente, pk=cliente_id) confirmados = request.POST.getlist('confirmacao') for reserva_id in confirmados:
try:
reserva = cliente.reserva_set.get(pk=reserva_id) except (KeyError, Reserva.DoesNotExist):
# Reapresenta o formulário do cliente.
return render(request, 'reservas/detail.html', { 'cliente': cliente,
'error_message': "Código da reserva não encontrado.", })
else:
reserva.confirmada = True reserva.save()
# Sempre retornar uma HttpResponseRedirect depois de tratar com # sucesso os dados do POST. Isso evita que dados sejam postados # novamente caso o usuário pressione o botão voltar.
return HttpResponseRedirect(reverse('reservas:reservas', args=(cliente.id,))) def contato(request): if request.method == 'POST': form = FormContato(request.POST) if form.is_valid(): assunto = form.cleaned_data['assunto'] comentarios = form.cleaned_data['comentarios'] remetente = form.cleaned_data['email'] destinatarios = ['[email protected]'] try:
send_mail(assunto, comentarios, remetente, destinatarios)
except BadHeaderError:
return HttpResponse('Cabeçalho inválido.') return HttpResponseRedirect('/reservas/obrigado/') else:
form = FormContato()
return render(request, 'reservas/contato.html', {'form': form}) def obrigado(request):
return render(request, ‘reservas/obrigado.html’)
A view contato começa testando o método usado. Quando a URL /reservas/contato é acessada, o método é GET e o resultado do teste condicional if é falso. Portanto, o bloco
Ca pí tu lo 9 - A rq ui vo s e st át ic os , v ie w s g en ér ic as e t es te s else: form = FormContato()
Quando o formulário é preenchido e o botão Enviar for pressionado a view contato é acio- nada novamente. Dessa vez o método é POST, fazendo com que seja criado um objeto Form com os dados do POST. O método is_valid() valida os campos do formulário e os valores que estiverem OK são disponibilizados no atributo cleaned_data. Se todos os campos forem válidos o método retorna True, fazendo com que o e-mail seja enviado através da função send_mail. É importante tratar a exceção BadHeaderError pois existe uma técnica conhecida que utiliza os formulários de e-mail para enviar cabeçalhos de e-mail maliciosos.
if request.method == 'POST': form = FormContato(request.POST) if form.is_valid(): assunto = form.cleaned_data['assunto'] comentarios = form.cleaned_data['comentarios'] remetente = form.cleaned_data['email'] destinatarios = ['[email protected]'] try:
send_mail(assunto, comentarios, remetente, destinatarios) except BadHeaderError:
return HttpResponse('Cabeçalho inválido.') return HttpResponseRedirect(‘/reservas/obrigado/’)
Se tudo der certo, o usuário é redirecionado para a página de agradecimento.