cherrypy-foundation 1.0.0__py3-none-any.whl → 1.0.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. cherrypy_foundation/components/ColorModes.jinja +4 -5
  2. cherrypy_foundation/components/Datatable.jinja +2 -2
  3. cherrypy_foundation/components/Datatable.js +2 -2
  4. cherrypy_foundation/components/Field.jinja +6 -16
  5. cherrypy_foundation/components/Fields.jinja +2 -0
  6. cherrypy_foundation/components/Typeahead.css +1 -6
  7. cherrypy_foundation/components/Typeahead.jinja +2 -2
  8. cherrypy_foundation/components/__init__.py +2 -2
  9. cherrypy_foundation/components/tests/test_static.py +1 -1
  10. cherrypy_foundation/error_page.py +17 -20
  11. cherrypy_foundation/flash.py +15 -17
  12. cherrypy_foundation/form.py +2 -2
  13. cherrypy_foundation/logging.py +2 -2
  14. cherrypy_foundation/passwd.py +2 -2
  15. cherrypy_foundation/plugins/db.py +9 -35
  16. cherrypy_foundation/plugins/ldap.py +38 -46
  17. cherrypy_foundation/plugins/restapi.py +1 -1
  18. cherrypy_foundation/plugins/scheduler.py +84 -208
  19. cherrypy_foundation/plugins/smtp.py +46 -78
  20. cherrypy_foundation/plugins/tests/test_db.py +4 -4
  21. cherrypy_foundation/plugins/tests/test_ldap.py +3 -76
  22. cherrypy_foundation/plugins/tests/test_scheduler.py +50 -58
  23. cherrypy_foundation/plugins/tests/test_smtp.py +7 -40
  24. cherrypy_foundation/tests/__init__.py +0 -72
  25. cherrypy_foundation/tests/test_error_page.py +1 -7
  26. cherrypy_foundation/tests/test_passwd.py +2 -2
  27. cherrypy_foundation/tools/auth.py +38 -59
  28. cherrypy_foundation/tools/auth_mfa.py +88 -89
  29. cherrypy_foundation/tools/errors.py +27 -0
  30. cherrypy_foundation/tools/i18n.py +153 -246
  31. cherrypy_foundation/tools/jinja2.py +13 -29
  32. cherrypy_foundation/tools/ratelimit.py +27 -37
  33. cherrypy_foundation/tools/secure_headers.py +5 -1
  34. cherrypy_foundation/tools/sessions_timeout.py +21 -23
  35. cherrypy_foundation/tools/tests/locales/en/LC_MESSAGES/messages.mo +0 -0
  36. cherrypy_foundation/tools/tests/locales/{de → en}/LC_MESSAGES/messages.po +2 -2
  37. cherrypy_foundation/tools/tests/test_auth.py +4 -21
  38. cherrypy_foundation/tools/tests/test_i18n.py +6 -81
  39. cherrypy_foundation/tools/tests/test_ratelimit.py +2 -2
  40. cherrypy_foundation/url.py +25 -25
  41. cherrypy_foundation/widgets.py +2 -2
  42. cherrypy_foundation-1.0.0a1.dist-info/METADATA +42 -0
  43. {cherrypy_foundation-1.0.0.dist-info → cherrypy_foundation-1.0.0a1.dist-info}/RECORD +46 -65
  44. {cherrypy_foundation-1.0.0.dist-info → cherrypy_foundation-1.0.0a1.dist-info}/WHEEL +1 -1
  45. cherrypy_foundation/components/Flash.jinja +0 -13
  46. cherrypy_foundation/components/LocaleSelection.jinja +0 -13
  47. cherrypy_foundation/components/LocaleSelection.js +0 -26
  48. cherrypy_foundation/plugins/tests/test_scheduler_db.py +0 -107
  49. cherrypy_foundation/sessions.py +0 -93
  50. cherrypy_foundation/tests/templates/test_flash.html +0 -9
  51. cherrypy_foundation/tests/templates/test_form.html +0 -16
  52. cherrypy_foundation/tests/templates/test_url.html +0 -15
  53. cherrypy_foundation/tests/test_flash.py +0 -61
  54. cherrypy_foundation/tests/test_form.py +0 -148
  55. cherrypy_foundation/tests/test_logging.py +0 -78
  56. cherrypy_foundation/tests/test_sessions.py +0 -89
  57. cherrypy_foundation/tests/test_url.py +0 -161
  58. cherrypy_foundation/tools/tests/components/Button.jinja +0 -2
  59. cherrypy_foundation/tools/tests/locales/de/LC_MESSAGES/messages.mo +0 -0
  60. cherrypy_foundation/tools/tests/templates/test_jinja2.html +0 -11
  61. cherrypy_foundation/tools/tests/templates/test_jinjax.html +0 -9
  62. cherrypy_foundation/tools/tests/templates/test_jinjax_i18n.html +0 -22
  63. cherrypy_foundation/tools/tests/test_auth_mfa.py +0 -369
  64. cherrypy_foundation/tools/tests/test_jinja2.py +0 -153
  65. cherrypy_foundation/tools/tests/test_secure_headers.py +0 -200
  66. cherrypy_foundation-1.0.0.dist-info/METADATA +0 -71
  67. {cherrypy_foundation-1.0.0.dist-info → cherrypy_foundation-1.0.0a1.dist-info}/licenses/LICENSE.md +0 -0
  68. {cherrypy_foundation-1.0.0.dist-info → cherrypy_foundation-1.0.0a1.dist-info}/top_level.txt +0 -0
