Destaques do Django: modelos, administração e aproveitamento do banco de dados relacional (Parte 3)
Publicados: 2022-03-10Antes de começarmos, quero observar que os recursos administrativos internos do Django, mesmo após a personalização, não são destinados aos usuários finais. O painel de administração existe como uma ferramenta de desenvolvedor, operador e administrador para criar e manter software. Não se destina a ser usado para fornecer recursos de moderação aos usuários finais ou quaisquer outras habilidades de administrador na plataforma que você desenvolve.
Este artigo é baseado em uma hipótese em duas partes:
- O painel de administração do Django é tão intuitivo que você basicamente já sabe como usá-lo.
- O painel de administração do Django é tão poderoso que podemos usá-lo como uma ferramenta para aprender a representar dados em um banco de dados relacional usando um modelo Django.
Eu ofereço essas ideias com a ressalva de que ainda precisaremos escrever algum código de configuração para ativar as habilidades mais poderosas do painel de administração, e ainda precisaremos usar o ORM (mapeamento relacional de objetos) baseado em modelos do Django para especificar a representação de dados em nosso sistema.
Leitura recomendada
“Django Highlights” é uma série que apresenta conceitos importantes de desenvolvimento web em Django. Você pode querer ler sobre como fornecer fluxos de autenticação de usuário seguros e acompanhar uma demonstração sobre o uso de modelos do Django para escrever páginas complexas.
Configurando
Vamos trabalhar com um projeto de amostra neste artigo. O projeto modela alguns dados que uma biblioteca armazenaria sobre seus livros e usuários. O exemplo deve ser bastante aplicável a muitos tipos de sistemas que gerenciam usuários e/ou inventário. Aqui está uma prévia de como são os dados:
Conclua as etapas a seguir para obter o código de exemplo em execução em sua máquina local.
1. Instalando Pacotes
Com o Python 3.6 ou superior instalado, crie um diretório e um ambiente virtual. Em seguida, instale os seguintes pacotes:
pip install django django-grappelli
Django é o framework web com o qual estamos trabalhando neste artigo. ( django-grappelli
é um tema do painel de administração que abordaremos brevemente.)
2. Obtendo o Projeto
Com os pacotes anteriores instalados, baixe o código de exemplo do GitHub. Corre:
git clone https://github.com/philipkiely/library_records.git cd library_records/library
3. Criando um superusuário
Usando os comandos a seguir, configure seu banco de dados e crie um superusuário. A interface de linha de comando o guiará pelo processo de criação de um superusuário. Sua conta de superusuário será como você acessa o painel de administração em um momento, então lembre-se da senha que você definiu. Usar:
python manage.py migrate python manage.py createsuperuser
4. Carregando os dados
Para nossa exploração, criei um conjunto de dados chamado fixture que você pode carregar no banco de dados (mais sobre como criar um fixture no final do artigo). Use o fixture para preencher seu banco de dados antes de explorá-lo no painel de administração. Corre:
python manage.py loaddata ../fixture.json
5. Executando o Projeto de Exemplo
Finalmente, você está pronto para executar o código de exemplo. Para executar o servidor, use o seguinte comando:
python manage.py runserver
Abra seu navegador em https://127.0.0.1:8000 para visualizar o projeto. Observe que você é redirecionado automaticamente para o painel de administração em /admin/ . Eu consegui isso com a seguinte configuração em library/urls.py :
from django.contrib import admin from django.urls import path from records import views urlpatterns = [ path('admin/', admin.site.urls), path('', views.index), ]
combinado com o seguinte redirecionamento simples em records/views.py :
from django.http import HttpResponseRedirect def index(request): return HttpResponseRedirect('/admin/')
Usando o painel de administração
Nós conseguimos! Ao carregar sua página, você deverá ver algo como o seguinte:
Essa visualização é realizada com o seguinte código clichê em records/admin.py :
from django.contrib import admin from .models import Book, Patron, Copy admin.site.register(Book) admin.site.register(Copy) admin.site.register(Patron)
Essa exibição deve fornecer uma compreensão inicial dos dados que o sistema armazena. Vou remover um pouco do mistério: Groups
e Users
são definidos pelo Django e armazenam informações e permissões para contas no sistema. Você pode ler mais sobre o modelo de User
em um artigo anterior desta série. Books
, Copys
e Patrons
são tabelas no banco de dados que criamos ao executar migrações e preenchidas ao carregar o equipamento. Observe que o Django ingenuamente pluraliza nomes de modelos anexando um “s”, mesmo em casos como “cópias” onde a ortografia está incorreta.
Em nosso projeto, um Book
é um registro com título, autor, data de publicação e ISBN (International Standard Book Number). A biblioteca mantém uma Copy
de cada Book
, ou possivelmente vários. Cada Copy
pode ser retirada por um Patron
, ou pode ser verificada atualmente. Um Patron
é uma extensão do User
que registra seu endereço e data de nascimento.
Criar, Ler, Atualizar, Destruir
Um recurso padrão do painel de administração é adicionar instâncias de cada modelo. Clique em “livros” para acessar a página do modelo e clique no botão “Adicionar livro” no canto superior direito. Fazê-lo vai abrir um formulário, que você pode preencher e salvar para criar um livro.
A criação de um Patron
revela outro recurso interno do formulário de criação do administrador: você pode criar o modelo conectado diretamente do mesmo formulário. A captura de tela abaixo mostra o pop-up que é acionado pelo sinal de mais verde à direita do menu suspenso User
. Assim, você pode criar os dois modelos na mesma página de administração.
Você pode criar uma COPY
através do mesmo mecanismo.
Para cada registro, você pode clicar na linha para editá-la usando o mesmo formulário. Você também pode excluir registros usando uma ação de administrador.
Ações do administrador
Embora os recursos integrados do painel de administração sejam amplamente úteis, você pode criar suas próprias ferramentas usando ações de administrador. Criaremos dois: um para criar cópias de livros e outro para fazer check-in de livros que foram devolvidos à biblioteca.
Para criar uma Copy
de um Book
, acesse a URL /admin/records/book/
e use o menu suspenso "Ação" para selecionar "Adicionar uma cópia do(s) livro(s)" e, em seguida, use as caixas de seleção na coluna da esquerda da tabela para selecionar de qual livro ou livros adicionar uma cópia ao inventário.
Criar isso depende de um método de modelo que abordaremos mais tarde. Podemos chamá-lo como uma ação de administrador criando uma classe ModelAdmin
para o modelo Profile
da seguinte forma em records/admin.py :
from django.contrib import admin from .models import Book, Patron, Copy class BookAdmin(admin.ModelAdmin): list_display = ("title", "author", "published") actions = ["make_copys"] def make_copys(self, request, queryset): for q in queryset: q.make_copy() self.message_user(request, "copy(s) created") make_copys.short_description = "Add a copy of book(s)" admin.site.register(Book, BookAdmin)
A propriedade list_display
denota quais campos são usados para representar o modelo na página de visão geral do modelo. A propriedade actions
lista as ações do administrador. Nossa ação admin é definida como uma função dentro do BookAdmin
e recebe três argumentos: o próprio objeto admin, a solicitação (a solicitação HTTP real enviada pelo cliente) e o queryset (a lista de objetos cujas caixas foram marcadas). Realizamos a mesma ação em cada item no conjunto de consultas e notificamos o usuário de que as ações foram concluídas. Cada ação do administrador requer uma breve descrição para que possa ser identificada corretamente no menu suspenso. Por fim, agora adicionamos BookAdmin
ao registrar o modelo.
Escrever ações de administrador para definir propriedades em massa é bastante repetitivo. Aqui está o código para fazer o check-in de um Copy
, observe sua quase equivalência com a ação anterior.
from django.contrib import admin from .models import Book, Patron, Copy class CopyAdmin(admin.ModelAdmin): actions = ["check_in_copys"] def check_in_copys(self, request, queryset): for q in queryset: q.check_in() self.message_user(request, "copy(s) checked in") check_in_copys.short_description = "Check in copy(s)" admin.site.register(Copy, CopyAdmin)
Tema do administrador
Por padrão, o Django fornece estilos bastante simples para o painel de administração. Você pode criar seu próprio tema ou usar um tema de terceiros para dar uma nova aparência ao painel de administração. Um tema de código aberto popular é o grappelli, que instalamos anteriormente neste artigo. Você pode conferir a documentação para seus recursos completos.
A instalação do tema é bastante simples, requer apenas duas linhas. Primeiro, adicione grappelli
a INSTALLED_APPS
da seguinte forma em library/settings.py :
INSTALLED_APPS = [ 'grappelli', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'records', ]
Em seguida, ajuste library/urls.py :
from django.contrib import admin from django.urls import path, include from records import views urlpatterns = [ path('grappelli/', include('grappelli.urls')), path('admin/', admin.site.urls), path('', views.index), ]
Com essas alterações em vigor, o painel de administração deve ter a seguinte aparência:
Existem vários outros temas por aí e, novamente, você pode desenvolver o seu próprio. Eu vou ficar com a aparência padrão para o resto deste artigo.
Entendendo os Modelos
Agora que você está confortável com o painel de administração e usando-o para navegar pelos dados, vamos dar uma olhada nos modelos que definem nossa estrutura de banco de dados. Cada modelo representa uma tabela em um banco de dados relacional.
Um banco de dados relacional armazena dados em uma ou mais tabelas. Cada uma dessas tabelas tem uma estrutura de coluna especificada, incluindo uma chave primária (um identificador exclusivo para cada elemento) e uma ou mais colunas de valores, que são de vários tipos, como strings, inteiros e datas. Cada objeto armazenado no banco de dados é representado como uma única linha. A parte “relacional” do nome vem do que é sem dúvida o recurso mais importante da tecnologia: criar relacionamentos entre tabelas. Um objeto (linha) pode ter um mapeamento um para um, um para muitos (chave estrangeira) ou muitos para muitos para linhas em outras tabelas. Discutiremos isso mais adiante nos exemplos.
O Django, por padrão, usa SQLite3 para desenvolvimento. SQLite3 é um mecanismo de banco de dados relacional simples e seu banco de dados é criado automaticamente como db.sqlite3 na primeira vez que você executa python manage.py migrate
. Continuaremos com o SQLite3 para este artigo, mas ele não é adequado para uso em produção, principalmente porque as substituições são possíveis com usuários simultâneos. Na produção, ou ao escrever um sistema que você pretende implantar um dia, use PostgreSQL ou MySQL.
O Django usa modelos para fazer interface com o banco de dados. Usando parte do ORM do Django, o arquivo records/models.py inclui vários modelos, que permitem especificar campos, propriedades e métodos para cada objeto. Ao criar modelos, buscamos uma arquitetura “Fat Model”, dentro do razoável. Isso significa que o máximo possível de validação de dados, análise, processamento, lógica de negócios, tratamento de exceções, resolução de casos extremos e tarefas semelhantes devem ser tratados na especificação do próprio modelo. Sob o capô, os modelos do Django são objetos muito complexos e cheios de recursos com comportamento padrão amplamente útil. Isso torna a arquitetura “Fat Model” fácil de alcançar, mesmo sem escrever uma quantidade substancial de código.
Vamos percorrer os três modelos em nosso aplicativo de exemplo. Não podemos cobrir tudo, pois este deveria ser um artigo introdutório, não a documentação completa do framework Django, mas vou destacar as escolhas mais importantes que fiz na construção desses modelos simples.
A classe Book
é a mais direta dos modelos. Aqui está de records/models.py :
from django.db import models class Book(models.Model): title = models.CharField(max_length=300) author = models.CharField(max_length=150) published = models.DateField() isbn = models.IntegerField(unique=True) def __str__(self): return self.title + " by " + self.author def make_copy(self): Copy.objects.create(book=self)
Todos os campos CharField
requerem um atributo max_length
especificado. O comprimento convencional é de 150 caracteres, que dobrei para title
no caso de títulos muito longos. Claro, ainda existe um limite arbitrário, que pode ser ultrapassado. Para comprimento de texto ilimitado, use um TextField
. O campo published
é um DateField
. A hora em que o livro foi publicado não importa, mas se tivesse eu usaria um DateTimeField
. Finalmente, o ISBN é um inteiro (ISBNs são 10 ou 13 dígitos e, portanto, todos se encaixam no valor máximo do inteiro) e usamos unique=True
, pois dois livros não podem ter o mesmo ISBN, que é então aplicado no nível do banco de dados.
Todos os objetos possuem um método __str__(self)
que define sua representação em string. Substituímos a implementação padrão fornecida pela classe models.Model
e, em vez disso, representamos livros como “título por autor” em todos os lugares onde o modelo seria representado como uma string. Lembre-se de que anteriormente usamos list_display
no objeto admin de Book
para determinar quais campos seriam mostrados na lista do painel de administração. Se esse list_display
não estiver presente, a lista de administradores mostrará a representação de string do modelo, como faz para Patron
e Copy
.
Finalmente, temos um método em Book
que chamamos em sua ação admin que escrevemos anteriormente. Esta função cria uma Copy
que está relacionada a uma determinada instância de um Book
no banco de dados.
Passando para Patron
, este modelo introduz o conceito de um relacionamento um para um, neste caso com o modelo User
interno. Confira em records/models.py :
from django.db import models from django.contrib.auth.models import User class Patron(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) address = models.CharField(max_length=150) dob = models.DateField() def __str__(self): return self.user.username
O campo de user
não é exatamente uma função bijetiva. PODE haver uma instância User
sem uma instância Patron
associada. No entanto, um User
NÃO PODE ser associado a mais de uma instância de Patron
, e um Patron
não pode existir sem exatamente uma relação com um usuário. Isso é aplicado no nível do banco de dados e é garantido pela especificação on_delete=models.CASCADE
: se uma instância de User
for excluída, um Profile
associado será excluído.
Os outros campos e __str__(self)
que vimos antes. Vale a pena notar que você pode alcançar através de uma relação um-para-um para obter atributos, neste caso user.username
, nas funções de um modelo.
Para expandir a utilidade das relações de banco de dados, vamos voltar nossa atenção para Copy
from records/models.py :
from django.db import models class Copy(models.Model): book = models.ForeignKey(Book, on_delete=models.CASCADE) out_to = models.ForeignKey(Patron, blank=True, null=True, on_delete=models.SET_NULL) def __str__(self): has_copy = "checked in" if self.out_to: has_copy = self.out_to.user.username return self.book.title + " -> " + has_copy def check_out(self, p): self.out_to = p self.save() def check_in(self): self.out_to = None self.save()
Novamente, já vimos a maior parte disso antes, então vamos nos concentrar nas novidades: models.ForeignKey
. Uma Copy
deve ser de um único Book
, mas a biblioteca pode ter várias Copy
de cada Book
. Um Book
pode existir no banco de dados sem que a biblioteca tenha uma Copy
em seu catálogo, mas uma Copy
não pode existir sem um Book
subjacente.
Essa relação complexa é expressa com a seguinte linha:
book = models.ForeignKey(Book, on_delete=models.CASCADE)
O comportamento de exclusão é o mesmo do Patron
em referência ao User
.
A relação entre um Copy
e um Patron
é um pouco diferente. Uma Copy
pode ser retirada para até um Patron
s, mas cada Patron
pode retirar quantas Copy
s a biblioteca permitir. No entanto, este não é um relacionamento permanente, a Copy
às vezes não é verificada. Patron
s e Copy
s existem independentemente um do outro no banco de dados; excluir uma instância de um não deve excluir nenhuma instância do outro.
Esse relacionamento ainda é um caso de uso para a chave estrangeira, mas com argumentos diferentes:
out_to = models.ForeignKey(Patron, blank=True, null=True, on_delete=models.SET_NULL)
Aqui, ter blank=True
permite que os formulários aceitem None
como o valor para a relação e null=True
permite que a coluna da relação Patron
na tabela de Copy
no banco de dados aceite null
como um valor. O comportamento de exclusão, que seria acionado em uma Copy
se uma instância Patron
fosse excluída enquanto a Copy
estava em check-out, é cortar a relação deixando a Copy
intacta definindo o campo Patron
como nulo.
O mesmo tipo de campo, models.ForeignKey
, pode expressar relacionamentos muito diferentes entre objetos. A única relação que não consegui encaixar claramente no exemplo é um campo muitos-para-muitos, que é como um campo um-para-um, exceto que, como sugerido pelo nome, cada instância pode ser relacionada a muitas outras instâncias e todos os outros e cada um deles pode ser relacionado a muitos outros, como como um livro pode ter vários autores, cada um dos quais escreveu vários livros.
Migrações
Você pode estar se perguntando como o banco de dados sabe o que é expresso no modelo. Na minha experiência, as migrações são uma daquelas coisas que são bastante diretas até que não sejam, e então comem seu rosto. Veja como manter sua caneca intacta, para iniciantes: aprenda sobre migrações e como interagir com elas, mas tente evitar fazer edições manuais nos arquivos de migração. Se você já sabe o que está fazendo, pule esta seção e mantenha o que funciona para você.
De qualquer forma, confira a documentação oficial para um tratamento completo do assunto.
As migrações convertem alterações em um modelo em alterações no esquema do banco de dados. Você não precisa escrevê-los você mesmo, o Django os cria com o comando python manage.py makemigrations
. Você deve executar este comando ao criar um novo modelo ou editar os campos de um modelo existente, mas não há necessidade de fazê-lo ao criar ou editar métodos de modelo. É importante observar que as migrações existem como uma cadeia, cada uma referencia a anterior para que possa fazer edições sem erros no esquema do banco de dados. Assim, se você estiver colaborando em um projeto, é importante manter um único histórico de migração consistente no controle de versão. Quando houver migrações não aplicadas, execute python manage.py migrate
para aplicá-las antes de executar o servidor.
O projeto de exemplo é distribuído com uma única migração, records/migrations/0001_initial.py . Novamente, este é um código gerado automaticamente que você não deveria ter que editar, então não vou copiá-lo aqui, mas se você quiser ter uma noção do que está acontecendo nos bastidores, vá em frente e dê uma olhada nele.
Luminárias
Ao contrário das migrações, os fixtures não são um aspecto comum do desenvolvimento do Django. Eu os uso para distribuir dados de amostra com artigos e nunca os usei de outra forma. No entanto, como usamos um anteriormente, sinto-me compelido a introduzir o tópico.
Pela primeira vez, a documentação oficial é um pouco escassa sobre o assunto. No geral, o que você deve saber é que os fixtures são uma forma de importar e exportar dados do seu banco de dados em vários formatos, incluindo JSON, que é o que eu uso. Esse recurso existe principalmente para ajudar em coisas como testes automatizados e não é um sistema de backup ou uma maneira de editar dados em um banco de dados ativo. Além disso, os fixtures não são atualizados com as migrações e, se você tentar aplicar um fixture a um banco de dados com um esquema incompatível, ele falhará.
Para gerar um fixture para todo o banco de dados, execute:
python manage.py dumpdata --format json > fixture.json
Para carregar um fixture, execute:
python manage.py loaddata fixture.json
Conclusão
Escrever modelos no Django é um tópico enorme, e usar o painel de administração é outro. Em 3.000 palavras, só consegui apresentar cada uma. Felizmente, o uso do painel de administração forneceu uma interface melhor para explorar como os modelos funcionam e se relacionam entre si, deixando você com a confiança de experimentar e desenvolver suas próprias representações relacionais de dados.
Se você está procurando um lugar fácil para começar, tente adicionar um modelo Librarian
que herda de User
como o Profile
faz. Para um desafio maior, tente implementar um histórico de checkout para cada Copy
e/ou Patron
(há várias maneiras de fazer isso).
Django Highlights é uma série que apresenta conceitos importantes de desenvolvimento web em Django. Cada artigo é escrito como um guia independente para uma faceta do desenvolvimento do Django destinada a ajudar desenvolvedores e designers de front-end a alcançar uma compreensão mais profunda da “outra metade” da base de código. Esses artigos são construídos principalmente para ajudá-lo a entender a teoria e a convenção, mas contêm alguns exemplos de código escritos em Django 3.0.
Leitura adicional
Você pode estar interessado nos seguintes artigos e documentação.
- Destaques do Django: Modelos de usuário e autenticação (Parte 1)
- Destaques do Django: A modelagem salva as linhas (Parte 2)
- Destaques do Django: disputando ativos estáticos e arquivos de mídia (Parte 4)
- Documentação de administração do Django
- Modelos Django
- Referência de campo do modelo Django
- Migrações do Django