Добавление Django M2M Отношения между двумя Сторонними Моделями - PullRequest
0 голосов
/ 08 мая 2018

Мне нужно создать отношения «многие ко многим» между двумя моделями. В Django вы обычно добавляете ManyToManyField к одной из двух моделей. В моем случае, однако:

  • Две модели из сторонних библиотек, тривиально User и Car
  • Оба не являются абстрактными, и я хочу, например, избежать сложности наследования нескольких таблиц, просто добавив поле M2M (которое на самом деле не требует дополнительных столбцов в любой таблице)
  • Я работаю в предположении, что другие будут использовать это как (если не буквально, как) стороннюю библиотеку, поэтому я действительно хочу предоставить стандартные интерфейсы, несмотря на ограничения.

Очевидно, я могу создать «ручную» таблицу M2M:

class UserCar(models.Model):
    user = models.ForeignKey(User)
    car = models.ForeignKey(Car)

    class Meta:
        unique_together = ('user', 'car')

... но Django будет рассматривать оба поля ForeignKey как многие-к-одному, поэтому стандартные «магические» средства доступа будут недоступны:

user.cars.add(car)
car in user.cars
User.objects.filter(cars=car)

Есть ли простой способ ввести обычную магию M2M с обеих сторон отношений? UserCar - это в основном таблица through=. Могу ли я создать фиктивное поле и вызвать метод или два (например, contribute_to_class и contribute_to_related_class), чтобы это сделать? Я пытался разобраться с ForeignKey и ManyToMany, но я недостаточно знаком с обычной обработкой на уровне поля, чтобы даже догадываться о критических методах (не говоря уже о секвенировании и отложенной обработке).

1 Ответ

0 голосов
/ 11 мая 2018

В комментариях @AlexandrTatarinov предлагает использовать add_to_class для добавления ManyToManyField к Car сразу после создания таблицы through. Я все еще открыт для лучших ответов, но это, безусловно, ответ (и в настоящее время лучший из доступных). Предположим, мы используем код вроде:

# guard against repeated import
if not hasattr(Car, 'users'):
    field = models.ManyToManyField(User, through=UserCar, related_name='cars')
    field.contribute_to_class(Car, 'users')

Эффект (включая недостатки) этого подхода:

  • Волшебные аксессуары добавляются к обеим сторонам отношений
  • Миграция (добавление поля M2M) размещена в приложении для Car. Эта миграция не будет зафиксирована в хранилище для моего пакета и может привести к некоторым странным проблемам. Например,
    • Если разработчик использует мой пакет в качестве зависимости, миграция будет (заново) создана, когда разработчик вызовет makemigrations. Если позднее пакет Car будет обновлен (и будет включать новые миграции), это может привести к ошибке, такой как «два конечных узла» на компьютере разработчика.
  • Когда я удаляю миграцию Car для имитации сценария производственного сценария, тривиальный тест работает (например, после создания двух взаимосвязанных объектов, user.places и place.users работает правильно)
  • Поскольку таблица M2M определяется как таблица through, такие методы, как place.users.add(), не работают "из коробки" (даже несмотря на то, что таблица M2M точно такая же, как таблица, автоматически создаваемая полем).

Проблема с place.users.add() возникает из-за auto_created=False на модели through=, созданной вручную. Если я установлю auto_created=Car в UserCar.Meta, магические методы работают, но миграции не создаются. Чтобы воспользоваться преимуществами миграций по умолчанию и восстановления с помощью поведений, я смог использовать следующее:

@receiver(request_started)
def set_auto_created(**kwargs):
    UserCar._meta.auto_created = Car

РЕДАКТИРОВАТЬ: На более ранней итерации я использовал auto_created=True. Это вызывало проблему с FLUSH в TransactionTestCase. После просмотра кода M2MField, похоже, что auto_created необходимо указать на модель, содержащую ManyToManyField. Я думаю, что это как-то связано с тем, как автоматически создаются таблицы в TransactionTestCase.

EDIT2: я также пытался подключить сигнал к post_migrate. Это хорошо работает для тестов (так как они всегда мигрируют), но не работало в производстве. request_started, кажется, работает для обоих.

С этим улучшением дополнительная миграция, созданная в приложении для Car, является единственной серьезной проблемой этой стратегии.

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