19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:39 +01:00
parent 38c6088dcc
commit d9452d2060
243 changed files with 30797 additions and 10815 deletions

View file

@ -4,6 +4,7 @@
from . import test_controller_args
from . import test_custom_snippet
from . import test_error
from . import test_form
from . import test_fuzzy
from . import test_image_upload_progress
from . import test_is_multilang
@ -13,9 +14,17 @@ from . import test_multi_company
from . import test_page_manager
from . import test_page
from . import test_performance
from . import test_qweb
from . import test_redirect
from . import test_reset_views
from . import test_restricted_editor
from . import test_session
from . import test_settings
from . import test_snippet_background_video
from . import test_systray
from . import test_theme_ir_asset
from . import test_translation
from . import test_views_during_module_operation
from . import test_website_controller_page
from . import test_website_page_properties
from . import test_website_field_sanitize

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<asset id="theme_default.test_asset_tag_aaa" name="Test asset tag (init active => keep active => update active)">
<bundle>test_asset_bundle</bundle>
<path>theme_default/tests/something.scss</path>
</asset>
<asset id="theme_default.test_asset_tag_aii" name="Test asset tag (init active => make inactive => update inactive)">
<bundle>test_asset_bundle</bundle>
<path>theme_default/tests/something.scss</path>
</asset>
<asset id="theme_default.test_asset_tag_aia" name="Test asset tag (init active => make inactive => update active)">
<bundle>test_asset_bundle</bundle>
<path>theme_default/tests/something.scss</path>
<field name="active">True</field> <!-- Take into account during update -->
</asset>
<asset id="theme_default.test_asset_tag_iii" name="Test asset tag (init inactive => keep inactive => update inactive)" active="False">
<bundle>test_asset_bundle</bundle>
<path>theme_default/tests/something.scss</path>
</asset>
<asset id="theme_default.test_asset_tag_iaa" name="Test asset tag (init inactive => make active => update active)" active="False">
<bundle>test_asset_bundle</bundle>
<path>theme_default/tests/something.scss</path>
</asset>
<asset id="theme_default.test_asset_tag_prepend" name="Test asset tag with directive">
<bundle directive="prepend">test_asset_bundle</bundle>
<path>theme_default/tests/something.scss</path>
</asset>
<asset id="theme_default.test_asset_tag_extra" name="Test asset tag with extra field">
<bundle>test_asset_bundle</bundle>
<path>theme_default/tests/something.scss</path>
<field name="sequence" eval="17"/>
</asset>
</odoo>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="test_website.test_template" name="test template 2">
&lt;!DOCTYPE html&gt;
<html>
<head>
<t t-call-assets="test_website.test_bundle" t-js="False"/>
<meta/>
<t t-call-assets="test_website.test_bundle" t-css="False"/>
</head>
<body>
<img src="http://test.external.link/img.png"/>
<img src="/test_website/static/img.png"/>
<a href="http://test.external.link/link">x</a>
<a href="/web/content/local_link">x</a>
<span t-attf-style="background-image: url('/web/image/2')" t-att-empty="False">xxx</span>
<div widget="html" t-field="user.signature"/>
<div widget="image" t-field="user.avatar_1920" t-options="{'widget': 'image'}"/>
</body>
</html>
</template>
<template id="test_website.test_template_tatt_qweb" name="t-att template">
<a t-att-href='"/"'>1</a>
<a t-att-href='False'>2</a>
<a t-att-href='None'>3</a>
<a t-att-href=''>4</a>
<a t-att-href='""'>5</a>
</template>
</odoo>

View file

@ -1,6 +1,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import odoo.tests
from odoo.tools import mute_logger
from unittest.mock import patch
@odoo.tests.common.tagged('post_install', '-at_install')
@ -32,9 +33,10 @@ class TestWebsiteControllerArgs(odoo.tests.HttpCase):
self.assertEqual(req.status_code, 200)
self.assertEqual(req.json(), {'a': 'valueA', 'kw': {'b': 'valueB'}})
req = self.url_open('/test_website/country/whatever-999999')
self.assertEqual(req.status_code, 404,
"Model converter record does not exist, return a 404.")
with patch.object(self.registry['ir.http'], '_get_error_html', lambda e, code, v: (code, '')):
req = self.url_open('/test_website/country/whatever-999999')
self.assertEqual(req.status_code, 404,
"Model converter record does not exist, return a 404.")
@odoo.tests.common.tagged('post_install', '-at_install')
@ -44,3 +46,8 @@ class TestWebsiteControllers(odoo.tests.TransactionCase):
website = self.env['website'].browse(1)
locs = website.with_user(website.user_id)._enumerate_pages(query_string='test_website_sitemap')
self.assertEqual(len(list(locs)), 1, "The same URL should only be shown once")
def test_02_search_controller(self):
website = self.env['website'].browse(1)
res = website._enumerate_pages(query_string="/test_website/country/elgium")
self.assertIn('/test_website/country/belgium', next(res).get('loc'))

View file

@ -0,0 +1,14 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import tagged, HttpCase
@tagged('-at_install', 'post_install')
class TestForm(HttpCase):
def test_form_conditional_visibility_record_field(self):
self.start_tour(
self.env['website'].get_client_action_url('/test_website/model_item/1'),
'test_form_conditional_visibility_record_field',
login='admin',
)

View file

