Django Multi-Table Inheritance VS Определение явных отношений OneToOne в моделях

Надеюсь, что все это имеет смысл:) Я уточню, если это необходимо, в комментариях. Кроме того, я экспериментирую с использованием жирного текста в этом вопросе и отредактирую его, если я (или вы) нахожу его отвлекающим. С этим с дороги...

Использование django.contrib.auth дает нам User и Group, среди других полезных вещей, с которыми я не могу обойтись (например, базовые сообщения).

В моем приложении у меня есть несколько разных типов пользователей. Пользователь может иметь только один тип. Это будет легко обрабатываться группами с небольшой осторожностью. Однако эти разные пользователи связаны друг с другом в иерархиях/отношениях.

Посмотрите на этих пользователей: -

Принципалы - пользователи верхнего уровня

Администраторы - каждый администратор сообщает Принципалу

Координаторы - каждый координатор отчитывается перед Администратором

Помимо этих существуют другие типы пользователей, которые напрямую не связаны с, но могут быть связаны позже. Например, "Компания" является другим типом пользователя и может иметь различные "Продукты", а продукты могут контролироваться "Координатором". "Покупатель" - это другой вид пользователя, который может покупать продукты.

Теперь все эти пользователи имеют различные другие атрибуты, некоторые из которых являются общими для всех типов пользователей, а некоторые из них отличаются только одним типом пользователя. Например, все типы пользователей должны иметь адрес. С другой стороны, только основной пользователь принадлежит к "BranchOffice".

Еще одна точка, о которой говорилось выше, заключается в том, что Пользователь может быть только одного типа.

Приложение также должно отслеживать, кто создал и/или изменил Принципов, Администраторов, Координаторов, Компаний, Продуктов и т.д.. (Так что еще две ссылки на модель пользователя.)

В этом случае целесообразно использовать наследование нескольких таблиц Django следующим образом: -

from django.contrib.auth.models import User
class Principal(User):
    #
    #
    #    
    branchoffice = models.ForeignKey(BranchOffice)
    landline = models.CharField(blank=True, max_length=20)    
    mobile = models.CharField(blank=True, max_length=20)
    created_by = models.ForeignKey(User, editable=False, blank=True, related_name="principalcreator")    
    modified_by = models.ForeignKey(User, editable=False, blank=True, related_name="principalmodifier")
    #
    #
    #

Или мне нужно делать это так: -

class Principal(models.Model):
    #
    #
    #
    user = models.OneToOneField(User, blank=True)
    branchoffice = models.ForeignKey(BranchOffice)
    landline = models.CharField(blank=True, max_length=20)    
    mobile = models.CharField(blank=True, max_length=20)
    created_by = models.ForeignKey(User, editable=False, blank=True, related_name="principalcreator")    
    modified_by = models.ForeignKey(User, editable=False, blank=True, related_name="principalmodifier")
    #
    #
    #

Пожалуйста, имейте в виду, что существуют другие типы пользователей, связанные с внешними ключами, например: -

class Administrator(models.Model):
    #
    #
    #
    principal = models.ForeignKey(Principal, help_text="The supervising principal for this Administrator")
    user = models.OneToOneField(User, blank=True)
    province = models.ForeignKey(         Province)
    landline = models.CharField(blank=True, max_length=20)    
    mobile = models.CharField(blank=True, max_length=20)
    created_by = models.ForeignKey(User, editable=False, blank=True, related_name="administratorcreator")    
    modified_by = models.ForeignKey(User, editable=False, blank=True, related_name="administratormodifier")

Мне известно, что Django использует отношения "один-к-одному" для наследования нескольких таблиц за кулисами. Я просто недостаточно квалифицирован, чтобы решить, какой из них более разумный.

Ответы

Ответ 1

Я хочу расширить решение от @thornomad.

Расширение класса Django User напрямую может вызвать всевозможные проблемы с внутренними механизмами django.auth. То, что я сделал в подобной ситуации, именно то, что предлагает @thornomad, - я создал мою собственную модель UserProfile, связанную один к одному с моделью Django User, в которой я содержал дополнительные пользовательские данные и из которых я унаследовал модели для разных типов пользователей.

Что-то, что вам понравилось:

class UserProfile(models.Model):
    user = models.OneToOneField(User, blank=True, related_name='profile')
    class Meta:
        abstract = True


