Skip to content

Repository files navigation

DevBlog Django

Documento de referência do projeto. Mantido manualmente a cada nova feature. Última atualização: Julho/2026


Índice

  1. Visão geral do projeto
  2. Stack e dependências
  3. Estrutura de pastas
  4. Arquitetura MTV — como o projeto se organiza
  5. Models
  6. URLs
  7. Views
  8. Templates
  9. CSS — classes do tema
  10. Admin
  11. Features implementadas
  12. Features pendentes / próximos passos
  13. Como rodar o projeto
  14. Decisões de arquitetura registradas
  15. Erros conhecidos e soluções

1. Visão geral do projeto

DevBlog Django é um blog de artigos técnicos sobre tecnologia, dados e Machine Learning. Os artigos são criados e gerenciados pelo Django Admin com editor de texto rico (TinyMCE). O site é público e exibe os artigos publicados em uma listagem com cards visuais, busca por texto, e filtros por eixo de tecnologia.

Item Valor
Nome do projeto Django cursoDjango
Nome do app principal motorartigos
Banco de dados MariaDB 10.5+ (via XAMPP)
Nome do banco djangoartigos

2. Stack e dependências

Django==4.2.x
django-tinymce
beautifulsoup4
mysqlclient

O projeto usa Django 4.2.x (série LTS) por compatibilidade com MariaDB 10.5. Django 5.x exige MariaDB 10.5+ e causava erros de versão no ambiente de desenvolvimento.

Instalar todas as dependências:

pip install -r requirements.txt

3. Estrutura de pastas

cursoDjango/                          ← raiz do projeto
├── manage.py                         ← ponto de entrada de todos os comandos Django
├── requirements.txt                  ← dependências do projeto
├── .env.example                      ← modelo de variáveis de ambiente (sem valores reais)
├── .gitignore                        ← arquivos ignorados pelo Git
│
├── cursoDjango/                      ← configurações do projeto Django
│   ├── __init__.py
│   ├── settings.py                   ← configurações globais (banco, apps, static, etc.)
│   └── urls.py                       ← URLconf raiz (inclui motorartigos.urls)
│
└── motorartigos/                     ← app principal do blog
    ├── __init__.py
    ├── admin.py                      ← configuração da interface administrativa
    ├── models.py                     ← Models: Autor, EixoTecnologia, Artigo
    ├── views.py                      ← Views: index, busca, artigo
    ├── urls.py                       ← Rotas do app
    │
    ├── templates/
    │   └── motorartigos/
    │       ├── base.html             ← layout base (head, nav, footer)
    │       ├── index.html            ← página inicial (Mais recentes + Todos os artigos)
    │       ├── busca.html            ← página de resultados de busca
    │       ├── artigo.html           ← detalhe de artigo (ainda hardcoded)
    │       └── partials/             ← fragmentos reutilizáveis de interface
    │           ├── _header.html      ← navbar + texto intro + busca + filtros de eixo
    │           ├── _footer.html      ← rodapé
    │           ├── _busca.html       ← formulário de busca (reutilizável)
    │           └── _card_artigo.html ← card de artigo (reutilizável)
    │
    └── static/
        └── css/
            └── styles.css            ← estilos do tema escuro

4. Arquitetura MTV — como o projeto se organiza

Django usa o padrão MTV (Model – Template – View), que é uma variação do MVC com nomenclatura própria:

Camada Arquivo Responsabilidade
Model models.py Define as tabelas do banco e as regras dos dados
Template templates/**/*.html Define como os dados são exibidos (HTML)
View views.py Recebe a requisição, consulta o Model, passa dados ao Template
URLconf urls.py Mapeia URLs para Views

Fluxo de uma requisição:

Navegador
   ↓  GET /busca/?q=python
URLconf (urls.py)
   ↓  chama views.busca()
View (views.py)
   ↓  lê request.GET.get('q')
   ↓  filtra Artigo.objects.filter(Q(...))
   ↓  monta contexto {'artigos': ..., 'q': ..., 'total': ...}
Template (busca.html)
   ↓  renderiza HTML com os dados do contexto
Navegador
   ← recebe HTML finalizado

5. Models

Arquivo: motorartigos/models.py

