Este é o segundo artigo da nossa série de migrações do Django:
- Parte 1:Migrações do Django:uma introdução
- Parte 2:Aprofundando nas migrações do Django (artigo atual)
- Parte 3:Migrações de dados
- Vídeo:Migrações do Django 1.7 - Uma introdução
No artigo anterior desta série, você aprendeu sobre o propósito das migrações do Django. Você se familiarizou com padrões de uso fundamentais, como criar e aplicar migrações. Agora é hora de aprofundar o sistema de migração e dar uma olhada em algumas de suas mecânicas subjacentes.
Ao final deste artigo, você saberá:
- Como o Django acompanha as migrações
- Como as migrações sabem quais operações de banco de dados devem ser executadas
- Como as dependências entre migrações são definidas
Depois de entender essa parte do sistema de migração do Django, você estará bem preparado para criar suas próprias migrações personalizadas. Vamos pular de onde paramos!
Este artigo usa o
bitcoin_tracker
Projeto Django construído em Django Migrations:A Primer. Você pode recriar esse projeto trabalhando com esse artigo ou baixar o código-fonte:Fazer download do código-fonte: Clique aqui para baixar o código do projeto de migração do Django que você usará neste artigo.
Como o Django sabe quais migrações aplicar
Vamos recapitular a última etapa do artigo anterior da série. Você criou uma migração e aplicou todas as migrações disponíveis com
python manage.py migrate
.Se esse comando foi executado com sucesso, suas tabelas de banco de dados agora correspondem às definições de seu modelo. O que acontece se você executar esse comando novamente? Vamos experimentar:
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
No migrations to apply.
Nada aconteceu! Depois que uma migração for aplicada a um banco de dados, o Django não aplicará essa migração a esse banco de dados específico novamente. Garantir que uma migração seja aplicada apenas uma vez requer o acompanhamento das migrações que foram aplicadas.
Django usa uma tabela de banco de dados chamada
django_migrations
. O Django cria automaticamente esta tabela em seu banco de dados na primeira vez que você aplica qualquer migração. Para cada migração aplicada ou falsificada, uma nova linha é inserida na tabela. Por exemplo, veja como esta tabela se parece em nosso
bitcoin_tracker
projeto:ID | Aplicativo | Nome | Aplicado |
---|---|---|---|
1 | contenttypes | 0001_initial | 2019-02-05 20:23:21.461496 |
2 | auth | 0001_initial | 2019-02-05 20:23:21.489948 |
3 | admin | 0001_initial | 2019-02-05 20:23:21.508742 |
4 | admin | 0002_logentry_remove... | 2019-02-05 20:23:21.531390 |
5 | admin | 0003_logentry_add_ac... | 2019-02-05 20:23:21.564834 |
6 | contenttypes | 0002_remove_content_... | 2019-02-05 20:23:21.597186 |
7 | auth | 0002_alter_permissio... | 2019-02-05 20:23:21.608705 |
8 | auth | 0003_alter_user_emai... | 2019-02-05 20:23:21.628441 |
9 | auth | 0004_alter_user_user... | 2019-02-05 20:23:21.646824 |
10 | auth | 0005_alter_user_last... | 2019-02-05 20:23:21.661182 |
11 | auth | 0006_require_content... | 2019-02-05 20:23:21.663664 |
12 | auth | 0007_alter_validator... | 2019-02-05 20:23:21.679482 |
13 | auth | 0008_alter_user_user... | 2019-02-05 20:23:21.699201 |
14 | auth | 0009_alter_user_last... | 2019-02-05 20:23:21.718652 |
15 | historical_data | 0001_initial | 2019-02-05 20:23:21.726000 |
16 | sessions | 0001_initial | 2019-02-05 20:23:21.734611 |
19 | historical_data | 0002_switch_to_decimals | 2019-02-05 20:30:11.337894 |
Como você pode ver, há uma entrada para cada migração aplicada. A tabela não contém apenas as migrações de nossos
historical_data
app, mas também as migrações de todos os outros aplicativos instalados. Na próxima vez que as migrações forem executadas, o Django ignorará as migrações listadas na tabela do banco de dados. Isso significa que, mesmo que você altere manualmente o arquivo de uma migração que já foi aplicada, o Django irá ignorar essas alterações, desde que já exista uma entrada para ela no banco de dados.
Você pode enganar o Django para executar novamente uma migração excluindo a linha correspondente da tabela, mas isso raramente é uma boa ideia e pode deixar você com um sistema de migração quebrado.
O arquivo de migração
O que acontece quando você executa
python manage.py makemigrations <appname>
? O Django procura por alterações feitas nos modelos em seu aplicativo <appname>
. Se encontrar algum, como um modelo que foi adicionado, ele cria um arquivo de migração em migrations
subdiretório. Este arquivo de migração contém uma lista de operações para sincronizar seu esquema de banco de dados com sua definição de modelo. Observação: Seu aplicativo deve estar listado em
INSTALLED_APPS
configuração e deve conter um migrations
diretório com um __init__.py
Arquivo. Caso contrário, o Django não criará nenhuma migração para ele. As
migrations
diretório é criado automaticamente quando você cria um novo aplicativo com o startapp
comando de gerenciamento, mas é fácil esquecer ao criar um aplicativo manualmente. Os arquivos de migração são apenas Python, então vamos dar uma olhada no primeiro arquivo de migração em
historical_prices
aplicativo. Você pode encontrá-lo em historical_prices/migrations/0001_initial.py
. Deve ser algo assim:from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = []
operations = [
migrations.CreateModel(
name='PriceHistory',
fields=[
('id', models.AutoField(
verbose_name='ID',
serialize=False,
primary_key=True,
auto_created=True)),
('date', models.DateTimeField(auto_now_add=True)),
('price', models.DecimalField(decimal_places=2, max_digits=5)),
('volume', models.PositiveIntegerField()),
('total_btc', models.PositiveIntegerField()),
],
options={
},
bases=(models.Model,),
),
]
Como você pode ver, ele contém uma única classe chamada
Migration
que herda de django.db.migrations.Migration
. Essa é a classe que a estrutura de migração procurará e executará quando você solicitar a aplicação de migrações. A
Migration
classe contém duas listas principais:dependencies
operations
Operações de migração
Vejamos as
operations
lista primeiro. Esta tabela contém as operações que devem ser executadas como parte da migração. As operações são subclasses da classe django.db.migrations.operations.base.Operation
. Aqui estão as operações comuns que são construídas no Django:Classe de operação | Descrição |
---|---|
CreateModel | Cria um novo modelo e a tabela de banco de dados correspondente |
DeleteModel | Exclui um modelo e descarta sua tabela de banco de dados |
RenameModel | Renomeia um modelo e renomeia sua tabela de banco de dados |
AlterModelTable | Renomeia a tabela de banco de dados para um modelo |
AlterUniqueTogether | Altera as restrições exclusivas de um modelo |
AlterIndexTogether | Altera os índices de um modelo |
AlterOrderWithRespectTo | Cria ou exclui o _order coluna para um modelo |
AlterModelOptions | Altera várias opções de modelo sem afetar o banco de dados |
AlterModelManagers | Altera os gerenciadores disponíveis durante as migrações |
AddField | Adiciona um campo a um modelo e a coluna correspondente no banco de dados |
RemoveField | Remove um campo de um modelo e remove a coluna correspondente do banco de dados |
AlterField | Altera a definição de um campo e altera sua coluna do banco de dados, se necessário |
RenameField | Renomeia um campo e, se necessário, também sua coluna do banco de dados |
AddIndex | Cria um índice na tabela de banco de dados para o modelo |
RemoveIndex | Remove um índice da tabela de banco de dados para o modelo |
Observe como as operações são nomeadas após as alterações feitas nas definições do modelo, não nas ações executadas no banco de dados. Ao aplicar uma migração, cada operação é responsável por gerar as instruções SQL necessárias para seu banco de dados específico. Por exemplo,
CreateModel
geraria um CREATE TABLE
instrução SQL. Fora da caixa, as migrações têm suporte para todos os bancos de dados padrão que o Django suporta. Portanto, se você seguir as operações listadas aqui, poderá fazer mais ou menos as alterações desejadas em seus modelos, sem ter que se preocupar com o SQL subjacente. Isso tudo é feito para você.
Observação: Em alguns casos, o Django pode não detectar corretamente suas alterações. Se você renomear um modelo e alterar vários de seus campos, o Django pode confundir isso com um novo modelo.
Em vez de um
RenameModel
e vários AlterField
operações, ele criará um DeleteModel
e um CreateModel
Operação. Em vez de renomear a tabela de banco de dados para o modelo, ele a descartará e criará uma nova tabela com o novo nome, excluindo efetivamente todos os seus dados! Crie o hábito de verificar as migrações geradas e testá-las em uma cópia do seu banco de dados antes de executá-las nos dados de produção.
O Django fornece mais três classes de operação para casos de uso avançados:
RunSQL
permite executar SQL personalizado no banco de dados.RunPython
permite que você execute qualquer código Python.SeparateDatabaseAndState
é uma operação especializada para usos avançados.
Com essas operações, você basicamente pode fazer as alterações que desejar em seu banco de dados. No entanto, você não encontrará essas operações em uma migração que foi criada automaticamente com o
makemigrations
comando de gestão. Desde o Django 2.0, há também algumas operações específicas do PostgreSQL disponíveis em
django.contrib.postgres.operations
que você pode usar para instalar várias extensões do PostgreSQL:BtreeGinExtension
BtreeGistExtension
CITextExtension
CryptoExtension
HStoreExtension
TrigramExtension
UnaccentExtension
Observe que uma migração contendo uma dessas operações requer um usuário de banco de dados com privilégios de superusuário.
Por último, mas não menos importante, você também pode criar suas próprias classes de operação. Se você quiser analisar isso, dê uma olhada na documentação do Django sobre como criar operações de migração personalizadas.
Dependências de migração
As
dependencies
list em uma classe de migração contém todas as migrações que devem ser aplicadas antes que essa migração possa ser aplicada. No
0001_initial.py
migration que você viu acima, nada precisa ser aplicado antes para que não haja dependências. Vamos dar uma olhada na segunda migração no historical_prices
aplicativo. No arquivo 0002_switch_to_decimals.py
, as dependencies
atributo de Migration
tem uma entrada:from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('historical_data', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='pricehistory',
name='volume',
field=models.DecimalField(decimal_places=3, max_digits=7),
),
]
A dependência acima diz que a migração
0001_initial
do aplicativo historical_data
deve ser executado primeiro. Isso faz sentido, porque a migração 0001_initial
cria a tabela contendo o campo que a migração 0002_switch_to_decimals
quer mudar. Uma migração também pode ter uma dependência de uma migração de outro aplicativo, assim:
class Migration(migrations.Migration):
...
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
]
Isso geralmente é necessário se um modelo tiver uma chave estrangeira apontando para um modelo em outro aplicativo.
Como alternativa, você também pode impor que uma migração seja executada antes outra migração usando o atributo
run_before
:class Migration(migrations.Migration):
...
run_before = [
('third_party_app', '0001_initial'),
]
As dependências também podem ser combinadas para que você possa ter várias dependências. Essa funcionalidade oferece muita flexibilidade, pois você pode acomodar chaves estrangeiras que dependem de modelos de aplicativos diferentes.
A opção de definir explicitamente as dependências entre as migrações também significa que a numeração das migrações (geralmente
0001
, 0002
, 0003
, …) não representa estritamente a ordem em que as migrações são aplicadas. Você pode adicionar qualquer dependência que desejar e assim controlar o pedido sem precisar renumerar todas as migrações. Visualizando a migração
Você geralmente não precisa se preocupar com o SQL que as migrações geram. Mas se você quiser verificar novamente se o SQL gerado faz sentido ou está apenas curioso para saber como ele se parece, o Django o cobre com o
sqlmigrate
comando de gerenciamento:$ python manage.py sqlmigrate historical_data 0001
BEGIN;
--
-- Create model PriceHistory
--
CREATE TABLE "historical_data_pricehistory" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"date" datetime NOT NULL,
"price" decimal NOT NULL,
"volume" integer unsigned NOT NULL
);
COMMIT;
Isso listará as consultas SQL subjacentes que serão geradas pela migração especificada, com base no banco de dados em seu
settings.py
Arquivo. Ao passar o parâmetro --backwards
, o Django gera o SQL para desaplicar a migração:$ python manage.py sqlmigrate --backwards historical_data 0001
BEGIN;
--
-- Create model PriceHistory
--
DROP TABLE "historical_data_pricehistory";
COMMIT;
Depois de ver a saída de
sqlmigrate
para uma migração um pouco mais complexa, você pode apreciar que não precisa criar todo esse SQL manualmente! Como o Django detecta alterações em seus modelos
Você viu como é um arquivo de migração e como sua lista de
Operation
classes define as mudanças realizadas no banco de dados. Mas como exatamente o Django sabe quais operações devem entrar em um arquivo de migração? Você pode esperar que o Django compare seus modelos com seu esquema de banco de dados, mas esse não é o caso. Ao executar
makemigrations
, o Django não inspecione seu banco de dados. Nem compara seu arquivo de modelo com uma versão anterior. Em vez disso, o Django passa por todas as migrações que foram aplicadas e cria um estado de projeto de como os modelos devem ser. Este estado do projeto é então comparado com suas definições de modelo atuais e uma lista de operações é criada, que, quando aplicada, atualizaria o estado do projeto com as definições do modelo. Jogando xadrez com Django
Você pode pensar em seus modelos como um tabuleiro de xadrez, e Django é um grande mestre de xadrez observando você jogar contra si mesmo. Mas o grande mestre não observa todos os seus movimentos. O grande mestre só olha para o quadro quando você grita
makemigrations
. Como há apenas um conjunto limitado de movimentos possíveis (e o grande mestre é um grande mestre), ela pode inventar os movimentos que aconteceram desde a última vez que olhou para o tabuleiro. Ela faz algumas anotações e deixa você jogar até gritar
makemigrations
novamente. Ao olhar para o tabuleiro da próxima vez, o grande mestre não se lembra de como era o tabuleiro da última vez, mas pode passar por suas anotações dos lances anteriores e construir um modelo mental de como era o tabuleiro.
Agora, quando você grita
migrate
, o grande mestre repetirá todos os lances registrados em outro tabuleiro de xadrez e anotará em uma planilha quais de seus registros já foram aplicados. Este segundo tabuleiro de xadrez é seu banco de dados, e a planilha é o django_migrations
tabela. Essa analogia é bastante adequada, porque ilustra bem alguns comportamentos das migrações do Django:
-
As migrações do Django tentam ser eficientes: Assim como o grande mestre assume que você fez o menor número de movimentos, o Django tentará criar as migrações mais eficientes. Se você adicionar um campo chamadoA
para um modelo e renomeie-o paraB
e executemakemigrations
, o Django criará uma nova migração para adicionar um campo chamadoB
.
-
As migrações do Django têm seus limites: Se você fizer muitos movimentos antes de deixar o grande mestre olhar para o tabuleiro de xadrez, talvez ele não consiga refazer os movimentos exatos de cada peça. Da mesma forma, o Django pode não apresentar a migração correta se você fizer muitas alterações de uma só vez.
-
A migração do Django espera que você siga as regras: Quando você faz algo inesperado, como tirar uma peça aleatória do quadro ou mexer nas notas, o grande mestre pode não perceber no início, mas mais cedo ou mais tarde, ele vai levantar as mãos e se recusar a continuar. O mesmo acontece quando você mexe com odjango_migrations
table ou altere seu esquema de banco de dados fora das migrações, por exemplo, excluindo a tabela de banco de dados de um modelo.
Compreendendo SeparateDatabaseAndState
Agora que você sabe sobre o estado do projeto que o Django constrói, é hora de dar uma olhada na operação
SeparateDatabaseAndState
. Esta operação pode fazer exatamente o que o nome indica:ela pode separar o estado do projeto (o modelo mental que o Django constrói) do seu banco de dados. SeparateDatabaseAndState
é instanciado com duas listas de operações:state_operations
contém operações que são aplicadas apenas ao estado do projeto.database_operations
contém operações que são aplicadas apenas ao banco de dados.
Esta operação permite que você faça qualquer tipo de alteração em seu banco de dados, mas é sua responsabilidade garantir que o estado do projeto se ajuste ao banco de dados posteriormente. Casos de uso de exemplo para
SeparateDatabaseAndState
estão movendo um modelo de um aplicativo para outro ou criando um índice em um banco de dados enorme sem tempo de inatividade. SeparateDatabaseAndState
é uma operação avançada e você não precisará trabalhar com migrações no primeiro dia e talvez nunca precise. SeparateDatabaseAndState
é semelhante à cirurgia cardíaca. Tem um pouco de risco e não é algo que você faz apenas por diversão, mas às vezes é um procedimento necessário para manter o paciente vivo. Conclusão
Isso conclui seu mergulho profundo nas migrações do Django. Parabéns! Você cobriu muitos tópicos avançados e agora tem uma compreensão sólida do que acontece sob o capô das migrações.
Você aprendeu que:
- O Django acompanha as migrações aplicadas na tabela de migrações do Django.
- As migrações do Django consistem em arquivos Python simples contendo uma
Migration
aula. - O Django sabe quais mudanças realizar a partir das
operations
lista naMigration
aulas. - O Django compara seus modelos com um estado de projeto que ele constrói a partir das migrações.
Com esse conhecimento, agora você está pronto para abordar a terceira parte da série sobre migrações do Django, onde aprenderá como usar migrações de dados para fazer alterações únicas com segurança em seus dados. Fique atento!
Este artigo usou o
bitcoin_tracker
Projeto Django construído em Django Migrations:A Primer. Você pode recriar esse projeto trabalhando com esse artigo ou baixar o código-fonte:Fazer download do código-fonte: Clique aqui para baixar o código do projeto de migração do Django que você usará neste artigo.