"""User domain models."""
from __future__ import annotations
import datetime
from enum import StrEnum
from typing import TYPE_CHECKING
from uuid import UUID
from advanced_alchemy.base import UUIDAuditBase
from sqlalchemy import Boolean, Date, DateTime, Enum, ForeignKey, String, Text, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
if TYPE_CHECKING:
from pydotorg.domains.sponsors.models import Sponsorship
VOTE_AFFIRMATION_DAYS = 365
[docs]
class SearchVisibility(StrEnum):
PUBLIC = "public"
PRIVATE = "private"
[docs]
class EmailPrivacy(StrEnum):
PUBLIC = "public"
PRIVATE = "private"
NEVER = "never"
[docs]
class MembershipType(StrEnum):
BASIC = "basic"
SUPPORTING = "supporting"
SPONSOR = "sponsor"
MANAGING = "managing"
CONTRIBUTING = "contributing"
FELLOW = "fellow"
[docs]
class UserGroupType(StrEnum):
MEETUP = "meetup"
DISTRIBUTION_LIST = "distribution_list"
OTHER = "other"
[docs]
class User(UUIDAuditBase):
__tablename__ = "users"
username: Mapped[str] = mapped_column(String(150), unique=True, index=True)
email: Mapped[str] = mapped_column(String(254), unique=True, index=True)
password_hash: Mapped[str | None] = mapped_column(String(255), nullable=True)
first_name: Mapped[str] = mapped_column(String(150), default="")
last_name: Mapped[str] = mapped_column(String(150), default="")
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
is_staff: Mapped[bool] = mapped_column(Boolean, default=False)
is_superuser: Mapped[bool] = mapped_column(Boolean, default=False)
email_verified: Mapped[bool] = mapped_column(Boolean, default=False)
oauth_provider: Mapped[str | None] = mapped_column(String(50), nullable=True, index=True)
oauth_id: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True)
date_joined: Mapped[datetime.datetime] = mapped_column(DateTime(timezone=True), default=func.now())
last_login: Mapped[datetime.datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
bio: Mapped[str] = mapped_column(Text, default="")
search_visibility: Mapped[SearchVisibility] = mapped_column(
Enum(SearchVisibility, values_callable=lambda x: [e.value for e in x]),
default=SearchVisibility.PUBLIC,
)
email_privacy: Mapped[EmailPrivacy] = mapped_column(
Enum(EmailPrivacy, values_callable=lambda x: [e.value for e in x]),
default=EmailPrivacy.PRIVATE,
)
public_profile: Mapped[bool] = mapped_column(Boolean, default=True)
membership: Mapped[Membership | None] = relationship(
"Membership",
back_populates="user",
uselist=False,
lazy="selectin",
)
sponsorships: Mapped[list[Sponsorship]] = relationship(
"Sponsorship",
foreign_keys="[Sponsorship.submitted_by_id]",
back_populates="submitted_by",
lazy="noload",
)
@property
def full_name(self) -> str:
return f"{self.first_name} {self.last_name}".strip()
@property
def has_membership(self) -> bool:
return self.membership is not None
[docs]
class Membership(UUIDAuditBase):
__tablename__ = "memberships"
user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), unique=True)
membership_type: Mapped[MembershipType] = mapped_column(
Enum(MembershipType, values_callable=lambda x: [e.value for e in x]),
default=MembershipType.BASIC,
)
legal_name: Mapped[str] = mapped_column(String(255), default="")
preferred_name: Mapped[str] = mapped_column(String(255), default="")
email_address: Mapped[str] = mapped_column(String(254), default="")
city: Mapped[str] = mapped_column(String(100), default="")
region: Mapped[str] = mapped_column(String(100), default="")
country: Mapped[str] = mapped_column(String(100), default="")
postal_code: Mapped[str] = mapped_column(String(20), default="")
psf_code_of_conduct: Mapped[bool] = mapped_column(Boolean, default=False)
psf_announcements: Mapped[bool] = mapped_column(Boolean, default=False)
votes: Mapped[bool] = mapped_column(Boolean, default=False)
last_vote_affirmation: Mapped[datetime.date | None] = mapped_column(Date, nullable=True)
user: Mapped[User] = relationship("User", back_populates="membership")
@property
def higher_level_member(self) -> bool:
return self.membership_type != MembershipType.BASIC
@property
def needs_vote_affirmation(self) -> bool:
if not self.last_vote_affirmation:
return True
today = datetime.datetime.now(tz=datetime.UTC).date()
days_since = (today - self.last_vote_affirmation).days
return days_since > VOTE_AFFIRMATION_DAYS
[docs]
class UserGroup(UUIDAuditBase):
__tablename__ = "user_groups"
name: Mapped[str] = mapped_column(String(255))
location: Mapped[str] = mapped_column(String(255), default="")
url: Mapped[str] = mapped_column(String(500), default="")
url_type: Mapped[UserGroupType] = mapped_column(
Enum(UserGroupType, values_callable=lambda x: [e.value for e in x]),
default=UserGroupType.OTHER,
)
start_date: Mapped[datetime.date | None] = mapped_column(Date, nullable=True)
approved: Mapped[bool] = mapped_column(Boolean, default=False)
trusted: Mapped[bool] = mapped_column(Boolean, default=False)