Files
guilan-ace-backend/apps/users/api/schemas.py
Amirhossein Khalili 6c5fadef2f feat: derive event lifecycle status from dates and flags
Replace persisted event statuses with publication and cancellation flags, and compute public lifecycle labels from event timing and registration capacity. Keep filters, registration checks, and profile responses aligned with the derived status.
2026-10-03 12:46:16 +03:30

378 lines
9.6 KiB
Python

"""Authentication-related API schemas."""
from datetime import datetime
from typing import List, Optional, Literal
from ninja import ModelSchema, Schema
from pydantic import Field
from apps.gamification.api.schemas import PublicGamificationBadgeSchema
from apps.users.models import User
from apps.blog.permissions import can_access_blog_admin, can_review_blog_posts, can_write_blog_posts
from core.media import PREVIEW_VARIANT, THUMBNAIL_VARIANT, derivative_url
class UserRegistrationSchema(Schema):
username: str
mobile: str
code: str
password: str
email: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
university: Optional[str] = None
student_id: Optional[str] = None
year_of_study: Optional[int] = None
major: Optional[str] = None
class UserLoginSchema(Schema):
identifier: str
password: str
class UserOtpLoginSchema(Schema):
mobile: str
code: str
class RegisterOtpVerifySchema(Schema):
mobile: str
code: str
class OtpSendSchema(Schema):
mobile: str
mode: str
class MobileOtpSendSchema(Schema):
mobile: str
class MobileOtpVerifySchema(Schema):
mobile: str
code: str
class GoogleFlowSchema(Schema):
flow: str
class GoogleClaimVerifySchema(Schema):
flow: str
code: str
class GoogleCompleteSchema(Schema):
flow: str
mobile: str
username: Optional[str] = None
student_id: Optional[str] = None
year_of_study: Optional[int] = None
major: Optional[str] = None
university: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
class GoogleFlowResponseSchema(Schema):
status: str
email: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
avatar_url: Optional[str] = None
resolution: Optional[str] = None
mobile: Optional[str] = None
mobile_hint: Optional[str] = None
detail: Optional[str] = None
access_token: Optional[str] = None
refresh_token: Optional[str] = None
class UserProfileSchema(ModelSchema):
profile_picture: Optional[str] = None
profile_picture_thumbnail_url: Optional[str] = None
profile_picture_preview_url: Optional[str] = None
student_id: Optional[str] = None
major: Optional[str] = None
major_code: Optional[str] = None
university: Optional[str] = None
university_code: Optional[str] = None
mobile: Optional[str] = None
requires_mobile_verification: bool
has_google_link: bool
can_access_blog_admin: bool
can_write_blog_posts: bool
can_review_blog_posts: bool
show_public_profile_details: bool = False
class Meta:
model = User
fields = [
"id",
"username",
"email",
"mobile",
"first_name",
"last_name",
"student_id",
"year_of_study",
"major",
"university",
"bio",
"date_joined",
"is_email_verified",
"is_mobile_verified",
"show_public_profile_details",
"is_active",
"is_staff",
"is_superuser",
"is_deleted",
"deleted_at",
]
@staticmethod
def resolve_major(obj):
return obj.get_major_display()
@staticmethod
def resolve_major_code(obj):
return obj.major.code if obj.major_id else None
@staticmethod
def resolve_university(obj):
return obj.get_university_display()
@staticmethod
def resolve_university_code(obj):
return obj.university.code if obj.university_id else None
@staticmethod
def resolve_requires_mobile_verification(obj):
return obj.requires_mobile_verification
@staticmethod
def resolve_has_google_link(obj):
return obj.has_google_link
@staticmethod
def resolve_can_access_blog_admin(obj):
return can_access_blog_admin(obj)
@staticmethod
def resolve_can_write_blog_posts(obj):
return can_write_blog_posts(obj)
@staticmethod
def resolve_can_review_blog_posts(obj):
return can_review_blog_posts(obj)
@staticmethod
def resolve_profile_picture(obj, context):
request = context["request"]
if obj.profile_picture and hasattr(obj.profile_picture, "url"):
return request.build_absolute_uri(obj.profile_picture.url)
return None
@staticmethod
def resolve_profile_picture_thumbnail_url(obj, context):
request = context["request"]
url = derivative_url(obj.profile_picture, THUMBNAIL_VARIANT)
return request.build_absolute_uri(url) if url else None
@staticmethod
def resolve_profile_picture_preview_url(obj, context):
request = context["request"]
url = derivative_url(obj.profile_picture, PREVIEW_VARIANT)
return request.build_absolute_uri(url) if url else None
class UserListSchema(ModelSchema):
major: Optional[str] = None
university: Optional[str] = None
mobile: Optional[str] = None
profile_picture: Optional[str] = None
profile_picture_thumbnail_url: Optional[str] = None
profile_picture_preview_url: Optional[str] = None
student_id: Optional[str] = None
year_of_study: Optional[int] = None
bio: Optional[str] = None
is_email_verified: bool
is_mobile_verified: bool
is_deleted: bool
deleted_at: Optional[datetime] = None
can_access_blog_admin: bool
can_write_blog_posts: bool
can_review_blog_posts: bool
class Meta:
model = User
fields = [
"id",
"username",
"email",
"mobile",
"first_name",
"last_name",
"student_id",
"year_of_study",
"bio",
"is_active",
"is_staff",
"is_superuser",
"date_joined",
"major",
"university",
"is_email_verified",
"is_mobile_verified",
"is_deleted",
"deleted_at",
]
@staticmethod
def resolve_major(obj):
return obj.get_major_display()
@staticmethod
def resolve_university(obj):
return obj.get_university_display()
@staticmethod
def resolve_can_access_blog_admin(obj):
return can_access_blog_admin(obj)
@staticmethod
def resolve_can_write_blog_posts(obj):
return can_write_blog_posts(obj)
@staticmethod
def resolve_can_review_blog_posts(obj):
return can_review_blog_posts(obj)
@staticmethod
def resolve_profile_picture(obj, context):
request = context["request"]
if obj.profile_picture and hasattr(obj.profile_picture, "url"):
return request.build_absolute_uri(obj.profile_picture.url)
return None
@staticmethod
def resolve_profile_picture_thumbnail_url(obj, context):
request = context["request"]
url = derivative_url(obj.profile_picture, THUMBNAIL_VARIANT)
return request.build_absolute_uri(url) if url else None
@staticmethod
def resolve_profile_picture_preview_url(obj, context):
request = context["request"]
url = derivative_url(obj.profile_picture, PREVIEW_VARIANT)
return request.build_absolute_uri(url) if url else None
class AuthorizationRoleSchema(Schema):
key: str
label: str
description: str
enabled: bool = False
locked: bool = False
class UserAuthorizationSchema(Schema):
id: int
username: str
email: Optional[str] = None
mobile: Optional[str] = None
first_name: str
last_name: str
is_active: bool
is_staff: bool
is_superuser: bool
groups: List[str]
roles: List[AuthorizationRoleSchema]
class UserAuthorizationUpdateSchema(Schema):
is_staff: bool = False
groups: List[str] = []
class UserUpdateSchema(Schema):
username: Optional[str] = None
email: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
bio: Optional[str] = Field(default=None, max_length=500)
year_of_study: Optional[int] = None
major: Optional[str] = None
university: Optional[str] = None
student_id: Optional[str] = None
show_public_profile_details: Optional[bool] = None
class PublicProfileEventSchema(Schema):
title: str
slug: str
start_time: datetime
end_time: datetime
registration_start_date: Optional[datetime] = None
registration_end_date: Optional[datetime] = None
effective_registration_end_date: datetime
capacity: Optional[int] = None
registration_count: int = 0
status: str
status_label: str
is_cancelled: bool = False
image_url: Optional[str] = None
participation: Literal["confirmed", "attended"]
class PublicMemberProfileSchema(Schema):
username: str
first_name: str
last_name: str
full_name: str
profile_picture: Optional[str] = None
bio: str = ""
details_visible: bool
mobile: Optional[str] = None
email: Optional[str] = None
student_id: Optional[str] = None
year_of_study: Optional[int] = None
university: Optional[str] = None
major: Optional[str] = None
badges: List[PublicGamificationBadgeSchema] = []
events: List[PublicProfileEventSchema] = []
class TokenSchema(Schema):
access_token: str
refresh_token: str
token_type: str = "bearer"
class TokenRefreshIn(Schema):
refresh_token: str
class PasswordResetSchema(Schema):
mobile: str
code: str
new_password: str
class UsernameCheckSchema(Schema):
exists: bool
class MobileLookupSchema(Schema):
exists: bool
has_password: bool
class OtpSendResponseSchema(Schema):
message: str
expires_in_seconds: int
expires_at: datetime