Skip to content

Commit

Permalink
organize models and forms
Browse files Browse the repository at this point in the history
  • Loading branch information
jrycw committed Feb 29, 2024
1 parent 1f447c6 commit b9d358c
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 39 deletions.
30 changes: 20 additions & 10 deletions app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,25 @@ class UserID(BaseModel):
id: uuid.UUID


class UserBase(BaseModel):
class UserName(BaseModel):
name: str = Field(title="Username", description="max length: 50", max_length=50)


class UserCreate(UserBase):
class UserCreate(UserName):
pass


class UserUpdate(UserCreate):
class UserNewName(BaseModel):
new_name: str = Field(
title="New Username", description="max length: 50", max_length=50
)


class UserFull(Auditable, UserBase, UserID):
class UserUpdate(UserNewName, UserCreate):
pass


class UserRepr(UserFull):
class UserFull(Auditable, UserCreate, UserID):
n_events: int


Expand All @@ -49,29 +49,39 @@ class EventID(BaseModel):
id: uuid.UUID


class EventBase(BaseModel):
class EventName(BaseModel):
name: str = Field(title="Eventname", description="max length: 50", max_length=50)


class EventCreate(EventBase):
class EventHostName(BaseModel):
host_name: str | None = Field(
title="Hostname", description="max length: 50", max_length=50
)


class EventAddress(BaseModel):
address: str | None


class EventSchedule(BaseModel):
schedule: str | None


class EventUpdate(EventCreate):
class EventCreate(EventSchedule, EventAddress, EventHostName, EventName):
pass


class EventNewName(BaseModel):
new_name: str | None = Field(
title="New Eventname", description="max length: 50", max_length=50
)


class EventFull(Auditable, EventCreate, EventID):
class EventUpdate(EventNewName, EventCreate):
pass


class EventRepr(EventFull):
class EventFull(Auditable, EventCreate, EventID):
pass


Expand Down
2 changes: 2 additions & 0 deletions fastui_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class Settings(BaseSettings):
backendhost: HttpUrl = "http://localhost"
backendport: int = 8000

tz: str = "UTC"

model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")


Expand Down
135 changes: 112 additions & 23 deletions fastui_app/forms.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import datetime

from pydantic import BaseModel, Field, field_validator
from zoneinfo import ZoneInfo

from .models import EventBase, UserCreate
from .config import settings
from .models import EventFull, UserFull

"""
Q: Why using forms instead of models?
Expand All @@ -20,16 +22,57 @@
* While displaying something like listview or detailview.
"""

################################
# Repr
################################
user_prefer_zoneinfo = ZoneInfo(settings.tz)


class DisplayAuditable:
@field_validator("created_at")
@classmethod
def validate_created_at(cls, v: datetime.datetime) -> datetime.datetime:
"""This function decides how to render `Created_at`"""
return v.astimezone(user_prefer_zoneinfo).replace(microsecond=0)

class UserCreationForm(UserCreate):

class UserRepr(UserFull, DisplayAuditable):
pass


class UserUpdateForm(BaseModel):
"""
We don't inherit `UserCreationForm`, since we know the `name` already from frontend already.
"""
class EventRepr(EventFull, DisplayAuditable):
@field_validator("schedule")
@classmethod
def validate_schedule(cls, v: str | None) -> str | None:
"""This function decides how to render `schedule`"""
if v is not None:
v = (
datetime.datetime.fromisoformat(v)
.astimezone(user_prefer_zoneinfo)
.isoformat()
)
return v


################################
# User
################################


class UserNameCreationForm(BaseModel):
name: str = Field(title="Username", description="max length: 50", max_length=50)

@field_validator("name")
@classmethod
def validate_name(cls, v: str) -> str:
return v.strip()


class UserCreationForm(UserNameCreationForm):
pass


class UserNewNameUpdateForm(BaseModel):
new_name: str = Field(
title="New Username", description="max length: 50", max_length=50
)
Expand All @@ -40,55 +83,75 @@ def validate_new_name(cls, v: str) -> str:
return v.strip()


class UserUpdateForm(UserNewNameUpdateForm):
pass


################################
# Event
################################
class EventCreationForm(EventBase):
"""
By using `datetime.datetime` for `schedule`, we can get the UI Widget
"""
class EventNameCreationForm(BaseModel):
name: str = Field(title="Eventname", description="max length: 50", max_length=50)

@field_validator("name")
@classmethod
def validate_name(cls, v: str) -> str:
return v.strip()


class EventHostNameCreationForm(BaseModel):
host_name: str = Field(
title="Hostname",
description="max length: 50 (This will create a new User if not found)",
max_length=50,
)
address: str | None = None
schedule: datetime.datetime | None = None

@field_validator("host_name")
@classmethod
def validate_host_name(cls, v: str) -> str:
return v.strip()


