multi-user: users, per-user profiles/filters/notifications, tab UI, apply forensics
* DB: users + user_profiles/filters/notifications/preferences; applications gets user_id + forensics_json + profile_snapshot_json; new errors table with 14d retention; schema versioning via MIGRATIONS list * auth: password hashes in DB (argon2); env vars seed first admin; per-user sessions; CSRF bound to user id * apply: personal info/WBS moved out of env into the request body; providers take an ApplyContext with Profile + submit_forms; full Playwright recorder (step log, console, page errors, network, screenshots, final HTML) * web: five top-level tabs (Wohnungen/Bewerbungen/Logs/Fehler/Einstellungen); settings sub-tabs profil/filter/benachrichtigungen/account/benutzer; per-user matching, auto-apply and notifications (UI/Telegram/SMTP); red auto-apply switch on Wohnungen tab; forensics detail view for bewerbungen and fehler; retention background thread Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e663386a19
commit
c630b500ef
36 changed files with 2763 additions and 1113 deletions
66
apply/classes/profile.py
Normal file
66
apply/classes/profile.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
"""Applicant profile passed from web → apply on each request."""
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class Profile:
|
||||
salutation: str = "Herr"
|
||||
firstname: str = ""
|
||||
lastname: str = ""
|
||||
email: str = ""
|
||||
telephone: str = ""
|
||||
street: str = ""
|
||||
house_number: str = ""
|
||||
postcode: str = ""
|
||||
city: str = ""
|
||||
|
||||
# WBS
|
||||
is_possessing_wbs: bool = False
|
||||
wbs_type: str = "0"
|
||||
wbs_valid_till: str = "1970-01-01" # ISO date string
|
||||
wbs_rooms: int = 0
|
||||
wbs_adults: int = 0
|
||||
wbs_children: int = 0
|
||||
is_prio_wbs: bool = False
|
||||
|
||||
# optional: immomio login for providers that need it
|
||||
immomio_email: str = ""
|
||||
immomio_password: str = ""
|
||||
|
||||
@property
|
||||
def person_count(self) -> int:
|
||||
return self.wbs_adults + self.wbs_children
|
||||
|
||||
@property
|
||||
def adult_count(self) -> int:
|
||||
return self.wbs_adults
|
||||
|
||||
@property
|
||||
def children_count(self) -> int:
|
||||
return self.wbs_children
|
||||
|
||||
def wbs_valid_till_dt(self) -> datetime:
|
||||
try:
|
||||
return datetime.strptime(self.wbs_valid_till, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
return datetime(1970, 1, 1)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict) -> "Profile":
|
||||
safe = {k: d.get(k) for k in cls.__dataclass_fields__.keys() if k in d and d[k] is not None}
|
||||
# normalise booleans + ints
|
||||
for k in ("is_possessing_wbs", "is_prio_wbs"):
|
||||
if k in safe:
|
||||
v = safe[k]
|
||||
if isinstance(v, str):
|
||||
safe[k] = v.lower() in ("true", "1", "yes", "on")
|
||||
else:
|
||||
safe[k] = bool(v)
|
||||
for k in ("wbs_rooms", "wbs_adults", "wbs_children"):
|
||||
if k in safe:
|
||||
try:
|
||||
safe[k] = int(safe[k])
|
||||
except (TypeError, ValueError):
|
||||
safe[k] = 0
|
||||
return cls(**safe)
|
||||
Loading…
Add table
Add a link
Reference in a new issue