class PositionHolderUserProfile(UserProfile):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    landline = models.CharField(blank=True, max_length=20)    
    mobile = models.CharField(blank=True, max_length=20)
    created_by = models.ForeignKey(PositionHolderUserProfile, editable=False, blank=True, related_name="created_users")    
    modified_by = models.ForeignKey(PositionHolderUserProfile, editable=False, blank=True, related_name="modified_users")

class Principal(PositionHolderUserProfile):
    branchoffice = models.ForeignKey(BranchOffice)

class Administrator(PositionHolderUserProfile):
    superior = models.ForeignKey(Principal, related_name="subordinates")
    province = models.ForeignKey(Province)

class Coordinator(PositionHolderUserProfile):
    superior = models.ForeignKey(Administrator, related_name="subordinates")


class Company(UserProfile):
    name = models.CharField(max_length=50)

class Product(models.Model):
    name = models.CharField(max_length=50)
    produced_by = models.ForeignKey(Company)

class Buyer(UserProfile):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    products_bought = models.ManyToManyField(Product)

Ответ 2

Недавно я переключился на использование моделей, которые наследуют от contrib.auto.models.User. Мое общее замечание состоит в том, что в теории они замечательные, но иногда они не получают автоматическое магическое обращение, как это предполагалось.

Я думаю, ваше решение относительно наследования против OneToOne сводится к следующему:

  • Я хочу, чтобы Django автоматически выполнял что-то правильно в 95% случаев, и нужно отлаживать другие 5%

-OR -

  • Я хочу сделать что-то вручную самостоятельно в 100% случаев

Если вы этого не заметили, в блоге Скотта Бархама есть отличный пост о наследовании пользователя, а также создание пользовательского конца, чтобы убедиться, что ваш пользовательский объект возвращается - Расширение пользователя Django.

Кроме того, интерес будет поле AutoOneToOne, предоставленное django-annoying. Это своего рода гибрид двух подходов здесь - нет наследования, но Django заботится о создании соответствующего OneToOneField, если он не присутствует.

Кроме того, thornomad делает хороший вывод об избыточности в ваших моделях. Вы можете легко реализовать абстрактный класс, чтобы очистить его так (при условии, что вы выполняете ручной OneToOne):

class BaseExtendedUser(models.Model):
    user = models.OneToOneField(User, blank=True, related_name='profile')
    landline = models.CharField(blank=True, max_length=20)    
    mobile = models.CharField(blank=True, max_length=20)
    created_by = models.ForeignKey(User, editable=False, blank=True, related_name="created_users")    
    modified_by = models.ForeignKey(User, editable=False, blank=True, related_name="modified_users")

    class Meta:
        abstract = True

class Administrator(BaseExtendedUser):
    province = models.ForeignKey(Province)

class Principal(BaseExtendedUser):
    branchoffice = models.ForeignKey(BranchOffice)

Ответ 3

Я не думаю, что наследовал бы модель User, а использовал бы пользовательский UserProfile - оставив только модель contrib.auth. С помощью пользовательской модели UserProfile вы можете настроить базовую модель профиля пользователя, которая может быть частью всех ваших разных типов пользователей.

Просто взглянув на это быстро, я бы внимательно посмотрел на любые модели, которые повторяют все те же поля (например, ваши последние две модели Principle и Administrator). Сочетание встроенных функциональных возможностей группы с идеей профиля пользователя может делать то, что вы ищете.

Ответ 4

Пожалуйста, подумайте о том, что происходит в модели данных, когда Координатор получает повышение до Принципала. В этом случае я бы не использовал наследование. Пожалуйста, пересмотреть предыдущее предложение плаката "Объединение встроенных функциональных возможностей группы с идеей профиля пользователя может делать то, что вы ищете".

Ответ 5

Вам нужны объекты ваших пользовательских классов, чтобы действовать как auth.User где угодно? Это была бы самая очевидная причина использования наследования над OneToOne. Один из подходов OneToOne - это легкость, с которой вы можете перейти к другой модели пользователя, если это вызывает беспокойство.

Реальная проблема, которую я вижу с тем, что у вас выше (с помощью любого метода), заключается в том, что, похоже, нет ничего, что мешает вам иметь объект Principal, а объект Administrator имеет один и тот же пользователь. OneToOneField может гарантировать взаимно однозначное отображение между любыми двумя отношениями.