Reported: clicking "Post a Listing" on /classifieds while logged out threw a raw 500 instead of prompting login. Root cause is upstream, in this Odoo 19 build's own http.py: when auth='user' raises SessionExpiredException for an anonymous visitor, Request._serve_db's `finally: self.env = None` clears the request env before the exception reaches the website error handler, which then tries to build the login redirect via self.env['ir.http']._redirect(...) and crashes with TypeError: 'NoneType' object is not subscriptable. This isn't specific to any one route - it reproduces on every auth='user' + website=True page hit anonymously, including stock Odoo's own /my (traced this back to the true cause rather than continuing to treat it as an unrelated environment quirk, since it now has a real reported symptom). Since core can't be patched here, worked around it at the route level across all 9 affected pages (classifieds new/my/renew, membership my/renew/card, benefits my, school attendance, portal my/school, event checkin): switched from auth='user' to auth='public' and added an explicit `if request.env.user._is_public(): return request.redirect(...)` check at the top of each handler, before Odoo's own auth layer ever gets a chance to raise. The jsonrpc AJAX endpoints (attendance save, checkin scan/dashboard) were left on auth='user' since they return a JSON error rather than attempting an HTML redirect, so they don't hit this path. Verified against a live Odoo 19 + Postgres 16 container: reproduced the original crash pre-fix, then confirmed all 9 previously-broken routes now 303-redirect to /web/login?redirect=<path> when hit anonymously, that the login page carries the redirect target, that logged-in access is unaffected (200), and that the separate "logged in but lacking a required group" case (event check-in without Registration Desk) still degrades gracefully to a clean 403 rather than a crash. Full regression: 48/48 tests pass across the six touched modules. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
110 lines
4.9 KiB
Python
110 lines
4.9 KiB
Python
import base64
|
|
|
|
from odoo import http
|
|
from odoo.http import request
|
|
|
|
MAX_IMAGES = 3
|
|
|
|
|
|
def _is_module_installed(env, module_name):
|
|
return bool(env['ir.module.module'].sudo().search_count(
|
|
[('name', '=', module_name), ('state', '=', 'installed')]
|
|
))
|
|
|
|
|
|
def _has_active_membership(partner):
|
|
"""Soft-check: only meaningful if community_membership is installed."""
|
|
if 'membership_state' not in partner._fields:
|
|
return True
|
|
return partner.membership_state in ('active', 'renewal_due')
|
|
|
|
|
|
def _redirect_to_login_if_public(path):
|
|
"""Manual login-required redirect.
|
|
|
|
Routes here use auth='public' (not 'user') and check this explicitly,
|
|
because Odoo's own auth='user' + website=True error handling has a bug
|
|
in this version: SessionExpiredException triggers a login redirect via
|
|
self.env['ir.http']._redirect(...), but self.env has already been reset
|
|
to None by the finally block in Request._serve_db by the time the error
|
|
handler runs, causing a 500 instead of a redirect. Checking auth
|
|
ourselves and issuing a plain redirect avoids that code path entirely.
|
|
"""
|
|
if request.env.user._is_public():
|
|
return request.redirect(f'/web/login?redirect={path}')
|
|
return None
|
|
|
|
|
|
class ClassifiedsController(http.Controller):
|
|
|
|
@http.route(['/classifieds'], type='http', auth='public', website=True, sitemap=True)
|
|
def classifieds_list(self, category=None, **kwargs):
|
|
domain = [('state', '=', 'published')]
|
|
if category:
|
|
domain.append(('category', '=', category))
|
|
listings = request.env['community.classified'].sudo().search(domain)
|
|
return request.render('community_classifieds.classifieds_list_page', {
|
|
'listings': listings,
|
|
'category': category,
|
|
})
|
|
|
|
@http.route(['/classifieds/<int:classified_id>'], type='http', auth='public', website=True, sitemap=False)
|
|
def classifieds_detail(self, classified_id, **kwargs):
|
|
listing = request.env['community.classified'].sudo().browse(classified_id)
|
|
if not listing.exists() or listing.state != 'published':
|
|
return request.not_found()
|
|
listing._increment_view_count()
|
|
return request.render('community_classifieds.classifieds_detail_page', {'listing': listing})
|
|
|
|
@http.route(['/classifieds/new'], type='http', auth='public', website=True)
|
|
def classifieds_new(self, **kwargs):
|
|
redirect = _redirect_to_login_if_public('/classifieds/new')
|
|
if redirect:
|
|
return redirect
|
|
partner = request.env.user.partner_id
|
|
if _is_module_installed(request.env, 'community_membership') and not _has_active_membership(partner):
|
|
return request.render('community_classifieds.classifieds_membership_required', {})
|
|
|
|
if request.httprequest.method == 'POST':
|
|
image_ids = []
|
|
for field_name in ('image1', 'image2', 'image3'):
|
|
upload = kwargs.get(field_name)
|
|
if upload and getattr(upload, 'filename', None):
|
|
image_ids.append((0, 0, {'image': base64.b64encode(upload.read())}))
|
|
|
|
listing = request.env['community.classified'].sudo().create({
|
|
'title': kwargs.get('title', '').strip(),
|
|
'category': kwargs.get('category', 'other'),
|
|
'description': kwargs.get('description', ''),
|
|
'contact_method': kwargs.get('contact_method', 'email'),
|
|
'contact_email': kwargs.get('contact_email', '').strip(),
|
|
'contact_phone': kwargs.get('contact_phone', '').strip(),
|
|
'poster_partner_id': partner.id,
|
|
'image_ids': image_ids[:MAX_IMAGES],
|
|
})
|
|
return request.redirect(f'/classifieds/my?posted={listing.id}')
|
|
|
|
return request.render('community_classifieds.classifieds_new_page', {})
|
|
|
|
@http.route(['/classifieds/my'], type='http', auth='public', website=True)
|
|
def classifieds_my(self, **kwargs):
|
|
redirect = _redirect_to_login_if_public('/classifieds/my')
|
|
if redirect:
|
|
return redirect
|
|
partner = request.env.user.partner_id
|
|
listings = request.env['community.classified'].sudo().search([('poster_partner_id', '=', partner.id)])
|
|
return request.render('community_classifieds.classifieds_my_page', {'listings': listings})
|
|
|
|
@http.route(['/classifieds/<int:classified_id>/renew'], type='http', auth='public', website=True)
|
|
def classifieds_renew(self, classified_id, **kwargs):
|
|
redirect = _redirect_to_login_if_public(f'/classifieds/{classified_id}/renew')
|
|
if redirect:
|
|
return redirect
|
|
partner = request.env.user.partner_id
|
|
listing = request.env['community.classified'].sudo().search([
|
|
('id', '=', classified_id), ('poster_partner_id', '=', partner.id),
|
|
], limit=1)
|
|
if listing:
|
|
listing.action_renew()
|
|
return request.redirect('/classifieds/my')
|