@ -5,7 +5,7 @@ import logging
import psycopg2
from odoo.addons.website.controllers.main import Website
from odoo.addons.website.tools import MockRequest
from odoo.addons.http_routing.tests.common import MockRequest
import odoo.tests
from odoo.tests.common import TransactionCase
@ -108,15 +108,15 @@ class TestAutoComplete(TransactionCase):
test_page.name = 'testTotallyUnique'
# Editor and Designer see pages in result
self._autocomplete_page('testTotallyUnique', 1, False)
self._autocomplete_page('testTotallyUnique', 1, None)
test_page.visibility = 'connected'
self._autocomplete_page('testTotallyUnique', 1, False)
test_page.visibility = False
test_page.groups_id = self.env.ref('base.group_public')
test_page.group_ids = self.env.ref('base.group_public')
self._autocomplete_page('testTotallyUnique', 1, False)
test_page.groups_id = False
test_page.group_ids = False
# Public user don't see restricted page
saved_env = self.env
@ -126,12 +126,12 @@ class TestAutoComplete(TransactionCase):
test_page.website_indexed = True
self._autocomplete_page('testTotallyUnique', 1, False)
test_page.groups_id = self.env.ref('base.group_system')
test_page.group_ids = self.env.ref('base.group_system')
self._autocomplete_page('testTotallyUnique', 0, "Not found")
test_page.groups_id = self.env.ref('base.group_public')
test_page.group_ids = self.env.ref('base.group_public')
self._autocomplete_page('testTotallyUnique', 1, False)
test_page.groups_id = False
test_page.group_ids = False
test_page.visibility = 'password'
self._autocomplete_page('testTotallyUnique', 0, "Not found")
@ -141,3 +141,11 @@ class TestAutoComplete(TransactionCase):
# restore website env for next tests
self.website.env = self.env = saved_env
def test_indirect(self):
self._autocomplete('module', 4, 'model')
self._autocomplete('rechord', 3, 'record')
self._autocomplete('suborder', 1, 'submodel')
# Sub-sub-fields are currently not supported.
# Adapt expected result if this becomes a feature.
self._autocomplete('tagg', 0, "Not found")

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.web_editor.controllers.main import Web_Editor
from odoo.addons.html_editor.controllers.main import HTML_Editor
from odoo.addons.web_unsplash.controllers.main import Web_Unsplash
import odoo.tests
@ -18,15 +18,15 @@ class TestImageUploadProgress(odoo.tests.HttpCase):
def test_02_image_upload_progress_unsplash(self):
BASE_URL = self.base_url()
@http.route('/web_editor/media_library_search', type='json', auth="user", website=True)
@http.route('/html_editor/media_library_search', type='jsonrpc', auth="user", website=True)
def media_library_search(self, **params):
return {"results": 0, "media": []}
# because not preprocessed by ControllerType metaclass
media_library_search.original_endpoint.routing_type = 'json'
# disable undraw, no third party should be called in tests
self.patch(Web_Editor, 'media_library_search', media_library_search)
self.patch(HTML_Editor, 'media_library_search', media_library_search)
@http.route("/web_unsplash/fetch_images", type='json', auth="user")
@http.route("/web_unsplash/fetch_images", type='jsonrpc', auth="user")
def fetch_unsplash_images(self, **post):
return {
'total': 1434,
@ -36,11 +36,11 @@ class TestImageUploadProgress(odoo.tests.HttpCase):
'alt_description': 'brown fox sitting on green grass field during daytime',
'urls': {
# 'regular': 'https://images.unsplash.com/photo-1462953491269-9aff00919695?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwzMDUwOHwwfDF8c2VhcmNofDF8fGZveHxlbnwwfHx8fDE2MzEwMzIzNDE&ixlib=rb-1.2.1&q=80&w=1080',
'regular': BASE_URL + '/website/static/src/img/phone.png',
'regular': BASE_URL + '/website/static/src/img/user-restricted-image.png',
},
'links': {
# 'download_location': 'https://api.unsplash.com/photos/HQqIOc8oYro/download?ixid=MnwzMDUwOHwwfDF8c2VhcmNofDF8fGZveHxlbnwwfHx8fDE2MzEwMzIzNDE'
'download_location': BASE_URL + '/website/static/src/img/phone.png',
'download_location': BASE_URL + '/website/static/src/img/user-restricted-image.png',
},
'user': {
'name': 'Mitchell Admin',

View file

@ -31,7 +31,7 @@ class TestIsMultiLang(odoo.tests.HttpCase):
it = self.env.ref('base.lang_it').sudo()
en = self.env.ref('base.lang_en').sudo()
be = self.env.ref('base.lang_fr_BE').sudo()
country1 = self.env['res.country'].create({'name': "My Super Country"})
country1 = self.env['res.country'].create({'name': "My Super Country", 'code': 'ZV'})
it.active = True
be.active = True

View file

@ -2,7 +2,6 @@
from lxml import html
from odoo.addons.website.tools import MockRequest
from odoo.tests import tagged, HttpCase
@ -21,6 +20,9 @@ class TestWebsiteMenu(HttpCase):
controller_url = '/test_website/model_item/'
website = self.env['website'].browse(1)
# First render to fill the cache.
self.url_open(f"{controller_url}{records[0].id}")
self.env['website.menu'].create([{
'name': records[0].name,
'url': f"{controller_url}{records[0].id}",
@ -36,10 +38,6 @@ class TestWebsiteMenu(HttpCase):
}])
for record in records:
record_url = f"{controller_url}{record.id}"
with MockRequest(self.env, website=website, url_root='', path=record_url):
tree = html.fromstring(self.env['ir.qweb']._render('test_website.model_item', {
'record': record,
'main_object': record,
}))
menu_link_el = tree.xpath(".//*[@id='top_menu']//a[@href='%s' and contains(@class, 'active')]" % record_url)
self.assertEqual(len(menu_link_el), 1, "The menu link related to the current record should be active")
tree = html.fromstring(self.url_open(record_url).content)
menu_link_el = tree.xpath(".//*[@id='top_menu']//a[@href='%s' and hasclass('active')]" % record_url)
self.assertEqual(len(menu_link_el), 1, "The menu link related to the current record should be active")

View file

@ -1,8 +1,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import HttpCase, tagged
from odoo.tests.common import HOST
from odoo.tools import config, mute_logger
from odoo.tools import mute_logger
@tagged('-at_install', 'post_install')
@ -12,7 +11,7 @@ class WithContext(HttpCase):
website = self.env['website'].browse([1])
website.write({
'name': 'Test Website',
'domain': f'http://{HOST}:{config["http_port"]}',
'domain': self.base_url(),
'homepage_url': '/unexisting',
})
home_url = '/'

View file

@ -2,10 +2,11 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import odoo.tests
from odoo.addons.website.tests.common import HttpCaseWithWebsiteUser
@odoo.tests.common.tagged('post_install', '-at_install')
class TestWebsitePageManager(odoo.tests.HttpCase):
class TestWebsitePageManager(HttpCaseWithWebsiteUser):
def test_page_manager_test_model(self):
if self.env['website'].search_count([]) == 1:
website2 = self.env['website'].create({
@ -21,8 +22,8 @@ class TestWebsitePageManager(odoo.tests.HttpCase):
"There should at least be one record without website_id and one for 2 different websites",
)
self.assertNotIn('website_id', self.env['test.model']._fields)
self.start_tour('/web#action=test_website.action_test_model_multi_website', 'test_website_page_manager', login="admin")
self.start_tour('/odoo/action-test_website.action_test_model_multi_website', 'test_website_page_manager', login="website_user")
# This second test is about ensuring that you can switch from a list
# view which has no `website_pages_list` js_class to its kanban view
self.start_tour('/web#action=test_website.action_test_model_multi_website_js_class_bug', 'test_website_page_manager_js_class_bug', login="admin")
self.start_tour('/web#action=test_website.action_test_model', 'test_website_page_manager_no_website_id', login="admin")
self.start_tour('/odoo/action-test_website.action_test_model_multi_website_js_class_bug', 'test_website_page_manager_js_class_bug', login="website_user")
self.start_tour('/odoo/action-test_website.action_test_model', 'test_website_page_manager_no_website_id', login="website_user")

View file

@ -8,7 +8,6 @@ class TestPerformance(UtilPerf):
def test_10_perf_sql_website_controller_minimalist(self):
url = '/empty_controller_test'
select_tables_perf = {
'base_registry_signaling': 1,
'orm_signaling_registry': 1,
}
self._check_url_hot_query(url, 1, select_tables_perf)
self.assertEqual(self._get_url_hot_query(url, cache=False), 1)

View file

@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from lxml import etree
import re
from odoo import tools
from odoo.addons.base.tests.common import TransactionCaseWithUserDemo
from odoo.addons.http_routing.tests.common import MockRequest
class TestQweb(TransactionCaseWithUserDemo):
def _load(self, module, filepath):
tools.convert_file(self.env, module, filepath, {}, 'init')
def test_qweb_cdn(self):
self._load('test_website', 'tests/template_qweb_test.xml')
website = self.env.ref('website.default_website')
website.write({
"cdn_activated": True,
"cdn_url": "http://test.cdn"
})
demo = self.env['res.users'].search([('login', '=', 'demo')])[0]
demo.write({"signature": '''<span class="toto">
span<span class="fa"></span><img src="/web/image/1"/>
</span>'''})
demo_env = self.env(user=demo)
html = demo_env['ir.qweb']._render('test_website.test_template', {"user": demo}, website_id=website.id)
asset_bundle_xmlid = 'test_website.test_bundle'
qweb = self.env['ir.qweb']
bundle = qweb._get_asset_bundle(asset_bundle_xmlid, css=True, js=True, assets_params={'website_id': website.id})
asset_version_js = bundle.get_version('js')
asset_version_css = bundle.get_version('css')
css_url, js_url = bundle.get_links()[-2:]
html = html.strip()
html = re.sub(r'\?unique=[^"]+', '', html).encode('utf8')
format_data = {
"css": css_url,
"js": js_url,
"user_id": demo.id,
"filename": "Marc%20Demo",
"alt": "Marc Demo",
"asset_xmlid": asset_bundle_xmlid,
"asset_version_css": asset_version_css,
"asset_version_js": asset_version_js,
}
self.assertHTMLEqual(html, ("""<!DOCTYPE html>
<html>
<head>
<link type="text/css" rel="stylesheet" href="http://test.external.link/style1.css"/>
<link type="text/css" rel="stylesheet" href="http://test.external.link/style2.css"/>
<link type="text/css" rel="stylesheet" href="http://test.cdn%(css)s"/>
<meta/>
<script type="text/javascript" src="http://test.external.link/javascript1.js"></script>
<script type="text/javascript" src="http://test.external.link/javascript2.js"></script>
<script type="text/javascript" src="http://test.cdn%(js)s" onerror="__odooAssetError=1"></script>
</head>
<body>
<img src="http://test.external.link/img.png" loading="lazy"/>
<img src="http://test.cdn/test_website/static/img.png" loading="lazy"/>
<a href="http://test.external.link/link">x</a>
<a href="http://test.cdn/web/content/local_link">x</a>
<span style="background-image: url(&#39;http://test.cdn/web/image/2&#39;)">xxx</span>
<div widget="html"><span class="toto">
span<span class="fa"></span><img src="http://test.cdn/web/image/1" loading="lazy">
</span></div>
<div widget="image"><img src="http://test.cdn/web/image/res.users/%(user_id)s/avatar_1920/%(filename)s" class="img img-fluid" alt="%(alt)s" loading="lazy"/></div>
</body>
</html>""" % format_data).encode('utf8'))
with MockRequest(self.env, website=website):
html = demo_env['ir.qweb']._render('test_website.test_template_tatt_qweb', {}, website_id=website.id)
self.assertHTMLEqual(html, ("""
<html>
<body><a href="/">1</a>
<a>2</a>
<a>3</a>
<a>4</a>
<a href="">5</a></body>
</html>
"""))

View file

@ -3,7 +3,6 @@
import odoo
from odoo.tests import HttpCase, tagged
from odoo.tools import mute_logger
from odoo.addons.http_routing.models.ir_http import slug
from unittest.mock import patch
@ -19,7 +18,7 @@ class TestRedirect(HttpCase):
'login': 'portal_user',
'password': 'portal_user',
'email': 'portal_user@mail.com',
'groups_id': [(6, 0, [self.env.ref('base.group_portal').id])]
'group_ids': [(6, 0, [self.env.ref('base.group_portal').id])]
})
def test_01_redirect_308_model_converter(self):
@ -37,7 +36,7 @@ class TestRedirect(HttpCase):
- Correct & working redirect as logged in user
- Correct replace of url_for() URLs in DOM
"""
url = '/test_website/country/' + slug(country_ad)
url = '/test_website/country/' + self.env['ir.http']._slug(country_ad)
redirect_url = url.replace('test_website', 'redirected')
# [Public User] Open the original url and check redirect OK
@ -57,12 +56,12 @@ class TestRedirect(HttpCase):
self.assertTrue(redirect_url in r.text, "Ensure the url_for has replaced the href URL in the DOM")
def test_redirect_308_by_method_url_rewrite(self):
self.env['website.rewrite'].create({
self.env['website.rewrite'].create([{
'name': 'Test Website Redirect',
'redirect_type': '308',
'url_from': url_from,
'url_to': f'{url_from}_new',
} for url_from in ('/get', '/post', '/get_post'))
} for url_from in ('/get', '/post', '/get_post')])
self.env.ref('test_website.test_view').arch = '''
<t>
@ -90,7 +89,7 @@ class TestRedirect(HttpCase):
rec_published = self.env['test.model'].create({'name': 'name', 'website_published': True})
rec_unpublished = self.env['test.model'].create({'name': 'name', 'website_published': False})
WebsiteHttp = odoo.addons.website.models.ir_http.Http
WebsiteHttp = odoo.addons.website.models.ir_http.IrHttp
def _get_error_html(env, code, value):
return str(code).split('_')[-1], f"CUSTOM {code}"
@ -201,7 +200,7 @@ class TestRedirect(HttpCase):
'name': '301 test record',
'is_published': True,
})
url_rec1 = '/test_website/200/' + slug(rec1)
url_rec1 = '/test_website/200/' + self.env['ir.http']._slug(rec1)
r = self.url_open(url_rec1)
self.assertEqual(r.status_code, 200)
@ -223,7 +222,7 @@ class TestRedirect(HttpCase):
# 4. Accessing unpublished record with redirect to another published
# record: expecting redirect to that record
rec2 = rec1.copy({'is_published': True})
url_rec2 = '/test_website/200/' + slug(rec2)
url_rec2 = '/test_website/200/' + self.env['ir.http']._slug(rec2)
redirect.url_to = url_rec2
r = self.url_open(url_rec1)
self.assertEqual(r.status_code, 200)
@ -264,7 +263,7 @@ class TestRedirect(HttpCase):
'name': '301 test record',
'is_published': True,
})
url_rec1 = f"/test_countries_308/{slug(rec1)}"
url_rec1 = f"/test_countries_308/{self.env['ir.http']._slug(rec1)}"
resp = self.url_open("/test_countries_308", allow_redirects=False)
self.assertEqual(resp.status_code, 308)

View file

@ -1,8 +1,11 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import json
import re
import odoo.tests
from odoo.tools import mute_logger
import unittest
def break_view(view, fr='<p>placeholder</p>', to='<p t-field="no_record.exist"/>'):
@ -17,8 +20,10 @@ class TestWebsiteResetViews(odoo.tests.HttpCase):
resp = self.url_open(page)
self.assertEqual(resp.status_code, 500, "Waiting 500")
self.assertTrue('<button data-mode="soft" class="reset_templates_button' in resp.text)
data = {'view_id': self.find_template(resp), 'redirect': page, 'mode': mode}
resp = self.url_open('/website/reset_template', data)
data = {'params': {'view_id': self.find_template(resp), 'mode': mode}}
self.url_open('/website/reset_template', data=json.dumps(data), headers={'Content-Type': 'application/json'})
resp = self.url_open(page)
self.assertTrue(resp.url.endswith(page), "We should be checking the test page")
self.assertEqual(resp.status_code, 200, "Waiting 200")
def find_template(self, response):
@ -104,6 +109,7 @@ class TestWebsiteResetViews(odoo.tests.HttpCase):
# Break it again to have a previous arch different than file arch
break_view(self.test_page_view.with_context(website_id=1))
self.assertEqual(total_views + 1, self.View.search_count([('type', '=', 'qweb')]), "Missing COW view")
with self.assertRaises(AssertionError):
# soft reset should not be able to reset the view as previous
# version is also broken

View file

@ -0,0 +1,52 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import unittest
import odoo.tests
from odoo.tools import mute_logger
from odoo.addons.website.tests.common import HttpCaseWithWebsiteUser
@odoo.tests.common.tagged('post_install', '-at_install')
class TestRestrictedEditor(HttpCaseWithWebsiteUser):
@classmethod
def setUpClass(cls):
super().setUpClass()
website = cls.env['website'].search([], limit=1)
fr = cls.env.ref('base.lang_fr').sudo()
en = cls.env.ref('base.lang_en').sudo()
fr.active = True
website.default_lang_id = en
website.language_ids = en + fr
cls.env['website.menu'].create({
'name': 'Model item',
'url': '/test_website/model_item/1',
'parent_id': website.menu_id.id,
'sequence': 100,
})
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
def test_01_restricted_editor_only(self):
self.start_tour(self.env['website'].get_client_action_url('/'), 'test_restricted_editor_only', login="website_user")
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
def test_02_restricted_editor_test_admin(self):
self.user_website_user.group_ids += self.env.ref("test_website.group_test_website_admin")
self.start_tour(self.env['website'].get_client_action_url('/'), 'test_restricted_editor_test_admin', login="website_user")
# FIXME the logic of the commit that introduced the fix at 8c41c147a4c6a415e
# was reverted, so this test is disabled for now. Branding *on views* as
# a restricted editor is something we want in some custo (e.g. odoo.com).
# See commit messages for details.
@unittest.skip
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
def test_03_restricted_editor_tester(self):
"""
Tests that restricted users cannot edit ir.ui.view records despite being
on a page of a record (main_object) they can edit.
"""
self.user_website_user.group_ids += self.env.ref("test_website.group_test_website_tester")
self.start_tour(self.env['website'].get_client_action_url('/test_model/1'), 'test_restricted_editor_tester', login='website_user')

View file

@ -1,7 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from lxml import html
from unittest.mock import patch
from odoo import http
from odoo.addons.website.models.website import Website
import odoo.tests
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
@ -13,27 +16,77 @@ class TestWebsiteSession(HttpCaseWithUserDemo):
def test_01_run_test(self):
self.start_tour('/', 'test_json_auth')
def test_02_inactive_session_lang(self):
session = self.authenticate(None, None)
self.env.ref('base.lang_fr').active = False
session.context['lang'] = 'fr_FR'
odoo.http.root.session_store.save(session)
# ensure that _get_current_website_id will be able to match a website
current_website_id = self.env["website"]._get_current_website_id(odoo.tests.HOST)
self.env["website"].browse(current_website_id).domain = odoo.tests.HOST
res = self.url_open('/test_website_sitemap') # any auth='public' route would do
res.raise_for_status()
def test_03_totp_login_with_inactive_session_lang(self):
session = self.authenticate(None, None)
self.env.ref('base.lang_fr').active = False
session.context['lang'] = 'fr_FR'
odoo.http.root.session_store.save(session)
# ensure that _get_current_website_id will be able to match a website
current_website_id = self.env["website"]._get_current_website_id(odoo.tests.HOST)
self.env["website"].browse(current_website_id).domain = odoo.tests.HOST
with patch.object(self.env.registry["res.users"], "_mfa_url", return_value="/web/login/totp"):
res = self.url_open('/web/login', allow_redirects=False, data={
'login': 'demo',
'password': 'demo',
'csrf_token': http.Request.csrf_token(self),
})
res.raise_for_status()
self.assertEqual(res.status_code, 303)
self.assertTrue(res.next.path_url.startswith("/web/login/totp"))
def test_04_ensure_website_get_cached_values_can_be_called(self):
session = self.authenticate('admin', 'admin')
# Force a browser language that is not installed
session.context['lang'] = 'fr_MC'
http.root.session_store.save(session)
# Disable cache in order to make sure that values would be fetched at any time
get_cached_values_without_cache = Website._get_cached_values.__cache__.method
with patch.object(Website, '_get_cached_values',
side_effect=get_cached_values_without_cache, autospec=True):
# ensure that permissions on logout are OK
res = self.url_open('/web/session/logout')
self.assertEqual(res.status_code, 200)
def test_branding_cache(self):
def has_branding(html_text):
el = html.fromstring(html_text)
return el.xpath('//*[@data-oe-model="test.model"]')
self.user_demo.groups_id += self.env.ref('website.group_website_restricted_editor')
self.user_demo.groups_id -= self.env.ref('website.group_website_designer')
self.user_demo.group_ids += self.env.ref('website.group_website_restricted_editor')
self.user_demo.group_ids += self.env.ref('test_website.group_test_website_admin')
self.user_demo.group_ids -= self.env.ref('website.group_website_designer')
# Create session for demo user.
public_session = self.authenticate(None, None)
demo_session = self.authenticate('demo', 'demo')
record = self.env['test.model'].search([], limit=1)
result = self.url_open(f'/test_website/model_item/{record.id}')
result = self.url_open(f'/test_website/model_item_sudo/{record.id}')
self.assertTrue(has_branding(result.text), "Should have branding for user demo")
# Public user.
self.opener.cookies['session_id'] = public_session.sid
result = self.url_open(f'/test_website/model_item/{record.id}')
self.opener.cookies.set("session_id", public_session.sid, domain=odoo.tests.common.HOST)
result = self.url_open(f'/test_website/model_item_sudo/{record.id}')
self.assertFalse(has_branding(result.text), "Should have no branding for public user")
# Back to demo user.
self.opener.cookies['session_id'] = demo_session.sid
result = self.url_open(f'/test_website/model_item/{record.id}')
self.opener.cookies.set("session_id", demo_session.sid, domain=odoo.tests.common.HOST)
result = self.url_open(f'/test_website/model_item_sudo/{record.id}')
self.assertTrue(has_branding(result.text), "Should have branding for user demo")

View file

@ -6,6 +6,7 @@ import odoo.tests
@odoo.tests.tagged('-at_install', 'post_install')
class TestWebsiteSettings(odoo.tests.HttpCase):
def test_01_multi_website_settings(self):
# If not enabled (like in demo data), landing on res.config will try
# to disable module_sale_quotation_builder and raise an issue
@ -13,4 +14,4 @@ class TestWebsiteSettings(odoo.tests.HttpCase):
if group_order_template:
self.env.ref('base.group_user').write({"implied_ids": [(4, group_order_template.id)]})
self.env['website'].create({'name': "Website Test Settings", 'specific_user_account': True})
self.start_tour("/web", 'website_settings_m2o_dirty', login="admin")
self.start_tour("/odoo", 'website_settings_m2o_dirty', login="admin")

View file

@ -0,0 +1,10 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import odoo.tests
@odoo.tests.common.tagged('post_install', '-at_install')
class TestSnippetBackgroundVideo(odoo.tests.HttpCase):
def test_snippet_background_video(self):
self.start_tour("/", "snippet_background_video", login="admin")

View file

@ -1,3 +1,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests.common import HOST, new_test_user, tagged
@ -31,7 +32,8 @@ class TestSystray(HttpCase):
</xpath>
"""
})
# Remain on page when switching website
cls.env['website'].search([]).homepage_url = '/test_model/1'
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
def test_01_admin(self):
@ -39,27 +41,43 @@ class TestSystray(HttpCase):
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
def test_02_reditor_tester(self):
self.user_test.groups_id |= self.group_restricted_editor
self.user_test.groups_id |= self.group_tester
self.user_test.group_ids |= self.group_restricted_editor
self.user_test.group_ids |= self.group_tester
self.start_tour(self.env['website'].get_client_action_url('/test_model/1'), 'test_systray_reditor_tester', login="testtest")
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
def test_03_reditor_not_tester(self):
self.user_test.groups_id |= self.group_restricted_editor
self.user_test.groups_id = self.user_test.groups_id.filtered(lambda group: group != self.group_tester)
self.assertNotIn(self.group_tester.id, self.user_test.groups_id.ids, "User should not be a group_tester")
self.user_test.group_ids |= self.group_restricted_editor
self.user_test.group_ids = self.user_test.group_ids.filtered(lambda group: group != self.group_tester)
self.assertNotIn(self.group_tester.id, self.user_test.group_ids.ids, "User should not be a group_tester")
self.start_tour(self.env['website'].get_client_action_url('/test_model/1'), 'test_systray_reditor_not_tester', login="testtest")
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
def test_04_not_reditor_tester(self):
self.user_test.groups_id = self.user_test.groups_id.filtered(lambda group: group != self.group_restricted_editor)
self.user_test.groups_id |= self.group_tester
self.assertNotIn(self.group_restricted_editor.id, self.user_test.groups_id.ids, "User should not be a group_restricted_editor")
self.user_test.group_ids = self.user_test.group_ids.filtered(lambda group: group != self.group_restricted_editor)
self.user_test.group_ids |= self.group_tester
self.assertNotIn(self.group_restricted_editor.id, self.user_test.group_ids.ids, "User should not be a group_restricted_editor")
self.start_tour(self.env['website'].get_client_action_url('/test_model/1'), 'test_systray_not_reditor_tester', login="testtest")
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
def test_05_not_reditor_not_tester(self):
self.user_test.groups_id = self.user_test.groups_id.filtered(lambda group: group not in [self.group_restricted_editor, self.group_tester])
self.assertNotIn(self.group_restricted_editor.id, self.user_test.groups_id.ids, "User should not be a group_restricted_editor")
self.assertNotIn(self.group_tester.id, self.user_test.groups_id.ids, "User should not be a group_tester")
self.user_test.group_ids = self.user_test.group_ids.filtered(lambda group: group not in [self.group_restricted_editor, self.group_tester])
self.assertNotIn(self.group_restricted_editor.id, self.user_test.group_ids.ids, "User should not be a group_restricted_editor")
self.assertNotIn(self.group_tester.id, self.user_test.group_ids.ids, "User should not be a group_tester")
self.start_tour(self.env['website'].get_client_action_url('/test_model/1'), 'test_systray_not_reditor_not_tester', login="testtest")
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
def test_06_single_website(self):
if self.env['website'].search_count([]) > 1:
website = self.env['website'].search([], limit=1)
websites_to_remove = self.env['website'].search([('id', '!=', website.id)])
websites_to_remove.unlink()
self.start_tour(self.env['website'].get_client_action_url('/test_model/1'), 'test_systray_single_website', login="admin")
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
def test_07_multi_website(self):
if self.env['website'].search_count([]) == 1:
self.env['website'].create({
'name': 'My Website 2',
})
self.start_tour(self.env['website'].get_client_action_url('/test_model/1'), 'test_systray_multi_website', login="admin")

