Модель взаимоотношений для разных ролей пользователей: чего мне не хватает?

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

Модели: Пользователь (с определенными ролями: Администратор, Студент, Учитель), Профиль, Ученик и Учитель.

Вот чего мне удалось достичь с помощью этих моделей и их взаимосвязей: (Установлено и используется: FastAPI, SqlAlchemy, Pydantic)

class User(Base):
    __tablename__= "users"

    id: Mapped[uuid_pkg.UUID] = mapped_column(default_factory=uuid_pkg.uuid4, primary_key=True, unique=True, index=True)
    email: Mapped[str] = mapped_column(String(), unique=True, index=True, default=None)
    hashed_password: Mapped[str] = mapped_column(String(), nullable=False, default=None)   
    user_role = relationship("UserRole", back_populates = "user", uselist=False,)

Я могу создавать пользователей на основе вышеизложенного, используя схему и свои функции CRUD.

class UserRole(Base):
    __tablename__ = "user_roles"
    user_id = Column(
        UUID(as_uuid=True),
        ForeignKey("users.id"),
        primary_key=True,
        nullable=False,
    )
    role_id = Column(
        UUID(as_uuid=True),
        ForeignKey("roles.id"),
        primary_key=True,
        nullable=False,
    )

    role = relationship("Role")
    user = relationship("User", back_populates = "user_role", uselist=False)  #one-to-one

Я могу создать роль пользователя на основе вышеизложенного, используя схему и мои функции CRUD.

class Role(Base):
    __tablename__ = "roles"

    id = Column(
        UUID(as_uuid=True), primary_key=True, index=True, default=uuid4
    )
    name = Column(String(100), index=True)   #Admin, Student, Teacher
    description = Column(Text) #describes the Role.name column


    __table_args__ = (
        UniqueConstraint("user_id", "role_id", name = "unique_user_role"),
    )

Я могу создать роль на основе вышеизложенного, используя схему и свои функции CRUD.

class Profile(Base):
    __tablename__ = "profile"

    id: Mapped[uuid_pkg.UUID] = mapped_column(default_factory=uuid_pkg.uuid4, primary_key=True, unique=True)

    first_name= mapped_column(String(50))
    last_name= mapped_column(String(50))
    
    phone: Mapped[str] = mapped_column(String(15), default=None)

    avatar: Mapped[str] = mapped_column(String, default = "https://something-something.com/xf.jpg")

    user_id: Mapped[uuid_pkg.UUID] = mapped_column(ForeignKey("users.id"), default_factory=uuid_pkg.uuid4, nullable=False)

    user = relationship("User", back_populates = "profile", uselist=False)
  

class BaseRole(Base):
    #holds similar data across role type: Admin, Student, Teacher
    __abstract__ = True   #abstract so no table gets created

    id: Mapped[uuid_pkg.UUID] = mapped_column(default_factory=uuid_pkg.uuid4, primary_key=True, unique=True)

    identification_document: Mapped[str] = mapped_column(String(), unique=True, index=True, default=None)
    identification_number: Mapped[str] = mapped_column(String(), unique=True, index=True, default=None)
    identification_type: Mapped[str] = mapped_column(String(), index=True, default=None)

    areas_of_interest: Mapped[List | None] = mapped_column(MutableList.as_mutable(ARRAY(String)))

class Student(BaseRole):
    __tablename__ = "student"

    class InterestedCourse(enum.Enum):  #one course track at any time
        SME = "sme"
        IT = "it"
        NA = "n/a"

    course: Mapped[str] = mapped_column(Enum(InterestedCourse), default=InterestedCourse.NA)

    @declared_attr
    def user_id(cls):
        return  mapped_column(ForeignKey("users.id"), primary_key=True, index=True, unique=True, nullable=False)
    
    @declared_attr
    def user(cls):
        return relationship("User", back_populates=Student.__tablename__, uselist=False)

    __mapper_args__ = {'polymorphic_identity':'student', 'concrete':True}



class Teacher(BaseRole):
    __tablename__ = "teacher"

    ......

У меня есть настройки разрешений для моей маршрутизации, используя приведенный ниже код, и это работает:

current_user: models.User = Security(
        dependencies.get_current_active_user,
        scopes=[Role.ADMIN["name"]],
    ),

ОШИБКА (эта ошибка связана с абстрактно-конкретным классом BaseRole и Student and Teacher):

sqlalchemy.exc.InvalidRequestError: Mapper properties (i.e. deferred,column_property(), relationship(), etc.) must be declared as @declared_attr callables on declarative mixin classes.  For dataclass field() objects, use a lambda:

ПРИМЕЧАНИЕ. user_id в таблице user_roles не является уникальным намеренно: пользователь может быть администратором и учителем.

КОНЕЦ КОДА.

Мои вопросы:

  1. Является ли модель отношений идеальным способом (с точки зрения создания ролей пользователей, профиля пользователя и ролевых моделей) или я слишком перекосил отношения и мне нужно вернуться к чертежной доске?
  2. Причина, по которой я наследую абстрактный класс, заключается в том, что мне не нужно переписывать общие поля для ученика и учителя. Как мне устранить ОШИБКУ? Что мне не хватает?
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
0
59
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Миксины!

Я практически просто использовал BaseRole в качестве примеси там, где мне нужно:

class Student(Base, BaseRole):
        __tablename__ = "student"
    
        class InterestedCourse(enum.Enum):  #one course track at any time
            SME = "sme"
            IT = "it"
            NA = "n/a"
    
        course: Mapped[str] = mapped_column(Enum(InterestedCourse), default=InterestedCourse.NA)
    
#    @declared_attr
#    def user_id(cls):
#        return  mapped_column(ForeignKey("users.id"), primary_key=True, //index=True, unique=True, nullable=False)
        
#    @declared_attr
#    def user(cls):
#       return relationship("User", back_populates=Student.__tablename__, uselist=False)

#    __mapper_args__ = {'polymorphic_identity':'student', 'concrete':True}  #could have stayed

Другие вопросы по теме