Files
ad/molodetz/models.py
T

138 lines
4.0 KiB
Python

# retoor <retoor@molodetz.nl>
import re
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field, field_validator
from molodetz.constants import JOIN_STATUSES, POST_STATUSES, ROLES, TOPICS
URL_HOST_PATTERN = re.compile(r"^https?://[^\s/]+\.[^\s/]+")
PROSE_CAP = 125000
def normalize_website(value):
value = (value or "").strip()
if not value:
return None
if not value.lower().startswith(("http://", "https://")):
value = "https://" + value
if not URL_HOST_PATTERN.match(value):
raise ValueError("Enter a valid link, for example https://git.example.com/name")
return value[:500]
def normalize_european_date(value):
value = (value or "").strip()
if not value:
return None
for fmt in ("%d/%m/%Y", "%Y-%m-%d"):
try:
return datetime.strptime(value, fmt).date().isoformat()
except ValueError:
continue
raise ValueError("Use DD/MM/YYYY")
class FormModel(BaseModel):
model_config = ConfigDict(str_strip_whitespace=True, extra="ignore")
class LoginForm(FormModel):
username: str = Field(min_length=3, max_length=255)
password: str = Field(min_length=6, max_length=128)
remember: bool = False
next: str | None = None
class PostForm(FormModel):
title: str = Field(min_length=3, max_length=200)
body: str = Field(min_length=10, max_length=PROSE_CAP)
topic: str = "roll"
status: str = "draft"
is_placeholder: bool = False
@field_validator("topic")
@classmethod
def check_topic(cls, value):
if value not in TOPICS:
raise ValueError("Unknown topic")
return value
@field_validator("status")
@classmethod
def check_status(cls, value):
if value not in POST_STATUSES:
raise ValueError("Unknown status")
return value
class JoinForm(FormModel):
name: str = Field(min_length=2, max_length=80)
contact: str = Field(min_length=3, max_length=255)
repo_url: str | None = Field(default=None, max_length=500)
message: str | None = Field(default=None, max_length=2000)
website: str | None = Field(default=None, max_length=200)
@field_validator("repo_url")
@classmethod
def check_repo(cls, value):
return normalize_website(value)
class JoinStatusForm(FormModel):
status: str
@field_validator("status")
@classmethod
def check_status(cls, value):
if value not in JOIN_STATUSES:
raise ValueError("Unknown status")
return value
class RoleForm(FormModel):
role: str
@field_validator("role")
@classmethod
def check_role(cls, value):
if value not in ROLES:
raise ValueError("Unknown role")
return value
class SettingsForm(FormModel):
site_title: str | None = Field(default=None, max_length=80)
site_tagline: str | None = Field(default=None, max_length=200)
site_intro: str | None = Field(default=None, max_length=20000)
maintenance_mode: str | None = None
maintenance_message: str | None = Field(default=None, max_length=500)
rate_limit_per_minute: str | None = None
rate_limit_window_seconds: str | None = None
session_max_age_days: str | None = None
session_remember_days: str | None = None
terms_version: str | None = None
friendly_404: str | None = None
audit_retention_days: str | None = None
outbound_proxy_url: str | None = Field(default=None, max_length=500)
@field_validator("site_intro")
@classmethod
def normalize_newlines(cls, value):
return value.replace("\r\n", "\n").replace("\r", "\n") if value else value
class ServiceConfigForm(FormModel):
model_config = ConfigDict(extra="allow", str_strip_whitespace=True)
class BackupForm(FormModel):
target: str = "database"
@field_validator("target")
@classmethod
def check_target(cls, value):
if value not in ("database", "uploads", "keys", "full"):
raise ValueError("Unknown target")
return value