View file

@ -0,0 +1,77 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests.common import TransactionCase, tagged
from odoo.tools import convert_file
from odoo.tools.misc import file_path
@tagged('-at_install', 'post_install')
class TestThemeAsset(TransactionCase):
def test_theme_asset_tag(self):
"""
Verify that assets defined with the <asset> tag are properly imported.
"""
# Load new records
convert_file(
self.env, 'theme_default',
file_path('test_website/tests/asset_tag.xml'),
{}, 'init', False,
)
active_keep_asset = self.env.ref('theme_default.test_asset_tag_aaa')
inactive_keep_asset = self.env.ref('theme_default.test_asset_tag_iii')
active_switch_asset_reset = self.env.ref('theme_default.test_asset_tag_aia')
active_switch_asset_ignore = self.env.ref('theme_default.test_asset_tag_aii')
inactive_switch_asset = self.env.ref('theme_default.test_asset_tag_iaa')
prepend_asset = self.env.ref('theme_default.test_asset_tag_prepend')
asset_with_extra_field = self.env.ref('theme_default.test_asset_tag_extra')
# Verify initial load
self.assertEqual(prepend_asset._name, 'theme.ir.asset', 'Model should be theme.ir.asset')
self.assertEqual(prepend_asset.name, 'Test asset tag with directive', 'Name not loaded')
self.assertEqual(prepend_asset.directive, 'prepend', 'Directive not loaded')
self.assertEqual(prepend_asset.bundle, 'test_asset_bundle', 'Bundle not loaded')
self.assertEqual(prepend_asset.path, 'theme_default/tests/something.scss', 'Path not loaded')
self.assertEqual(asset_with_extra_field.sequence, 17, 'Sequence not loaded')
self.assertTrue(active_keep_asset.active, 'Should be active')
self.assertTrue(active_switch_asset_reset.active, 'Should be active')
self.assertTrue(active_switch_asset_ignore.active, 'Should be active')
self.assertFalse(inactive_keep_asset.active, 'Should be inactive')
self.assertFalse(inactive_switch_asset.active, 'Should be inactive')
# Patch records
prepend_asset.name = 'changed'
prepend_asset.directive = 'append'
prepend_asset.bundle = 'changed'
prepend_asset.path = 'theme_default/tests/changed.scss'
asset_with_extra_field.sequence = 3
active_switch_asset_reset.active = False
active_switch_asset_ignore.active = False
inactive_switch_asset.active = True
# Update records
convert_file(
self.env, 'theme_default',
file_path('test_website/tests/asset_tag.xml'),
{
'theme_default.test_asset_tag_aaa': active_keep_asset.id,
'theme_default.test_asset_tag_iii': inactive_keep_asset.id,
'theme_default.test_asset_tag_aia': active_switch_asset_reset.id,
'theme_default.test_asset_tag_aii': active_switch_asset_ignore.id,
'theme_default.test_asset_tag_iaa': inactive_switch_asset.id,
'theme_default.test_asset_tag_prepend': prepend_asset.id,
'theme_default.test_asset_tag_extra': asset_with_extra_field.id,
}, 'update', False,
)
# Verify updated load
self.assertEqual(prepend_asset.name, 'Test asset tag with directive', 'Name not restored')
self.assertEqual(prepend_asset.directive, 'prepend', 'Directive not restored')
self.assertEqual(prepend_asset.bundle, 'test_asset_bundle', 'Bundle not restored')
self.assertEqual(prepend_asset.path, 'theme_default/tests/something.scss', 'Path not restored')
self.assertEqual(asset_with_extra_field.sequence, 17, 'Sequence not restored')
self.assertTrue(active_keep_asset.active, 'Should be active')
self.assertTrue(active_switch_asset_reset.active, 'Should be reset to active')
self.assertFalse(active_switch_asset_ignore.active, 'Should be kept inactive')
self.assertFalse(inactive_keep_asset.active, 'Should be inactive')
self.assertTrue(inactive_switch_asset.active, 'Should be kept active')

