Add base for members

This commit is contained in:
Sebastiaan
2025-06-17 21:23:14 +02:00
parent 1e6b138873
commit 479ca1986f
16 changed files with 921 additions and 63 deletions

View File

@@ -58,6 +58,7 @@ class ApiTags(DocumentedStrEnum):
TEAMS = "Teams"
ASSOCIATIONS = "Associations"
DIVISIONS = "Divisions"
MEMBERS = "Members"
# endregion

View File

@@ -57,8 +57,6 @@ class DivisionTeamLink(DivisionTeamLinkBase, table=True):
division: "Division" = Relationship(back_populates="team_links")
team: "Team" = Relationship(back_populates="division_link")
# Members (1 lid > meerdere teams | many-to-one)
# --- CRUD actions ---------------------------------------------------------
@classmethod
def create(cls, *, session: Session, create_obj: DivisionTeamLinkCreate, team: "Team") -> "DivisionTeamLink":

View File

@@ -0,0 +1,191 @@
from typing import TYPE_CHECKING, Optional
from sqlmodel import (
Session,
Field,
Relationship,
)
from . import mixin
from .base import (
BaseSQLModel,
DocumentedIntFlag,
auto_enum,
RowId,
)
if TYPE_CHECKING:
from .user import User
from .team import Team
# region # Member / Teams ######################################################
class MemberRank(DocumentedIntFlag):
TEAM_MEMBER = auto_enum()
TEAM_ASSISTANT_LEADER = auto_enum()
TEAM_LEADER = auto_enum()
DIVISION_LEADER = auto_enum()
VOLUNTEER = auto_enum()
# ##############################################################################
# Shared properties
class MemberTeamLinkBase(BaseSQLModel):
rank: MemberRank = Field(default=MemberRank.TEAM_MEMBER, nullable=False)
# Properties to receive via API on creation
class MemberTeamLinkCreate(MemberTeamLinkBase):
member_id: RowId = Field(default=None, nullable=False)
# Properties to receive via API on update, all are optional
class MemberTeamLinkUpdate(MemberTeamLinkBase):
pass
# Database model, database table inferred from class name
class MemberTeamLink(MemberTeamLinkBase, table=True):
# --- database only items --------------------------------------------------
# --- read only items ------------------------------------------------------
# --- back_populates links -------------------------------------------------
member_id: RowId = Field(
foreign_key="member.id",
primary_key=True,
nullable=False,
ondelete="CASCADE",
)
team_id: RowId = Field(
foreign_key="team.id",
primary_key=True,
nullable=False,
ondelete="CASCADE",
)
member: "Member" = Relationship(back_populates="team_links")
team: "Team" = Relationship(back_populates="member_links")
# --- CRUD actions ---------------------------------------------------------
@classmethod
def create(cls, *, session: Session, create_obj: MemberTeamLinkCreate, team: "Team") -> "MemberTeamLink":
data_obj = create_obj.model_dump(exclude_unset=True)
db_obj = cls.model_validate(data_obj, update={"team_id": team.id})
session.add(db_obj)
session.commit()
session.refresh(db_obj)
return db_obj
@classmethod
def update(
cls, *, session: Session, db_obj: "MemberTeamLink", in_obj: MemberTeamLinkUpdate
) -> "MemberTeamLink":
data_obj = in_obj.model_dump(exclude_unset=True)
db_obj.sqlmodel_update(data_obj)
session.add(db_obj)
session.commit()
session.refresh(db_obj)
return db_obj
# Properties to return via API
class MemberTeamLinkPublic(MemberTeamLinkBase):
member_id: RowId
team_id: RowId
class MemberTeamLinksPublic(BaseSQLModel):
data: list[MemberTeamLinkPublic]
count: int
# endregion
# region # Member ##############################################################
# Shared properties
class MemberBase(
mixin.Name,
mixin.Contact,
mixin.ScoutingId,
mixin.Comment,
mixin.Allergy,
mixin.Birthday,
mixin.Canceled,
BaseSQLModel,
):
pass
# Properties to receive via API on creation
class MemberCreate(MemberBase):
pass
# Properties to receive via API on update, all are optional
class MemberUpdate(MemberBase):
pass
# Database model, database table inferred from class name
class Member(mixin.RowId, mixin.Created, MemberBase, table=True):
# --- database only items --------------------------------------------------
# --- read only items ------------------------------------------------------
# --- back_populates links -------------------------------------------------
user: Optional["User"] = Relationship(
back_populates="member",
sa_relationship_kwargs={"foreign_keys": "User.member_id"},
)
team_links: list["MemberTeamLink"] = Relationship(back_populates="member", cascade_delete=True)
# --- CRUD actions ---------------------------------------------------------
@classmethod
def create(cls, *, session: Session, create_obj: MemberCreate, user: Optional["User"] = None) -> "Member":
data_obj = create_obj.model_dump(exclude_unset=True)
extra_fields = {}
if user:
extra_fields["created_by"] = user.id
db_obj = cls.model_validate(data_obj, update=extra_fields)
session.add(db_obj)
session.commit()
session.refresh(db_obj)
return db_obj
@classmethod
def update(
cls, *, session: Session, db_obj: "Member", in_obj: MemberUpdate
) -> "Member":
data_obj = in_obj.model_dump(exclude_unset=True)
db_obj.sqlmodel_update(data_obj)
session.add(db_obj)
session.commit()
session.refresh(db_obj)
return db_obj
# Properties to return via API, id is always required
class MemberPublic(mixin.RowIdPublic, MemberBase):
# TODO: Return user_id
pass
class MembersPublic(BaseSQLModel):
data: list[MemberPublic]
count: int
# endregion