Autor

Campo Tipo Descrição
id AutoField (PK) Gerado automaticamente
nome CharField Nome completo do autor
email EmailField E-mail do autor
biografia TextField Texto livre de apresentação

EixoTecnologia

Campo Tipo Descrição
id AutoField (PK) Gerado automaticamente
nome CharField Nome do eixo (ex: "Inteligência Artificial")

Artigo

Campo Tipo Descrição
id AutoField (PK) Gerado automaticamente
titulo CharField Título do artigo
texto TextField Corpo do artigo em HTML (editado via TinyMCE)
id_fk_autor ForeignKey → Autor Autor do artigo
id_fk_eixo ForeignKey → EixoTecnologia Categoria/eixo do artigo
nivel CharField (choices) 'B' = Básico, 'I' = Intermédio, 'A' = Avançado
data_publicacao DateField Data de publicação
publicada BooleanField True = visível no site, False = rascunho
foto ImageField Imagem de capa do artigo (opcional)

get_nivel_display() — método automático do Django para campos com choices. Retorna o texto legível ("Básico") a partir do valor interno ("B").


6. URLs

URLconf raiz — cursoDjango/urls.py

path('', include('motorartigos.urls')),
path('tinymce/', include('tinymce.urls')),

URLs do app — motorartigos/urls.py

urlpatterns = [
    path('',        index,  name='index'),   # página inicial
    path('artigo/', artigo, name='artigo'),  # detalhe do artigo (hardcoded)
    path('busca/',  busca,  name='busca'),   # resultados de busca
]

Uso nos templates: {% url 'index' %}, {% url 'busca' %}, {% url 'artigo' %}


7. Views

Arquivo: motorartigos/views.py

index(request)

URL: /
Template: motorartigos/index.html

Consulta dois QuerySets a partir da mesma base filtrada e ordenada:

  • artigos_recentes → 4 artigos publicados mais recentes ([:4] = LIMIT 4)
  • artigos → todos os artigos publicados, ordenados por data decrescente
artigos_publicados = Artigo.objects.filter(publicada=True).order_by('-data_publicacao')
artigos_recentes   = artigos_publicados[:4]
artigos            = artigos_publicados

Contexto passado ao template:

{'artigos_recentes': artigos_recentes, 'artigos': artigos}

busca(request)

URL: /busca/?q=termo
Template: motorartigos/busca.html

Lê o parâmetro q da URL (request.GET.get('q', '')). Filtra artigos usando Q() com OR em 4 campos simultâneos:

Q(titulo__icontains=q)            |   # busca no título
Q(id_fk_autor__nome__icontains=q) |   # busca pelo nome do autor (atravessa FK)
Q(id_fk_eixo__nome__icontains=q)  |   # busca pelo nome do eixo (atravessa FK)
Q(texto__icontains=q)                  # busca no corpo do artigo

.distinct() evita duplicatas quando o termo aparece em mais de um campo do mesmo artigo.

Contexto passado ao template:

{'artigos': artigos, 'q': q, 'total': artigos.count()}

artigo(request)

URL: /artigo/
Template: motorartigos/artigo.html

⚠️ Ainda hardcoded — exibe sempre o mesmo artigo estático. Feature pendente: receber id via URL (/artigo/<int:id>/) e buscar no banco.


8. Templates

Sistema de herança

base.html
├── {% include '_header.html' %}    ← em todas as páginas
│   └── {% include '_busca.html' %} ← formulário de busca
├── {% block content %}
│   ├── index.html    extends base.html
│   ├── busca.html    extends base.html
│   └── artigo.html   extends base.html
└── {% include '_footer.html' %}    ← em todas as páginas

base.html

Layout base. Define <html>, <head>, imports de Bootstrap e CSS, inclui header e footer, e declara {% block content %} para as páginas filhas preencherem.


partials/_header.html

Incluído em todas as páginas via base.html. Contém:

  • Navbar com logo "DevBlog Django" (link para /)
  • Texto introdutório do blog
  • {% include '_busca.html' %} — formulário de busca
  • Filtros de eixo (ainda hardcoded — feature futura: dinâmico via banco)

partials/_busca.html

