Джанго: Как перейти с ManyToMany на ForeignKey? - PullRequest
0 голосов
/ 15 февраля 2019

Я создаю REST API и сервер, используя Django и Django Rest Framework.Мы используем базу данных postgres.

Мне нужно упростить плохо спроектированное отношение.У нас есть модель (House), которая имеет отношение ManyToMany к другой (City).На самом деле этого было бы достаточно, когда это отношения ForeignKey.

Я погуглил и не смог найти ни одного поста в блоге или документации, как правильно перейти в этом направлении.Я мог найти только другой путь (от FK до M2M).

Я на 98% уверен, что все данные на сервере будут соответствовать отношениям FK (то есть я уверен, что все дома имеют толькоодин город).Нам нужно изменить отношения по нескольким причинам и мы не можем сохранить M2M.

Я боюсь просто сменить модель и запустить makemigrations и migrate.Мне было интересно, как вы правильно переходите с M2M на FK?Есть ли какие-то предостережения, которые я должен принять во внимание?Как я могу работать с данными, если на удивление есть дома с несколькими городами?Набор данных все еще довольно мал (менее 10 тыс. Записей), если это имеет значение.

Большое спасибо.

Ответы [ 4 ]

0 голосов
/ 15 февраля 2019

На основании ответа Тимми вот что я сделал:

  1. Я добавил поле, подобное этому city = models.ForeignKey(City, related_name='has_houses', blank=True, null=True), чтобы избежать related_name для обратных отношений и бланка FK.Затем я запустил makemigrations и migrate.

  2. Затем я запустил python manage.py makemigrations --empty houses, потому что мое приложение называется houses.

  3. Я добавил код для миграции (см. Ниже).Затем я запустил migrate.

  4. Я удалил поле M2M и ограничения related_name, null и blank и выполнил makemigrations и migrate в последний раз.

Код для миграции:

# -*- coding: utf-8 -*-
# Generated by Django 1.11.15 on 2019-02-15 09:09
from __future__ import unicode_literals

from django.db import migrations


def save_city_fk(apps, schema):
    City = apps.get_model('cities', 'City')
    House = apps.get_model('houses', 'House')
    for house in House.objects.all():
        house.city = house.cities.all().first()
        house.save()


class Migration(migrations.Migration):

    dependencies = [
        ('houses', '0003_auto_20190215_0949'),
    ]

    operations = [
        migrations.RunPython(save_city_fk),
    ]
0 голосов
/ 15 февраля 2019

для отношения M2M лучше построить новую модель для представления отношения.Ниже приведен пример того, как «сотрудник» имеет несколько назначений и назначений, полученных несколькими сотрудниками

class Designation(models.Model):
desig_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=60, unique=True)
job_discription = models.CharField(max_length=2000)

def __str__(self):
    return self.title

class Employee(AbstractUser):
middle_name = models.CharField(max_length=20, blank=True,  null=True)
basic_salary = models.FloatField(default=1)
designation = models.ManyToManyField(Designation, default=None, blank=True,
                                     through='EmployeeDesignation')
certification = models.ManyToManyField(Certification, default=None, null=True, blank=True,
                                        through='EmployeeCertification')
emp_img = models.FileField(default=None,upload_to='employees')
leaves_allowed = models.IntegerField(default=25)
leave_balance = models.IntegerField(default=25)
leave_count = models.IntegerField(default=0)
objects = EmployeeManager()

def __str__(self):
    return self.first_name+' '+self.last_name

class EmployeeDesignation(models.Model):

desig = models.ForeignKey(Designation, on_delete=models.CASCADE)
emp = models.ForeignKey(Employee, on_delete=models.CASCADE)



class Meta:
    unique_together = (('emp', 'desig'),)
0 голосов
/ 15 февраля 2019

Во-первых, очевидно, вам нужно убедиться (сделав соответствующий запрос), что у вас действительно есть только один город на дом.Если есть дома, в которых имеется более одного города, необходимо разрешить конфликт, удалив города из отношений, разделив дома и т. Д.

После этого вы можете сделать это поэтапно:

  • создайте новый FK в House, перенесите и заполните его одним идентификатором города из старого отношения m2m
  • переименуйте поле m2m, мигрируйте, а затем дайте новому FK имя старого m2mполе, если необходимо, и выполнить миграцию снова.
  • проверьте, работают ли ваши запросы, и при необходимости измените их
  • , убедившись, что все работает, удалите старое поле m2m - только после переноса этого шага вы потеряете способностьоткатить БД
0 голосов
/ 15 февраля 2019

РЕДАКТИРОВАТЬ Сначала создайте резервную копию БД

Сначала создайте новое временное отношение FK

_city = models.ForeignKey(...)

и выполните миграцию

python manage.py makemigration
python manage.py migrate

Затем вам нужно создать новые данные миграция в соответствующем приложении, создав пустой файл миграции:

python manage.py makemigration --empty myhouseapp

и в этом файле вручную назначить новые отношенияот М2М до ФК.Это будет выглядеть примерно так:

from django.db import migrations


def save_city_fk(apps, schema):
    City = apps.get_model('myapp', 'City')
    House = apps.get_model('myapp', 'City')
    for house in House.objects.all():
        house._city = house.cities.all().first()  # Or whatever criterea you want
        house._city.save()


def save_city_m2m(apps, schema):
    # This should do the reverse
    City = apps.get_model('myapp', 'City')
    House = apps.get_model('myapp', 'City')
    for house in House.objects.all():
        if house._city:
            house.cities.add(house._city)


class Migration(migrations.Migration):

    dependencies = [
    ]

    operations = [
        migrations.RunPython(save_city_fk, save_city_m2m)
    ]

Удалите поле M2M и создайте другую миграцию.

python manage.py makemigrations

Переименуйте FK из _city в city и создайте окончательную миграцию

python manage.py makemigrations

Затем мигрируйте:

python manage.py migrate
...