использовать и учитьсяDjangoПри кадрировании я обнаружил, что многие люди, в том числе и я,DjangoКогда проект управляется версиями, обычноmigrationsфайл добавлен в.gitignoreсередина.
Автору всегда было интересно, верен ли такой подход, поэтому я пошел проверять официальную документацию и нашел следующий абзац.
оригинал:
You should think of migrations as a version control system for your database schema. makemigrations is responsible for packaging up your model changes into individual migration files - analogous to commits - and migrate is responsible for applying those to your database.
Файлы миграции для каждого приложения находятся в каталоге «миграции» внутри этого приложения и предназначены для фиксации и распространения как части его кодовой базы.Вы должны создать их один раз на своем компьютере для разработки, а затем запустить тот же миграции на компьютерах ваших коллег, ваших промежуточных компьютерах и, в конечном итоге, на ваших производственных компьютерах.
Китайский перевод:
Вы можете представить миграцию как систему контроля версий для вашей базы данных. Команда makemigrations отвечает за сохранение изменений вашей модели в файле миграции — так же, как коммиты — в то время как migrate отвечает за фиксацию изменений в базе данных.
Файл миграции для каждого приложения сохраняется в папке «миграции» каждого соответствующего приложения и подготавливает способ его выполнения в виде распределенной базы кода. Вы должны воссоздавать эти файлы каждый раз, когда запускаете одну и ту же миграцию на своем компьютере для разработки или на компьютере вашего коллеги, а затем и на рабочем компьютере.
Согласно официальной документации,migrationsПрактика фиксации в репозитории неверна.
и если вы хотите использоватьdjangoПоставляется упакованнымTestCaseвыполнять модульные тесты,migrationsтакже должны быть сохранены.
В следующей статье автор также представитdjangoсерединаTestCaseопыт использования.
Далее в проектеmigrationsНекоторый опыт и некоторые проблемы, с которыми столкнулись.
Инициализация данных с миграциями
теперь у нас естьBookМодель, которую я хочу использовать вmigrateПосле этого инициализируйте некоторые данные.
class Book(models.Model):
name = models.CharField(max_length=32)Например: сгенерируйте три имени какHamlet,Tempest,The Little Princeкнига.
в исполненииpython manage.py makemigrationsпослеmigrationsпапка будет создана0001_initial.pyдокумент.
файл содержитBookНекоторый код для инициализации этой модели.
Знакомство с использованиемmigrationsПри инициализации данных сначала введитеmigrationsДве часто используемые операции:
RunSQL,RunPython
Как следует из названия, выполнитьSQL语句иPython函数.
Ниже я используюmigrationsсерединаRunPythonдля инициализации данных.
в соответствующем приложении
migrationsфайл новый0002_init_book_data.pymigrations/.
├── 0001_initial.py.
└── 0002_init_book_data.py.затем увеличить
Migrationнаследование классовdjango.db.migrations.Migration, И вoperationsДобавьте код, который необходимо выполнить.
from django.db import migrations
"""
make_good_use_of_migrations 是App的名字
"""
def init_book_data(apps, schema_editor):
Book = apps.get_model('make_good_use_of_migrations', 'Book')
init_data = ['Hamlet', 'Tempest', 'The Little Prince']
for name in init_data:
book = Book(name=name)
book.save()
class Migration(migrations.Migration):
dependencies = [
('make_good_use_of_migrations', '0001_initial'),
]
# 这里要注意dependencies为上一次migrations的文件名称
operations = [
migrations.RunPython(init_book_data)
]- бегать
python manage.py migrate, вы можете видеть, что данные были сгенерированы в базе данных.
Восстановление данных с помощью миграции
Мы часто сталкиваемся с такой ситуацией, например мне нужно датьBookМодель добавляет поле внешнего ключа, и это поле не может быть пустым, поэтому старые данные нужно обработать и исправить, мы можем это сделать.
- Поля, которые необходимо добавить в первую очередь
nullсвойство установлено наTrue, затем выполнитеmakemigrations
class Author(models.Model):
name = models.CharField(max_length=32)
class Book(models.Model):
name = models.CharField(max_length=32)
author = models.ForeignKey(to=Author, on_delete=models.CASCADE, null=False)- в соответствующем приложении
migrationsфайл новый0004_fix_book_data.pymigrations/.
├── 0001_initial.py.
├── 0002_init_book_data.py.
├── 0003_auto_20181204_0533.py.
└── 0004_fix_book_data.py.
from django.db import migrations
def fix_book_data(apps, schema_editor):
Book = apps.get_model('make_good_use_of_migrations', 'Book')
Author = apps.get_model('make_good_use_of_migrations', 'Author')
for book in Book.objects.all():
author, _ = Author.objects.get_or_create(name='%s author' % book.name)
book.author = author
book.save()
class Migration(migrations.Migration):
dependencies = [
('make_good_use_of_migrations', '0003_auto_20181204_0533'),
]
operations = [
migrations.RunPython(fix_book_data)
]-
Наконец,
Bookв моделиauthorСвойства поляnullустановить какFalseи выполнитьmakemigrations. После выполнения появится,You are trying to change the nullable field 'author' on book to non-nullable without a default; we can't do that (the database needs something to populate existing rows). Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Ignore for now, and let me handle existing rows with NULL myself (e.g. because you added a RunPython or RunSQL operation to handle NULL values in a previous data migration) 3) Quit, and let me add a default in models.py Select an option:Выберите здесь
2элемент, что означает игнорировать данные о том, что поле уже пусто, используйтеRunPythonилиRunSQLСделай сам.Выполнить после завершения выбора
python manage.py migrate, вы обнаружите, что данные в базе данных будут обрабатываться так, как мы ожидали.
Устранение конфликтов, вызванных миграциями во время разработки с участием нескольких человек.
Чтобы смоделировать многоотраслевую разработку с участием нескольких человек, создайте новыйmaster-2, и версия создаетсяAuthorперед уроком и вBookдобавил в модельremarkполе.
model.pyСодержимое файла следующее:
class Book(models.Model):
name = models.CharField(max_length=32)
remark = models.CharField(max_length=32, null=True)migrationsКаталог файлов выглядит следующим образом:
migrations/.
├── 0001_initial.py.
├── 0002_init_book_data.py.
└──0003_book_remark.py.
когда мы ставимmaster-2Код объединен вmaster, ты найдешьmigrationsПовторяющиеся номера появляются в0003и они совместно зависят от0002_init_book_data.
migrations/.
├── 0001_initial.py.
├── 0002_init_book_data.py.
├── 0003_auto_20181204_0533.py
├── 0003_book_remark.py
├── 0004_fix_book_data.py
└──0005_auto_20181204_0610.py.
Затем нужно использовать команду:
python manage.py makemigrations --merge
затем вmigrationsкаталог для создания0006_merge_20181204_0622.pyдокумент
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('make_good_use_of_migrations', '0005_auto_20181204_0610'),
('make_good_use_of_migrations', '0003_book_remark'),
]
operations = [
]В это время выполняетсяpython manage.py migrateВот и все.
Вопросы, требующие внимания при использовании миграции. RunPython
Невозможно вызвать функцию класса модели в функции
предположим вBookВ модели определены две функцииprint_nameи функции классаprint_class_name
class Book(models.Model):
name = models.CharField(max_length=32)
author = models.ForeignKey(to=Author, on_delete=models.CASCADE, null=False)
remark = models.CharField(max_length=32, null=True)
def print_name(self):
print(self.name)
@classmethod
def print_class_name(cls):
print(cls.__name__)существуетmigrationsЕго нельзя назвать средним, да и автор не изучал его внимательно.BookПри инициализации класса инициализируются только поля.
from django.db import migrations
def fix_book_data(apps, schema_editor):
Book = apps.get_model('make_good_use_of_migrations', 'Book')
Author = apps.get_model('make_good_use_of_migrations', 'Author')
for book in Book.objects.all():
author, _ = Author.objects.get_or_create(name='%s author' % book.name)
book.author = author
"""
book.print_name()
book.print_class_name()
这样调用会报错
"""
book.save()
class Migration(migrations.Migration):
dependencies = [
('make_good_use_of_migrations', '0003_auto_20181204_0533'),
]
operations = [
migrations.RunPython(fix_book_data)
]Метод сохранения, переопределенный классом модели в функции, недействителен, включая унаследованный метод сохранения.
существуетmigrationsВсе переписано вsaveметоды не будут работать, например:
class Book(models.Model):
name = models.CharField(max_length=32)
author = models.ForeignKey(to=Author, on_delete=models.CASCADE, null=False)
remark = models.CharField(max_length=32, null=True)
def print_name(self):
print(self.name)
@classmethod
def print_class_name(cls):
print(cls.__name__)
def save(self, *args, **kwargs):
if not self.remark:
self.remark = 'This is a book.'Окончательная инициализация сгенерированных данныхremarkЗначение поля по-прежнему пусто.
Все сигналы, зарегистрированные моделью в функции, недействительны
Хотя даваяBookмодель зарегистрированаsignal,Но когдаmigrationsвсе равно не будет работать
@receiver(pre_save, sender=Book)
def generate_book_remark(sender, instance, *args, **kwargs):
print(instance)
if not instance.remark:
instance.remark = 'This is a book.'Не помещайте обработку данных в файл миграции для изменений модели
При восстановлении данных или создании данных инициализации не помещайте функции обработчика в автоматически сгенерированные изменения или сгенерированные поля или модели.migrationsфайл, например:
class Migration(migrations.Migration):
dependencies = [
('make_good_use_of_migrations', '0004_fix_book_data'),
]
operations = [
migrations.AlterField(
model_name='book',
name='author',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
to='make_good_use_of_migrations.Author'),
),
"""
migrations.RunPython(xxx) 不要把数据处理放到模型变更中
"""
]Основная причина не ставить их вместе заключается в том, что когдаRunPythonЛогика обработки функции не может выполняться вниз после возникновения исключения.
django_migrationsЭта транзакция не будет зарегистрирована,Но изменения в структуре таблицы выполнены!
Это тожеDjango migrationsПлохая часть, правильная должна быть откатом базы данных при возникновении исключения.
Как только это произойдет, вы сможете только вручнуюmigrationsимя как0005_auto_20181204_0610, записать в таблицу базы данныхdjango_migrations, тоRunPythonВходная логика вынесена отдельно.
Суммировать
Вышеуказанный автор использовал в проектеDjangoобрамленныйmigrationsопыт, следующая статья познакомитDjangoобрамленныйTestCase.
Исходный код этой статьи будет размещен вgithubначальство,GitHub.com/ELFGuangzhouP/Великое ограбление…
本人博客原文地址:elfgzp.cn/2018/12/04/…