Ground the 21-day habit myth in actual research (Lally et al. 2010) and let users pick a target period (21/66/254 days or unlimited) matching that study's easy/median/hard automaticity timelines when creating a habit, with progress shown on the habits list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
66 lines
1.6 KiB
Python
66 lines
1.6 KiB
Python
from datetime import datetime, time
|
|
|
|
from pydantic import BaseModel, ConfigDict, field_validator
|
|
|
|
from app.models.habit import ALL_WEEKDAYS_MASK, HabitDifficulty, HabitStatus, HabitType
|
|
|
|
|
|
class HabitBase(BaseModel):
|
|
name: str
|
|
habit_type: HabitType
|
|
weekdays_mask: int = ALL_WEEKDAYS_MASK
|
|
difficulty: HabitDifficulty = HabitDifficulty.MEDIUM
|
|
condition_text: str | None = None
|
|
reminder_time: time | None = None
|
|
|
|
@field_validator("name")
|
|
@classmethod
|
|
def name_not_blank(cls, v: str) -> str:
|
|
v = v.strip()
|
|
if not v:
|
|
raise ValueError("습관 이름을 입력해주세요")
|
|
return v
|
|
|
|
@field_validator("condition_text")
|
|
@classmethod
|
|
def blank_condition_to_none(cls, v: str | None) -> str | None:
|
|
if v is None:
|
|
return None
|
|
v = v.strip()
|
|
return v or None
|
|
|
|
@field_validator("weekdays_mask")
|
|
@classmethod
|
|
def mask_in_range(cls, v: int) -> int:
|
|
if not (1 <= v <= ALL_WEEKDAYS_MASK):
|
|
raise ValueError("요일을 최소 하루 이상 선택해주세요")
|
|
return v
|
|
|
|
|
|
class HabitCreate(HabitBase):
|
|
pass
|
|
|
|
|
|
class HabitUpdate(HabitBase):
|
|
pass
|
|
|
|
|
|
class HabitOut(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: int
|
|
name: str
|
|
habit_type: HabitType
|
|
status: HabitStatus
|
|
weekdays_mask: int
|
|
difficulty: HabitDifficulty
|
|
condition_text: str | None
|
|
reminder_time: time | None
|
|
created_at: datetime
|
|
completed_at: datetime | None
|
|
abandoned_at: datetime | None
|
|
|
|
|
|
class HabitReorderRequest(BaseModel):
|
|
habit_ids: list[int]
|