View file

@ -0,0 +1,129 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import Command
from odoo.tests import HttpCase, tagged
@tagged('post_install', '-at_install', 'is_tour')
class TestTranslation(HttpCase):
def _single_language_fr_user_fr_site(self):
self.start_tour(self.env['website'].get_client_action_url('/'), 'translation_single_language_fr_user_fr_site', login='admin')
def _single_language_en_user_fr_site(self):
self.start_tour(self.env['website'].get_client_action_url('/'), 'translation_single_language_en_user_fr_site', login='admin')
def _single_language_fr_user_en_site(self):
self.start_tour(self.env['website'].get_client_action_url('/'), 'translation_single_language_fr_user_en_site', login='admin')
def _multi_language_fr_user_fr_en_site(self):
self.start_tour(self.env['website'].get_client_action_url('/'), 'translation_multi_language_fr_user_fr_en_site', login='admin', timeout=250)
def _multi_language_fr_user_en_fr_site(self):
self.start_tour(self.env['website'].get_client_action_url('/'), 'translation_multi_language_fr_user_en_fr_site', login='admin', timeout=250)
def _multi_language_en_user_fr_en_site(self):
self.start_tour(self.env['website'].get_client_action_url('/'), 'translation_multi_language_en_user_fr_en_site', login='admin', timeout=250)
def _multi_language_en_user_en_fr_site(self):
self.start_tour(self.env['website'].get_client_action_url('/'), 'translation_multi_language_en_user_en_fr_site', login='admin', timeout=250)
def _fr_db(self):
self._fr_en_db()
lang_en = self.env.ref('base.lang_en')
lang_en.active = False
def _fr_en_db(self):
lang_en = self.env.ref('base.lang_en')
lang_fr = self.env.ref('base.lang_fr')
self.env["base.language.install"].create({
'overwrite': True,
'lang_ids': [(6, 0, [lang_fr.id])],
}).lang_install()
for website in self.env['website'].search([]):
website.language_ids += lang_fr
website.default_lang_id = lang_fr
website.language_ids -= lang_en
self.env['website'].create({
'sequence': 1,
'name': 'Test FR Website',
'language_ids': [
Command.link(lang_fr.id),
],
'default_lang_id': lang_fr.id,
})
for user in self.env['res.users'].search([]):
user.lang = lang_fr.code
for partner in self.env['res.partner'].search([]):
partner.lang = lang_fr.code
for user in self.env['res.users'].with_context(active_test=False).search([]):
user.lang = lang_fr.code
def _en_fr_db(self):
lang_fr = self.env.ref('base.lang_fr')
self.env["base.language.install"].create({
'overwrite': True,
'lang_ids': [(6, 0, [lang_fr.id])],
}).lang_install()
def test_fr_db_fr_site(self):
self._fr_db()
self._single_language_fr_user_fr_site()
def test_fr_en_db_fr_site(self):
self._fr_en_db()
self._single_language_fr_user_fr_site()
def test_fr_en_db_en_site(self):
self._fr_en_db()
lang_en = self.env.ref('base.lang_en')
lang_fr = self.env.ref('base.lang_fr')
for website in self.env['website'].search([]):
website.language_ids += lang_en
website.default_lang_id = lang_en
website.language_ids -= lang_fr
self._single_language_fr_user_en_site()
def test_fr_en_db_fr_en_site(self):
self._fr_en_db()
lang_en = self.env.ref('base.lang_en')
for website in self.env['website'].search([]):
website.language_ids += lang_en
self._multi_language_fr_user_fr_en_site()
def test_fr_en_db_en_fr_site(self):
self._fr_en_db()
lang_en = self.env.ref('base.lang_en')
for website in self.env['website'].search([]):
website.language_ids += lang_en
website.default_lang_id = lang_en
self._multi_language_fr_user_en_fr_site()
def test_en_fr_db_fr_site(self):
self._en_fr_db()
lang_en = self.env.ref('base.lang_en')
lang_fr = self.env.ref('base.lang_fr')
self.env["base.language.install"].create({
'overwrite': True,
'lang_ids': [(6, 0, [lang_fr.id])],
}).lang_install()
for website in self.env['website'].search([]):
website.language_ids += lang_fr
website.default_lang_id = lang_fr
website.language_ids -= lang_en
self._single_language_en_user_fr_site()
def test_en_fr_db_fr_en_site(self):
self._en_fr_db()
lang_fr = self.env.ref('base.lang_fr')
for website in self.env['website'].search([]):
website.language_ids += lang_fr
website.default_lang_id = lang_fr
self._multi_language_en_user_fr_en_site()
def test_en_fr_db_en_fr_site(self):
self._en_fr_db()
lang_fr = self.env.ref('base.lang_fr')
for website in self.env['website'].search([]):
website.language_ids += lang_fr
self._multi_language_en_user_en_fr_site()

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.website.tools import MockRequest
from odoo.addons.http_routing.tests.common import MockRequest
from odoo.tests import standalone
@ -70,8 +70,7 @@ def test_01_cow_views_unlink_on_module_update(env):
# Upgrade the module
test_website_module = env['ir.module.module'].search([('name', '=', 'test_website')])
test_website_module.button_immediate_upgrade()
env.reset() # clear the set of environments
env = env() # get an environment that refers to the new registry
env.transaction.reset() # clear the set of environments
# Ensure generic views got removed
view = env.ref('test_website.update_module_view_to_be_t_called', raise_if_not_found=False)
@ -178,10 +177,22 @@ def test_02_copy_ids_views_unlink_on_module_update(env):
view_website_1, view_website_2, theme_child_view = _simulate_xml_view()
old_registry = env.registry
# Upgrade the module
theme_default.button_immediate_upgrade()
env.reset() # clear the set of environments
env = env() # get an environment that refers to the new registry
env.transaction.reset() # clear the set of environments
# Beware: records do not belong to the correct registry anymore
assert env.registry is not old_registry
# Therefore we need to re-obtain them
View = env['ir.ui.view']
ThemeView = env['theme.ir.ui.view']
Imd = env['ir.model.data']
website_1 = env['website'].browse(1)
website_2 = env['website'].browse(2)
theme_default = env.ref('base.module_theme_default')
# Ensure the theme.ir.ui.view got removed (since there is an IMD but not
# present in XML files)
@ -204,8 +215,7 @@ def test_02_copy_ids_views_unlink_on_module_update(env):
# Upgrade the module
with MockRequest(env, website=website_1):
theme_default.button_immediate_upgrade()
env.reset() # clear the set of environments
env = env() # get an environment that refers to the new registry
env.transaction.reset() # clear the set of environments
# Ensure the theme.ir.ui.view got removed (since there is an IMD but not
# present in XML files)