@@ -1,148 +0,0 @@
1
- # Cherrypy-foundation
2
- # Copyright (C) 2026 IKUS Software
3
- #
4
- # This program is free software: you can redistribute it and/or modify
5
- # it under the terms of the GNU General Public License as published by
6
- # the Free Software Foundation, either version 3 of the License, or
7
- # (at your option) any later version.
8
- #
9
- # This program is distributed in the hope that it will be useful,
10
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- # GNU General Public License for more details.
13
- #
14
- # You should have received a copy of the GNU General Public License
15
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
- import importlib
17
- from unittest import skipUnless
18
- from urllib.parse import urlencode
19
-
20
- import cherrypy
21
- from cherrypy.test import helper
22
- from parameterized import parameterized
23
- from wtforms.fields import BooleanField, PasswordField, StringField, SubmitField
24
- from wtforms.validators import InputRequired, Length
25
-
26
- import cherrypy_foundation.tools.jinja2 # noqa
27
- from cherrypy_foundation.form import CherryForm
28
- from cherrypy_foundation.tools.i18n import gettext_lazy as _
29
-
30
- HAS_JINJAX = importlib.util.find_spec("jinjax") is not None
31
-
32
- env = cherrypy.tools.jinja2.create_env(
33
- package_name=__package__,
34
- )
35
-
36
-
37
- class LoginForm(CherryForm):
38
- login = StringField(
39
- _('User'),
40
- validators=[
41
- InputRequired(),
42
- Length(max=256, message=_('User too long.')),
43
- ],
44
- render_kw={
45
- "placeholder": _('User'),
46
- "autocorrect": "off",
47
- "autocapitalize": "none",
48
- "autocomplete": "off",
49
- "autofocus": "autofocus",
50
- },
51
- )
52
- password = PasswordField(
53
- _('Password'),
54
- validators=[
55
- InputRequired(),
56
- Length(max=256, message=_('Password too long.')),
57
- ],
58
- render_kw={"placeholder": _("Password")},
59
- )
60
- persistent = BooleanField(
61
- _('Remember me'),
62
- # All `label-*` are assigned to the label tag.
63
- render_kw={'container_class': 'col-sm-6', 'label-attr': 'FOO'},
64
- )
65
- submit = SubmitField(
66
- _('Login'),
67
- # All `container-*` are assigned to the container tag.
68
- render_kw={"class": "btn-primary float-end", 'container_class': 'col-sm-6', 'container-attr': 'BAR'},
69
- )
70
-
71
-
72
- @cherrypy.tools.sessions()
73
- @cherrypy.tools.jinja2(env=env)
74
- class Root:
75
-
76
- @cherrypy.expose
77
- def index(self, **kwargs):
78
- if 'login' not in cherrypy.session:
79
- raise cherrypy.HTTPRedirect('/login')
80
- return 'OK'
81
-
82
- @cherrypy.expose
83
- @cherrypy.tools.jinja2(template='test_form.html')
84
- def login(self, **kwargs):
85
- form = LoginForm()
86
- if form.validate_on_submit():
87
- # login user with cherrypy.tools.auth
88
- cherrypy.session['login'] = True
89
- raise cherrypy.HTTPRedirect('/')
90
- return {'form': form}
91
-
92
-
93
- @skipUnless(HAS_JINJAX, reason='Required jinjax')
94
- class FormTest(helper.CPWebCase):
95
- default_lang = None
96
- interactive = False
97
-
98
- @classmethod
99
- def setup_server(cls):
100
- cherrypy.tree.mount(Root(), '/')
101
-
102
- def test_get_form(self):
103
- # Given a form
104
- # When querying the page that include this form
105
- self.getPage("/login")
106
- self.assertStatus(200)
107
- # Then each field is render properly.
108
- # 1. Check title
109
- self.assertInBody('test-form')
110
- # 2. Check user field
111
- self.assertInBody('<label class="form-label" for="login">User</label>')
112
- self.assertInBody(
113
- '<input autocapitalize="none" autocomplete="off" autocorrect="off" autofocus="autofocus" class="form-control" id="login" maxlength="256" name="login" placeholder="User" required type="text" value="">'
114
- )
115
- # 3. Check password
116
- self.assertInBody('<label class="form-label" for="password">Password</label>')
117
- self.assertInBody(
118
- '<input class="form-control" id="password" maxlength="256" name="password" placeholder="Password" required type="password" value="">'
119
- )
120
- # 4 Check remember me
121
- self.assertInBody(
122
- '<input class="form-check-input" container-class="col-sm-6" id="persistent" label-attr="FOO" name="persistent" type="checkbox" value="y">'
123
- )
124
- self.assertInBody('<label attr="FOO" class="form-check-label" for="persistent">Remember me</label>')
125
- # 5. check submit button (regex matches because class could have different order with jinjax<=0.57)
126
- self.assertInBody('<div attr="BAR"')
127
- self.assertMatchesBody(
128
- '<input class="(btn-primary ?|float-end ?|btn ?){3}" container-attr="BAR" container-class="col-sm-6" id="submit" name="submit" type="submit" value="Login">'
129
- )
130
-
131
- @parameterized.expand(
132
- [
133
- ('myuser', 'mypassword', 0, 303, False),
134
- ('myuser', '', 0, 200, 'Password: This field is required.'),
135
- ('', 'mypassword', 0, 200, 'User: This field is required.'),
136
- ]
137
- )
138
- def test_post_form(self, login, password, persistent, expect_status, expect_error):
139
- # Given a page with a form.
140
- # When data is sent to the form.
141
- self.getPage(
142
- "/login", method='POST', body=urlencode({'login': login, 'password': password, 'persistent': persistent})
143
- )
144
- # Then page return a status
145
- self.assertStatus(expect_status)
146
- # Then page may return an error
147
- if expect_error:
148
- self.assertInBody(expect_error)
@@ -1,78 +0,0 @@
1
- # CherryPy
2
- # Copyright (C) 2026 IKUS Software
3
- #
4
- # This program is free software: you can redistribute it and/or modify
5
- # it under the terms of the GNU General Public License as published by
6
- # the Free Software Foundation, either version 3 of the License, or
7
- # (at your option) any later version.
8
- #
9
- # This program is distributed in the hope that it will be useful,
10
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- # GNU General Public License for more details.
13
- #
14
- # You should have received a copy of the GNU General Public License
15
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
-
17
- import tempfile
18
- from pathlib import Path
19
-
20
- import cherrypy
21
- from cherrypy.test import helper
22
-
23
- from ..logging import setup_logging
24
-
25
-
26
- class Root:
27
-
28
- @cherrypy.expose
29
- def index(self):
30
- return "OK"
31
-
32
- @cherrypy.expose
33
- def error(self):
34
- cherrypy.log('error messages to be logged')
35
-
36
-
37
- class LoggingTest(helper.CPWebCase):
38
- interactive = False
39
-
40
- @classmethod
41
- def setup_server(cls):
42
- cls.tempdir = tempfile.TemporaryDirectory(prefix='cherrypy-foundation-', suffix='-logging')
43
- cls.log_access_file = f'{cls.tempdir.name}/access.log'
44
- cls.log_file = f'{cls.tempdir.name}/error.log'
45
- setup_logging(log_file=cls.log_file, log_access_file=cls.log_access_file, level='DEBUG')
46
- cherrypy.tree.mount(Root(), '/')
47
-
48
- @classmethod
49
- def teardown_class(cls):
50
- # Delete temp folder
51
- cls.tempdir.cleanup()
52
- # Stop server
53
- super().teardown_class()
54
- # Reset logging to default.
55
- # Re-enable screen logging
56
- cherrypy.config.update({'log.screen': True, 'log.error_file': '', 'log.access_file': ''})
57
- # Reset internal logger references
58
- cherrypy.log.error_file = None
59
- cherrypy.log.access_file = None
60
-
61
- def test_logging_access(self):
62
- mtime = Path(self.log_access_file).stat().st_mtime
63
- # When page get queried
64
- self.getPage('/')
65
- # Then access files get updated
66
- mtime2 = Path(self.log_access_file).stat().st_mtime
67
- self.assertNotEqual(mtime, mtime2)
68
-
69
- def test_logging_error(self):
70
- data = Path(self.log_file).read_text()
71
- self.assertNotIn('error messages to be logged', data)
72
- # When page get queried
73
- self.getPage('/error')
74
- self.assertStatus(200)
75
- # Then access files get updated
76
- data2 = Path(self.log_file).read_text()
77
- self.assertNotEqual(data, data2)
78
- self.assertIn('error messages to be logged', data2)
@@ -1,89 +0,0 @@
1
- # Cherrypy-foundation
2
- # Copyright (C) 2026 IKUS Software
3
- #
4
- # This program is free software: you can redistribute it and/or modify
5
- # it under the terms of the GNU General Public License as published by
6
- # the Free Software Foundation, either version 3 of the License, or
7
- # (at your option) any later version.
8
- #
9
- # This program is distributed in the hope that it will be useful,
10
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- # GNU General Public License for more details.
13
- #
14
- # You should have received a copy of the GNU General Public License
15
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
-
17
- import os
18
- import tempfile
19
-
20
- import cherrypy
21
- from cherrypy.test import helper
22
-
23
- from cherrypy_foundation.sessions import FileSession, session_lock
24
-
25
-
26
- @cherrypy.tools.sessions(on=True, locking='explicit', storage_class=FileSession)
27
- class Root:
28
-
29
- @cherrypy.expose
30
- def index(self, value='OK'):
31
- if value:
32
- with session_lock() as s:
33
- s['value'] = value
34
- return s['value']
35
-
36
-
37
- class FileSessionTest(helper.CPWebCase):
38
- interactive = False
39
-
40
- @classmethod
41
- def setup_class(cls):
42
- cls.tempdir = tempfile.TemporaryDirectory(prefix='cherrypy-foundation-', suffix='-file-session-test')
43
- cls.storage_path = cls.tempdir.name
44
- super().setup_class()
45
-
46
- @classmethod
47
- def teardown_class(cls):
48
- cls.tempdir.cleanup()
49
- super().teardown_class()
50
-
51
- @classmethod
52
- def setup_server(cls):
53
- cherrypy.config.update(
54
- {
55
- 'tools.sessions.storage_path': cls.storage_path,
56
- }
57
- )
58
- cherrypy.tree.mount(Root(), '/')
59
-
60
- @property
61
- def _session_id(self):
62
- """Return session id from cookie."""
63
- if hasattr(self, 'cookies') and self.cookies:
64
- for unused, value in self.cookies:
65
- for part in value.split(';'):
66
- key, unused, value = part.partition('=')
67
- if key == 'session_id':
68
- return value
69
-
70
- def test_get_page(self):
71
- # Given a page with session enabled
72
- # When the page get queried
73
- self.getPage("/")
74
- # Then a session is created with a id
75
- self.assertStatus(200)
76
- self.assertTrue(self._session_id)
77
- # Then this session is created on disk.
78
- s = FileSession(id=self._session_id, storage_path=self.storage_path)
79
- self.assertTrue(s._exists())
80
- # When session timeout and get clean-up
81
- s.acquire_lock()
82
- s.load()
83
- s.timeout = 0
84
- s.save()
85
- s.clean_up()
86
- # Then session get deleted
87
- self.assertFalse(s._exists())
88
- # Lock file also get deleted.
89
- self.assertFalse(os.path.exists(s._get_file_path() + FileSession.LOCK_SUFFIX))
@@ -1,161 +0,0 @@
1
- # Cherrypy-foundation
2
- # Copyright (C) 2026 IKUS Software
3
- #
4
- # This program is free software: you can redistribute it and/or modify
5
- # it under the terms of the GNU General Public License as published by
6
- # the Free Software Foundation, either version 3 of the License, or
7
- # (at your option) any later version.
8
- #
9
- # This program is distributed in the hope that it will be useful,
10
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- # GNU General Public License for more details.
13
- #
14
- # You should have received a copy of the GNU General Public License
15
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
-
17
- import cherrypy
18
- from cherrypy.test import helper
19
-
20
- import cherrypy_foundation.tools.jinja2 # noqa
21
-
22
- from ..url import url_for
23
-
24
- env = cherrypy.tools.jinja2.create_env(
25
- package_name=__package__,
26
- globals={
27
- 'url_for': url_for,
28
- },
29
- )
30
-
31
-
32
- class SubPage:
33
-
34
- @cherrypy.expose
35
- @cherrypy.tools.jinja2(template='test_url.html')
36
- def index(self, **kwargs):
37
- _relative = cherrypy.request.headers.get('-Relative')
38
- return {'kwargs': {'_relative': _relative or None}}
39
-
40
-
41
- @cherrypy.tools.proxy(base='https://www.example.com')
42
- class ProxiedPage:
43
-
44
- @cherrypy.expose
45
- @cherrypy.tools.jinja2(template='test_url.html')
46
- def index(self, **kwargs):
47
- _relative = cherrypy.request.headers.get('-Relative')
48
- return {'kwargs': {'_relative': _relative or None}}
49
-
50
-
51
- @cherrypy.tools.jinja2(env=env)
52
- class Root:
53
- sub_page = SubPage()
54
- proxied = ProxiedPage()
55
-
56
- @cherrypy.expose
57
- @cherrypy.tools.jinja2(template='test_url.html')
58
- def index(self, **kwargs):
59
- _relative = cherrypy.request.headers.get('-Relative')
60
- return {'kwargs': {'_relative': _relative or None}}
61
-
62
-
63
- class UrlTest(helper.CPWebCase):
64
- default_lang = None
65
- interactive = False
66
-
67
- @classmethod
68
- def setup_server(cls):
69
- cherrypy.tree.mount(Root(), '/')
70
-
71
- def test_url_for(self):
72
- self.assertEqual(url_for("foo", "bar"), 'http://127.0.0.1:54583/foo/bar')
73
- self.assertEqual(url_for("foo", "bar", _relative='server'), '/foo/bar')
74
- # Outside a request, relative url doesn't make alot of sens.
75
- self.assertEqual(url_for("foo", "bar", _relative=1), '127.0.0.1:54583/foo/bar')
76
- self.assertEqual(url_for("foo", "bar", _base='http://test.com'), 'http://test.com/foo/bar')
77
-
78
- def test_get_page(self):
79
- # Given a form
80
- # When querying the page that include this form
81
- self.getPage("/")
82
- self.assertStatus(200)
83
- # Then each field is render properly.
84
- # 1. Check title
85
- self.assertInBody('test-url')
86
- # 2. Check user field
87
- self.assertInBody(f'Empty: http://{self.HOST}:{self.PORT}/')
88
- self.assertInBody(f'Dot: http://{self.HOST}:{self.PORT}/')
89
- self.assertInBody(f'Dot page: http://{self.HOST}:{self.PORT}/my-page')
90
- self.assertInBody(f'Slash: http://{self.HOST}:{self.PORT}/')
91
- self.assertInBody(f'Page: http://{self.HOST}:{self.PORT}/my-page')
92
- self.assertInBody(f'Slash page: http://{self.HOST}:{self.PORT}/my-page')
93
- self.assertInBody(f'Query: http://{self.HOST}:{self.PORT}/my-page?bar=test+with+space&amp;foo=1')
94
-
95
- def test_get_page_relative_true(self):
96
- # Given a form
97
- # When querying the page that include this form
98
- self.getPage("/", headers=[('_relative', '1')])
99
- self.assertStatus(200)
100
- # Then each field is render properly.
101
- # 1. Check title
102
- self.assertInBody('test-url')
103
- # 2. Check user field
104
- self.assertInBody('Empty: <br/>')
105
- self.assertInBody('Dot: <br/>')
106
- self.assertInBody('Dot page: my-page<br/>')
107
- self.assertInBody('Slash: <br/>')
108
- self.assertInBody('Page: my-page<br/>')
109
- self.assertInBody('Slash page: my-page<br/>')
110
- self.assertInBody('Query: my-page?bar=test+with+space&amp;foo=1<br/>')
111
-
112
- def test_get_page_relative_server(self):
113
- # Given a form
114
- # When querying the page that include this form
115
- self.getPage("/", headers=[('_relative', 'server')])
116
- self.assertStatus(200)
117
- # Then each field is render properly.
118
- # 1. Check title
119
- self.assertInBody('test-url')
120
- # 2. Check user field
121
- self.assertInBody('Empty: /<br/>')
122
- self.assertInBody('Dot: /<br/>')
123
- self.assertInBody('Dot page: /my-page<br/>')
124
- self.assertInBody('Slash: /<br/>')
125
- self.assertInBody('Page: /my-page<br/>')
126
- self.assertInBody('Slash page: /my-page<br/>')
127
- self.assertInBody('Query: /my-page?bar=test+with+space&amp;foo=1<br/>')
128
-
129
- def test_get_page_proxied(self):
130
- # Given a form
131
- # When querying the page that include this form
132
- self.getPage("/proxied/")
133
- self.assertStatus(200)
134
- # Then each field is render properly.
135
- # 1. Check title
136
- self.assertInBody('test-url')
137
- # 2. Check user field
138
- self.assertInBody('Empty: https://www.example.com/')
139
- self.assertInBody('Dot: https://www.example.com/proxied/')
140
- self.assertInBody('Dot page: https://www.example.com/proxied/my-page<br/>')
141
- self.assertInBody('Slash: https://www.example.com/')
142
- self.assertInBody('Page: https://www.example.com/my-page')
143
- self.assertInBody('Slash page: https://www.example.com/my-page')
144
- self.assertInBody('Query: https://www.example.com/my-page?bar=test+with+space&amp;foo=1')
145
-
146
- def test_get_sub_page(self):
147
- # Given a form
148
- # When querying the page that include this form
149
- self.getPage("/sub-page/")
150
- self.assertStatus(200)
151
- # Then each field is render properly.
152
- # 1. Check title
153
- self.assertInBody('test-url')
154
- # 2. Check user field
155
- self.assertInBody(f'Empty: http://{self.HOST}:{self.PORT}/sub-page/')
156
- self.assertInBody(f'Dot: http://{self.HOST}:{self.PORT}/sub-page/')
157
- self.assertInBody(f'Dot page: http://{self.HOST}:{self.PORT}/sub-page/my-page')
158
- self.assertInBody(f'Slash: http://{self.HOST}:{self.PORT}/')
159
- self.assertInBody(f'Page: http://{self.HOST}:{self.PORT}/my-page')
160
- self.assertInBody(f'Slash page: http://{self.HOST}:{self.PORT}/my-page')
161
- self.assertInBody(f'Query: http://{self.HOST}:{self.PORT}/my-page?bar=test+with+space&amp;foo=1')
@@ -1,2 +0,0 @@
1
- {# def label, link #}
2
- <a {{ attrs.render(class='btn btn-primary', href=link) }}>{{ label }}</a>
@@ -1,11 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <title>test-jinja2</title>
5
- </head>
6
- <body>
7
- var1: {{ var1 }}<br/>
8
- var2: {{ var2 }}<br/>
9
- const1: {{ const1 }}<br/>
10
- </body>
11
- </html>
@@ -1,9 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <title>test-jinjax</title>
5
- </head>
6
- <body>
7
- <Button link="http://example.com" label="foo" />
8
- </body>
9
- </html>
@@ -1,22 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="{{ get_translation().locale }}">
3
- <head>
4
- <title>test-jinja2</title>
5
- {{ catalog.render_assets() }}
6
- </head>
7
- <body>
8
- <!-- Selector -->
9
- <div class="dropdown">
10
- <button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
11
- Dropdown button
12
- </button>
13
- <ul class="dropdown-menu">
14
- <LocaleSelection />
15
- </ul>
16
- </div>
17
- {{ get_language_name(get_translation().locale) }}<br/>
18
- {% trans %}Some text to translate{% endtrans %}<br/>
19
- {{ my_datetime | format_datetime(format='full') }}<br/>
20
- {{ my_date | format_date(format='full') }}
21
- </body>
22
- </html>