Files
odoo_source/addons/website/controllers/model_page.py
T
198e226f5b [IMP] website: Expose models on the website
This commit implements a way to expose any model publicly on the website. Both manual and
existing models can create such pages called 'Model Pages'.
A manual model is a model that has been created by the user at runtime, via the technical menu,
via studio, or with an xml declaration.

The main interface to easily create such page is in Studio, in the tab Website Integration. But a
partner or developer could easily import those without studio installed. That's the reason most of
the code to handle the display of such pages is present in the website module directly.

The listing can handle two display modes (Grid or List). Once the user changes the display mode,
the latest value is set in the session to be remembered for the next visit of the page.
A default_layout can be set and is customizable in the website editor or from the backend of Website.
This value is linked to the page to display, so each listing page can use a different layout by default.

The route on which the models are exposed is:
"/model/<string:page_name_slugified>",

With the derived routes:
"/model/<string:page_name_slugified>/page/<int:page_number>",
"/model/<string:page_name_slugified>/<string:record_slug>"

task-id-3231144

Part-of: odoo/odoo#130544
Co-authored-by: Florent Dardenne (dafl) <dafl@odoo.com>
Co-authored-by: Luca Vitali (luvi) <luvi@odoo.com>
2023-10-26 12:10:08 +00:00

135 lines
5.1 KiB
Python

import ast
import werkzeug
from odoo.addons.http_routing.models.ir_http import slug, unslug
from odoo.http import Controller, request, route
from odoo.osv.expression import AND, OR
class ModelPageController(Controller):
pager_step = 20
@route([
"/model/<string:page_name_slugified>",
"/model/<string:page_name_slugified>/page/<int:page_number>",
"/model/<string:page_name_slugified>/<string:record_slug>",
], website=True, auth="public")
def generic_model(self, page_name_slugified=None, page_number=1, record_slug=None, **searches):
if not page_name_slugified:
raise werkzeug.exceptions.NotFound()
website = request.website
page_type = "listing"
if record_slug is not None:
page_type = "single"
website_page_domain = AND([
[("page_type", "=", page_type)],
[("name_slugified", "=", page_name_slugified)],
[("website_published", "=", True)],
website.website_domain(),
])
page = request.env["website.controller.page"].search(website_page_domain, limit=1)
if not page:
raise werkzeug.exceptions.NotFound()
view = page.sudo().view_id
if not view:
raise werkzeug.exceptions.NotFound()
target_model_name = page.sudo().model_id.model
Model = request.env[target_model_name]
if not Model.check_access_rights("read", raise_exception=False):
raise werkzeug.exceptions.Forbidden()
rec_domain = ast.literal_eval(page.record_domain or "[]")
domains = [rec_domain]
if record_slug:
_, res_id = unslug(record_slug)
record = Model.browse(res_id).filtered_domain(AND(domains))
# We check for slug matching because we are not entirely sure
# that we end up seeing record for the right model
# i.e. in case of a redirect when a "single" page doesn't match the listing
if not record.exists() or record_slug != slug(record):
raise werkzeug.exceptions.NotFound()
listing = request.env["website.controller.page"].search(AND([
[("name_slugified", "=", page_name_slugified)],
[("page_type", "=", "listing")],
[("model", "=", target_model_name)],
website.website_domain(),
]))
render_context = {
"main_object": page.sudo(), # The template reads some fields that are actually on view
"record": record,
"listing": {
'href': '.',
'name': listing.page_name
} if listing else False
}
return request.render(view.key, render_context)
layout_mode = request.session.get(f'website_{view.id}_layout_mode')
if not layout_mode:
# use the default layout set for this page
layout_mode = page.default_layout
searches.setdefault("search", "")
searches.setdefault("order", "create_date desc")
single_record_pages = request.env["website.controller.page"].search(AND([
[("page_type", "=", "single")],
[("model", "=", target_model_name)],
website.website_domain(),
]))
single_record_page = single_record_pages.filtered(lambda rec: rec.name_slugified == page_name_slugified)
if single_record_page:
single_record_page = single_record_page[0]
else:
single_record_page = single_record_pages[:1]
def record_to_url(record):
if not single_record_page:
return None
return "/model/%s/%s" % (single_record_page.name_slugified, slug(record))
if searches["search"]:
# _name_search doesn't take offset, we reimplement the logic that builds the name domain here
search_fnames = set(Model._rec_names_search or ([Model._rec_name] if Model._rec_name else []))
if "seo_name" in Model._fields:
search_fnames.add("seo_name")
if search_fnames:
name_domain = OR([[(name_field, "ilike", searches["search"])] for name_field in search_fnames])
domains.append(name_domain)
search_count = Model.search_count(AND(domains))
pager = website.pager(
url=f"/model/{page.name_slugified}",
url_args=searches,
total=search_count,
page=page_number,
step=self.pager_step,
scope=5,
)
records = Model.search(AND(domains), limit=self.pager_step, offset=self.pager_step * (page_number - 1), order=searches["order"])
render_context = {
"order_by": searches["order"],
"search": searches["search"],
"search_count": search_count,
"pager": pager,
"records": records,
"record_to_url": record_to_url,
"layout_mode": layout_mode,
"view_id": view.id,
"main_object": page.sudo(), # The template reads some fields that are actually on view
}
return request.render(view.key, render_context)