Formulário de busca reutilizável. Pode ser incluído em qualquer página:

{% include 'motorartigos/partials/_busca.html' %}
  • method="GET" → termo vai para a URL como ?q=
  • action="{% url 'busca' %}" → aponta para a view de resultados
  • name="q" → nome do parâmetro lido pela view
  • value="{{ q|default:'' }}" → mantém o campo preenchido na página de resultados

partials/_card_artigo.html

Card de artigo reutilizável. Deve sempre ser incluído com with:

{% include 'motorartigos/partials/_card_artigo.html' with artigo=artigo %}

Usado em:

  • index.html — seção "Mais recentes"
  • index.html — seção "Todos os Artigos"
  • busca.html — grid de resultados

Lógica do thumbnail:

  • Se artigo.foto existe → exibe <img> com class="card-thumb"
  • Se não existe → exibe <div> colorido baseado no nível:
    • Avançado → thumb-gradient (rosa/roxo/azul)
    • Intermédio → thumb-mockup (cinza escuro)
    • Básico → thumb-dashboard (verde escuro)

Lógica do badge de nível:

  • Avançado → bg-danger-custom (vermelho)
  • Intermédio → bg-warning-custom text-dark (amarelo)
  • Básico → bg-success-custom (verde)

index.html

Estende base.html. Duas seções:

"Mais recentes" — itera sobre artigos_recentes (4 itens):

{% for artigo in artigos_recentes %}
    {% include '.../_card_artigo.html' with artigo=artigo %}
{% endfor %}

Layout: flex-nowrap (linha horizontal — visual de carrossel).

"Todos os Artigos" — itera sobre artigos (todos publicados):

{% for artigo in artigos %}
    {% include '.../_card_artigo.html' with artigo=artigo %}
{% endfor %}

Layout: row g-4 (grade que quebra em múltiplas linhas).


busca.html

Estende base.html. Exibe:

  • Cabeçalho com contagem: "X artigos encontrados para 'termo'"
  • Badge com o termo buscado (classe .badge-termo)
  • Estado vazio com sugestões quando nenhum resultado é encontrado
  • Grid de cards usando _card_artigo.html

artigo.html

Estende base.html.

⚠️ Conteúdo ainda completamente hardcoded. Feature pendente.


9. CSS — classes do tema

Arquivo: motorartigos/static/css/styles.css Bootstrap 5.3.2 via CDN + classes customizadas para o tema escuro.

Cores base

Variável visual Hex Uso
Fundo principal #0b0f19 body, bg-dark-custom
Fundo cards #111c30 .card-custom
Fundo inputs #111827 .search-box, .btn-filter
Bordas #1f2937 .search-box, .btn-filter
Azul destaque #3b82f6 .btn-filter.active, .btn-busca
Texto secundário #9ca3af .text-secondary

Classes de componente