class EventAddressCreationForm(BaseModel):
address: str | None = None

@field_validator("address")
@classmethod
def validate_address(cls, v: str | None) -> str | None:
if v is not None:
v = v.strip()
return v


class EventScheduleCreationForm(BaseModel):
"""
By using `datetime.datetime` for `schedule`, we can get the UI Widget
"""

schedule: datetime.datetime | None = None

@field_validator("schedule")
@classmethod
def validate_schedule(cls, v: datetime.datetime | None) -> datetime.datetime | None:
"""Coercing timezone info"""
"""Add timezone info"""
if v is not None:
v = v.astimezone(datetime.timezone.utc)
return v


class EventUpdateForm(BaseModel):
class EventCreationForm(
EventScheduleCreationForm,
EventAddressCreationForm,
EventHostNameCreationForm,
EventNameCreationForm,
):
pass


class EventNewNameUpdateForm(BaseModel):
new_name: str | None = Field(
default=None, title="New Eventname", description="max length: 50", max_length=50
)
host_name: str | None = Field(
default=None,
title="Hostname",
description="max length: 50 (This will create a new User if not found)",
max_length=50,
)
address: str | None = None
schedule: datetime.datetime | None = None

@field_validator("new_name")
@classmethod
Expand All @@ -97,24 +160,50 @@ def validate_new_name(cls, v: str | None) -> str | None:
v = v.strip()
return v


class EvenHostNameUpdateForm(BaseModel):
host_name: str | None = Field(
default=None,
title="Hostname",
description="max length: 50 (This will create a new User if not found)",
max_length=50,
)

@field_validator("host_name")
@classmethod
def validate_host_name(cls, v: str | None) -> str | None:
if v is not None:
v = v.strip()
return v


class EvenAddressUpdateForm(BaseModel):
address: str | None = None

@field_validator("address")
@classmethod
def validate_address(cls, v: str | None) -> str | None:
if v is not None:
v = v.strip()
return v


class EventScheduleUpdateForm(BaseModel):
schedule: datetime.datetime | None = None

@field_validator("schedule")
@classmethod
def validate_schedule(cls, v: datetime.datetime | None) -> datetime.datetime | None:
"""Coercing timezone info"""
"""Add timezone info"""
if v is not None:
v = v.astimezone(datetime.timezone.utc)
return v


class EventUpdateForm(
EventScheduleUpdateForm,
EvenAddressUpdateForm,
EvenHostNameUpdateForm,
EventNewNameUpdateForm,
):
pass
3 changes: 2 additions & 1 deletion fastui_app/models.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from app.models import * # good or bad?
from app.models import EventFull, UserFull # noqa: F401
# good or bad?
7 changes: 3 additions & 4 deletions fastui_app/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
from httpx import AsyncClient

from .forms import UserCreationForm, UserUpdateForm
from .models import UserRepr
from .shared import demo_page
from .utils import _raise_for_status
from .utils import _form_user_repr, _raise_for_status

router = APIRouter(include_in_schema=False)

Expand Down Expand Up @@ -64,7 +63,7 @@ async def user_detailview(
client = await services.aget(AsyncClient)
resp = await client.get("/users", params={"name": name})
resp_json = _raise_for_status(resp) # try using prebuilt_html
user = UserRepr(**resp_json)
user = _form_user_repr(resp_json)
page_comp_list = [
c.Heading(text=user.name, level=2),
c.Link(components=[c.Text(text="Back")], on_click=BackEvent()),
Expand Down Expand Up @@ -194,7 +193,7 @@ async def user_listview(
client = await services.aget(AsyncClient)
resp = await client.get("/users")
resp_json_list = _raise_for_status(resp, HTTPStatus.OK)
users = [UserRepr(**resp_json) for resp_json in resp_json_list]
users = [_form_user_repr(resp_json) for resp_json in resp_json_list]
page_comp_list = [
c.Heading(text="Users", level=2),
c.Div(
Expand Down
6 changes: 5 additions & 1 deletion fastui_app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from fastapi import HTTPException
from fastapi.responses import Response

from .models import EventRepr
from .forms import EventRepr, UserRepr


def _raise_for_status(response: Response, status_code: HTTPStatus = HTTPStatus.OK):
Expand All @@ -16,6 +16,10 @@ def _raise_for_status(response: Response, status_code: HTTPStatus = HTTPStatus.O
return resp_json


def _form_user_repr(resp_json: dict[str, str]) -> UserRepr:
return UserRepr(**resp_json)


def _form_event_repr(resp_json: dict[str, str | dict[str, str] | None]) -> EventRepr:
"""
`host_name = resp_json["host"]["name"]` works because host
Expand Down

0 comments on commit b9d358c

Please sign in to comment.