View file

@ -0,0 +1,195 @@
from lxml import html
from odoo.tools import mute_logger
from odoo.exceptions import AccessError, ValidationError
from odoo.tests import HttpCase, tagged
import unittest
from odoo.addons.website.controllers.model_page import ModelPageController
@tagged('post_install', '-at_install')
class TestWebsiteControllerPage(HttpCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.model = cls.env["ir.model"]._get("test.model.exposed")
cls.model_acl = cls.env["ir.model.access"].create({
"name": "test acl expose",
"model_id": cls.model.id,
"group_id": cls.env.ref("website.website_page_controller_expose").id,
"perm_read": True,
})
cls.listing_view = cls.env["ir.ui.view"].create({
"type": "qweb",
"model": cls.model.model,
"arch": """<t t-call="website.layout">
<t t-set="_activeClasses" t-translation="off">border-primary</t>
<div t-attf-class="listing_layout_switcher btn-group ms-3" t-att-data-active-classes="_activeClasses" t-att-data-view-id="view_id">
<input type="radio" class="btn-check" name="wstudio_layout" id="o_wstudio_apply_grid" value="grid" t-att-checked="'checked' if layout_mode != 'list' else None"/>
<label t-attf-class="btn btn-light #{_activeClasses if layout_mode != 'list' else None} o_wstudio_apply_grid" title="Grid" for="o_wstudio_apply_grid">
<i class="fa fa-th-large"/>
</label>
<input type="radio" class="btn-check" name="wstudio_layout" id="o_wstudio_apply_list" t-att-checked="'checked' if layout_mode == 'list' else None" value="list"/>
<label t-attf-class="btn btn-light #{_activeClasses if layout_mode == 'list' else None} o_wstudio_apply_list" title="List" for="o_wstudio_apply_list">
<i class="oi oi-view-list"/>
</label>
</div>
<div t-attf-class="row mx-n2 mt8 #{'o_website_grid' if layout_mode == 'grid' else 'o_website_list'}">
<t t-foreach="records" t-as="record">
<a class="test_record_listing" t-out="record.display_name" t-att-href="record_to_url(record)" />
</t>
</div>
</t> """
})
cls.single_view = cls.env["ir.ui.view"].create({
"type": "qweb",
"model": cls.model.model,
"arch": """<t t-call="website.layout">
<div class="test_record" t-out="record.display_name" />
</t> """
})
cls.listing_controller_page = cls.env["website.controller.page"].create({
"name": "Exposed Model",
"view_id": cls.listing_view.id,
"record_view_id": cls.single_view.id,
"record_domain": "[('name', '=ilike', 'test_partner_%')]",
"website_published": True,
})
partner_data = {}
if "is_published" in cls.env[cls.model.model]._fields:
partner_data["is_published"] = True
records_to_create = [dict(name=f"test_partner_{i}", **partner_data) for i in range(2)]
cls.exposed_records = cls.env[cls.model.model].create(records_to_create)
def test_cannot_bypass_read_rights(self):
self.env["ir.model.access"].search([("model_id", "=", self.model.id)]).perm_read = False
with self.assertRaises(AccessError) as cm:
self.env["website.controller.page"].with_user(2).create({
"name": "Exposed Model Read",
"website_id": False,
"view_id": self.single_view.id,
"record_domain": "[('name', '=ilike', 'test_partner_%')]",
"website_published": True,
})
self.assertEqual(str(cm.exception).split("\n")[0], "You are not allowed to access 'Website Model Test Exposed' (test.model.exposed) records.")
def test_access_rights_and_rules(self):
self.authenticate(None, None)
self.model_acl.active = False
with mute_logger("odoo.http"):
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}")
self.assertEqual(response.status_code, 403)
self.model_acl.active = True
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}")
self.assertEqual(response.status_code, 200)
tree = html.fromstring(response.content.decode())
rec_nodes = tree.xpath("//a[@class='test_record_listing']")
self.assertEqual(len(rec_nodes), 2)
self.env["ir.rule"].create({
"name": "dummy",
"model_id": self.model.id,
"domain_force": "[('name', '=', 'test_partner_1')]",
"groups": self.env.ref("base.group_public"),
})
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}")
tree = html.fromstring(response.content.decode())
rec_nodes = tree.xpath("//a[@class='test_record_listing']")
self.assertEqual(len(rec_nodes), 1)
def test_expose_model(self):
self.authenticate(None, None)
slug = self.env['ir.http']._slug
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}")
tree = html.fromstring(response.content.decode())
rec_nodes = tree.xpath("//a[@class='test_record_listing']")
self.assertEqual(len(rec_nodes), 2)
for n, record in zip(rec_nodes, self.exposed_records):
self.assertEqual(n.get("href"), f"/model/{self.listing_controller_page.name_slugified}/{slug(record)}")
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}/{slug(self.exposed_records[0])}")
tree = html.fromstring(response.content.decode())
self.assertEqual(len(tree.xpath("//div[@class='test_record']")), 1)
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}/fake-slug-{self.exposed_records[0].id}")
self.assertEqual(response.status_code, 404)
non_reachable_record = self.env[self.model.model].create({"name": "non_reachable"})
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}/{slug(non_reachable_record)}")
self.assertEqual(response.status_code, 404)
response = self.url_open("/model/some-other-slug")
self.assertEqual(response.status_code, 404)
self.listing_controller_page.website_published = False
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}")
self.assertEqual(response.status_code, 404)
def test_search_listing(self):
self.authenticate(None, None)
slug = self.env['ir.http']._slug
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}?search=1")
tree = html.fromstring(response.content.decode())
rec_nodes = tree.xpath("//a[@class='test_record_listing']")
self.assertEqual(len(rec_nodes), 1)
self.assertEqual(rec_nodes[0].get("href"), f"/model/{self.listing_controller_page.name_slugified}/{slug(self.exposed_records[1])}")
self.patch(ModelPageController, "pager_step", 1)
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}/page/2")
tree = html.fromstring(response.content.decode())
rec_nodes = tree.xpath("//a[@class='test_record_listing']")
self.assertEqual(len(rec_nodes), 1)
self.assertEqual(rec_nodes[0].get("href"), f"/model/{self.listing_controller_page.name_slugified}/{slug(self.exposed_records[1])}")
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}/page/2?search={self.exposed_records[0].name}")
self.assertEqual(response.url.partition('/model/')[2], f"exposed-model?search={self.exposed_records[0].name}&order=create_date+desc")
tree = html.fromstring(response.content.decode())
rec_nodes = tree.xpath("//a[@class='test_record_listing']")
self.assertEqual(len(rec_nodes), 1)
self.assertEqual(rec_nodes[0].get("href"), f"/model/{self.listing_controller_page.name_slugified}/{slug(self.exposed_records[0])}")
def test_default_layout(self):
self.assertEqual(self.listing_controller_page.default_layout, 'grid')
self.start_tour('/model/exposed-model', 'website_controller_page_listing_layout', login='admin')
self.assertEqual(self.listing_controller_page.default_layout, 'list')
#check that the user that has not previously interacted with the layout switcher will prompt on the default layout
self.start_tour('/model/exposed-model', 'website_controller_page_default_page_check', login='admin')
def test_model_constrains(self):
def get_model_meta_params(model_name):
m = self.env[model_name]
return (m._abstract, m._auto, m._transient)
default_page_vals = {
"arch": "<t><div/></t>",
"type": "qweb",
"name": "some_name"
}
# Abstract Model
self.assertEqual(get_model_meta_params("website.published.mixin"), (True, False, False))
with self.assertRaises(ValidationError) as cm:
self.env["website.controller.page"].create({**default_page_vals, "model": "website.published.mixin"})
self.assertEqual(str(cm.exception), "A page must be set to display a concrete model.")
# Transient Model
self.assertEqual(get_model_meta_params("base.partner.merge.automatic.wizard"), (False, True, True))
with self.assertRaises(ValidationError) as cm:
self.env["website.controller.page"].create({**default_page_vals, "model": "base.partner.merge.automatic.wizard"})
self.assertEqual(str(cm.exception), "A page must be set to display a concrete model.")
# _auto = False Model
self.assertEqual(get_model_meta_params("res.device"), (False, False, False))
with self.assertRaises(ValidationError) as cm:
self.env["website.controller.page"].create({**default_page_vals, "model": "res.device"})
self.assertEqual(str(cm.exception), "A page must be set to display a concrete model.")

