From 4e0799e16319e03797e915e93db8326a8b302784 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 25 Jun 2014 11:47:44 +0200 Subject: [PATCH 1/9] [FIX] _handle_exception() logic amongst requests and ir_http objects Changes in website's ir_http#_handle_exception(): - exception is mandatory, can't be None anymore - we don't touch non website_enabled requests - we don't touch explicits plain responses from parent - logic flow is now easier to read (I hope so) Change in website's ir_http#_dispatch(): - In case of real 404, instead of returning self._handle_exception(), just let parent do the job (so we call super()) --- addons/website/models/ir_http.py | 85 ++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index 65773837214..be1e46fe2e2 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -60,7 +60,6 @@ class ir_http(orm.AbstractModel): request.lang = request.context['lang'] = path.pop(1) path = '/'.join(path) or '/' return self.reroute(path) - return self._handle_exception(code=404) return super(ir_http, self)._dispatch() def reroute(self, path): @@ -93,51 +92,61 @@ class ir_http(orm.AbstractModel): if str(arg.id) != placeholder.value and placeholder.value != good_slug: # TODO: properly recompose the url instead of using replace() url = url.replace(placeholder.value, good_slug) - except KeyError: - return self._handle_exception(werkzeug.exceptions.NotFound()) + except KeyError, e: + return self._handle_exception(e, code=404) if url != original_url: werkzeug.exceptions.abort(werkzeug.utils.redirect(url)) - def _handle_exception(self, exception=None, code=500): - try: + def _handle_exception(self, exception, code=500): + is_website_request = getattr(request, 'website_enabled', False) and request.website + if not is_website_request: + # Don't touch non website requests exception handling return super(ir_http, self)._handle_exception(exception) - except Exception: - if getattr(request, 'website_enabled', False) and request.website: - values = dict( - exception=exception, - traceback=traceback.format_exc(exception), - ) - if exception: - code = getattr(exception, 'code', code) - if isinstance(exception, ir_qweb.QWebException): - values.update(qweb_exception=exception) - if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError): - code = 403 - if code == 500: - logger.error("500 Internal Server Error:\n\n%s", values['traceback']) - if 'qweb_exception' in values: - view = request.registry.get("ir.ui.view") - views = view._views_get(request.cr, request.uid, exception.qweb['template'], request.context) - to_reset = [v for v in views if v.model_data_id.noupdate is True] - values['views'] = to_reset - elif code == 403: - logger.warn("403 Forbidden:\n\n%s", values['traceback']) + else: + try: + response = super(ir_http, self)._handle_exception(exception) + if isinstance(response, Exception): + exception = response + else: + # if parent excplicitely returns a plain response, then we don't touch it + return response + except Exception, e: + exception = e - values.update( - status_message=werkzeug.http.HTTP_STATUS_CODES[code], - status_code=code, - ) + values = dict( + exception=exception, + traceback=traceback.format_exc(exception), + ) + code = getattr(exception, 'code', code) - if not request.uid: - self._auth_method_public() + if isinstance(exception, ir_qweb.QWebException): + values.update(qweb_exception=exception) + if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError): + code = 403 - try: - html = request.website._render('website.%s' % code, values) - except Exception: - html = request.website._render('website.http_error', values) - return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8') + if code == 500: + logger.error("500 Internal Server Error:\n\n%s", values['traceback']) + if 'qweb_exception' in values: + view = request.registry.get("ir.ui.view") + views = view._views_get(request.cr, request.uid, exception.qweb['template'], request.context) + to_reset = [v for v in views if v.model_data_id.noupdate is True] + values['views'] = to_reset + elif code == 403: + logger.warn("403 Forbidden:\n\n%s", values['traceback']) - raise + values.update( + status_message=werkzeug.http.HTTP_STATUS_CODES[code], + status_code=code, + ) + + if not request.uid: + self._auth_method_public() + + try: + html = request.website._render('website.%s' % code, values) + except Exception: + html = request.website._render('website.http_error', values) + return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8') class ModelConverter(ir.ir_http.ModelConverter): def __init__(self, url_map, model=False): From f988d7503537990b31b9e643e3d69ae7e27b1375 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 25 Jun 2014 12:20:40 +0200 Subject: [PATCH 2/9] [FIX] check after openerp's AccessErrors in website's _handle_exception --- addons/website/models/ir_http.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index be1e46fe2e2..d16077a3828 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -98,7 +98,7 @@ class ir_http(orm.AbstractModel): werkzeug.exceptions.abort(werkzeug.utils.redirect(url)) def _handle_exception(self, exception, code=500): - is_website_request = getattr(request, 'website_enabled', False) and request.website + is_website_request = bool(getattr(request, 'website_enabled', False) and request.website) if not is_website_request: # Don't touch non website requests exception handling return super(ir_http, self)._handle_exception(exception) @@ -119,6 +119,9 @@ class ir_http(orm.AbstractModel): ) code = getattr(exception, 'code', code) + if isinstance(exception, openerp.exceptions.AccessError): + code = 403 + if isinstance(exception, ir_qweb.QWebException): values.update(qweb_exception=exception) if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError): From 1a4bfa28c42ea5147efefef34c4982e37f9cff68 Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Wed, 25 Jun 2014 13:38:56 +0200 Subject: [PATCH 3/9] [FIX] Sitemap - Back forward from saas-5 5cc1317a5f4525e8748692c69c3854773cf95454 to have the url_root in sitemap --- addons/website/controllers/main.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index 5025e5dbd83..e2aeb0daf48 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -105,8 +105,11 @@ class Website(openerp.addons.web.controllers.main.Home): locs = request.website.enumerate_pages() while True: start = pages * LOC_PER_SITEMAP - loc_slice = islice(locs, start, start + LOC_PER_SITEMAP) - urls = iuv.render(cr, uid, 'website.sitemap_locs', dict(locs=loc_slice), context=context) + values = { + 'locs': islice(locs, start, start + LOC_PER_SITEMAP), + 'url_root': request.httprequest.url_root[:-1], + } + urls = iuv.render(cr, uid, 'website.sitemap_locs', values, context=context) if urls.strip(): page = iuv.render(cr, uid, 'website.sitemap_xml', dict(content=urls), context=context) if not first_page: From c1378cc1997e20b1015cfaf91d73a4ef91e68cb8 Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Wed, 25 Jun 2014 13:41:35 +0200 Subject: [PATCH 4/9] [FIX] Forum / Sitemap - Restrcit the domain from controller, else the sitemap fetch response without the question --- addons/website_forum/controllers/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/website_forum/controllers/main.py b/addons/website_forum/controllers/main.py index e98af2f21ac..f1a510a18f8 100644 --- a/addons/website_forum/controllers/main.py +++ b/addons/website_forum/controllers/main.py @@ -208,7 +208,7 @@ class WebsiteForum(http.Controller): }, context=context) return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), new_question_id)) - @http.route(['''/forum//question//question/ Date: Wed, 25 Jun 2014 13:45:03 +0200 Subject: [PATCH 5/9] [FIX] Forum - Redirect permanently to the question, if post_id in url is not an answer. --- addons/website_forum/controllers/main.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/website_forum/controllers/main.py b/addons/website_forum/controllers/main.py index f1a510a18f8..346445f478b 100644 --- a/addons/website_forum/controllers/main.py +++ b/addons/website_forum/controllers/main.py @@ -214,6 +214,10 @@ class WebsiteForum(http.Controller): # increment view counter request.registry['forum.post'].set_viewed(cr, SUPERUSER_ID, [question.id], context=context) + if question.parent_id: + redirect_url = "/forum/%s/question/%s" % (slug(forum), slug(question.parent_id)) + return werkzeug.utils.redirect(redirect_url, 301) + filters = 'question' values = self._prepare_forum_values(forum=forum, searches=post) values.update({ From 46d69b53087785f16ef3ecd4684cd619206765e4 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Wed, 25 Jun 2014 15:32:57 +0200 Subject: [PATCH 6/9] [FIX] purchase_requisition: revert f68c835 These changes are not needed anymore with new WMS. --- addons/purchase_requisition/purchase_requisition.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/addons/purchase_requisition/purchase_requisition.py b/addons/purchase_requisition/purchase_requisition.py index 678aadfee6c..5aecc397f25 100644 --- a/addons/purchase_requisition/purchase_requisition.py +++ b/addons/purchase_requisition/purchase_requisition.py @@ -91,8 +91,6 @@ class purchase_requisition(osv.osv): for purchase_order in tender.purchase_ids: purchase_order_obj.action_cancel(cr, uid, [purchase_order.id], context=context) purchase_order_obj.message_post(cr, uid, [purchase_order.id], body=_('Cancelled by the tender associated to this quotation.'), context=context) - procurement_ids = self.pool['procurement.order'].search(cr, uid, [('requisition_id', 'in', ids)], context=context) - self.pool['procurement.order'].action_done(cr, uid, procurement_ids) return self.write(cr, uid, ids, {'state': 'cancel'}) def tender_in_progress(self, cr, uid, ids, context=None): @@ -110,8 +108,6 @@ class purchase_requisition(osv.osv): return True def tender_done(self, cr, uid, ids, context=None): - procurement_ids = self.pool['procurement.order'].search(cr, uid, [('requisition_id', 'in', ids)], context=context) - self.pool['procurement.order'].action_done(cr, uid, procurement_ids) return self.write(cr, uid, ids, {'state': 'done'}, context=context) def open_product_line(self, cr, uid, ids, context=None): @@ -356,10 +352,6 @@ class purchase_order(osv.osv): proc_obj.write(cr, uid, proc_ids, {'purchase_id': po.id}) self.signal_purchase_cancel(cr, uid, [order.id]) po.requisition_id.tender_done(context=context) - if po.requisition_id and all(purchase_id.state in ['draft', 'cancel'] for purchase_id in po.requisition_id.purchase_ids if purchase_id.id != po.id): - procurement_ids = self.pool['procurement.order'].search(cr, uid, [('requisition_id', '=', po.requisition_id.id)], context=context) - for procurement in proc_obj.browse(cr, uid, procurement_ids, context=context): - procurement.move_id.write({'location_id': procurement.move_id.location_dest_id.id}) return res def copy(self, cr, uid, id, default=None, context=None): From 45b2e4e1226d79a4afe515675535ffb27d2505a6 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Wed, 25 Jun 2014 15:38:49 +0200 Subject: [PATCH 7/9] [IMP] purchase_requisition: lint file --- .../purchase_requisition.py | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/addons/purchase_requisition/purchase_requisition.py b/addons/purchase_requisition/purchase_requisition.py index 5aecc397f25..3324bcad7d0 100644 --- a/addons/purchase_requisition/purchase_requisition.py +++ b/addons/purchase_requisition/purchase_requisition.py @@ -19,9 +19,6 @@ # along with this program. If not, see . # ############################################################################## -from datetime import datetime -from dateutil.relativedelta import relativedelta -import time from openerp.osv import fields, osv from openerp.tools.translate import _ @@ -55,7 +52,7 @@ class purchase_requisition(osv.osv): 'procurement_id': fields.many2one('procurement.order', 'Procurement', ondelete='set null'), 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse'), 'state': fields.selection([('draft', 'Draft'), ('in_progress', 'Confirmed'), ('open', 'Bid Selection'), ('done', 'PO Created'), ('cancel', 'Cancelled')], - 'Status', track_visibility='onchange', required=True), + 'Status', track_visibility='onchange', required=True), 'multiple_rfq_per_supplier': fields.boolean('Multiple RFQ per supplier'), 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'), 'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type', required=True), @@ -63,7 +60,7 @@ class purchase_requisition(osv.osv): def _get_picking_in(self, cr, uid, context=None): obj_data = self.pool.get('ir.model.data') - return obj_data.get_object_reference(cr, uid, 'stock','picking_type_in')[1] + return obj_data.get_object_reference(cr, uid, 'stock', 'picking_type_in')[1] _defaults = { 'state': 'draft', @@ -85,8 +82,7 @@ class purchase_requisition(osv.osv): def tender_cancel(self, cr, uid, ids, context=None): purchase_order_obj = self.pool.get('purchase.order') - #try to set all associated quotations to cancel state - purchase_ids = [] + # try to set all associated quotations to cancel state for tender in self.browse(cr, uid, ids, context=context): for purchase_order in tender.purchase_ids: purchase_order_obj.action_cancel(cr, uid, [purchase_order.id], context=context) @@ -163,8 +159,11 @@ class purchase_requisition(osv.osv): date_order = requisition.ordering_date or fields.date.context_today(self, cr, uid, context=context) qty = product_uom._compute_qty(cr, uid, requisition_line.product_uom_id.id, requisition_line.product_qty, default_uom_po_id) supplier_pricelist = supplier.property_product_pricelist_purchase and supplier.property_product_pricelist_purchase.id or False - vals = po_line_obj.onchange_product_id(cr, uid, [], supplier_pricelist, product.id, qty, default_uom_po_id, - supplier.id, date_order=date_order, fiscal_position_id=supplier.property_account_position, date_planned=requisition_line.schedule_date, + vals = po_line_obj.onchange_product_id( + cr, uid, [], supplier_pricelist, product.id, qty, default_uom_po_id, + supplier.id, date_order=date_order, + fiscal_position_id=supplier.property_account_position, + date_planned=requisition_line.schedule_date, name=False, price_unit=False, state='draft', context=context)['value'] vals.update({ 'order_id': purchase_id, @@ -233,8 +232,6 @@ class purchase_requisition(osv.osv): """ Generate all purchase order based on selected lines, should only be called on one tender at a time """ - if context is None: - contex = {} po = self.pool.get('purchase.order') poline = self.pool.get('purchase.order.line') id_per_supplier = {} @@ -267,8 +264,7 @@ class purchase_requisition(osv.osv): id_per_supplier[po_line.partner_id.id] = [po_line] #generate po based on supplier and cancel all previous RFQ - ctx = context.copy() - ctx['force_requisition_id'] = True + ctx = dict(context or {}, force_requisition_id=True) for supplier, product_line in id_per_supplier.items(): #copy a quotation for this supplier and change order_line then validate it quotation_id = po.search(cr, uid, [('requisition_id', '=', tender.id), ('partner_id', '=', supplier)], limit=1)[0] @@ -429,12 +425,9 @@ class procurement_order(osv.osv): return super(procurement_order, self)._run(cr, uid, procurement, context=context) def _check(self, cr, uid, procurement, context=None): - requisition_obj = self.pool.get('purchase.requisition') if procurement.rule_id and procurement.rule_id.action == 'buy' and procurement.product_id.purchase_requisition: if procurement.requisition_id.state == 'done': if any([purchase.shipped for purchase in procurement.requisition_id.purchase_ids]): return True return False return super(procurement_order, self)._check(cr, uid, procurement, context=context) - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 141e1b2615ee992b111da7d7a2070f25b5de8318 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 25 Jun 2014 17:41:24 +0200 Subject: [PATCH 8/9] [FIX] http.dispatch: call handle_exception() even w/ session missing/expired This gives JSONRequests a chance to return a proper JSON-RPC result when an HTTPException is raised downstream, instead of returning a plain HTML 404 error. --- openerp/http.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openerp/http.py b/openerp/http.py index d32d00326f7..15565d2f7fa 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -1118,7 +1118,10 @@ class Root(object): request = self.get_request(httprequest) def _dispatch_nodb(): - func, arguments = self.nodb_routing_map.bind_to_environ(request.httprequest.environ).match() + try: + func, arguments = self.nodb_routing_map.bind_to_environ(request.httprequest.environ).match() + except werkzeug.exceptions.HTTPException, e: + return request._handle_exception(e) request.set_handler(func, arguments, "none") result = request.dispatch() return result From fa739ac8deccbcaa494b170fa238b6b60a0867f3 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 25 Jun 2014 18:37:03 +0200 Subject: [PATCH 9/9] [FIX] website: default mimetype for web-published attachments, avoids crash --- addons/website/models/ir_http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index 9250a2f6109..75a665f6440 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -120,7 +120,7 @@ class ir_http(orm.AbstractModel): if response.status_code == 304: return response - response.mimetype = attach[0]['mimetype'] + response.mimetype = attach[0]['mimetype'] or 'application/octet-stream' response.data = datas.decode('base64') return response