Преобразуйте отношения многие ко многим в один ко многим в django - PullRequest
1 голос
/ 06 января 2020

Чего я пытаюсь достичь

До сих пор у нас есть модели, которые имеют много-много взаимосвязей, как показано ниже.

class A(models.Model):
    name = models.CharField(max_length=255)
    data = models.ManyToManyField(B, related_name='data', through='C')

class B(models.Model):
    name=models.CharField(max_length=255)

class C(models.Model):
    a = ForeignKey(A)
    b = ForeignKey(B)
    c = model.CharField(max_length=255)
    active = models.BooleanField(default=True)
    and so on....

Хотя Django автоматически создает промежуточную таблицу для многих-многих отношений, если мы не определяем ее явно, как вы можете заметить, я явно определил третью промежуточную таблицу для хранения дополнительных метаданных о каждом отношении.

Итак, согласно В новых бизнес-требованиях нам необходимо удалить из таблицы многие-многие отношения и заменить их на отношения один-ко-многим , т. е. каждая запись B может иметь более одной ссылки на объект A, но запись A всегда будет иметь одна ссылка на A obj.

также, поскольку эти таблицы уже используются в производстве, поэтому нам необходимо перенести и существующие данные.

Вещи, которые я пробовал так far

Удалено много ко многим из модели A.

class A(models.Model):
    name = models.CharField(max_length=255) 
    data = models.ForeignKey(B)

Обновлен существующий ряд модели C.

, например:

#this might return more than one obj because of many to many relationship
test = C.objects.filter(a__id=<a obj id>)

for t in test:
   t.b_id = <some id pointing to record of B>

в основном, после выполнения вышеупомянутых шагов, он обновит записи с a_id до id некоторого объекта из b.

Проблема, с которой я сталкиваюсь

, когда я пытался запустить makemigrations, запрашивалось значение данных по умолчанию, я не уверен, что туда поместить. В модели A я могу указать поле данных на любую запись B, но тогда я потеряю использование третьей таблицы C, также таблица C широко используется в системе. Итак, мне нужно сбросить таблицу C? Кроме того, хотелось бы знать, есть ли лучший способ изменить отношения «многие ко многим к одному». Любая помощь будет полезна.

1 Ответ

1 голос
/ 07 января 2020

В том случае, если ваши данные находятся в промежуточной таблице, я бы порекомендовал вам сделать следующее.

  1. Добавить и временный столбец к вашей A таблице
class A(models.Model):
    name = models.CharField(max_length=255)
    data = models.ManyToManyField(B, related_name='data', through='C')
    # the on_delete part is not related, I added it just to avoid errors.
    tmp_data = models.ForeignKey(B, on_delete=models.DO_NOTHING)

и затем python manage.py makemigrations

Создайте пустой файл миграции и заставьте его выполнить этот код, чтобы скопировать все A-B данные отношения из таблицы C в таблицу A
# Command
python manage.py makemigrations <<<APP_NAME>>> --empty
# Inside Empty migrations file
def migrate_c_to_a(apps, schema_editor):
    C = apps.get_model('<<<APP NAME>>>', 'C')
    for c in C.objects.all().iterator():
        a = c.a
        a.tmp_data = c.b
        a.save()

class Migration(migrations.Migration):
    ...
    ...
    operations = [
        migrations.RunPython(migrate_c_to_a)
    ]
Удалите data столбец из вашей A модели и makemigtations Переименуйте tmp_data столбец в data на вашей A модели, затем makemigrations

Осторожно, убедитесь, что makemigrations не удалит tmp_data и data, а затем добавит новое поле data, это приведет к потере данных, я покажу вам, как это сделать, во фрагменте ниже.

Внутри файла миграции, если это произошло

...
operations = [
        migrations.RemoveField(
            model_name='a',
            name='tmp_data',
        ),
        migrations.RemoveField(
            model_name='a',
            name='data',
        ),
        migrations.AddField(
            model_name='a',
            name='data',
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='tsting.B'),
        ),
    ]
...

Измените его на

...
operations = [
        migrations.RemoveField(
            model_name='a',
            name='data',
        ),
        migrations.RenameField(
            model_name='a',
            old_name='tmp_data',
            new_name='data'
        ),
    ]
...
python manage.py migrate

Это должно сделать это навсегда. после этого, если вы хотите удалить таблицу C, вам решать, так как данные внутри нее безопасно перенесены в таблицу A

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...