Перенос данных из Many-To-Many в Many-To-Many через django

у меня есть модель

class Category(models.Model):
    title           = models.CharField(...)
    entry           = models.ManyToManyField(Entry,null=True,blank=True,
                                             related_name='category_entries',
                                             )

Что я хочу реорганизовать, чтобы иметь дополнительные данные для каждого отношения:

class Category(models.Model):
    title           = models.CharField(...)
    entry           = models.ManyToManyField(Entry,null=True,blank=True,
                                             related_name='category_entries',
                                             through='CategoryEntry',
                                             )

Но юг удаляет существующую таблицу. Как я могу сохранить существующие отношения m-t-m?


person Bryce    schedule 13.07.2012    source источник
comment
Связанный вопрос в stackoverflow.com/questions/2224410/   -  person Bryce    schedule 13.07.2012


Ответы (4)


  1. Пока создайте свою промежуточную модель без дополнительных полей. Дайте ему уникальное ограничение, чтобы оно соответствовало существующему, и укажите имя таблицы, соответствующее существующему:

    class CategoryEntry(models.Model):
        category = models.ForeignKey(Category)
        entry = models.ForeignKey(Entry)   
    
        class Meta:
            db_table='main_category_entries'   #change main_ to your application
            unique_together = (('category', 'entry'))
    
  2. Запустите миграцию схемы South.

  3. Отредактируйте сгенерированный сценарий миграции схемы и закомментируйте все прямые и обратные записи, поскольку вы будете повторно использовать существующую таблицу пересечений. Добавьте pass для завершения методов.

  4. Запустите миграцию.

  5. Обновите любой существующий код. Как сказано в https://docs.djangoproject.com/en/dev/topics/db/models/#many-to-many-relationships, "В отличие от обычных полей "многие ко многим", вы не можете использовать добавление, создание или назначение для создания отношения», поэтому вам нужно будет изменить любой существующий код приложения, например.

    c.entry.add(e)
    

    мог стать:

    try:
        categoryentry = c.categoryentry_set.get(entry = e)
    except CategoryEntry.DoesNotExist:
        categoryentry = CategoryEntry(category=c, entry=e)
        categoryentry.save()
    

    и:

    e.category_entries.add(c)
    

    мог стать:

    categoryentry = CategoryEntry(category=c, entry=e)  #set extra fields here
    categoryentry.save()                
    

    и:

    c.entry.remove(e)
    

    мог стать:

    categoryentry = c.categoryentry_set.get(entry = e)
    categoryentry.delete()
    
  6. Как только эта первоначальная псевдомиграция будет выполнена, вы сможете добавить дополнительные поля в CategoryEntry и создать дальнейшие миграции, как обычно.

person greg    schedule 16.08.2012
comment
Вместо п.1 Create your intermediate model without any extra fields, for now можно запустить ./manage.py inspectdb > temp_models.py и оттуда получить определение таблицы - person pymen; 07.08.2019

Во встроенных миграциях Django 1.7+ способ вычисления «состояния кода» (т. е. определение кода моделей) отличается и требует другого решения.

В South (Django до 1.7) все «состояние кода» сохраняется при каждой миграции, но во встроенных миграциях Django 1.7+ оно получается из просмотра всего набора миграций, поэтому вам нужно указать «код state" изменение миграции без изменения базы данных.

Как и выше, это нужно будет сделать в несколько шагов.

  1. Создайте промежуточную модель, как в ответе выше:

    class CategoryEntry(models.Model):
        category = models.ForeignKey(Category, on_delete=models.CASCADE)
        entry = models.ForeignKey(Entry, on_delete=models.CASCADE)   
    
        class Meta:
             db_table = 'main_category_entries'   #change main_ to your application
             unique_together = ('category', 'entry')
    
  2. Создайте автоматическую миграцию с помощью django-admin.py makemigrations и измените код; переместить список операций в аргумент state_operations операции migrations.SeparateDatabaseAndState и оставьте список database_operations пустым. Это должно выглядеть так:

    class Migration(migrations.Migration):
        operations = [
            migrations.SeparateDatabaseAndState(
                state_operations=[ 
                    migrations.CreateModel(CategoryEntry..)
                    ...
                ],
                database_operations=[]
            ),
        ]
    
  3. Отредактируйте CategoryEntry, чтобы он содержал то, что вы хотите, и создайте новую автоматическую миграцию с помощью django-admin.py makemigrations.