View File

@@ -1,5 +1,5 @@
import uuid
from datetime import datetime
from datetime import datetime, date
from pydantic import BaseModel, EmailStr
from sqlmodel import (
@@ -7,6 +7,7 @@ from sqlmodel import (
)
from .base import RowId as RowIdType
from ..core.config import settings
class Name(BaseModel):
@@ -89,6 +90,14 @@ class Description(BaseModel):
description: str | None = Field(default=None, nullable=True, max_length=512)
class Comment(BaseModel):
comment: str | None = Field(default=None, nullable=True, max_length=512)
class Allergy(BaseModel):
allergy: str | None = Field(default=None, nullable=True, max_length=512)
class StartEndDate:
start_at: datetime | None = Field(default=None, nullable=True)
end_at: datetime | None = Field(default=None, nullable=True)
@@ -104,3 +113,10 @@ class CheckInCheckOut(BaseModel):
checkout_at: datetime | None = Field(default=None, nullable=True)
class Birthday(BaseModel):
birthday_at: date | None = Field(default=None, nullable=True)
class Created(BaseModel):
created_at: datetime | None = Field(nullable=False, default_factory=lambda: datetime.now(settings.tz_info))
created_by: RowIdType | None = Field(default=None, nullable=True, foreign_key="user.id", ondelete="SET NULL")

View File

@@ -15,6 +15,7 @@ from .base import (
if TYPE_CHECKING:
from .event import Event
from .division import DivisionTeamLink
from .member import MemberTeamLink
# region # Team ################################################################
@@ -49,6 +50,7 @@ class Team(mixin.RowId, TeamBase, table=True):
# --- back_populates links -------------------------------------------------
event: "Event" = Relationship(back_populates="teams")
division_link: "DivisionTeamLink" = Relationship(back_populates="team", cascade_delete=True)
member_links: list["MemberTeamLink"] = Relationship(back_populates="team", cascade_delete=True)
# --- CRUD actions ---------------------------------------------------------
@classmethod

View File

@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional
from pydantic import EmailStr, field_validator
from sqlmodel import Field, Relationship, Session, select
@@ -17,6 +17,7 @@ from .base import (
if TYPE_CHECKING:
from .apikey import ApiKey
from .event import EventUserLink
from .member import Member
# region # User ################################################################
@@ -29,6 +30,7 @@ class PermissionModule(DocumentedStrEnum):
TEAM = auto_enum()
ASSOCIATION = auto_enum()
DIVISION = auto_enum()
MEMBER = auto_enum()
class PermissionPart(DocumentedStrEnum):
@@ -45,8 +47,17 @@ class PermissionRight(DocumentedIntFlag):
MANAGE_USERS = auto_enum()
MANAGE_TEAMS = auto_enum()
MANAGE_DIVISIONS = auto_enum()
MANAGE_MEMBERS = auto_enum()
ADMIN = CREATE | READ | UPDATE | DELETE | MANAGE_USERS | MANAGE_TEAMS | MANAGE_DIVISIONS
ADMIN = ( CREATE
| READ
| UPDATE
| DELETE
| MANAGE_USERS
| MANAGE_TEAMS
| MANAGE_DIVISIONS
| MANAGE_MEMBERS
)
# ##############################################################################
@@ -75,13 +86,16 @@ class UserRoleLink(BaseSQLModel, table=True):
class UserBase(
mixin.UserName,
mixin.Email,
mixin.FullName,
mixin.ScoutingId,
mixin.IsActive,
mixin.IsVerified,
BaseSQLModel,
):
pass
member_id: RowId | None = Field(
default=None,
foreign_key="member.id",
nullable=True,
ondelete="SET NULL",
)
# Properties to receive via API on creation
@@ -89,7 +103,7 @@ class UserCreate(mixin.Password, UserBase):
pass
class UserRegister(mixin.Password, mixin.FullName, BaseSQLModel):
class UserRegister(mixin.Password, BaseSQLModel):
email: EmailStr = Field(max_length=255)
@@ -98,7 +112,7 @@ class UserUpdate(mixin.EmailUpdate, mixin.PasswordUpdate, UserBase):
pass
class UserUpdateMe(mixin.FullName, mixin.EmailUpdate, BaseSQLModel):
class UserUpdateMe(mixin.EmailUpdate, BaseSQLModel):
pass
@@ -113,6 +127,10 @@ class User(mixin.RowId, UserBase, table=True):
hashed_password: str
# --- back_populates links -------------------------------------------------
member: Optional["Member"] = Relationship(
back_populates="user",
sa_relationship_kwargs={"foreign_keys": "User.member_id"},
)
api_keys: list["ApiKey"] = Relationship(back_populates="user", cascade_delete=True)
# --- many-to-many links ---------------------------------------------------