У меня была эта головоломка уже целый день, и я только что перешел точку самопомощи к обращению за помощью к другим. По сути, я хочу сопоставить определенные модели для создания 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 не является уникальным намеренно: пользователь может быть администратором и учителем.
КОНЕЦ КОДА.
Мои вопросы:
Миксины!
Я практически просто использовал 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