Files
dtf-system/local/models.py
Cauê Faleiros 4d707009ba
All checks were successful
Build and deploy / Validate source (push) Successful in 10s
Build and deploy / Publish images and notify Portainer (push) Successful in 56s
feat: configure Kanban login with optional email
2026-09-18 14:11:55 -03:00

110 lines
3.7 KiB
Python

import re
from decimal import Decimal
from typing import Literal
from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field, field_validator
class StrictModel(BaseModel):
model_config = ConfigDict(extra='forbid', allow_inf_nan=False)
class Customer(StrictModel):
cnpj: str
zap: str
mail: str = Field(max_length=254)
@field_validator('cnpj')
@classmethod
def cnpj_valid(cls, value):
digits = re.sub(r'\D', '', value)
if len(digits) != 14 or len(set(digits)) == 1 or not digits.isascii():
raise ValueError('Invalid CNPJ')
def check(base, weights):
rem = sum(int(n) * w for n,w in zip(base, weights)) % 11
return 0 if rem < 2 else 11-rem
if check(digits[:12], [5,4,3,2,9,8,7,6,5,4,3,2]) != int(digits[12]) or check(digits[:13], [6,5,4,3,2,9,8,7,6,5,4,3,2]) != int(digits[13]):
raise ValueError('Invalid CNPJ')
return digits
@field_validator('zap')
@classmethod
def phone_valid(cls, value):
digits = re.sub(r'\D', '', value)
if len(digits) not in (10,11) or not digits.isascii():
raise ValueError('Invalid phone')
return digits
@field_validator('mail')
@classmethod
def email_valid(cls, value):
if not re.fullmatch(r'[^\s@]+@[^\s@]+\.[a-zA-Z]{2,}', value.strip()):
raise ValueError('Invalid email')
return value.strip()
class Freight(StrictModel):
service: Literal['pickup','mock-standard'] = 'pickup'
postal_code: str = Field(default='', max_length=8)
class UploadStart(StrictModel):
name: str = Field(min_length=1, max_length=200, pattern=r'^[^/\\\x00-\x1f]+$')
size: int = Field(gt=0, strict=True)
@field_validator('name')
@classmethod
def artwork_extension(cls, value):
# Union of existing Site product formats; this is NOT malware/pre-flight validation.
if not re.search(r'\.(png|jpe?g|webp|tiff?|pdf|psd|psb|ai|cdr)$', value, re.I):
raise ValueError('Unsupported artwork file extension')
return value
class Item(StrictModel):
mode: Literal['file','avulsa','uvfile','uv']
metres: Decimal = Field(gt=0, le=12000)
grade: int = Field(ge=0, le=100, strict=True)
uploads: list[UUID] = Field(min_length=1, max_length=20)
class QuoteRequest(StrictModel):
request_key: UUID
customer: Customer
items: list[Item] = Field(min_length=1, max_length=30)
freight: Freight
class Review(StrictModel):
items: list[Item] = Field(min_length=1, max_length=30)
class Pay(StrictModel):
quote_id: UUID
class Move(StrictModel):
state: Literal['rec','tra','fil','imp','cor','fin']
version: int = Field(ge=0)
reason: str = Field(default='', max_length=1000)
class Register(StrictModel):
customer: Customer
password: str = Field(min_length=12, max_length=128)
class Login(StrictModel):
email: str = Field(min_length=3, max_length=254)
password: str = Field(min_length=1, max_length=128)
class OperatorLogin(StrictModel):
email: str = Field(min_length=3, max_length=254)
password: str = Field(min_length=1, max_length=128)
@field_validator('email')
@classmethod
def operator_email_valid(cls, value):
value = value.strip().lower()
if not re.fullmatch(r'[^\s@]+@[^\s@]+\.[a-zA-Z]{2,}', value):
raise ValueError('Invalid email')
return value
class FileReference(StrictModel):
upload_id: UUID
item_index: int = Field(ge=0, strict=True)
class ArtworkSubmission(StrictModel):
version: int = Field(ge=0, strict=True)
files: list[FileReference] = Field(min_length=1, max_length=60)
note: str = Field(min_length=1, max_length=1000)