[IMP] website(_*): replace search callbacks by a mixin
Before this commit the `_search_get_detail` result contained callback functions to handle special behavior during fetching and rendering. After this commit a `website.searchable.mixin` is introduced that must be inherited by models that participate in website-based searches. Custom behavior previously achieved with callbacks is now achieved by overloading methods of this mixin. task-2379555 https://github.com/odoo/odoo/pull/65871 Part-of: odoo/odoo#65871
This commit is contained in:
@@ -551,7 +551,7 @@ class Website(Home):
|
||||
|
||||
pages = Page.search(domain, order=sort_order)
|
||||
if sortby != 'url' or not request.session.debug:
|
||||
pages = pages.filtered(pages._is_most_specific_page)
|
||||
pages = pages._get_most_specific_pages()
|
||||
pages_count = len(pages)
|
||||
|
||||
step = 50
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.addons.website.tools import text_from_html
|
||||
from odoo.http import request
|
||||
from odoo.osv import expression
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.tools import escape_psql
|
||||
from odoo.tools.json import scriptsafe as json_safe
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -252,3 +255,85 @@ class WebsitePublishedMultiMixin(WebsitePublishedMixin):
|
||||
return (['!'] if value is False else []) + expression.AND([is_published, on_current_website])
|
||||
else: # should be in the backend, return things that are published anywhere
|
||||
return is_published
|
||||
|
||||
|
||||
class WebsiteSearchableMixin(models.AbstractModel):
|
||||
"""Mixin to be inherited by all models that need to searchable through website"""
|
||||
_name = 'website.searchable.mixin'
|
||||
_description = 'Website Searchable Mixin'
|
||||
|
||||
@api.model
|
||||
def _search_build_domain(self, domain_list, search, fields, extra=None):
|
||||
"""
|
||||
Builds a search domain AND-combining a base domain with partial matches of each term in
|
||||
the search expression in any of the fields.
|
||||
|
||||
:param domain_list: base domain list combined in the search expression
|
||||
:param search: search expression string
|
||||
:param fields: list of field names to match the terms of the search expression with
|
||||
:param extra: function that returns an additional subdomain for a search term
|
||||
|
||||
:return: domain limited to the matches of the search expression
|
||||
"""
|
||||
domains = domain_list.copy()
|
||||
if search:
|
||||
for search_term in search.split(' '):
|
||||
subdomains = [[(field, 'ilike', escape_psql(search_term))] for field in fields]
|
||||
if extra:
|
||||
subdomains.append(extra(self.env, search_term))
|
||||
domains.append(expression.OR(subdomains))
|
||||
return expression.AND(domains)
|
||||
|
||||
@api.model
|
||||
def _search_get_detail(self, website, order, options):
|
||||
"""
|
||||
Returns indications on how to perform the searches
|
||||
|
||||
:param website: website within which the search is done
|
||||
:param order: order in which the results are to be returned
|
||||
:param options: search options
|
||||
|
||||
:return: search detail as expected in elements of the result of website._search_get_details()
|
||||
These elements contain the following fields:
|
||||
- model: name of the searched model
|
||||
- base_domain: list of domains within which to perform the search
|
||||
- search_fields: fields within which the search term must be found
|
||||
- fetch_fields: fields from which data must be fetched
|
||||
- mapping: mapping from the results towards the structure used in rendering templates.
|
||||
The mapping is a dict that associates the rendering name of each field
|
||||
to a dict containing the 'name' of the field in the results list and the 'type'
|
||||
that must be used for rendering the value
|
||||
- icon: name of the icon to use if there is no image
|
||||
|
||||
This method must be implemented by all models that inherit this mixin.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@api.model
|
||||
def _search_fetch(self, search_detail, search, limit, order):
|
||||
fields = search_detail['search_fields']
|
||||
base_domain = search_detail['base_domain']
|
||||
domain = self._search_build_domain(base_domain, search, fields, search_detail.get('search_extra'))
|
||||
model = self.sudo() if search_detail.get('requires_sudo') else self
|
||||
results = model.search(
|
||||
domain,
|
||||
limit=limit,
|
||||
order=search_detail.get('order', order)
|
||||
)
|
||||
count = model.search_count(domain)
|
||||
return results, count
|
||||
|
||||
def _search_render_results(self, fetch_fields, mapping, icon, limit):
|
||||
results_data = self.read(fetch_fields)[:limit]
|
||||
for result in results_data:
|
||||
result['_fa'] = icon
|
||||
result['_mapping'] = mapping
|
||||
html_fields = [config['name'] for config in mapping.values() if config.get('html')]
|
||||
if html_fields:
|
||||
for result, data in zip(self, results_data):
|
||||
for html_field in html_fields:
|
||||
if data[html_field]:
|
||||
text = text_from_html(data[html_field])
|
||||
text = re.sub('\\s+', ' ', text).strip()
|
||||
data[html_field] = text
|
||||
return results_data
|
||||
|
||||
@@ -1483,19 +1483,7 @@ class Website(models.Model):
|
||||
:param order: order in which the results are to be returned
|
||||
:param options: search options
|
||||
|
||||
:return list of objects with:
|
||||
- model: name of the searched model
|
||||
- base_domain: domain within which to perform the search
|
||||
- search_fields: fields within which the search term must be found
|
||||
- secondary_search_fields: optionally performs a second search
|
||||
- filter_function: optionally filters obtained results
|
||||
- fetch_fields: fields from which data must be fetched
|
||||
- patch_data_function: optionally sets additional fields for rendering
|
||||
- mapping: mapping from the results towards the structure used in rendering templates.
|
||||
The mapping is a dict that associates the rendering name of each field
|
||||
to a dict containing the 'name' of the field in the results list and the 'type'
|
||||
that must be used for rendering the value
|
||||
- icon: name of the icon to use if there is no image
|
||||
:return: list of search details obtained from the `website.searchable.mixin`'s `_search_get_detail()`
|
||||
"""
|
||||
result = []
|
||||
if search_type in ['pages', 'all']:
|
||||
@@ -1554,36 +1542,11 @@ class Website(models.Model):
|
||||
total_count = 0
|
||||
for search_detail in search_details:
|
||||
model = self.env[search_detail['model']]
|
||||
fields = search_detail['search_fields']
|
||||
base_domain = search_detail['base_domain']
|
||||
domain = self._search_build_domain(base_domain, search, fields, search_detail.get('search_extra'))
|
||||
if search_detail.get('requires_sudo'):
|
||||
model = model.sudo()
|
||||
results = model.search(
|
||||
domain,
|
||||
limit=limit,
|
||||
order=search_detail.get('order', order)
|
||||
)
|
||||
if search:
|
||||
base_results = results
|
||||
secondary_fields = search_detail.get('secondary_search_fields')
|
||||
if secondary_fields:
|
||||
domain = self._search_build_domain(base_domain, search, secondary_fields)
|
||||
results = model.search(
|
||||
domain,
|
||||
limit=limit,
|
||||
order=search_detail.get('order', order)
|
||||
)
|
||||
results = results.union(base_results)
|
||||
filter_function = search_detail.get('filter_function')
|
||||
if filter_function:
|
||||
results = results.filtered(lambda result: filter_function(search, result, base_results, results))
|
||||
results, count = model._search_fetch(search_detail, search, limit, order)
|
||||
search_detail['results'] = results
|
||||
count = model.search_count(domain)
|
||||
total_count += count
|
||||
search_detail['count'] = count
|
||||
all_results.append(search_detail)
|
||||
|
||||
return total_count, all_results
|
||||
|
||||
def _search_render_results(self, search_details, limit):
|
||||
@@ -1599,23 +1562,9 @@ class Website(models.Model):
|
||||
for search_detail in search_details:
|
||||
fields = search_detail['fetch_fields']
|
||||
results = search_detail['results']
|
||||
results_data = results.read(fields)[:limit]
|
||||
icon = search_detail['icon']
|
||||
mapping = search_detail['mapping']
|
||||
for result in results_data:
|
||||
result['_fa'] = icon
|
||||
result['_mapping'] = mapping
|
||||
html_fields = [config['name'] for config in mapping.values() if config.get('html')]
|
||||
patch_function = search_detail.get('patch_data_function')
|
||||
if patch_function or html_fields:
|
||||
for result, data in zip(results, results_data):
|
||||
for html_field in html_fields:
|
||||
if data[html_field]:
|
||||
text = self._search_text_from_html(data[html_field])
|
||||
text = re.sub('\\s+', ' ', text).strip()
|
||||
data[html_field] = text
|
||||
if patch_function:
|
||||
patch_function(result, data)
|
||||
results_data = results._search_render_results(fields, mapping, icon, limit)
|
||||
search_detail['results_data'] = results_data
|
||||
return search_details
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import re
|
||||
|
||||
from odoo.addons.http_routing.models.ir_http import slugify
|
||||
from odoo.addons.website.tools import text_from_html
|
||||
from odoo import api, fields, models
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
@@ -11,7 +12,10 @@ from odoo.tools.safe_eval import safe_eval
|
||||
class Page(models.Model):
|
||||
_name = 'website.page'
|
||||
_inherits = {'ir.ui.view': 'view_id'}
|
||||
_inherit = 'website.published.multi.mixin'
|
||||
_inherit = [
|
||||
'website.published.multi.mixin',
|
||||
'website.searchable.mixin',
|
||||
]
|
||||
_description = 'Page'
|
||||
_order = 'website_id'
|
||||
|
||||
@@ -57,14 +61,16 @@ class Page(models.Model):
|
||||
not page.date_publish or page.date_publish < fields.Datetime.now()
|
||||
)
|
||||
|
||||
def _is_most_specific_page(self, page_to_test):
|
||||
'''This will test if page_to_test is the most specific page in self.'''
|
||||
pages_for_url = self.sorted(key=lambda p: not p.website_id).filtered(lambda page: page.url == page_to_test.url)
|
||||
|
||||
# this works because pages are _order'ed by website_id
|
||||
most_specific_page = pages_for_url[0]
|
||||
|
||||
return most_specific_page == page_to_test
|
||||
def _get_most_specific_pages(self):
|
||||
''' Returns the most specific pages in self. '''
|
||||
ids = []
|
||||
previous_page = None
|
||||
# Iterate a single time on the whole list sorted on specific-website first.
|
||||
for page in self.sorted(key=lambda p: (p.url, not p.website_id)):
|
||||
if not previous_page or page.url != previous_page.url:
|
||||
ids.append(page.id)
|
||||
previous_page = page
|
||||
return self.filtered(lambda page: page.id in ids)
|
||||
|
||||
def get_page_properties(self):
|
||||
self.ensure_one()
|
||||
@@ -233,15 +239,6 @@ class Page(models.Model):
|
||||
|
||||
@api.model
|
||||
def _search_get_detail(self, website, order, options):
|
||||
"""
|
||||
Returns indications on how to perform the searches
|
||||
|
||||
:param website: website within which the search is done
|
||||
:param order: order in which the results are to be returned
|
||||
:param options: search options
|
||||
|
||||
:return search detail as expected in elements of the result of website._search_get_details()
|
||||
"""
|
||||
with_description = options['displayDescription']
|
||||
# Read access on website.page requires sudo.
|
||||
requires_sudo = True
|
||||
@@ -250,36 +247,42 @@ class Page(models.Model):
|
||||
# Rule must be reinforced because of sudo.
|
||||
domain.append([('website_published', '=', True)])
|
||||
|
||||
search_fields = ['name', 'url']
|
||||
fetch_fields = ['id', 'name', 'url']
|
||||
mapping = {
|
||||
'name': {'name': 'name', 'type': 'text', 'match': True},
|
||||
'website_url': {'name': 'url', 'type': 'text'},
|
||||
}
|
||||
if with_description:
|
||||
search_fields.append('view_id.arch_db')
|
||||
fetch_fields.append('arch')
|
||||
mapping['description'] = {'name': 'arch', 'type': 'text', 'html': True, 'match': True}
|
||||
def filter_page(search, page, base_pages, all_pages):
|
||||
if not all_pages._is_most_specific_page(page):
|
||||
return False
|
||||
if page in base_pages:
|
||||
return True
|
||||
text = '%s %s' % (page.name, page.url)
|
||||
if with_description:
|
||||
text = '%s %s' % (text, website._search_text_from_html(page.arch))
|
||||
pattern = '|'.join([re.escape(search_term) for search_term in search.split()])
|
||||
return re.findall('(%s)' % pattern, text, flags=re.I) if pattern else False
|
||||
return {
|
||||
'model': 'website.page',
|
||||
'base_domain': domain,
|
||||
'requires_sudo': requires_sudo,
|
||||
'search_fields': ['name', 'url'],
|
||||
'secondary_search_fields': ['name', 'url', 'view_id.arch_db'] if with_description else None,
|
||||
'filter_function': filter_page,
|
||||
'search_fields': search_fields,
|
||||
'fetch_fields': fetch_fields,
|
||||
'mapping': mapping,
|
||||
'icon': 'fa-file-o',
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _search_fetch(self, search_detail, search, limit, order):
|
||||
with_description = 'description' in search_detail['mapping']
|
||||
results, count = super()._search_fetch(search_detail, search, limit, order)
|
||||
def filter_page(search, page, all_pages):
|
||||
# Search might have matched words in the xml tags and parameters therefore we make
|
||||
# sure the terms actually appear inside the text.
|
||||
text = '%s %s %s' % (page.name, page.url, text_from_html(page.arch))
|
||||
pattern = '|'.join([re.escape(search_term) for search_term in search.split()])
|
||||
return re.findall('(%s)' % pattern, text, flags=re.I) if pattern else False
|
||||
if 'url' not in order:
|
||||
results = results._get_most_specific_pages()
|
||||
if search and with_description:
|
||||
results = results.filtered(lambda result: filter_page(search, result, results))
|
||||
return results, count
|
||||
|
||||
|
||||
# this is just a dummy function to be used as ormcache key
|
||||
def _cached_response():
|
||||
|
||||
@@ -14,7 +14,13 @@ from odoo.tools import html2plaintext
|
||||
class Blog(models.Model):
|
||||
_name = 'blog.blog'
|
||||
_description = 'Blog'
|
||||
_inherit = ['mail.thread', 'website.seo.metadata', 'website.multi.mixin', 'website.cover_properties.mixin']
|
||||
_inherit = [
|
||||
'mail.thread',
|
||||
'website.seo.metadata',
|
||||
'website.multi.mixin',
|
||||
'website.cover_properties.mixin',
|
||||
'website.searchable.mixin',
|
||||
]
|
||||
_order = 'name'
|
||||
|
||||
name = fields.Char('Blog Name', required=True, translate=True)
|
||||
@@ -88,7 +94,6 @@ class Blog(models.Model):
|
||||
|
||||
@api.model
|
||||
def _search_get_detail(self, website, order, options):
|
||||
"""See website_page._search_get_detail()"""
|
||||
with_description = options['displayDescription']
|
||||
search_fields = ['name']
|
||||
fetch_fields = ['id', 'name']
|
||||
@@ -100,19 +105,21 @@ class Blog(models.Model):
|
||||
search_fields.append('subtitle')
|
||||
fetch_fields.append('subtitle')
|
||||
mapping['description'] = {'name': 'subtitle', 'type': 'text', 'match': True}
|
||||
def patch_blog(blog, data):
|
||||
data['url'] = '/blog/%s' % data['id']
|
||||
return {
|
||||
'model': 'blog.blog',
|
||||
'base_domain': [website.website_domain()],
|
||||
'search_fields': search_fields,
|
||||
'fetch_fields': fetch_fields,
|
||||
'patch_data_function': patch_blog,
|
||||
'mapping': mapping,
|
||||
'icon': 'fa-rss-square',
|
||||
'order': 'name desc, id desc' if 'name desc' in order else 'name asc, id desc',
|
||||
}
|
||||
|
||||
def _search_render_results(self, fetch_fields, mapping, icon, limit):
|
||||
results_data = super()._search_render_results(fetch_fields, mapping, icon, limit)
|
||||
for data in results_data:
|
||||
data['url'] = '/blog/%s' % data['id']
|
||||
return results_data
|
||||
|
||||
class BlogTagCategory(models.Model):
|
||||
_name = 'blog.tag.category'
|
||||
@@ -145,7 +152,8 @@ class BlogTag(models.Model):
|
||||
class BlogPost(models.Model):
|
||||
_name = "blog.post"
|
||||
_description = "Blog Post"
|
||||
_inherit = ['mail.thread', 'website.seo.metadata', 'website.published.multi.mixin', 'website.cover_properties.mixin']
|
||||
_inherit = ['mail.thread', 'website.seo.metadata', 'website.published.multi.mixin',
|
||||
'website.cover_properties.mixin', 'website.searchable.mixin']
|
||||
_order = 'id DESC'
|
||||
_mail_post_access = 'read'
|
||||
|
||||
@@ -297,7 +305,6 @@ class BlogPost(models.Model):
|
||||
|
||||
@api.model
|
||||
def _search_get_detail(self, website, order, options):
|
||||
"""See website_page._search_get_detail()"""
|
||||
with_description = options['displayDescription']
|
||||
with_date = options['displayDetail']
|
||||
blog = options.get('blog')
|
||||
|
||||
@@ -23,7 +23,8 @@ class Event(models.Model):
|
||||
'event.event',
|
||||
'website.seo.metadata',
|
||||
'website.published.multi.mixin',
|
||||
'website.cover_properties.mixin'
|
||||
'website.cover_properties.mixin',
|
||||
'website.searchable.mixin',
|
||||
]
|
||||
|
||||
def _default_cover_properties(self):
|
||||
@@ -467,7 +468,6 @@ class Event(models.Model):
|
||||
|
||||
@api.model
|
||||
def _search_get_detail(self, website, order, options):
|
||||
"""See website_page._search_get_detail()"""
|
||||
with_description = options['displayDescription']
|
||||
with_date = options['displayDetail']
|
||||
date = options.get('date', 'all')
|
||||
@@ -527,16 +527,11 @@ class Event(models.Model):
|
||||
mapping['description'] = {'name': 'subtitle', 'type': 'text', 'match': True}
|
||||
if with_date:
|
||||
mapping['detail'] = {'name': 'range', 'type': 'html'}
|
||||
def patch_event(event, data):
|
||||
begin = self.env['ir.qweb.field.date'].record_to_html(event, 'date_begin', {})
|
||||
end = self.env['ir.qweb.field.date'].record_to_html(event, 'date_end', {})
|
||||
data['range'] = '%s🠖%s' % (begin, end) if begin != end else begin
|
||||
return {
|
||||
'model': 'event.event',
|
||||
'base_domain': domain,
|
||||
'search_fields': search_fields,
|
||||
'fetch_fields': fetch_fields,
|
||||
'patch_data_function': patch_event if with_date else None,
|
||||
'mapping': mapping,
|
||||
'icon': 'fa-ticket',
|
||||
# for website_event main controller:
|
||||
@@ -546,3 +541,13 @@ class Event(models.Model):
|
||||
'no_date_domain': no_date_domain,
|
||||
'no_country_domain': no_country_domain,
|
||||
}
|
||||
|
||||
def _search_render_results(self, fetch_fields, mapping, icon, limit):
|
||||
with_date = 'detail' in mapping
|
||||
results_data = super()._search_render_results(fetch_fields, mapping, icon, limit)
|
||||
if with_date:
|
||||
for event, data in zip(self, results_data):
|
||||
begin = self.env['ir.qweb.field.date'].record_to_html(event, 'date_begin', {})
|
||||
end = self.env['ir.qweb.field.date'].record_to_html(event, 'date_end', {})
|
||||
data['range'] = '%s🠖%s' % (begin, end) if begin != end else begin
|
||||
return results_data
|
||||
|
||||
@@ -19,7 +19,13 @@ _logger = logging.getLogger(__name__)
|
||||
class Forum(models.Model):
|
||||
_name = 'forum.forum'
|
||||
_description = 'Forum'
|
||||
_inherit = ['mail.thread', 'image.mixin', 'website.seo.metadata', 'website.multi.mixin']
|
||||
_inherit = [
|
||||
'mail.thread',
|
||||
'image.mixin',
|
||||
'website.seo.metadata',
|
||||
'website.multi.mixin',
|
||||
'website.searchable.mixin',
|
||||
]
|
||||
_order = "sequence"
|
||||
|
||||
# description and use
|
||||
@@ -263,7 +269,6 @@ class Forum(models.Model):
|
||||
|
||||
@api.model
|
||||
def _search_get_detail(self, website, order, options):
|
||||
"""See website_page._search_get_detail()"""
|
||||
with_description = options['displayDescription']
|
||||
search_fields = ['name']
|
||||
fetch_fields = ['id', 'name']
|
||||
@@ -275,25 +280,32 @@ class Forum(models.Model):
|
||||
search_fields.append('description')
|
||||
fetch_fields.append('description')
|
||||
mapping['description'] = {'name': 'description', 'type': 'text', 'match': True}
|
||||
def patch_forum(forum, data):
|
||||
data['website_url'] = forum._compute_website_url()
|
||||
return {
|
||||
'model': 'forum.forum',
|
||||
'base_domain': [website.website_domain()],
|
||||
'search_fields': search_fields,
|
||||
'fetch_fields': fetch_fields,
|
||||
'patch_data_function': patch_forum,
|
||||
'mapping': mapping,
|
||||
'icon': 'fa-comments-o',
|
||||
'order': 'name desc, id desc' if 'name desc' in order else 'name asc, id desc',
|
||||
}
|
||||
|
||||
def _search_render_results(self, fetch_fields, mapping, icon, limit):
|
||||
results_data = super()._search_render_results(fetch_fields, mapping, icon, limit)
|
||||
for forum, data in zip(self, results_data):
|
||||
data['website_url'] = forum._compute_website_url()
|
||||
return results_data
|
||||
|
||||
|
||||
class Post(models.Model):
|
||||
|
||||
_name = 'forum.post'
|
||||
_description = 'Forum Post'
|
||||
_inherit = ['mail.thread', 'website.seo.metadata']
|
||||
_inherit = [
|
||||
'mail.thread',
|
||||
'website.seo.metadata',
|
||||
'website.searchable.mixin',
|
||||
]
|
||||
_order = "is_correct DESC, vote_count DESC, write_date DESC"
|
||||
|
||||
name = fields.Char('Title')
|
||||
@@ -963,7 +975,6 @@ class Post(models.Model):
|
||||
|
||||
@api.model
|
||||
def _search_get_detail(self, website, order, options):
|
||||
"""See website_page._search_get_detail()"""
|
||||
with_description = options['displayDescription']
|
||||
with_date = options['displayDetail']
|
||||
search_fields = ['name']
|
||||
@@ -1012,21 +1023,25 @@ class Post(models.Model):
|
||||
if with_date:
|
||||
fetch_fields.append('write_date')
|
||||
mapping['detail'] = {'name': 'date', 'type': 'html'}
|
||||
def patch_forum_post(post, data):
|
||||
data['website_url'] = post._compute_website_url()
|
||||
if with_date:
|
||||
data['date'] = self.env['ir.qweb.field.date'].record_to_html(post, 'write_date', {})
|
||||
return {
|
||||
'model': 'forum.post',
|
||||
'base_domain': [domain],
|
||||
'search_fields': search_fields,
|
||||
'fetch_fields': fetch_fields,
|
||||
'patch_data_function': patch_forum_post,
|
||||
'mapping': mapping,
|
||||
'icon': 'fa-comment-o',
|
||||
'order': order,
|
||||
}
|
||||
|
||||
def _search_render_results(self, fetch_fields, mapping, icon, limit):
|
||||
with_date = 'detail' in mapping
|
||||
results_data = super()._search_render_results(fetch_fields, mapping, icon, limit)
|
||||
for post, data in zip(self, results_data):
|
||||
data['website_url'] = post._compute_website_url()
|
||||
if with_date:
|
||||
data['date'] = self.env['ir.qweb.field.date'].record_to_html(post, 'write_date', {})
|
||||
return results_data
|
||||
|
||||
|
||||
class PostReason(models.Model):
|
||||
_name = "forum.post.reason"
|
||||
|
||||
@@ -139,7 +139,12 @@ class ProductPricelist(models.Model):
|
||||
|
||||
class ProductPublicCategory(models.Model):
|
||||
_name = "product.public.category"
|
||||
_inherit = ["website.seo.metadata", "website.multi.mixin", 'image.mixin']
|
||||
_inherit = [
|
||||
'website.seo.metadata',
|
||||
'website.multi.mixin',
|
||||
'website.searchable.mixin',
|
||||
'image.mixin',
|
||||
]
|
||||
_description = "Website Product Category"
|
||||
_parent_store = True
|
||||
_order = "sequence, name, id"
|
||||
@@ -179,7 +184,6 @@ class ProductPublicCategory(models.Model):
|
||||
|
||||
@api.model
|
||||
def _search_get_detail(self, website, order, options):
|
||||
"""See website_page._search_get_detail()"""
|
||||
with_description = options['displayDescription']
|
||||
search_fields = ['name']
|
||||
fetch_fields = ['id', 'name']
|
||||
@@ -191,15 +195,18 @@ class ProductPublicCategory(models.Model):
|
||||
search_fields.append('website_description')
|
||||
fetch_fields.append('website_description')
|
||||
mapping['description'] = {'name': 'website_description', 'type': 'text', 'match': True}
|
||||
def patch_category(category, data):
|
||||
data['url'] = '/shop/category/%s' % data['id']
|
||||
return {
|
||||
'model': 'product.public.category',
|
||||
'base_domain': [], # categories are not website-specific
|
||||
'search_fields': search_fields,
|
||||
'fetch_fields': fetch_fields,
|
||||
'patch_data_function': patch_category,
|
||||
'mapping': mapping,
|
||||
'icon': 'fa-folder-o',
|
||||
'order': 'name desc, id desc' if 'name desc' in order else 'name asc, id desc',
|
||||
}
|
||||
|
||||
def _search_render_results(self, fetch_fields, mapping, icon, limit):
|
||||
results_data = super()._search_render_results(fetch_fields, mapping, icon, limit)
|
||||
for data in results_data:
|
||||
data['url'] = '/shop/category/%s' % data['id']
|
||||
return results_data
|
||||
|
||||
@@ -8,7 +8,13 @@ from odoo.osv import expression
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = ["product.template", "website.seo.metadata", 'website.published.multi.mixin', 'rating.mixin']
|
||||
_inherit = [
|
||||
"product.template",
|
||||
"website.seo.metadata",
|
||||
'website.published.multi.mixin',
|
||||
'website.searchable.mixin',
|
||||
'rating.mixin',
|
||||
]
|
||||
_name = 'product.template'
|
||||
_mail_post_access = 'read'
|
||||
_check_company_auto = True
|
||||
@@ -323,7 +329,6 @@ class ProductTemplate(models.Model):
|
||||
|
||||
@api.model
|
||||
def _search_get_detail(self, website, order, options):
|
||||
"""See website_page._search_get_detail()"""
|
||||
with_image = options['displayImage']
|
||||
with_description = options['displayDescription']
|
||||
with_category = options['displayExtraLink']
|
||||
@@ -367,15 +372,29 @@ class ProductTemplate(models.Model):
|
||||
fetch_fields.append('description_sale')
|
||||
mapping['description'] = {'name': 'description_sale', 'type': 'text', 'match': True}
|
||||
if with_price:
|
||||
mapping['detail'] = {'name': 'price', 'type': 'html'}
|
||||
mapping['detail_strike'] = {'name': 'list_price', 'type': 'html'}
|
||||
mapping['detail'] = {'name': 'price', 'type': 'html', 'display_currency': options['display_currency']}
|
||||
mapping['detail_strike'] = {'name': 'list_price', 'type': 'html', 'display_currency': options['display_currency']}
|
||||
if with_category:
|
||||
mapping['extra_link'] = {'name': 'category', 'type': 'text', 'match': True}
|
||||
mapping['extra_link_url'] = {'name': 'category_url', 'type': 'text'}
|
||||
def patch_product(product, data):
|
||||
return {
|
||||
'model': 'product.template',
|
||||
'base_domain': domains,
|
||||
'search_fields': search_fields,
|
||||
'fetch_fields': fetch_fields,
|
||||
'mapping': mapping,
|
||||
'icon': 'fa-shopping-cart',
|
||||
}
|
||||
|
||||
def _search_render_results(self, fetch_fields, mapping, icon, limit):
|
||||
with_image = 'image_url' in mapping
|
||||
with_category = 'extra_link' in mapping
|
||||
with_price = 'detail' in mapping
|
||||
results_data = super()._search_render_results(fetch_fields, mapping, icon, limit)
|
||||
for product, data in zip(self, results_data):
|
||||
if with_price:
|
||||
combination_info = product._get_combination_info(only_template=True)
|
||||
monetary_options = {'display_currency': options['display_currency']}
|
||||
monetary_options = {'display_currency': mapping['detail']['display_currency']}
|
||||
data['price'] = self.env['ir.qweb.field.monetary'].value_to_html(combination_info['price'], monetary_options)
|
||||
if combination_info['has_discounted_price']:
|
||||
data['list_price'] = self.env['ir.qweb.field.monetary'].value_to_html(combination_info['list_price'], monetary_options)
|
||||
@@ -385,12 +404,4 @@ class ProductTemplate(models.Model):
|
||||
data['category'] = _('Category: %s', product.public_categ_ids.name)
|
||||
slugs = [slug(category) for category in product.public_categ_ids]
|
||||
data['category_url'] = '/shop/category/%s' % ','.join(slugs)
|
||||
return {
|
||||
'model': 'product.template',
|
||||
'base_domain': domains,
|
||||
'search_fields': search_fields,
|
||||
'fetch_fields': fetch_fields,
|
||||
'patch_data_function': patch_product,
|
||||
'mapping': mapping,
|
||||
'icon': 'fa-shopping-cart',
|
||||
}
|
||||
return results_data
|
||||
|
||||
@@ -137,7 +137,10 @@ class Channel(models.Model):
|
||||
'mail.thread', 'rating.mixin',
|
||||
'mail.activity.mixin',
|
||||
'image.mixin',
|
||||
'website.seo.metadata', 'website.published.multi.mixin']
|
||||
'website.seo.metadata',
|
||||
'website.published.multi.mixin',
|
||||
'website.searchable.mixin',
|
||||
]
|
||||
_order = 'sequence, id'
|
||||
|
||||
def _default_access_token(self):
|
||||
@@ -834,7 +837,6 @@ class Channel(models.Model):
|
||||
|
||||
@api.model
|
||||
def _search_get_detail(self, website, order, options):
|
||||
"""See website_page._search_get_detail()"""
|
||||
with_description = options['displayDescription']
|
||||
with_date = options['displayDetail']
|
||||
my = options.get('my')
|
||||
|
||||
@@ -116,7 +116,10 @@ class Slide(models.Model):
|
||||
_inherit = [
|
||||
'mail.thread',
|
||||
'image.mixin',
|
||||
'website.seo.metadata', 'website.published.mixin']
|
||||
'website.seo.metadata',
|
||||
'website.published.mixin',
|
||||
'website.searchable.mixin',
|
||||
]
|
||||
_description = 'Slides'
|
||||
_mail_post_access = 'read'
|
||||
_order_by_strategy = {
|
||||
@@ -890,7 +893,6 @@ class Slide(models.Model):
|
||||
|
||||
@api.model
|
||||
def _search_get_detail(self, website, order, options):
|
||||
"""See website_page._search_get_detail()"""
|
||||
with_description = options['displayDescription']
|
||||
search_fields = ['name']
|
||||
fetch_fields = ['id', 'name']
|
||||
@@ -904,6 +906,17 @@ class Slide(models.Model):
|
||||
search_fields.append('description')
|
||||
fetch_fields.append('description')
|
||||
mapping['description'] = {'name': 'description', 'type': 'text', 'html': True, 'match': True}
|
||||
return {
|
||||
'model': 'slide.slide',
|
||||
'base_domain': [website.website_domain()],
|
||||
'search_fields': search_fields,
|
||||
'fetch_fields': fetch_fields,
|
||||
'mapping': mapping,
|
||||
'icon': 'fa-shopping-cart',
|
||||
'order': 'name desc, id desc' if 'name desc' in order else 'name asc, id desc',
|
||||
}
|
||||
|
||||
def _search_render_results(self, fetch_fields, mapping, icon, limit):
|
||||
icon_per_type = {
|
||||
'infographic': 'fa-file-picture-o',
|
||||
'webpage': 'fa-file-text',
|
||||
@@ -913,18 +926,10 @@ class Slide(models.Model):
|
||||
'quiz': 'fa-question-circle',
|
||||
'link': 'fa-file-code-o', # appears in template "slide_icon"
|
||||
}
|
||||
def patch_slide(slide, data):
|
||||
results_data = super()._search_render_results(fetch_fields, mapping, icon, limit)
|
||||
for slide, data in zip(self, results_data):
|
||||
data['_fa'] = icon_per_type.get(slide.slide_type, 'fa-file-pdf-o')
|
||||
data['url'] = slide.website_url
|
||||
data['course'] = _('Course: %s', slide.channel_id.name)
|
||||
data['course_url'] = slide.channel_id.website_url
|
||||
return {
|
||||
'model': 'slide.slide',
|
||||
'base_domain': [website.website_domain()],
|
||||
'search_fields': search_fields,
|
||||
'fetch_fields': fetch_fields,
|
||||
'patch_data_function': patch_slide,
|
||||
'mapping': mapping,
|
||||
'icon': 'fa-shopping-cart',
|
||||
'order': 'name desc, id desc' if 'name desc' in order else 'name asc, id desc',
|
||||
}
|
||||
return results_data
|
||||
|
||||
Reference in New Issue
Block a user