Как добавить через существующий 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_operationsdatabase_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.