View file

@ -0,0 +1,32 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import odoo
import odoo.tests
@odoo.tests.tagged('-at_install', 'post_install')
class TestWebsiteFieldSanitize(odoo.tests.HttpCase):
def test_sanitize_video_iframe(self):
self.env['res.users'].create({
'name': 'Restricted Editor',
'login': 'restricted',
'password': 'restricted',
'group_ids': [(6, 0, [
self.ref('base.group_user'),
self.ref('website.group_website_restricted_editor'),
self.ref('test_website.group_test_website_admin'),
])]
})
# Add a video to an HTML field (admin).
self.start_tour(
self.env['website'].get_client_action_url('/test_website/model_item/1'),
'website_designer_iframe_video',
login='admin'
)
# Make sure a user can still edit the content (restricted editor).
self.start_tour(
self.env['website'].get_client_action_url('/test_website/model_item/1'),
'website_restricted_editor_iframe_video',
login='restricted'
)

View file

@ -0,0 +1,27 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import HttpCase, tagged
@tagged('-at_install', 'post_install')
class TestWebsitePageProperties(HttpCase):
def test_website_page_properties_common(self):
self.start_tour('/test_view', 'website_page_properties_common', login='admin')
def test_website_page_properties_can_publish(self):
self.start_tour('/test_website/model_item/1', 'website_page_properties_can_publish', login='admin')
def test_website_page_properties_website_page(self):
# Create a website page with a different URL to be tested for dependency
# tracking
self.env['website.page'].create({
'name': 'Base',
'type': 'qweb',
'arch': '<div><a href="/cool-page">Cool page</a></div>',
'key': 'test.cool_page',
'url': '/dependency_page',
'website_id': self.env['website'].search([], limit=1).id,
})
self.start_tour('/', 'website_page_properties_website_page', login='admin')