From 9e9788ea22d526b8f186990d162d2750311b88ad Mon Sep 17 00:00:00 2001 From: BlackCyan Date: Fri, 12 Jun 2026 17:36:57 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=91=F0=9F=9B=A0=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add show price preference(store in cookies) --- auth.py | 10 ++- config.py | 10 +++ main.py | 10 +++ price_pref.py | 19 +++++ properties.py | 18 ++++- templates/components/navbar.html | 69 ++++++++++++++++++ templates/components/property_card.html | 4 +- templates/dashboard/index.html | 4 +- templates/index.html | 2 +- templates/properties/create.html | 87 ++++++++++++++++++++++- templates/properties/detail.html | 2 +- templates/properties/edit.html | 94 ++++++++++++++++++++++++- templates/properties/list.html | 9 ++- 13 files changed, 320 insertions(+), 18 deletions(-) create mode 100644 price_pref.py diff --git a/auth.py b/auth.py index 75be3f0..1d6d7a5 100644 --- a/auth.py +++ b/auth.py @@ -204,7 +204,9 @@ def _clear_auth_cookies(response: RedirectResponse) -> RedirectResponse: @router.get("/login", response_class=HTMLResponse) async def login_page(request: Request): - return templates.TemplateResponse(request, "auth/login.html", {}) + return templates.TemplateResponse(request, "auth/login.html", { + "price_pref": request.state.price_pref, + }) @router.post("/login") @@ -216,6 +218,7 @@ async def login_submit(request: Request, db: db_dependency): if not user: return templates.TemplateResponse(request, "auth/login.html", { "error": "Invalid username or password", + "price_pref": request.state.price_pref, }) access = create_access_token(user.username, user.id) refresh = create_refresh_token(user.username, user.id, db) @@ -226,7 +229,9 @@ async def login_submit(request: Request, db: db_dependency): @router.get("/register", response_class=HTMLResponse) async def register_page(request: Request): - return templates.TemplateResponse(request, "auth/register.html", {}) + return templates.TemplateResponse(request, "auth/register.html", { + "price_pref": request.state.price_pref, + }) @router.post("/register") @@ -258,6 +263,7 @@ async def register_submit(request: Request, db: db_dependency): "username": username, "email": email, "full_name": full_name, + "price_pref": request.state.price_pref, }) db.add(User( diff --git a/config.py b/config.py index befc5a2..a44220c 100644 --- a/config.py +++ b/config.py @@ -14,6 +14,16 @@ _loader = jinja2.FileSystemLoader("templates") _env = jinja2.Environment(loader=_loader, autoescape=jinja2.select_autoescape(), cache_size=0) templates = Jinja2Templates(env=_env) +# Custom Jinja2 filter: {{ prop.price|price_fmt(price_pref, prop.area_sqft) }} +from price_pref import format_price as _format_price + + +def _price_fmt(value, price_pref="total", area_sqft=0): + return _format_price(value, price_pref, area_sqft) + + +_env.filters["price_fmt"] = _price_fmt + bcrypt_context = CryptContext(schemes=["bcrypt"], deprecated="auto") oauth2_bearer = OAuth2PasswordBearer(tokenUrl="auth/token", auto_error=False) diff --git a/main.py b/main.py index 79a2976..39a1d2d 100644 --- a/main.py +++ b/main.py @@ -35,6 +35,15 @@ class RefreshTokenMiddleware(BaseHTTPMiddleware): app.add_middleware(RefreshTokenMiddleware) +class PricePrefMiddleware(BaseHTTPMiddleware): + async def dispatch(self, request: Request, call_next): + from price_pref import get_price_pref + request.state.price_pref = get_price_pref(request) + return await call_next(request) + +app.add_middleware(PricePrefMiddleware) + + db_dependency = Annotated[Session, Depends(get_db)] user_dependency = Annotated[Optional[dict], Depends(get_current_user)] @@ -64,4 +73,5 @@ async def homepage(request: Request, db: db_dependency, user: user_dependency): return templates.TemplateResponse(request, "index.html", { "user": user, "properties": featured, + "price_pref": request.state.price_pref, }) diff --git a/price_pref.py b/price_pref.py new file mode 100644 index 0000000..0ad38fe --- /dev/null +++ b/price_pref.py @@ -0,0 +1,19 @@ +VALID_PRICE_PREFS = ("total", "sqft", "both") + + +def get_price_pref(request) -> str: + pref = request.cookies.get("price_pref", "total") + if pref not in VALID_PRICE_PREFS: + pref = "total" + return pref + + +def format_price(value: int, price_pref: str = "total", area_sqft: int = 0) -> str: + + if area_sqft > 0: + unit_price = round(value / area_sqft) + if price_pref == "sqft": + return f"${unit_price:,}/sqft" + if price_pref == "both": + return f"${value:,} (${unit_price:,}/sqft)" + return f"${value:,}" diff --git a/properties.py b/properties.py index c3a5e12..34044d7 100644 --- a/properties.py +++ b/properties.py @@ -52,9 +52,17 @@ async def property_list(request: Request, db: Session = Depends(get_db)): if prop_type: query = query.filter(Property.property_type == prop_type) if min_price: - query = query.filter(Property.price >= int(min_price)) + min_val = int(min_price) + if request.state.price_pref == "sqft": + query = query.filter(Property.price >= min_val * Property.area_sqft) + else: + query = query.filter(Property.price >= min_val) if max_price: - query = query.filter(Property.price <= int(max_price)) + max_val = int(max_price) + if request.state.price_pref == "sqft": + query = query.filter(Property.price <= max_val * Property.area_sqft) + else: + query = query.filter(Property.price <= max_val) if bedrooms: query = query.filter(Property.bedrooms >= int(bedrooms)) if city: @@ -91,6 +99,7 @@ async def property_list(request: Request, db: Session = Depends(get_db)): "bedrooms": bedrooms, "city_filter": city, "state_filter": state, + "price_pref": request.state.price_pref, }) @@ -102,6 +111,7 @@ async def property_create_form(request: Request, db: Session = Depends(get_db)): return templates.TemplateResponse(request, "properties/create.html", { "user": user, "values": {}, + "price_pref": request.state.price_pref, }) @@ -154,6 +164,7 @@ async def property_create_submit( "user": user, "values": {k: v for k, v in request._form.items()} if hasattr(request, '_form') else {}, "error": "Please provide at least one contact method (email or phone).", + "price_pref": request.state.price_pref, }) prop = Property( @@ -211,6 +222,7 @@ async def property_detail(prop_id: int, request: Request, db: Session = Depends( "primary_image": primary_image, "images": images, "is_favorited": is_favorited, + "price_pref": request.state.price_pref, }) @@ -231,6 +243,7 @@ async def property_edit_form(prop_id: int, request: Request, db: Session = Depen "user": user, "prop": prop, "primary_image": primary_image, + "price_pref": request.state.price_pref, }) @@ -365,4 +378,5 @@ async def dashboard(request: Request, db: Session = Depends(get_db)): "user": user, "my_properties": my_properties, "favorites": favorites, + "price_pref": request.state.price_pref, }) diff --git a/templates/components/navbar.html b/templates/components/navbar.html index eecffa0..6098b3e 100644 --- a/templates/components/navbar.html +++ b/templates/components/navbar.html @@ -16,6 +16,38 @@ Rent Sell + +
+ + +
+ {% if user %} Dashboard
@@ -52,6 +84,27 @@ Buy Rent Sell + +
+ Price Display +
+ + + +
+
{% if user %} Dashboard Logout ({{ user.username }}) @@ -68,4 +121,20 @@ document.getElementById('mobile-menu-btn')?.addEventListener('click', () => { document.getElementById('mobile-menu').classList.toggle('hidden'); }); + + function togglePricePref() { + document.getElementById('pricePrefDropdown').classList.toggle('hidden'); + } + + function setPricePref(pref) { + document.cookie = 'price_pref=' + pref + ';path=/;max-age=31536000;samesite=lax'; + location.reload(); + } + + document.addEventListener('click', function(e) { + var wrapper = document.getElementById('pricePrefWrapper'); + if (wrapper && !wrapper.contains(e.target)) { + document.getElementById('pricePrefDropdown').classList.add('hidden'); + } + }); diff --git a/templates/components/property_card.html b/templates/components/property_card.html index 63d3aef..979dfb8 100644 --- a/templates/components/property_card.html +++ b/templates/components/property_card.html @@ -1,4 +1,4 @@ -{% macro property_card(prop) %} +{% macro property_card(prop, price_pref="total") %} {% else %} diff --git a/templates/index.html b/templates/index.html index 83066aa..25c05ea 100644 --- a/templates/index.html +++ b/templates/index.html @@ -42,7 +42,7 @@
{% from "components/property_card.html" import property_card %} {% for prop in properties %} - {{ property_card(prop) }} + {{ property_card(prop, price_pref) }} {% endfor %}
{% else %} diff --git a/templates/properties/create.html b/templates/properties/create.html index cddc761..f5b6cf3 100644 --- a/templates/properties/create.html +++ b/templates/properties/create.html @@ -34,13 +34,37 @@
-
+ +
+ + +
+ +
$ -
+ + + +
@@ -71,7 +95,7 @@
- @@ -170,4 +194,61 @@
+ + {% endblock %} diff --git a/templates/properties/detail.html b/templates/properties/detail.html index e8d15e8..7f566ce 100644 --- a/templates/properties/detail.html +++ b/templates/properties/detail.html @@ -82,7 +82,7 @@

{{ prop.title }}

- ${{ "{:,}".format(prop.price) }} + {{ prop.price|price_fmt(price_pref, prop.area_sqft) }}

diff --git a/templates/properties/edit.html b/templates/properties/edit.html index 088337e..304e7b5 100644 --- a/templates/properties/edit.html +++ b/templates/properties/edit.html @@ -32,12 +32,36 @@

-
+ +
+ + +
+ +
$ -
+ + + +
@@ -67,7 +91,7 @@
-
@@ -173,4 +197,68 @@
+ + {% endblock %} diff --git a/templates/properties/list.html b/templates/properties/list.html index 3bf865c..8d04689 100644 --- a/templates/properties/list.html +++ b/templates/properties/list.html @@ -30,7 +30,12 @@
- +
{% from "components/property_card.html" import property_card %} {% for prop in properties %} - {{ property_card(prop) }} + {{ property_card(prop, price_pref) }} {% endfor %}