summaryrefslogtreecommitdiff
path: root/addons/website/models/website.py
diff options
context:
space:
mode:
authorJeremy Kersten <jke@odoo.com>2019-01-17 15:09:45 +0100
committerJeremy Kersten <jke@odoo.com>2019-01-17 15:09:45 +0100
commitd6244e9a7d2f7545adf81eaedcff14beea0d9bac (patch)
tree13b0aa49415eb0a0e15619e9f69f36955f5bbad0 /addons/website/models/website.py
parent188ca66dbce8794d0130a975cd7f808d49e4fc8f (diff)
[REF] website: add new file for website_page.py
Diffstat (limited to 'addons/website/models/website.py')
-rw-r--r--addons/website/models/website.py1252
1 files changed, 0 insertions, 1252 deletions
diff --git a/addons/website/models/website.py b/addons/website/models/website.py
deleted file mode 100644
index ac75255a30c..00000000000
--- a/addons/website/models/website.py
+++ /dev/null
@@ -1,1252 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-import inspect
-import logging
-import hashlib
-import re
-
-from werkzeug import urls
-from werkzeug.exceptions import NotFound
-
-from odoo import api, fields, models, tools
-from odoo.addons.http_routing.models.ir_http import slugify, _guess_mimetype
-from odoo.addons.website.models.ir_http import sitemap_qs2dom
-from odoo.addons.portal.controllers.portal import pager
-from odoo.http import request
-from odoo.osv import expression
-from odoo.osv.expression import FALSE_DOMAIN
-from odoo.tools.translate import _
-
-logger = logging.getLogger(__name__)
-
-
-DEFAULT_CDN_FILTERS = [
- "^/[^/]+/static/",
- "^/web/(css|js)/",
- "^/web/image",
- "^/web/content",
- # retrocompatibility
- "^/website/image/",
-]
-
-
-class Website(models.Model):
-
- _name = "website"
- _description = "Website"
-
- @api.model
- def website_domain(self, website_id=False):
- return [('website_id', 'in', (False, website_id or self.id))]
-
- def _active_languages(self):
- return self.env['res.lang'].search([]).ids
-
- def _default_language(self):
- lang_code = self.env['ir.default'].get('res.partner', 'lang')
- def_lang = self.env['res.lang'].search([('code', '=', lang_code)], limit=1)
- return def_lang.id if def_lang else self._active_languages()[0]
-
- name = fields.Char('Website Name', required=True)
- domain = fields.Char('Website Domain')
- country_group_ids = fields.Many2many('res.country.group', 'website_country_group_rel', 'website_id', 'country_group_id',
- string='Country Groups', help='Used when multiple websites have the same domain.')
- company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.ref('base.main_company').id, required=True)
- language_ids = fields.Many2many('res.lang', 'website_lang_rel', 'website_id', 'lang_id', 'Languages', default=_active_languages)
- default_lang_id = fields.Many2one('res.lang', string="Default Language", default=_default_language, required=True)
- default_lang_code = fields.Char("Default language code", related='default_lang_id.code', store=True, readonly=False)
- auto_redirect_lang = fields.Boolean('Autoredirect Language', default=True, help="Should users be redirected to their browser's language")
-
- def _default_social_facebook(self):
- return self.env.ref('base.main_company').social_facebook
-
- def _default_social_github(self):
- return self.env.ref('base.main_company').social_github
-
- def _default_social_linkedin(self):
- return self.env.ref('base.main_company').social_linkedin
-
- def _default_social_youtube(self):
- return self.env.ref('base.main_company').social_youtube
-
- def _default_social_googleplus(self):
- return self.env.ref('base.main_company').social_googleplus
-
- def _default_social_instagram(self):
- return self.env.ref('base.main_company').social_instagram
-
- def _default_social_twitter(self):
- return self.env.ref('base.main_company').social_twitter
-
- social_twitter = fields.Char('Twitter Account', default=_default_social_twitter)
- social_facebook = fields.Char('Facebook Account', default=_default_social_facebook)
- social_github = fields.Char('GitHub Account', default=_default_social_github)
- social_linkedin = fields.Char('LinkedIn Account', default=_default_social_linkedin)
- social_youtube = fields.Char('Youtube Account', default=_default_social_youtube)
- social_googleplus = fields.Char('Google+ Account', default=_default_social_googleplus)
- social_instagram = fields.Char('Instagram Account', default=_default_social_instagram)
- social_default_image = fields.Binary(string="Default Social Share Image", help="If set, replaces the company logo as the default social share image.")
-
- google_analytics_key = fields.Char('Google Analytics Key')
- google_management_client_id = fields.Char('Google Client ID')
- google_management_client_secret = fields.Char('Google Client Secret')
-
- google_maps_api_key = fields.Char('Google Maps API Key')
-
- user_id = fields.Many2one('res.users', string='Public User', required=True)
- cdn_activated = fields.Boolean('Content Delivery Network (CDN)')
- cdn_url = fields.Char('CDN Base URL', default='')
- cdn_filters = fields.Text('CDN Filters', default=lambda s: '\n'.join(DEFAULT_CDN_FILTERS), help="URL matching those filters will be rewritten using the CDN Base URL")
- partner_id = fields.Many2one(related='user_id.partner_id', relation='res.partner', string='Public Partner', readonly=False)
- menu_id = fields.Many2one('website.menu', compute='_compute_menu', string='Main Menu')
- homepage_id = fields.Many2one('website.page', string='Homepage')
- favicon = fields.Binary(string="Website Favicon", help="This field holds the image used to display a favicon on the website.")
- theme_id = fields.Many2one('ir.module.module', help='Installed theme')
-
- specific_user_account = fields.Boolean('Specific User Account', help='If True, new accounts will be associated to the current website')
- auth_signup_uninvited = fields.Selection([
- ('b2b', 'On invitation'),
- ('b2c', 'Free sign up'),
- ], string='Customer Account', default='b2b')
-
- @api.onchange('language_ids')
- def _onchange_language_ids(self):
- if self.language_ids and self.default_lang_id not in self.language_ids:
- self.default_lang_id = self.language_ids[0]
-
- @api.multi
- def _compute_menu(self):
- Menu = self.env['website.menu']
- for website in self:
- website.menu_id = Menu.search([('parent_id', '=', False), ('website_id', '=', website.id)], order='id', limit=1).id
-
- @api.model
- def create(self, vals):
- if 'user_id' not in vals:
- company = self.env['res.company'].browse(vals.get('company_id'))
- vals['user_id'] = company._get_public_user().id if company else self.env.ref('base.public_user').id
-
- res = super(Website, self).create(vals)
- res._bootstrap_homepage()
-
- if not self.env.user.has_group('website.group_multi_website') and self.search_count([]) > 1:
- all_user_groups = 'base.group_portal,base.group_user,base.group_public'
- groups = self.env['res.groups'].concat(*(self.env.ref(it) for it in all_user_groups.split(',')))
- groups.write({'implied_ids': [(4, self.env.ref('website.group_multi_website').id)]})
-
- return res
-
- @api.multi
- def write(self, values):
- self._get_languages.clear_cache(self)
- if 'company_id' in values and 'user_id' not in values:
- company = self.env['res.company'].browse(values['company_id'])
- values['user_id'] = company._get_public_user().id
-
- result = super(Website, self).write(values)
- if 'cdn_activated' in values or 'cdn_url' in values or 'cdn_filters' in values:
- # invalidate the caches from static node at compile time
- self.env['ir.qweb'].clear_caches()
- return result
-
- # ----------------------------------------------------------
- # Page Management
- # ----------------------------------------------------------
- def _bootstrap_homepage(self):
- standard_homepage = self.env.ref('website.homepage', raise_if_not_found=False)
- if not standard_homepage:
- return
-
- new_homepage_view = '''<t name="Homepage" t-name="website.homepage%s">
- <t t-call="website.layout">
- <t t-set="pageName" t-value="'homepage'"/>
- <div id="wrap" class="oe_structure oe_empty"/>
- </t>
- </t>''' % (self.id)
- standard_homepage.with_context(website_id=self.id).arch_db = new_homepage_view
-
- self.homepage_id = self.env['website.page'].search([('website_id', '=', self.id),
- ('key', '=', standard_homepage.key)])
-
- # Bootstrap default menu hierarchy, create a new minimalist one if no default
- default_menu = self.env.ref('website.main_menu')
- self.copy_menu_hierarchy(default_menu)
-
- def copy_menu_hierarchy(self, top_menu):
- def copy_menu(menu, t_menu):
- new_menu = menu.copy({
- 'parent_id': t_menu.id,
- 'website_id': self.id,
- })
- for submenu in menu.child_id:
- copy_menu(submenu, new_menu)
- for website in self:
- new_top_menu = top_menu.copy({
- 'name': _('Top Menu for Website %s') % website.id,
- 'website_id': website.id,
- })
- for submenu in top_menu.child_id:
- copy_menu(submenu, new_top_menu)
-
- @api.model
- def new_page(self, name=False, add_menu=False, template='website.default_page', ispage=True, namespace=None):
- """ Create a new website page, and assign it a xmlid based on the given one
- :param name : the name of the page
- :param template : potential xml_id of the page to create
- :param namespace : module part of the xml_id if none, the template module name is used
- """
- if namespace:
- template_module = namespace
- else:
- template_module, _ = template.split('.')
- page_url = '/' + slugify(name, max_length=1024, path=True)
- page_url = self.get_unique_path(page_url)
- page_key = slugify(name)
- result = dict({'url': page_url, 'view_id': False})
-
- if not name:
- name = 'Home'
- page_key = 'home'
-
- template_record = self.env.ref(template)
- website_id = self._context.get('website_id')
- key = self.get_unique_key(page_key, template_module)
- view = template_record.copy({'website_id': website_id, 'key': key})
-
- view.with_context(lang=None).write({
- 'arch': template_record.arch.replace(template, key),
- 'name': name,
- })
-
- if view.arch_fs:
- view.arch_fs = False
-
- website = self.get_current_website()
- if ispage:
- page = self.env['website.page'].create({
- 'url': page_url,
- 'website_id': website.id, # remove it if only one webiste or not?
- 'view_id': view.id,
- })
- result['view_id'] = view.id
- if add_menu:
- self.env['website.menu'].create({
- 'name': name,
- 'url': page_url,
- 'parent_id': website.menu_id.id,
- 'page_id': page.id,
- 'website_id': website.id,
- })
- return result
-
- @api.model
- def guess_mimetype(self):
- return _guess_mimetype()
-
- def get_unique_path(self, page_url):
- """ Given an url, return that url suffixed by counter if it already exists
- :param page_url : the url to be checked for uniqueness
- """
- inc = 0
- # we only want a unique_path for website specific.
- # we need to be able to have /url for website=False, and /url for website=1
- # in case of duplicate, page manager will allow you to manage this case
- domain_static = [('website_id', '=', self.get_current_website().id)] # .website_domain()
- page_temp = page_url
- while self.env['website.page'].with_context(active_test=False).sudo().search([('url', '=', page_temp)] + domain_static):
- inc += 1
- page_temp = page_url + (inc and "-%s" % inc or "")
- return page_temp
-
- def get_unique_key(self, string, template_module=False):
- """ Given a string, return an unique key including module prefix.
- It will be suffixed by a counter if it already exists to garantee uniqueness.
- :param string : the key to be checked for uniqueness, you can pass it with 'website.' or not
- :param template_module : the module to be prefixed on the key, if not set, we will use website
- """
- if template_module:
- string = template_module + '.' + string
- else:
- if not string.startswith('website.'):
- string = 'website.' + string
-
- # Look for unique key
- key_copy = string
- inc = 0
- domain_static = self.get_current_website().website_domain()
- while self.env['website.page'].with_context(active_test=False).sudo().search([('key', '=', key_copy)] + domain_static):
- inc += 1
- key_copy = string + (inc and "-%s" % inc or "")
- return key_copy
-
- @api.model
- def page_search_dependencies(self, page_id=False):
- """ Search dependencies just for information. It will not catch 100%
- of dependencies and False positive is more than possible
- Each module could add dependences in this dict
- :returns a dictionnary where key is the 'categorie' of object related to the given
- view, and the value is the list of text and link to the resource using given page
- """
- dependencies = {}
- if not page_id:
- return dependencies
-
- page = self.env['website.page'].browse(int(page_id))
- website = self.env['website'].browse(self._context.get('website_id'))
- url = page.url
-
- # search for website_page with link
- website_page_search_dom = [('view_id.arch_db', 'ilike', url)] + website.website_domain()
- pages = self.env['website.page'].search(website_page_search_dom)
- page_key = _('Page')
- if len(pages) > 1:
- page_key = _('Pages')
- page_view_ids = []
- for page in pages:
- dependencies.setdefault(page_key, [])
- dependencies[page_key].append({
- 'text': _('Page <b>%s</b> contains a link to this page') % page.url,
- 'item': page.name,
- 'link': page.url,
- })
- page_view_ids.append(page.view_id.id)
-
- # search for ir_ui_view (not from a website_page) with link
- page_search_dom = [('arch_db', 'ilike', url), ('id', 'not in', page_view_ids)] + website.website_domain()
- views = self.env['ir.ui.view'].search(page_search_dom)
- view_key = _('Template')
- if len(views) > 1:
- view_key = _('Templates')
- for view in views:
- dependencies.setdefault(view_key, [])
- dependencies[view_key].append({
- 'text': _('Template <b>%s (id:%s)</b> contains a link to this page') % (view.key or view.name, view.id),
- 'link': '/web#id=%s&view_type=form&model=ir.ui.view' % view.id,
- 'item': _('%s (id:%s)') % (view.key or view.name, view.id),
- })
- # search for menu with link
- menu_search_dom = [('url', 'ilike', '%s' % url)] + website.website_domain()
-
- menus = self.env['website.menu'].search(menu_search_dom)
- menu_key = _('Menu')
- if len(menus) > 1:
- menu_key = _('Menus')
- for menu in menus:
- dependencies.setdefault(menu_key, []).append({
- 'text': _('This page is in the menu <b>%s</b>') % menu.name,
- 'link': '/web#id=%s&view_type=form&model=website.menu' % menu.id,
- 'item': menu.name,
- })
-
- return dependencies
-
- @api.model
- def page_search_key_dependencies(self, page_id=False):
- """ Search dependencies just for information. It will not catch 100%
- of dependencies and False positive is more than possible
- Each module could add dependences in this dict
- :returns a dictionnary where key is the 'categorie' of object related to the given
- view, and the value is the list of text and link to the resource using given page
- """
- dependencies = {}
- if not page_id:
- return dependencies
-
- page = self.env['website.page'].browse(int(page_id))
- website = self.env['website'].browse(self._context.get('website_id'))
- key = page.key
-
- # search for website_page with link
- website_page_search_dom = [
- ('view_id.arch_db', 'ilike', key),
- ('id', '!=', page.id)
- ] + website.website_domain()
- pages = self.env['website.page'].search(website_page_search_dom)
- page_key = _('Page')
- if len(pages) > 1:
- page_key = _('Pages')
- page_view_ids = []
- for p in pages:
- dependencies.setdefault(page_key, [])
- dependencies[page_key].append({
- 'text': _('Page <b>%s</b> is calling this file') % p.url,
- 'item': p.name,
- 'link': p.url,
- })
- page_view_ids.append(p.view_id.id)
-
- # search for ir_ui_view (not from a website_page) with link
- page_search_dom = [
- ('arch_db', 'ilike', key), ('id', 'not in', page_view_ids),
- ('id', '!=', page.view_id.id),
- ] + website.website_domain()
- views = self.env['ir.ui.view'].search(page_search_dom)
- view_key = _('Template')
- if len(views) > 1:
- view_key = _('Templates')
- for view in views:
- dependencies.setdefault(view_key, [])
- dependencies[view_key].append({
- 'text': _('Template <b>%s (id:%s)</b> is calling this file') % (view.key or view.name, view.id),
- 'item': _('%s (id:%s)') % (view.key or view.name, view.id),
- 'link': '/web#id=%s&view_type=form&model=ir.ui.view' % view.id,
- })
-
- return dependencies
-
- # ----------------------------------------------------------
- # Languages
- # ----------------------------------------------------------
-
- @api.multi
- def get_languages(self):
- self.ensure_one()
- return self._get_languages()
-
- @tools.cache('self.id')
- def _get_languages(self):
- return [(lg.code, lg.name) for lg in self.language_ids]
-
- @api.multi
- def get_alternate_languages(self, req=None):
- langs = []
- if req is None:
- req = request.httprequest
- default = self.get_current_website().default_lang_code
- shorts = []
-
- def get_url_localized(router, lang):
- arguments = dict(request.endpoint_arguments)
- for key, val in list(arguments.items()):
- if isinstance(val, models.BaseModel):
- arguments[key] = val.with_context(lang=lang)
- return router.build(request.endpoint, arguments)
-
- router = request.httprequest.app.get_db_router(request.db).bind('')
- for code, dummy in self.get_languages():
- lg_path = ('/' + code) if code != default else ''
- lg_codes = code.split('_')
- shorts.append(lg_codes[0])
- uri = get_url_localized(router, code) if request.endpoint else request.httprequest.path
- if req.query_string:
- uri += u'?' + req.query_string.decode('utf-8')
- lang = {
- 'hreflang': ('-'.join(lg_codes)).lower(),
- 'short': lg_codes[0],
- 'href': req.url_root[0:-1] + lg_path + uri,
- }
- langs.append(lang)
- for lang in langs:
- if shorts.count(lang['short']) == 1:
- lang['hreflang'] = lang['short']
- return langs
-
- # ----------------------------------------------------------
- # Utilities
- # ----------------------------------------------------------
-
- @api.model
- def get_current_website(self, fallback=True):
- if request and request.session.get('force_website_id'):
- return self.browse(request.session['force_website_id'])
-
- website_id = self.env.context.get('website_id')
- if website_id:
- return self.browse(website_id)
-
- domain_name = request and request.httprequest.environ.get('HTTP_HOST', '').split(':')[0] or None
-
- country = request.session.geoip.get('country_code') if request and request.session.geoip else False
- country_id = False
- if country:
- country_id = self.env['res.country'].search([('code', '=', country)], limit=1).id
-
- website_id = self._get_current_website_id(domain_name, country_id, fallback=fallback)
- return self.browse(website_id)
-
- @tools.cache('domain_name', 'country_id', 'fallback')
- def _get_current_website_id(self, domain_name, country_id, fallback=True):
- # sort on country_group_ids so that we fall back on a generic website (empty country_group_ids)
- websites = self.search([('domain', '=', domain_name)]).sorted('country_group_ids')
-
- if not websites:
- if not fallback:
- return False
- return self.search([], limit=1).id
- elif len(websites) == 1:
- return websites.id
- else: # > 1 website with the same domain
- country_specific_websites = websites.filtered(lambda website: country_id in website.country_group_ids.mapped('country_ids').ids)
- return country_specific_websites[0].id if country_specific_websites else websites[0].id
-
- def _force(self):
- self._force_website(self.id)
-
- def _force_website(self, website_id):
- if request:
- request.session['force_website_id'] = website_id and str(website_id).isdigit() and int(website_id)
-
- @api.model
- def is_publisher(self):
- return self.env['ir.model.access'].check('ir.ui.view', 'write', False)
-
- @api.model
- def is_user(self):
- return self.env['ir.model.access'].check('ir.ui.menu', 'read', False)
-
- @api.model
- def is_public_user(self):
- return request.env.user.id == request.website.user_id.id
-
- @api.model
- def viewref(self, view_id, raise_if_not_found=True):
- ''' Given an xml_id or a view_id, return the corresponding view record.
- In case of website context, return the most specific one.
- :param view_id: either a string xml_id or an integer view_id
- :param raise_if_not_found: should the method raise an error if no view found
- :return: The view record or empty recordset
- '''
- View = self.env['ir.ui.view']
- view = None
- if isinstance(view_id, str):
- if 'website_id' in self._context:
- domain = [('key', '=', view_id)] + self.env['website'].website_domain(self._context.get('website_id'))
- order = 'website_id'
- else:
- domain = [('key', '=', view_id)]
- order = View._order
- views = View.with_context(active_test=False).search(domain, order=order)
- if views:
- view = views.filter_duplicate()
- else:
- view = self.env.ref(view_id)
- # self.env.ref might return something else than an ir.ui.view (eg: a theme.ir.ui.view)
- if view._name != 'ir.ui.view':
- view = None
- elif isinstance(view_id, int):
- view = View.browse(view_id)
- else:
- raise ValueError('Expecting a string or an integer, not a %s.' % (type(view_id)))
-
- if view:
- return view
- if raise_if_not_found:
- raise ValueError('No record found for unique ID %s. It may have been deleted.' % (view_id))
- return None
-
- @api.model
- def get_template(self, template):
- View = self.env['ir.ui.view']
- if isinstance(template, int):
- view_id = template
- else:
- if '.' not in template:
- template = 'website.%s' % template
- view_id = View.get_view_id(template)
- if not view_id:
- raise NotFound
- return View.browse(view_id)
-
- @api.model
- def pager(self, url, total, page=1, step=30, scope=5, url_args=None):
- return pager(url, total, page=page, step=step, scope=scope, url_args=url_args)
-
- def rule_is_enumerable(self, rule):
- """ Checks that it is possible to generate sensible GET queries for
- a given rule (if the endpoint matches its own requirements)
- :type rule: werkzeug.routing.Rule
- :rtype: bool
- """
- endpoint = rule.endpoint
- methods = endpoint.routing.get('methods') or ['GET']
-
- converters = list(rule._converters.values())
- if not ('GET' in methods and
- endpoint.routing['type'] == 'http' and
- endpoint.routing['auth'] in ('none', 'public') and
- endpoint.routing.get('website', False) and
- all(hasattr(converter, 'generate') for converter in converters)):
- return False
-
- # dont't list routes without argument having no default value or converter
- spec = inspect.getargspec(endpoint.method.original_func)
-
- # remove self and arguments having a default value
- defaults_count = len(spec.defaults or [])
- args = spec.args[1:(-defaults_count or None)]
-
- # check that all args have a converter
- return all((arg in rule._converters) for arg in args)
-
- @api.multi
- def enumerate_pages(self, query_string=None, force=False):
- """ Available pages in the website/CMS. This is mostly used for links
- generation and can be overridden by modules setting up new HTML
- controllers for dynamic pages (e.g. blog).
- By default, returns template views marked as pages.
- :param str query_string: a (user-provided) string, fetches pages
- matching the string
- :returns: a list of mappings with two keys: ``name`` is the displayable
- name of the resource (page), ``url`` is the absolute URL
- of the same.
- :rtype: list({name: str, url: str})
- """
-
- router = request.httprequest.app.get_db_router(request.db)
- # Force enumeration to be performed as public user
- url_set = set()
-
- sitemap_endpoint_done = set()
-
- for rule in router.iter_rules():
- if 'sitemap' in rule.endpoint.routing:
- if rule.endpoint in sitemap_endpoint_done:
- continue
- sitemap_endpoint_done.add(rule.endpoint)
-
- func = rule.endpoint.routing['sitemap']
- if func is False:
- continue
- for loc in func(self.env, rule, query_string):
- yield loc
- continue
-
- if not self.rule_is_enumerable(rule):
- continue
-
- converters = rule._converters or {}
- if query_string and not converters and (query_string not in rule.build([{}], append_unknown=False)[1]):
- continue
- values = [{}]
- # converters with a domain are processed after the other ones
- convitems = sorted(
- converters.items(),
- key=lambda x: (hasattr(x[1], 'domain') and (x[1].domain != '[]'), rule._trace.index((True, x[0]))))
-
- for (i, (name, converter)) in enumerate(convitems):
- newval = []
- for val in values:
- query = i == len(convitems) - 1 and query_string
- if query:
- r = "".join([x[1] for x in rule._trace[1:] if not x[0]]) # remove model converter from route
- query = sitemap_qs2dom(query, r, self.env[converter.model]._rec_name)
- if query == FALSE_DOMAIN:
- continue
- for value_dict in converter.generate(uid=self.env.uid, dom=query, args=val):
- newval.append(val.copy())
- value_dict[name] = value_dict['loc']
- del value_dict['loc']
- newval[-1].update(value_dict)
- values = newval
-
- for value in values:
- domain_part, url = rule.build(value, append_unknown=False)
- if not query_string or query_string.lower() in url.lower():
- page = {'loc': url}
- for key, val in value.items():
- if key.startswith('__'):
- page[key[2:]] = val
- if url in ('/sitemap.xml',):
- continue
- if url in url_set:
- continue
- url_set.add(url)
-
- yield page
-
- # '/' already has a http.route & is in the routing_map so it will already have an entry in the xml
- domain = [('url', '!=', '/')]
- if not force:
- domain += [('website_indexed', '=', True)]
- # is_visible
- domain += [('website_published', '=', True), '|', ('date_publish', '=', False), ('date_publish', '<=', fields.Datetime.now())]
-
- if query_string:
- domain += [('url', 'like', query_string)]
-
- pages = self.get_website_pages(domain)
-
- for page in pages:
- record = {'loc': page['url'], 'id': page['id'], 'name': page['name']}
- if page.view_id and page.view_id.priority != 16:
- record['__priority'] = min(round(page.view_id.priority / 32.0, 1), 1)
- if page['write_date']:
- record['__lastmod'] = page['write_date'].date()
- yield record
-
- @api.multi
- def get_website_pages(self, domain=[], order='name', limit=None):
- domain += self.get_current_website().website_domain()
- pages = self.env['website.page'].search(domain, order='name', limit=limit)
- return pages
-
- @api.multi
- def search_pages(self, needle=None, limit=None):
- name = slugify(needle, max_length=50, path=True)
- res = []
- for page in self.enumerate_pages(query_string=name, force=True):
- res.append(page)
- if len(res) == limit:
- break
- return res
-
- @api.model
- def image_url(self, record, field, size=None):
- """ Returns a local url that points to the image field of a given browse record. """
- sudo_record = record.sudo()
- sha = hashlib.sha1(str(getattr(sudo_record, '__last_update')).encode('utf-8')).hexdigest()[0:7]
- size = '' if size is None else '/%s' % size
- return '/web/image/%s/%s/%s%s?unique=%s' % (record._name, record.id, field, size, sha)
-
- def get_cdn_url(self, uri):
- self.ensure_one()
- if not uri:
- return ''
- cdn_url = self.cdn_url
- cdn_filters = (self.cdn_filters or '').splitlines()
- for flt in cdn_filters:
- if flt and re.match(flt, uri):
- return urls.url_join(cdn_url, uri)
- return uri
-
- @api.model
- def action_dashboard_redirect(self):
- if self.env.user.has_group('base.group_system') or self.env.user.has_group('website.group_website_designer'):
- return self.env.ref('website.backend_dashboard').read()[0]
- return self.env.ref('website.action_website').read()[0]
-
- def button_go_website(self):
- self._force()
- return {
- 'type': 'ir.actions.act_url',
- 'url': '/',
- 'target': 'self',
- }
-
-
-class SeoMetadata(models.AbstractModel):
-
- _name = 'website.seo.metadata'
- _description = 'SEO metadata'
-
- is_seo_optimized = fields.Boolean("SEO optimized", compute='_compute_is_seo_optimized')
- website_meta_title = fields.Char("Website meta title", translate=True)
- website_meta_description = fields.Text("Website meta description", translate=True)
- website_meta_keywords = fields.Char("Website meta keywords", translate=True)
- website_meta_og_img = fields.Char("Website opengraph image")
-
- @api.multi
- def _compute_is_seo_optimized(self):
- for record in self:
- record.is_seo_optimized = record.website_meta_title and record.website_meta_description and record.website_meta_keywords
-
- def _default_website_meta(self):
- """ This method will return default meta information. It return the dict
- contains meta property as a key and meta content as a value.
- e.g. 'og:type': 'website'.
-
- Override this method in case you want to change default value
- from any model. e.g. change value of og:image to product specific
- images instead of default images
- """
- self.ensure_one()
- company = request.website.company_id.sudo()
- title = (request.website or company).name
- if 'name' in self:
- title = '%s | %s' % (self.name, title)
- if request.website.social_default_image:
- img = '/web/image/website/%s/social_default_image' % request.website.id
- else:
- img = '/web/image/res.company/%s/logo' % company.id
- # Default meta for OpenGraph
- default_opengraph = {
- 'og:type': 'website',
- 'og:title': title,
- 'og:site_name': company.name,
- 'og:url': request.httprequest.url,
- 'og:image': img,
- }
- # Default meta for Twitter
- default_twitter = {
- 'twitter:card': 'summary_large_image',
- 'twitter:title': title,
- 'twitter:image': img + '/300x300',
- }
- if company.social_twitter:
- default_twitter['twitter:site'] = "@%s" % company.social_twitter.split('/')[-1]
-
- return {
- 'default_opengraph': default_opengraph,
- 'default_twitter': default_twitter
- }
-
- def get_website_meta(self):
- """ This method will return final meta information. It will replace
- default values with user's custom value (if user modified it from
- the seo popup of fronted)
-
- This method is not meant for overridden. To customize meta values
- override `_default_website_meta` method instead of this method. This
- method only replaces user custom values in defaults.
- """
- root_url = request.httprequest.url_root.strip('/')
- default_meta = self._default_website_meta()
- opengraph_meta, twitter_meta = default_meta['default_opengraph'], default_meta['default_twitter']
- if self.website_meta_title:
- opengraph_meta['og:title'] = self.website_meta_title
- twitter_meta['twitter:title'] = self.website_meta_title
- if self.website_meta_description:
- opengraph_meta['og:description'] = self.website_meta_description
- twitter_meta['twitter:description'] = self.website_meta_description
- meta_image = self.website_meta_og_img or opengraph_meta['og:image']
- if meta_image.startswith('/'):
- meta_image = "%s%s" % (root_url, meta_image)
- opengraph_meta['og:image'] = meta_image
- twitter_meta['twitter:image'] = meta_image
- return {
- 'opengraph_meta': opengraph_meta,
- 'twitter_meta': twitter_meta
- }
-
-
-class WebsiteMultiMixin(models.AbstractModel):
-
- _name = 'website.multi.mixin'
- _description = 'Multi Website Mixin'
-
- website_id = fields.Many2one('website', string='Website', help='Restrict publishing to this website.')
-
- @api.multi
- def can_access_from_current_website(self, website_id=False):
- can_access = True
- for record in self:
- if (website_id or record.website_id.id) not in (False, request.website.id):
- can_access = False
- continue
- return can_access
-
-
-class WebsitePublishedMixin(models.AbstractModel):
-
- _name = "website.published.mixin"
- _description = 'Website Published Mixin'
-
- website_published = fields.Boolean('Visible on current website', related='is_published', readonly=False)
- is_published = fields.Boolean('Is published')
- website_url = fields.Char('Website URL', compute='_compute_website_url', help='The full URL to access the document through the website.')
-
- @api.multi
- def _compute_website_url(self):
- for record in self:
- record.website_url = '#'
-
- @api.multi
- def website_publish_button(self):
- self.ensure_one()
- if self.env.user.has_group('website.group_website_publisher') and self.website_url != '#':
- # Force website to land on record's website to publish/unpublish it
- if 'website_id' in self and self.env.user.has_group('website.group_multi_website'):
- self.website_id._force()
- return self.open_website_url()
- return self.write({'website_published': not self.website_published})
-
- def open_website_url(self):
- return {
- 'type': 'ir.actions.act_url',
- 'url': self.website_url,
- 'target': 'self',
- }
-
- def create_and_get_website_url(self, **kwargs):
- return self.create(kwargs).website_url
-
-
-class WebsitePublishedMultiMixin(WebsitePublishedMixin):
-
- _name = 'website.published.multi.mixin'
- _inherit = ['website.published.mixin', 'website.multi.mixin']
- _description = 'Multi Website Published Mixin'
-
- website_published = fields.Boolean(compute='_compute_website_published',
- inverse='_inverse_website_published',
- search='_search_website_published',
- related=False, readonly=False)
-
- @api.multi
- @api.depends('is_published', 'website_id')
- def _compute_website_published(self):
- current_website_id = self._context.get('website_id')
- for record in self:
- if current_website_id:
- record.website_published = record.is_published and (not record.website_id or record.website_id.id == current_website_id)
- else:
- record.website_published = record.is_published
-
- @api.multi
- def _inverse_website_published(self):
- for record in self:
- record.is_published = record.website_published
-
- def _search_website_published(self, operator, value):
- if not isinstance(value, bool) or operator not in ('=', '!='):
- logger.warning('unsupported search on website_published: %s, %s', operator, value)
- return [()]
-
- if operator in expression.NEGATIVE_TERM_OPERATORS:
- value = not value
-
- current_website_id = self._context.get('website_id')
- is_published = [('is_published', '=', value)]
- if current_website_id:
- on_current_website = self.env['website'].website_domain(current_website_id)
- 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 Page(models.Model):
- _name = 'website.page'
- _inherits = {'ir.ui.view': 'view_id'}
- _inherit = 'website.published.multi.mixin'
- _description = 'Page'
- _order = 'website_id'
-
- url = fields.Char('Page URL')
- view_id = fields.Many2one('ir.ui.view', string='View', required=True, ondelete="cascade")
- website_indexed = fields.Boolean('Page Indexed', default=True)
- date_publish = fields.Datetime('Publishing Date')
- # This is needed to be able to display if page is a menu in /website/pages
- menu_ids = fields.One2many('website.menu', 'page_id', 'Related Menus')
- is_homepage = fields.Boolean(compute='_compute_homepage', inverse='_set_homepage', string='Homepage')
- is_visible = fields.Boolean(compute='_compute_visible', string='Is Visible')
-
- # Page options
- header_overlay = fields.Boolean()
- header_color = fields.Char()
-
- # don't use mixin website_id but use website_id on ir.ui.view instead
- website_id = fields.Many2one(related='view_id.website_id', store=True, readonly=False)
-
- @api.one
- def _compute_homepage(self):
- self.is_homepage = self == self.env['website'].get_current_website().homepage_id
-
- @api.one
- def _set_homepage(self):
- website = self.env['website'].get_current_website()
- if self.is_homepage:
- if website.homepage_id != self:
- website.write({'homepage_id': self.id})
- else:
- if website.homepage_id == self:
- website.write({'homepage_id': None})
-
- @api.one
- def _compute_visible(self):
- self.is_visible = self.website_published and (not self.date_publish or self.date_publish < fields.Datetime.now())
-
- @api.multi
- 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
-
- @api.model
- def get_page_info(self, id):
- return self.browse(id).read(
- ['id', 'name', 'url', 'website_published', 'website_indexed', 'date_publish', 'menu_ids', 'is_homepage', 'website_id'],
- )
-
- @api.multi
- def get_view_identifier(self):
- """ Get identifier of this page view that may be used to render it """
- return self.view_id.id
-
- @api.model
- def save_page_info(self, website_id, data):
- website = self.env['website'].browse(website_id)
- page = self.browse(int(data['id']))
-
- # If URL has been edited, slug it
- original_url = page.url
- url = data['url']
- if not url.startswith('/'):
- url = '/' + url
- if page.url != url:
- url = '/' + slugify(url, max_length=1024, path=True)
- url = self.env['website'].get_unique_path(url)
-
- # If name has changed, check for key uniqueness
- if page.name != data['name']:
- page_key = self.env['website'].get_unique_key(slugify(data['name']))
- else:
- page_key = page.key
-
- menu = self.env['website.menu'].search([('page_id', '=', int(data['id']))])
- if not data['is_menu']:
- # If the page is no longer in menu, we should remove its website_menu
- if menu:
- menu.unlink()
- else:
- # The page is now a menu, check if has already one
- if menu:
- menu.write({'url': url})
- else:
- self.env['website.menu'].create({
- 'name': data['name'],
- 'url': url,
- 'page_id': data['id'],
- 'parent_id': website.menu_id.id,
- 'website_id': website.id,
- })
-
- # Edits via the page manager shouldn't trigger the COW
- # mechanism and generate new pages. The user manages page
- # visibility manually with is_published here.
- w_vals = {
- 'key': page_key,
- 'name': data['name'],
- 'url': url,
- 'is_published': data['website_published'],
- 'website_indexed': data['website_indexed'],
- 'date_publish': data['date_publish'] or None,
- 'is_homepage': data['is_homepage'],
- }
- page.with_context(no_cow=True).write(w_vals)
-
- # Create redirect if needed
- if data['create_redirect']:
- self.env['website.redirect'].create({
- 'redirect_type': data['redirect_type'],
- 'url_from': original_url,
- 'url_to': url,
- 'website_id': website.id,
- })
-
- return url
-
- @api.multi
- @api.returns('self', lambda value: value.id)
- def copy(self, default=None):
- if default:
- if not default.get('view_id'):
- view = self.env['ir.ui.view'].browse(self.view_id.id)
- new_view = view.copy({'website_id': default.get('website_id')})
- default['view_id'] = new_view.id
-
- default['url'] = default.get('url', self.env['website'].get_unique_path(self.url))
- return super(Page, self).copy(default=default)
-
- @api.model
- def clone_page(self, page_id, clone_menu=True):
- """ Clone a page, given its identifier
- :param page_id : website.page identifier
- """
- page = self.browse(int(page_id))
- new_page = page.copy(dict(name=page.name, website_id=self.env['website'].get_current_website().id))
- # Should not clone menu if the page was cloned from one website to another
- # Eg: Cloning a generic page (no website) will create a page with a website, we can't clone menu (not same container)
- if clone_menu and new_page.website_id == page.website_id:
- menu = self.env['website.menu'].search([('page_id', '=', page_id)], limit=1)
- if menu:
- # If the page being cloned has a menu, clone it too
- menu.copy({'url': new_page.url, 'name': menu.name, 'page_id': new_page.id})
-
- return new_page.url + '?enable_editor=1'
-
- @api.multi
- def unlink(self):
- # When a website_page is deleted, the ORM does not delete its
- # ir_ui_view. So we got to delete it ourself, but only if the
- # ir_ui_view is not used by another website_page.
- for page in self:
- # Other pages linked to the ir_ui_view of the page being deleted (will it even be possible?)
- pages_linked_to_iruiview = self.search(
- [('view_id', '=', page.view_id.id), ('id', '!=', page.id)]
- )
- if not pages_linked_to_iruiview and not page.view_id.inherit_children_ids:
- # If there is no other pages linked to that ir_ui_view, we can delete the ir_ui_view
- page.view_id.unlink()
- return super(Page, self).unlink()
-
- @api.multi
- def write(self, vals):
- if 'url' in vals and not vals['url'].startswith('/'):
- vals['url'] = '/' + vals['url']
- return super(Page, self).write(vals)
-
- def get_website_meta(self):
- self.ensure_one()
- return self.view_id.get_website_meta()
-
-
-class Menu(models.Model):
-
- _name = "website.menu"
- _description = "Website Menu"
-
- _parent_store = True
- _order = "sequence, id"
-
- def _default_sequence(self):
- menu = self.search([], limit=1, order="sequence DESC")
- return menu.sequence or 0
-
- name = fields.Char('Menu', required=True, translate=True)
- url = fields.Char('Url', default='')
- page_id = fields.Many2one('website.page', 'Related Page', ondelete='cascade')
- new_window = fields.Boolean('New Window')
- sequence = fields.Integer(default=_default_sequence)
- website_id = fields.Many2one('website', 'Website')
- parent_id = fields.Many2one('website.menu', 'Parent Menu', index=True, ondelete="cascade")
- child_id = fields.One2many('website.menu', 'parent_id', string='Child Menus')
- parent_path = fields.Char(index=True)
- is_visible = fields.Boolean(compute='_compute_visible', string='Is Visible')
-
- @api.multi
- def name_get(self):
- res = []
- for menu in self:
- website_suffix = '%s - %s' % (menu.name, menu.website_id.name)
- res.append((menu.id, website_suffix if menu.website_id and self.env.user.has_group('website.group_multi_website') else menu.name))
- return res
-
- @api.model
- def create(self, vals):
- ''' In case a menu without a website_id is trying to be created, we duplicate
- it for every website.
- Note: Particulary useful when installing a module that adds a menu like
- /shop. So every website has the shop menu.
- Be careful to return correct record for ir.model.data xml_id in case
- of default main menus creation.
- '''
- # Only used when creating website_data.xml default menu
- if vals.get('url') == '/default-main-menu':
- return super(Menu, self).create(vals)
-
- if 'website_id' in vals:
- return super(Menu, self).create(vals)
- elif self._context.get('website_id'):
- vals['website_id'] = self._context.get('website_id')
- return super(Menu, self).create(vals)
- else:
- # create for every site
- for website in self.env['website'].search([]):
- w_vals = dict(vals, **{
- 'website_id': website.id,
- 'parent_id': website.menu_id.id,
- })
- res = super(Menu, self).create(w_vals)
- # if creating a default menu, we should also save it as such
- default_menu = self.env.ref('website.main_menu', raise_if_not_found=False)
- if default_menu and vals.get('parent_id') == default_menu.id:
- res = super(Menu, self).create(vals)
- return res # Only one record is returned but multiple could have been created
-
- @api.multi
- def unlink(self):
- default_menu = self.env.ref('website.main_menu', raise_if_not_found=False)
- for menu in self.filtered(lambda m: default_menu and m.parent_id.id == default_menu.id):
- self.env['website.menu'].search([('url', '=', menu.url), ('id', '!=', menu.id)]).unlink()
- return super(Menu, self).unlink()
-
- @api.one
- def _compute_visible(self):
- visible = True
- if self.page_id and not self.page_id.sudo().is_visible and not self.user_has_groups('base.group_user'):
- visible = False
- self.is_visible = visible
-
- @api.model
- def clean_url(self):
- # clean the url with heuristic
- if self.page_id:
- url = self.page_id.sudo().url
- else:
- url = self.url
- if url and not self.url.startswith('/'):
- if '@' in self.url:
- if not self.url.startswith('mailto'):
- url = 'mailto:%s' % self.url
- elif not self.url.startswith('http'):
- url = '/%s' % self.url
- return url
-
- # would be better to take a menu_id as argument
- @api.model
- def get_tree(self, website_id, menu_id=None):
- def make_tree(node):
- page_id = node.page_id.id if node.page_id else None
- is_homepage = page_id and self.env['website'].browse(website_id).homepage_id.id == page_id
- menu_node = dict(
- id=node.id,
- name=node.name,
- url=node.page_id.url if page_id else node.url,
- new_window=node.new_window,
- sequence=node.sequence,
- parent_id=node.parent_id.id,
- children=[],
- is_homepage=is_homepage,
- )
- for child in node.child_id:
- menu_node['children'].append(make_tree(child))
- return menu_node
- if menu_id:
- menu = self.browse(menu_id)
- else:
- menu = self.env['website'].browse(website_id).menu_id
- return make_tree(menu)
-
- @api.model
- def save(self, website_id, data):
- def replace_id(old_id, new_id):
- for menu in data['data']:
- if menu['id'] == old_id:
- menu['id'] = new_id
- if menu['parent_id'] == old_id:
- menu['parent_id'] = new_id
- to_delete = data['to_delete']
- if to_delete:
- self.browse(to_delete).unlink()
- for menu in data['data']:
- mid = menu['id']
- # new menu are prefixed by new-
- if isinstance(mid, str):
- new_menu = self.create({'name': menu['name'], 'website_id': website_id})
- replace_id(mid, new_menu.id)
- for menu in data['data']:
- menu_id = self.browse(menu['id'])
- # if the url match a website.page, set the m2o relation
- # except if the menu url is '#', meaning it will be used as a menu container, most likely for a dropdown
- if menu['url'] == '#':
- if menu_id.page_id:
- menu_id.page_id = None
- else:
- page = self.env['website.page'].search(['|', ('url', '=', menu['url']), ('url', '=', '/' + menu['url'])], limit=1)
- if page:
- menu['page_id'] = page.id
- menu['url'] = page.url
- elif menu_id.page_id:
- menu_id.page_id.write({'url': menu['url']})
- menu_id.write(menu)
-
- return True
-
-
-class WebsiteRedirect(models.Model):
- _name = "website.redirect"
- _description = "Website Redirect"
- _order = "sequence, id"
- _rec_name = 'url_from'
-
- redirect_type = fields.Selection([('301', 'Moved permanently (301)'), ('302', 'Moved temporarily (302)')], string='Redirection Type', required=True, oldname='type')
- url_from = fields.Char('Redirect From', required=True)
- url_to = fields.Char('Redirect To', required=True)
- website_id = fields.Many2one('website', 'Website')
- active = fields.Boolean(default=True)
- sequence = fields.Integer()