person ygram    schedule 18.06.2015
comment
Ссылка на документацию django о SeparateDatabaseAndState: docs.djangoproject.com/en/1.7 /ref/миграционные-операции/ - person mnach; 25.11.2015
comment
Работал отлично. Исходя из моего использования, я ожидаю, что state_operations будет содержать: (1) CreateModel('CategoryEntry', ...) — убедитесь, что db_table соответствует существующему имени сквозной таблицы. (2) AddField(...) для полей "от" и "до". Убедитесь, что имена этих полей совпадают с именами, которые Django использует по умолчанию. Для отношений между двумя разными моделями это будут category и entry; отношения между одной и той же моделью будут from_category и to_category. (3) AlterUniqueTogether(...) для полей "от" и "до". (4) AlterField(...) для поля M2M, указывающего на сквозную таблицу. - person flyingfred0; 17.07.2016
comment
теперь следует принять ответ, что Django ‹ 1.7 полностью не поддерживается ☺ - person supervacuo; 09.05.2018
comment
Используя Django 1.11, когда я пытаюсь запустить вторую миграцию, мне сообщается, что таблица не существует. Что мне не хватает?? - person James Parker; 09.06.2018
comment
кажется, некоторые шаги отсутствуют... Я предполагаю, что после № 2 вам нужно отредактировать поле entry, чтобы использовать through и указать на CategoryEntry. Я думаю, вам нужно добавить в state_operations изменение в поле entry на Category. @flyingfred0 тоже упоминает об этом. - person Tim Tisdall; 11.09.2018
comment
Когда я пытаюсь это сделать с помощью Django 2.2, я получаю следующую ошибку: The field's intermediary table 'api_promo_issues' clashes with the table name of 'api.PromoIssues'. - person James Parker; 13.05.2021

Я бы сделал это следующим образом:

  1. Добавьте в модель класс CategoryEntry и выполните автоматическую миграцию схемы. Это добавит пустую таблицу, содержащую свойства CategoryEntry. Следует отметить, что старая таблица M2M остается нетронутой, поскольку through='CategoryEntry' еще не добавлено.

  2. Выполните миграцию данных, чтобы скопировать все данные из существующей таблицы M2M в таблицу, созданную на шаге 1. Для этого запустите команду datamigration и соответствующим образом отредактируйте методы forward() и backward() в автоматически сгенерированном сценарии миграции.

  3. Теперь добавьте часть through='CategoryEntry' (именно так, как вы хотели) и выполните миграцию схемы. это удалит старую таблицу M2M.

person Debasish    schedule 12.12.2012

В документации Django есть именно этот случай как применение операции migrations.SeparateDatabaseAndState. Я сделал именно то, что сказано в документации, но Django продолжал выдавать исключение, говоря, что таблица, используемая для сопоставления M2M, не существует.

Я назначал имя таблицы через атрибут «db_table» класса Meta промежуточной модели, и это вызывало проблему (не знаю почему). Затем я понял, что код SQL, показанный в примере в документации Django, предназначен для изменения имени, присвоенного Django таблице отношений M2M в стандартном отношении M2M, на новое имя, присвоенное Django таблице, соответствующей используемой промежуточной модели.

            database_operations=[
            # Old table name from checking with sqlmigrate, new table
            # name from AuthorBook._meta.db_table.
            migrations.RunSQL(
                sql='ALTER TABLE core_book_authors RENAME TO core_authorbook',
                reverse_sql='ALTER TABLE core_authorbook RENAME TO core_book_authors',
            ),
        ],

В этом примере «core_book_authors» — это старое имя, а «core_authorbook» — это новое имя таблицы отношений M2M. Если вы не включите этот код в миграцию, вы не сможете добавить дополнительные поля в промежуточную модель (и я предполагаю, что это основная причина создания пользовательских отношений M2M), поскольку Django будет искать новое имя таблицы. .

Подводя итог тому, что я сделал, чтобы изменить стандартное отношение M2M на пользовательское, используя «через»:

  1. Создал промежуточную модель без дополнительных полей (только два внешних ключа) и указал, что отношения M2M теперь должны выполняться через эту модель (используя «через»).
  2. Запустите команду py manage.py makemigrations. Я изменил эту автоматически сгенерированную миграцию, чтобы она выглядела как в документации, указанной выше.
  3. Выполните команду py manage.py migrate.
  4. Добавил все дополнительные поля, которые мне были нужны в промежуточной модели.
  5. Выполните команду py manage.py makemigrations.
  6. Выполните команду py manage.py migrate.

Таблица, которая ранее представляла стандартную связь M2M, теперь будет иметь другое имя и все новые столбцы. Важно, чтобы эти столбцы имели значение по умолчанию, если у вас уже были данные в таблице. Я считаю, что это самый простой способ сделать это БЕЗ потери каких-либо данных.

person Zufra    schedule 05.06.2020