Database
 sql >> Base de Dados >  >> RDS >> Database

Aprofundando-se nas migrações do Django


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:
  1. dependencies
  2. 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:
  1. RunSQL permite executar SQL personalizado no banco de dados.
  2. RunPython permite que você execute qualquer código Python.
  3. 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 chamado A para um modelo e renomeie-o para B e execute makemigrations , o Django criará uma nova migração para adicionar um campo chamado B .

  • 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 o django_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:
  1. state_operations contém operações que são aplicadas apenas ao estado do projeto.
  2. 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 na Migration 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.