Classe Descrição
.card-custom Card com fundo #111c30, border-radius 12px e hover com translateY
.card-thumb Padroniza altura (180px) e object-fit: cover dos thumbnails
.thumb-gradient Thumbnail colorido — gradiente rosa/roxo/azul (Avançado)
.thumb-mockup Thumbnail colorido — gradiente cinza escuro (Intermédio)
.thumb-dashboard Thumbnail colorido — gradiente verde escuro (Básico)
.btn-filter Botão de filtro de eixo (pill arredondado, tema escuro)
.btn-filter.active Filtro selecionado (fundo azul)
.btn-busca Botão submit do formulário de busca
.btn-nav Botões ‹ › do carrossel (circular, sem preenchimento)
.search-box Container do campo de busca (fundo escuro, borda sutil)
.badge-status Badge da navbar ("Mapeamento de Modelos Django Ativo")
.badge-termo Badge que exibe o termo buscado na página de resultados
.estado-vazio Container de mensagem quando busca não retorna resultados
.avatar Círculo com inicial do autor (24×24px, roxo #6366f1)
.bg-danger-custom Badge vermelho para nível Avançado
.bg-warning-custom Badge amarelo para nível Intermédio
.bg-success-custom Badge verde para nível Básico
.text-primary-custom Azul claro #60a5fa para "Ler mais →"
.rodape Estilo do <footer>

10. Admin

Arquivo: motorartigos/admin.py

AutorAdmin

list_display  = ('id', 'nome', 'email')
search_fields = ('nome', 'email')

EixoTecnologiaAdmin

list_display  = ('id', 'nome')
search_fields = ('nome',)

ArtigoAdmin

list_display       = ('id', 'titulo', 'id_fk_autor', 'id_fk_eixo',
                       'nivel', 'resumo_texto', 'data_publicacao')
search_fields      = ('titulo', 'id_fk_autor__nome', 'texto')
list_filter        = ('id_fk_eixo', 'nivel', 'data_publicacao')
list_display_links = ('id', 'titulo')
formfield_overrides = {
    db_models.TextField: {'widget': TinyMCE()},
}

resumo_texto() — método customizado de exibição. Usa BeautifulSoup para extrair texto puro do HTML do campo texto, exibe os primeiros 80 caracteres seguidos de ... se o texto for mais longo.

TinyMCE — substitui todos os campos TextField no formulário de edição por um editor visual WYSIWYG (rich text), gerando HTML salvo no banco.


11. Features implementadas

✅ Estrutura base do projeto

  • App motorartigos criado e registrado em INSTALLED_APPS
  • base.html com sistema de herança de templates ({% extends %}, {% block %})
  • Partials separados: _header.html, _footer.html
  • Bootstrap 5.3.2 via CDN
  • CSS tema escuro (styles.css)

✅ Models e Admin

  • Models Autor, EixoTecnologia, Artigo criados e com migrations aplicadas
  • Admin configurado com list_display, search_fields, list_filter
  • TinyMCE integrado como widget para campos TextField no admin
  • BeautifulSoup usado no admin para preview de texto limpo (sem tags HTML)

✅ Página inicial (index.html)

  • Seção "Mais recentes" — 4 artigos mais recentes do banco (antes hardcoded)
  • Seção "Todos os Artigos" — todos os artigos com publicada=True
  • Ambas as seções usam o partial _card_artigo.html

✅ Card de artigo padronizado (_card_artigo.html)

  • Partial reutilizável incluído com {% include ... with artigo=artigo %}
  • Thumbnail com altura fixa de 180px via .card-thumb
  • Se artigo tem foto → exibe a imagem real com object-fit: cover
  • Se não tem foto → exibe div colorido pelo nível (Avançado/Intermédio/Básico)
  • Badge de nível com cor dinâmica (bg-danger-custom, bg-warning-custom, bg-success-custom)
  • Avatar com inicial do nome do autor (|slice:":1"|upper)

✅ Busca (_busca.html + view busca + busca.html)

Formulário (_busca.html):

  • <form method="GET" action="{% url 'busca' %}">
  • <input name="q"> com value="{{ q|default:'' }}" para manter o termo após busca
  • type="search" com botão submit .btn-busca

View (busca):

  • request.GET.get('q', '').strip()
  • Filtra com Q() em 4 campos: titulo, autor__nome, eixo__nome, texto
  • .filter(publicada=True).distinct() garante só artigos publicados, sem duplicatas
  • Passa q, artigos e total para o template

Página de resultados (busca.html):

  • Exibe contagem: "X artigos encontrados"
  • Badge com o termo buscado (.badge-termo)
  • Estado vazio com sugestões quando não encontra nada
  • Grid de cards usando o mesmo _card_artigo.html

12. Features pendentes / próximos passos

Feature Descrição Complexidade
🔲 Detalhe do artigo artigo.html dinâmico recebendo id via URL Baixa
🔲 Filtros de eixo dinâmicos Eixos no _header.html vindo do banco Baixa
🔲 Iniciais duplas no avatar Custom template tag para "AC" de "Ana Costa" Baixa
🔲 Carrossel funcional JavaScript nos botões ‹ › da seção "Mais recentes" Média
🔲 Busca em tempo real Live search com HTMX ou JavaScript Média
🔲 Paginação Dividir "Todos os Artigos" em páginas Média
🔲 Filtro combinado Eixo + busca ao mesmo tempo na mesma URL Média
🔲 Página de autor Listar artigos de um autor específico Média

13. Como rodar o projeto

Pré-requisitos

  • Python 3.10+
  • XAMPP com MariaDB 10.5+ rodando
  • Banco djangoartigos criado no MySQL

Passo a passo

# 1. Clonar o repositório
git clone https://github.com/SEU_USUARIO/cursoDjango.git
cd cursoDjango

# 2. Criar e ativar o ambiente virtual
python -m venv .venv

# Windows PowerShell:
.venv\Scripts\Activate.ps1

# Linux/macOS:
source .venv/bin/activate

# 3. Instalar dependências
pip install -r requirements.txt

# 4. Configurar variáveis de ambiente
# Copie .env.example para .env e preencha os valores:
# SECRET_KEY=sua-chave-aqui
# DB_NAME=djangoartigos
# DB_USER=root
# DB_PASSWORD=

# 5. Aplicar migrations
python manage.py migrate

# 6. Criar superusuário para o admin
python manage.py createsuperuser

# 7. Rodar o servidor
python manage.py runserver

Acesse:


14. Decisões de arquitetura registradas

Por que Django 4.2.x e não 5.x?

O ambiente de desenvolvimento usa XAMPP com MariaDB. A versão disponível na máquina era 10.4.x. Django 5.x exige MariaDB 10.5+. Após atualizar o XAMPP para MariaDB 10.6, o Django 5.x passou a funcionar — mas mantivemos o 4.2.x por ser a versão LTS e mais estável para o curso.


Por que method="GET" na busca e não POST?

Com GET, o termo buscado vai para a URL (/busca/?q=python). Isso permite: compartilhar o link dos resultados, usar o botão "voltar" do navegador, fazer bookmark da busca. Com POST, a URL não muda e o usuário perde o resultado ao recarregar a página.


Por que _card_artigo.html como partial em vez de repetir o HTML?

Princípio DRY (Don't Repeat Yourself). O card é usado em 3 lugares: "Mais recentes", "Todos os Artigos" e "Busca". Com um partial único, qualquer mudança visual no card é feita em um arquivo e reflete nos três lugares automaticamente.


Por que {% include ... with artigo=artigo %} e não só {% include %}?

Dentro de um {% for artigo in artigos %}, a variável artigo existe no contexto da página mãe. Mas {% include %} cria um contexto próprio para o partial. Sem with artigo=artigo, o partial não recebe a variável e renderiza vazio. O with é a forma explícita e segura de passar dados para partials.


Por que .distinct() na query de busca?

Se um artigo contém o termo em mais de um campo (ex: a palavra "python" aparece no título E no texto), o filter(Q(...) | Q(...)) pode retornar o mesmo artigo mais de uma vez. .distinct() garante unicidade.


15. Erros conhecidos e soluções

ModuleNotFoundError: No module named 'bs4'

Causa: beautifulsoup4 não instalado no venv. Solução: pip install beautifulsoup4


NotSupportedError: MariaDB 10.5 or later is required

Causa: Django 5.x/4.2.x com MariaDB 10.4.x no XAMPP. Solução: Atualizar o XAMPP para versão com MariaDB 10.5+.


InnoDB: Upgrade after a crash is not supported

Causa: A pasta data/ do MariaDB foi de uma versão para outra sem shutdown limpo. O redo log estava corrompido. Solução: Recriar o banco do zero — fazer backup da pasta C:\xampp\mysql\data, usar a pasta backup/ limpa do XAMPP, recriar o banco djangoartigos e rodar python manage.py migrate.


Table 'djangoartigos.django_migrations' doesn't exist in engine

Causa: O banco existia nos metadados mas os arquivos .ibd do InnoDB estavam ausentes ou corrompidos após reinstalação do MariaDB. Solução: Dropar e recriar o banco:

DROP DATABASE djangoartigos;
CREATE DATABASE djangoartigos CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Depois: python manage.py migrate


Campo de busca não funcionava (apenas visual)

Causa: O <input> estava solto no HTML, sem <form>, sem name, sem action. O formulário não enviava dados para nenhuma view. Solução: Envolver em <form method="GET" action="{% url 'busca' %}">, adicionar name="q" ao input e criar a view + URL de busca.

About

Um projeto Django do curso do SENAI/DF implementado com Python, Django, SQLite e Machine Learning

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages