06/18 Phase 7
This commit is contained in:
@@ -14,6 +14,7 @@ from app.services import listings as svc
|
||||
from app.services.geo import geocode_zip
|
||||
from app.services.images import process_upload, delete_image_files, ImageError
|
||||
from app.services import reports as rsvc
|
||||
from app.services import reviews as rev_svc
|
||||
from app.blueprints.listings.forms import ListingForm, ImageUploadForm
|
||||
|
||||
listings_bp = Blueprint("listings", __name__)
|
||||
@@ -103,8 +104,18 @@ def detail(listing_id):
|
||||
if not is_owner:
|
||||
listing.view_count = (listing.view_count or 0) + 1
|
||||
db.session.commit()
|
||||
# determine if current user can leave a review
|
||||
can_review = False
|
||||
if (current_user.is_authenticated and not is_owner
|
||||
and listing.status == ListingStatus.sold):
|
||||
from app.models.review import Review
|
||||
already = Review.query.filter_by(
|
||||
listing_id=listing.id, author_id=current_user.id).first()
|
||||
can_review = already is None
|
||||
listing_rating = rev_svc.seller_rating(listing.user_id)
|
||||
return render_template("listings/detail.html", listing=listing,
|
||||
is_owner=is_owner)
|
||||
is_owner=is_owner, can_review=can_review,
|
||||
listing_rating=listing_rating)
|
||||
|
||||
|
||||
# --- create ---
|
||||
@@ -243,6 +254,31 @@ def report(listing_id):
|
||||
return redirect(url_for("listings.detail", listing_id=listing.id))
|
||||
|
||||
|
||||
# --- leave review ---
|
||||
@listings_bp.route("/listings/<int:listing_id>/review", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def leave_review(listing_id):
|
||||
listing = Listing.query.get_or_404(listing_id)
|
||||
if listing.status != ListingStatus.sold:
|
||||
flash(_("You can only review sold listings."), "warning")
|
||||
return redirect(url_for("listings.detail", listing_id=listing.id))
|
||||
if listing.user_id == current_user.id:
|
||||
flash(_("You cannot review your own listing."), "warning")
|
||||
return redirect(url_for("listings.detail", listing_id=listing.id))
|
||||
|
||||
if request.method == "POST":
|
||||
rating = request.form.get("rating", type=int)
|
||||
body = request.form.get("body", "").strip()
|
||||
try:
|
||||
rev_svc.create_review(listing, current_user, rating, body)
|
||||
flash(_("Review submitted. Thanks!"), "success")
|
||||
return redirect(url_for("listings.detail", listing_id=listing.id))
|
||||
except rev_svc.ReviewError as e:
|
||||
flash(str(e), "danger")
|
||||
|
||||
return render_template("listings/review_form.html", listing=listing)
|
||||
|
||||
|
||||
# --- my listings ---
|
||||
@listings_bp.route("/my/listings")
|
||||
@login_required
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Main blueprint: landing page and health check."""
|
||||
from flask import Blueprint, render_template, jsonify
|
||||
"""Main blueprint: landing page, health check, SEO files."""
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, render_template, jsonify, Response, request
|
||||
|
||||
main_bp = Blueprint("main", __name__)
|
||||
|
||||
@@ -12,3 +13,91 @@ def index():
|
||||
@main_bp.route("/healthz")
|
||||
def healthz():
|
||||
return jsonify(status="ok")
|
||||
|
||||
|
||||
@main_bp.route("/robots.txt")
|
||||
def robots():
|
||||
lines = [
|
||||
"User-agent: *",
|
||||
"Disallow: /admin",
|
||||
"Disallow: /auth/",
|
||||
"Disallow: /my/",
|
||||
"Disallow: /messages",
|
||||
"Disallow: /billing/",
|
||||
f"Sitemap: {request.host_url}sitemap.xml",
|
||||
]
|
||||
return Response("\n".join(lines), mimetype="text/plain")
|
||||
|
||||
|
||||
@main_bp.route("/sitemap.xml")
|
||||
def sitemap():
|
||||
from app.extensions import db
|
||||
from app.models.listing import Listing
|
||||
from app.models.category import Category
|
||||
from app.models.enums import ListingStatus
|
||||
|
||||
base = request.host_url.rstrip("/")
|
||||
urls = []
|
||||
|
||||
# static pages
|
||||
for path in ("", "/listings", "/pricing", "/sponsors"):
|
||||
urls.append({"loc": f"{base}{path}", "changefreq": "daily", "priority": "0.8"})
|
||||
|
||||
# categories
|
||||
for cat in Category.query.filter_by(is_active=True).all():
|
||||
urls.append({
|
||||
"loc": f"{base}/listings?category={cat.id}",
|
||||
"changefreq": "daily",
|
||||
"priority": "0.7",
|
||||
})
|
||||
|
||||
# active listings (last 500 by bump/created for crawl budget)
|
||||
listings = (Listing.query
|
||||
.filter(Listing.status == ListingStatus.active,
|
||||
Listing.expires_at > datetime.utcnow())
|
||||
.order_by(Listing.created_at.desc())
|
||||
.limit(500).all())
|
||||
for l in listings:
|
||||
urls.append({
|
||||
"loc": f"{base}/listings/{l.id}",
|
||||
"lastmod": l.updated_at.strftime("%Y-%m-%d"),
|
||||
"changefreq": "weekly",
|
||||
"priority": "0.6",
|
||||
})
|
||||
|
||||
xml = render_template("sitemap.xml", urls=urls)
|
||||
return Response(xml, mimetype="application/xml")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SEO landing pages
|
||||
# ---------------------------------------------------------------------------
|
||||
@main_bp.route("/classifieds/category/<slug>")
|
||||
def category_landing(slug):
|
||||
from app.extensions import db
|
||||
from app.models.category import Category
|
||||
from app.models.listing import Listing
|
||||
from app.models.enums import ListingStatus
|
||||
cat = Category.query.filter_by(slug=slug, is_active=True).first_or_404()
|
||||
listings = (Listing.query
|
||||
.filter(Listing.category_id == cat.id,
|
||||
Listing.status == ListingStatus.active,
|
||||
Listing.expires_at > datetime.utcnow())
|
||||
.order_by(Listing.created_at.desc())
|
||||
.limit(20).all())
|
||||
return render_template("main/category_landing.html", cat=cat, listings=listings)
|
||||
|
||||
|
||||
@main_bp.route("/classifieds/state/<state>")
|
||||
def state_landing(state):
|
||||
from app.extensions import db
|
||||
from app.models.listing import Listing
|
||||
from app.models.enums import ListingStatus
|
||||
state = state.upper()
|
||||
listings = (Listing.query
|
||||
.filter(Listing.state == state,
|
||||
Listing.status == ListingStatus.active,
|
||||
Listing.expires_at > datetime.utcnow())
|
||||
.order_by(Listing.created_at.desc())
|
||||
.limit(20).all())
|
||||
return render_template("main/state_landing.html", state=state, listings=listings)
|
||||
|
||||
Reference in New Issue
Block a user