CSRF: novo app/csrf.py + middleware em app/main.py garantem um cookie csrf_token por navegador; todo formulario que muda estado (criar/editar/ excluir cliente, adicionar/editar/excluir contato) embute esse valor como campo oculto, e Depends(exigir_csrf) confere no POST que o valor enviado bate com o cookie antes de deixar a rota executar. Verificado ponta a ponta com o app real rodando (curl): token errado -> 403, token correto -> 303. Paginacao: db_clientes.listar_clientes ganha offset, nova contar_clientes(); GET /clientes aceita ?pagina=N (100 clientes por pagina), template mostra Anterior/Proxima e o total. 53/53 testes passando (8 novos).
64 lines
2.9 KiB
HTML
64 lines
2.9 KiB
HTML
<!-- app/templates/cliente_detalhe.html -->
|
|
{% extends "base.html" %}
|
|
{% block titulo %}{{ cliente.RAZAO }}{% endblock %}
|
|
{% block conteudo %}
|
|
<h1>{{ cliente.FANTASIA }} <small>({{ cliente.CODIGO }})</small></h1>
|
|
<p>{{ cliente.RAZAO }} — {{ cliente.CNPJ }}</p>
|
|
<p>
|
|
<a class="botao" href="/clientes/{{ cliente.ID }}/editar">Editar</a>
|
|
<form method="post" action="/clientes/{{ cliente.ID }}/excluir" style="display:inline">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<button type="submit" onclick="return confirm('Excluir este cliente?')">Excluir</button>
|
|
</form>
|
|
</p>
|
|
|
|
<h2>Contatos</h2>
|
|
<table>
|
|
<tr><th>Nome</th><th>Telefone</th><th>E-mail</th><th></th></tr>
|
|
{% for ct in contatos %}
|
|
<tr>
|
|
<td>{{ ct.NOME }}</td><td>{{ ct.TELEFONE }}</td><td>{{ ct.EMAIL or "" }}</td>
|
|
<td>
|
|
<details style="display:inline-block">
|
|
<summary class="botao" style="display:inline-block;cursor:pointer">Editar</summary>
|
|
<form method="post" action="/clientes/{{ cliente.ID }}/contatos/{{ ct.ID }}/editar" style="margin-top:8px">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<label for="nome-{{ ct.ID }}">Nome</label>
|
|
<input type="text" id="nome-{{ ct.ID }}" name="nome" value="{{ ct.NOME or '' }}">
|
|
<label for="telefone-{{ ct.ID }}">Telefone</label>
|
|
<input type="text" id="telefone-{{ ct.ID }}" name="telefone" value="{{ ct.TELEFONE }}" required>
|
|
<label for="email-{{ ct.ID }}">E-mail</label>
|
|
<input type="text" id="email-{{ ct.ID }}" name="email" value="{{ ct.EMAIL or '' }}">
|
|
<button type="submit">Salvar</button>
|
|
</form>
|
|
</details>
|
|
<form method="post" action="/clientes/{{ cliente.ID }}/contatos/{{ ct.ID }}/excluir" style="display:inline">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<button type="submit" onclick="return confirm('Excluir este contato?')">Excluir</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</table>
|
|
<form method="post" action="/clientes/{{ cliente.ID }}/contatos">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<label for="nome">Nome</label>
|
|
<input type="text" id="nome" name="nome">
|
|
<label for="telefone">Telefone</label>
|
|
<input type="text" id="telefone" name="telefone" required>
|
|
<label for="email">E-mail</label>
|
|
<input type="text" id="email" name="email">
|
|
<button type="submit">Adicionar contato</button>
|
|
</form>
|
|
|
|
<h2>Status em Clientes_Chaves</h2>
|
|
{% if status_chaves %}
|
|
<table>
|
|
<tr>{% for mes in status_chaves.meses %}<th>{{ mes }}</th>{% endfor %}</tr>
|
|
<tr>{% for mes, valor in status_chaves.meses.items() %}<td>{{ "✓" if valor else "—" }}</td>{% endfor %}</tr>
|
|
</table>
|
|
{% else %}
|
|
<p>Nenhuma linha em Clientes_Chaves para o código {{ cliente.CODIGO }}.</p>
|
|
{% endif %}
|
|
{% endblock %}
|