06/15 Phase 3 codes
This commit is contained in:
@@ -101,7 +101,7 @@ def verify_email(token):
|
||||
if user_id is None:
|
||||
flash(_("Verification link is invalid or expired."), "danger")
|
||||
return redirect(url_for("auth.login"))
|
||||
user = User.query.get(user_id)
|
||||
user = db.session.get(User, user_id)
|
||||
if user is None:
|
||||
abort(404)
|
||||
if not user.email_verified:
|
||||
@@ -136,7 +136,7 @@ def reset_password(token):
|
||||
if user_id is None:
|
||||
flash(_("Reset link is invalid or expired."), "danger")
|
||||
return redirect(url_for("auth.reset_request"))
|
||||
user = User.query.get(user_id)
|
||||
user = db.session.get(User, user_id)
|
||||
if user is None:
|
||||
abort(404)
|
||||
form = ResetForm()
|
||||
|
||||
@@ -109,7 +109,7 @@ def create():
|
||||
|
||||
category = None
|
||||
if request.method == "POST" and form.category_id.data:
|
||||
category = Category.query.get(form.category_id.data)
|
||||
category = db.session.get(Category, form.category_id.data)
|
||||
|
||||
if form.validate_on_submit() and category:
|
||||
raw_attrs = _parse_attributes(category)
|
||||
@@ -156,7 +156,7 @@ def edit(listing_id):
|
||||
form.price.data = (listing.price_cents / 100) if listing.price_cents else None
|
||||
form.zip.data = listing.zip
|
||||
|
||||
category = Category.query.get(form.category_id.data or listing.category_id)
|
||||
category = db.session.get(Category, form.category_id.data or listing.category_id)
|
||||
|
||||
if form.validate_on_submit() and category:
|
||||
raw_attrs = _parse_attributes(category)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
"""Messaging form."""
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import TextAreaField, SubmitField
|
||||
from wtforms.validators import DataRequired, Length
|
||||
from flask_babel import lazy_gettext as _l
|
||||
|
||||
|
||||
class MessageForm(FlaskForm):
|
||||
body = TextAreaField(_l("Message"),
|
||||
validators=[DataRequired(), Length(1, 4000)])
|
||||
submit = SubmitField(_l("Send"))
|
||||
@@ -0,0 +1,122 @@
|
||||
"""Messaging + Favorites routes."""
|
||||
from flask import (Blueprint, render_template, redirect, url_for,
|
||||
flash, abort, request, jsonify)
|
||||
from flask_login import login_required, current_user
|
||||
from flask_babel import gettext as _
|
||||
|
||||
from app.extensions import db, limiter
|
||||
from app.models.listing import Listing
|
||||
from app.models.messaging import Conversation
|
||||
from app.services import messaging as msvc
|
||||
from app.services import favorites as fsvc
|
||||
from app.services.contact import contact_revealed, mask_body
|
||||
from app.blueprints.messaging.forms import MessageForm
|
||||
|
||||
messaging_bp = Blueprint("messaging", __name__)
|
||||
|
||||
|
||||
# ── Inbox ──────────────────────────────────────────────────────────────────
|
||||
@messaging_bp.route("/messages")
|
||||
@login_required
|
||||
def inbox():
|
||||
page = request.args.get("page", 1, type=int)
|
||||
pagination = msvc.inbox(current_user, page=page)
|
||||
return render_template("messaging/inbox.html",
|
||||
pagination=pagination,
|
||||
unread_total=msvc.total_unread(current_user))
|
||||
|
||||
|
||||
# ── Conversation thread ────────────────────────────────────────────────────
|
||||
@messaging_bp.route("/messages/<int:conv_id>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def conversation(conv_id):
|
||||
conv = db.session.get(Conversation, conv_id)
|
||||
if conv is None:
|
||||
abort(404)
|
||||
if current_user.id not in (conv.buyer_id, conv.seller_id):
|
||||
abort(403)
|
||||
|
||||
msvc.mark_conversation_read(conv, current_user)
|
||||
reveal = contact_revealed(current_user)
|
||||
form = MessageForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
try:
|
||||
msvc.send_message(conv, current_user, form.body.data)
|
||||
return redirect(url_for("messaging.conversation", conv_id=conv.id))
|
||||
except msvc.MessagingError as e:
|
||||
flash(str(e), "danger")
|
||||
|
||||
# mask contact info in messages for low-trust users
|
||||
masked_messages = [
|
||||
(msg, mask_body(msg.body, reveal=reveal))
|
||||
for msg in conv.messages
|
||||
]
|
||||
return render_template("messaging/conversation.html",
|
||||
conv=conv, form=form,
|
||||
masked_messages=masked_messages,
|
||||
reveal=reveal,
|
||||
other=conv.other_party(current_user))
|
||||
|
||||
|
||||
# ── Start conversation from listing detail ─────────────────────────────────
|
||||
@messaging_bp.route("/listings/<int:listing_id>/contact", methods=["GET", "POST"])
|
||||
@login_required
|
||||
@limiter.limit("20 per hour", methods=["POST"])
|
||||
def start_conversation(listing_id):
|
||||
listing = db.session.get(Listing, listing_id)
|
||||
if listing is None:
|
||||
abort(404)
|
||||
if listing.user_id == current_user.id:
|
||||
flash(_("You cannot message your own listing."), "warning")
|
||||
return redirect(url_for("listings.detail", listing_id=listing_id))
|
||||
|
||||
try:
|
||||
conv, created = msvc.get_or_create_conversation(listing, current_user)
|
||||
except msvc.MessagingError as e:
|
||||
flash(str(e), "danger")
|
||||
return redirect(url_for("listings.detail", listing_id=listing_id))
|
||||
|
||||
form = MessageForm()
|
||||
if form.validate_on_submit():
|
||||
try:
|
||||
msvc.send_message(conv, current_user, form.body.data)
|
||||
flash(_("Message sent."), "success")
|
||||
return redirect(url_for("messaging.conversation", conv_id=conv.id))
|
||||
except msvc.MessagingError as e:
|
||||
flash(str(e), "danger")
|
||||
|
||||
return render_template("messaging/start.html",
|
||||
listing=listing, conv=conv, form=form,
|
||||
created=created)
|
||||
|
||||
|
||||
# ── Favorites ──────────────────────────────────────────────────────────────
|
||||
@messaging_bp.route("/listings/<int:listing_id>/favorite", methods=["POST"])
|
||||
@login_required
|
||||
def toggle_favorite(listing_id):
|
||||
listing = db.session.get(Listing, listing_id)
|
||||
if listing is None:
|
||||
abort(404)
|
||||
now_fav = fsvc.toggle_favorite(current_user.id, listing_id)
|
||||
# AJAX or form fallback
|
||||
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
||||
return jsonify(favorited=now_fav)
|
||||
flash(_("Saved.") if now_fav else _("Removed from saved."), "info")
|
||||
return redirect(request.referrer or url_for("listings.detail",
|
||||
listing_id=listing_id))
|
||||
|
||||
|
||||
@messaging_bp.route("/my/favorites")
|
||||
@login_required
|
||||
def my_favorites():
|
||||
page = request.args.get("page", 1, type=int)
|
||||
pagination = fsvc.user_favorites(current_user.id, page=page)
|
||||
return render_template("messaging/favorites.html", pagination=pagination)
|
||||
|
||||
|
||||
# ── Unread count API (for nav badge polling) ───────────────────────────────
|
||||
@messaging_bp.route("/api/unread")
|
||||
@login_required
|
||||
def unread_count():
|
||||
return jsonify(unread=msvc.total_unread(current_user))
|
||||
Reference in New Issue
Block a user