Как добавить через существующий ManyToManyField с миграциями и данными в django
Я не могу найти ссылку на конкретную проблему в документах или в Интернете.
У меня есть существующее много-много отношение.
class Books(models.Model):
name = models.CharField(max_length=100)
class Authors(models.Model):
name = models.CharField(max_length=100)
books = models.ManyToManyField(Books)
У этого есть миграции и данные. Теперь мне нужно использовать через опцию, чтобы добавить одно дополнительное поле в таблицу, содержащую много-много отношений.
class Authorship(models.Model):
book = models.ForeignKey(Books)
author = models.ForeignKey(Authors)
ordering = models.PositiveIntegerField(default=1)
class Authors(models.Model):
name = models.CharField(max_length=100)
books = models.ManyToManyField(Books, through=Authorship)
Когда я запускаю миграции, django создает новую миграцию для модели Authorship
. Я попытался создать файл миграции вручную, добавив столбец ordering
в таблицу Authorship
и изменив столбец books
в таблице Authors
, но я получаю некоторые проблемы с миграцией.
operations = [
migrations.AddField(
model_name='authorship',
name='ordering',
field=models.PositiveIntegerField(default=1),
),
migrations.AlterField(
model_name='authors',
name='books',
field=models.ManyToManyField(to='app_name.Books', through='app_name.Authorship'),
),
]
При попытке переноса, он дает KeyError: ('app_name', u'authorship')
, я уверен, что есть другие вещи, которые затронуты и, следовательно, ошибки.
Какие вещи мне не хватает? Есть ли другой подход к работе с этим?
Ответы
Ответ 1
Похоже, что нет возможности использовать этот параметр без необходимости переноса данных. Поэтому пришлось прибегнуть к подходу к миграции данных, я взял некоторые идеи из ответа @pista329 и решил проблему, выполнив следующие шаги.
-
Создайте модель Authorship
class Authorship(models.Model):
book = models.ForeignKey(Books)
author = models.ForeignKey(Authors)
ordering = models.PositiveIntegerField(default=1)
-
Сохраняйте исходное отношение ManyToManyField, но добавьте другое поле, используя указанную выше модель, как через модель:
class Authors(models.Model):
name = models.CharField(max_length=100)
books = models.ManyToManyField(Books)
published_books = models.ManyToManyField(Books, through=Authorship, related_name='authors_lst') # different related name is needed.
-
Добавить перенос данных для копирования всех данных из старой таблицы в новую таблицу Authorship
.
После этого поле books
на модели Authors
может быть удалено, так как у нас есть новое поле с именем published_books
.
Ответ 2
Существует способ добавить "через" без переноса данных.
Мне удалось сделать это на основе этого ответа @MatthewWilkes.
Итак, чтобы перевести его в вашу модель данных:
-
Создайте модель Authorship
только с полями book
и author
. Укажите имя таблицы, чтобы использовать то же имя, что и автоматически созданная таблица M2M. Добавьте параметр "сквозной".
class Authorship(models.Model):
book = models.ForeignKey(Books)
author = models.ForeignKey(Authors)
class Meta:
db_table = 'app_name_authors_books'
class Authors(models.Model):
name = models.CharField(max_length=100)
books = models.ManyToManyField(Books, through=Authorship)
-
Создайте перенос, но еще не запускайте его.
-
Отредактируйте сгенерированную миграцию и заверните операции переноса в операцию migrations. SeparateDatabaseAndState
со всеми операциями внутри поля state_operations
(с database_operations
осталось пустым). Вы получите что-то вроде этого:
operations = [
migrations.SeparateDatabaseAndState(state_operations=[
migrations.CreateModel(
name='Authorship',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('book', models.ForeignKey(to='app_name.Books')),
],
options={
'db_table': 'app_name_authors_books',
},
),
migrations.AlterField(
model_name='authors',
name='books',
field=models.ManyToManyField(through='app_name.Authorship', to='app_name.Books'),
),
migrations.AddField(
model_name='authorship',
name='author',
field=models.ForeignKey( to='app_name.Author'),
),
])
]
-
Теперь вы можете выполнить миграцию и добавить дополнительное поле ordering
в таблицу M2M.
Edit:
По-видимому, имена столбцов в БД генерируются несколько иначе для автоматических таблиц M2M, как для таблиц, определенных моделями. (Я использую Django 1.9.3.)
После описанной процедуры мне также пришлось вручную изменить имена столбцов поля с двухсловным именем (two_words=models.ForeignKey(...)
) от twowords_id
до two_words_id
.
Ответ 3
Миграции иногда могут быть грязными.
Если вы хотите изменить поле m2m с помощью сквозного, я бы предложил переименовать измененное поле Authors.books
в Authors.book
. Когда вас спрашивают makemigrations
, если вы изменили имя из книги на книгу? [yN]
, выберите "N
", поскольку № Django удалит books
и создаст поле book
вместо изменения.
class Authorship(models.Model):
book = models.ForeignKey("Books")
author = models.ForeignKey("Authors")
ordering = models.PositiveIntegerField(default=1)
class Authors(models.Model):
name = models.CharField(max_length=100)
book = models.ManyToManyField("Books", through="Authorship")
Если вы все равно хотите использовать books
, измените book
на books
и повторите процесс миграции с помощью y
в качестве ответа на вопрос о переименовании makemigrations.