django-cms-qe 3.1.1__py3-none-any.whl → 3.3.0__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.
cms_qe/apps.py ADDED
@@ -0,0 +1,12 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class CmsQEConfig(AppConfig):
5
+ name = 'cms_qe'
6
+
7
+ def ready(self):
8
+ from cms.signals import urls_need_reloading # pylint: disable=import-outside-toplevel
9
+
10
+ from .signals import reload_site # pylint: disable=import-outside-toplevel
11
+
12
+ urls_need_reloading.connect(reload_site)
@@ -26,6 +26,7 @@ INSTALLED_APPS = [
26
26
  'cms_qe_table',
27
27
  'cms_qe_video',
28
28
  'cms_qe_analytical',
29
+ 'cms_qe_plugins',
29
30
 
30
31
  # Must be before django.contrib.admin.
31
32
  'djangocms_admin_style',
@@ -131,3 +132,6 @@ MIDDLEWARE = [
131
132
  # Must be the last.
132
133
  'django.middleware.cache.FetchFromCacheMiddleware',
133
134
  ]
135
+
136
+ # Reload site. For example ['uwsgi', '--reload', '/var/run/uwsgi.pid'] or ['touch', 'manage.py'].
137
+ RELOAD_SITE: list[str] = []
cms_qe/signals.py ADDED
@@ -0,0 +1,42 @@
1
+ import logging
2
+ import subprocess
3
+ import time
4
+ from multiprocessing import Lock, Process
5
+ from typing import Any, Optional, Union
6
+
7
+ from django.conf import settings
8
+ from django.db.models import Model
9
+
10
+ DelayType = Union[int, float]
11
+ ArgsType = Union[tuple[str, ...], list[str]]
12
+
13
+ logger = logging.getLogger(__name__)
14
+ lock = Lock()
15
+
16
+
17
+ def run_subprocess(args: ArgsType, delay: Optional[DelayType] = None) -> Optional[subprocess.CompletedProcess]:
18
+ """Run the system command in subprocess."""
19
+ if delay is not None:
20
+ time.sleep(delay)
21
+ logger.info('subprocess.run(%s)', args)
22
+ lock.acquire()
23
+ try:
24
+ return subprocess.run(args, check=False)
25
+ except Exception as err: # pylint: disable=broad-exception-caught
26
+ logger.error(str(err))
27
+ finally:
28
+ lock.release()
29
+ return None
30
+
31
+
32
+ def run_process(args: ArgsType, delay: Optional[DelayType] = None) -> Process:
33
+ """Run the process and no wait for the result."""
34
+ proc = Process(target=run_subprocess, args=(args, delay))
35
+ proc.start()
36
+ return proc
37
+
38
+
39
+ def reload_site(sender: Optional[Model], **kwargs: dict[str, Any]) -> None: # pylint: disable=unused-argument
40
+ "Reload the site."
41
+ if settings.RELOAD_SITE:
42
+ run_process(settings.RELOAD_SITE, 3)
cms_qe/urls.py CHANGED
@@ -13,6 +13,7 @@ from django.contrib.sitemaps.views import sitemap
13
13
  from django.urls import include, path
14
14
  from django.views.i18n import JavaScriptCatalog
15
15
 
16
+ from cms_qe.views.maintenance import HealthCheckView, ReloadSiteView
16
17
  from cms_qe.views.search_result import SiteSearchView
17
18
  from cms_qe.views.security import SecurityTxtView
18
19
 
@@ -41,6 +42,8 @@ urlpatterns = [
41
42
  path('api/monitoring', views.get_monitoring),
42
43
  path('.well-known/security.txt', SecurityTxtView.as_view(), name='security-txt'),
43
44
  path('site-search-result/', SiteSearchView.as_view(), name='site-search-result'),
45
+ path("healthcheck/", HealthCheckView.as_view(), name='healthcheck'), # Used by uwsgi in docker.
46
+ path("superuser/reload-site/", ReloadSiteView.as_view(), name='reload-site'),
44
47
  ]
45
48
 
46
49
  # django-simple-captcha
@@ -0,0 +1,39 @@
1
+ from django.conf import settings
2
+ from django.contrib.sites.models import Site
3
+ from django.core.cache import cache
4
+ from django.http import HttpRequest, HttpResponse
5
+ from django.views import View
6
+
7
+ from cms_qe.signals import run_subprocess
8
+
9
+
10
+ class HealthCheckView(View):
11
+ """Health check View."""
12
+
13
+ def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
14
+ """Apply HTTP GET. Used by uwsgi in docker."""
15
+ # Check database connection.
16
+ site = Site.objects.first()
17
+ if not site.domain:
18
+ raise RuntimeError('db')
19
+ # Check cache.
20
+ key = 'health-check-test'
21
+ cache.set(key, 'OK', 2)
22
+ if cache.get(key) != 'OK':
23
+ raise RuntimeError('cache')
24
+ return HttpResponse("OK", content_type="text/plain")
25
+
26
+
27
+ class ReloadSiteView(View):
28
+ """Reload site View."""
29
+
30
+ def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
31
+ """Run subprocess defined in settings."""
32
+ if request.user.is_superuser:
33
+ if settings.RELOAD_SITE:
34
+ msg = str(run_subprocess(settings.RELOAD_SITE))
35
+ else:
36
+ msg = "SKIP: No settings.RELOAD_SITE."
37
+ else:
38
+ msg = "ERROR: User is not is_superuser."
39
+ return HttpResponse(msg, content_type="text/plain")
File without changes
@@ -0,0 +1,50 @@
1
+ CROSS_ORIGINS = (
2
+ 'anonymous',
3
+ 'use-credentials',
4
+ )
5
+
6
+ LOADING = (
7
+ 'eager',
8
+ 'lazy',
9
+ )
10
+
11
+ SANDBOX = (
12
+ 'allow-forms',
13
+ 'allow-pointer-lock',
14
+ 'allow-popups',
15
+ 'allow-same-origin',
16
+ 'allow-scripts',
17
+ 'allow-top-navigation',
18
+ )
19
+
20
+ REFERRER_POLICY = (
21
+ 'no-referrer',
22
+ 'no-referrer-when-downgrade',
23
+ 'origin',
24
+ 'origin-when-cross-origin',
25
+ 'unsafe-url',
26
+ )
27
+
28
+ SCRIPT_REFERRER_POLICY = REFERRER_POLICY + (
29
+ 'same-origin',
30
+ 'strict-origin',
31
+ 'strict-origin-when-cross-origin',
32
+ )
33
+
34
+ REL = (
35
+ 'alternate',
36
+ 'author',
37
+ 'dns-prefetch',
38
+ 'help',
39
+ 'icon',
40
+ 'license',
41
+ 'next',
42
+ 'pingback',
43
+ 'preconnect',
44
+ 'prefetch',
45
+ 'preload',
46
+ 'prerender',
47
+ 'prev',
48
+ 'search',
49
+ 'stylesheet',
50
+ )
@@ -0,0 +1,31 @@
1
+ from cms.plugin_base import CMSPluginBase
2
+ from cms.plugin_pool import plugin_pool
3
+
4
+ from .models import Iframe, Link, Script
5
+
6
+
7
+ @plugin_pool.register_plugin
8
+ class LinkTagPlugin(CMSPluginBase):
9
+
10
+ model = Link
11
+ name = 'LINK'
12
+ module = 'HTML Elements'
13
+ render_template = "cms_qe_plugins/link.html"
14
+
15
+
16
+ @plugin_pool.register_plugin
17
+ class ScriptTagPlugin(CMSPluginBase):
18
+
19
+ model = Script
20
+ name = 'SCRIPT'
21
+ module = 'HTML Elements'
22
+ render_template = "cms_qe_plugins/script.html"
23
+
24
+
25
+ @plugin_pool.register_plugin
26
+ class IframeTagPlugin(CMSPluginBase):
27
+
28
+ model = Iframe
29
+ name = 'IFRAME'
30
+ module = 'HTML Elements'
31
+ render_template = "cms_qe_plugins/iframe.html"
@@ -0,0 +1,433 @@
1
+ # Generated by Django 4.2.13 on 2024-06-04 08:08
2
+
3
+ import cms_qe_plugins.validators
4
+ import django.core.serializers.json
5
+ from django.db import migrations, models
6
+ import django.db.models.deletion
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+
11
+ initial = True
12
+
13
+ dependencies = []
14
+
15
+ operations = [
16
+ migrations.CreateModel(
17
+ name="Iframe",
18
+ fields=[
19
+ (
20
+ "cmsplugin_ptr",
21
+ models.OneToOneField(
22
+ auto_created=True,
23
+ on_delete=django.db.models.deletion.CASCADE,
24
+ parent_link=True,
25
+ primary_key=True,
26
+ related_name="%(app_label)s_%(class)s",
27
+ serialize=False,
28
+ to="cms.cmsplugin",
29
+ ),
30
+ ),
31
+ (
32
+ "src",
33
+ models.CharField(
34
+ help_text="Specifies the address of the document to embed in the <iframe>.",
35
+ max_length=255,
36
+ validators=[cms_qe_plugins.validators.validate_url_or_path],
37
+ ),
38
+ ),
39
+ (
40
+ "allow",
41
+ models.CharField(
42
+ blank=True,
43
+ help_text="Specifies a feature policy for the &lt;iframe&gt;. Form more see <a href='https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#allow' target='_blank'>IFrame attributes.</a>.",
44
+ max_length=255,
45
+ null=True,
46
+ ),
47
+ ),
48
+ (
49
+ "allow_full_screen",
50
+ models.BooleanField(
51
+ blank=True,
52
+ help_text="Set to true if the &lt;iframe&gt; can activate fullscreen mode by calling the requestFullscreen() method.",
53
+ null=True,
54
+ ),
55
+ ),
56
+ (
57
+ "allow_payment_request",
58
+ models.BooleanField(
59
+ blank=True,
60
+ help_text="Set to true if a cross-origin &lt;iframe&gt; should be allowed to invoke the Payment Request API.",
61
+ null=True,
62
+ ),
63
+ ),
64
+ (
65
+ "height",
66
+ models.IntegerField(
67
+ blank=True,
68
+ help_text="Specifies the height of an &lt;iframe&gt;. Default height is 150 pixels.",
69
+ null=True,
70
+ ),
71
+ ),
72
+ (
73
+ "width",
74
+ models.IntegerField(
75
+ blank=True,
76
+ help_text="Specifies the width of an &lt;iframe&gt;. Default width is 300 pixels.",
77
+ null=True,
78
+ ),
79
+ ),
80
+ (
81
+ "loading",
82
+ models.CharField(
83
+ blank=True,
84
+ choices=[("", ""), ("eager", "eager"), ("lazy", "lazy")],
85
+ default=None,
86
+ help_text="Specifies whether a browser should load an iframe immediately or to defer loading of iframes until some conditions are met.",
87
+ max_length=255,
88
+ null=True,
89
+ ),
90
+ ),
91
+ (
92
+ "name",
93
+ models.CharField(
94
+ blank=True,
95
+ help_text="Specifies the name of an &lt;iframe&gt;.",
96
+ max_length=255,
97
+ null=True,
98
+ ),
99
+ ),
100
+ (
101
+ "referrer_policy",
102
+ models.CharField(
103
+ blank=True,
104
+ choices=[
105
+ ("", ""),
106
+ ("no-referrer", "no-referrer"),
107
+ (
108
+ "no-referrer-when-downgrade",
109
+ "no-referrer-when-downgrade",
110
+ ),
111
+ ("origin", "origin"),
112
+ ("origin-when-cross-origin", "origin-when-cross-origin"),
113
+ ("unsafe-url", "unsafe-url"),
114
+ ("same-origin", "same-origin"),
115
+ ("strict-origin", "strict-origin"),
116
+ (
117
+ "strict-origin-when-cross-origin",
118
+ "strict-origin-when-cross-origin",
119
+ ),
120
+ ],
121
+ default=None,
122
+ help_text="Specifies which referrer information to send when fetching the iframe.",
123
+ max_length=50,
124
+ null=True,
125
+ ),
126
+ ),
127
+ (
128
+ "sandbox",
129
+ models.CharField(
130
+ blank=True,
131
+ choices=[
132
+ ("", ""),
133
+ ("allow-forms", "allow-forms"),
134
+ ("allow-pointer-lock", "allow-pointer-lock"),
135
+ ("allow-popups", "allow-popups"),
136
+ ("allow-same-origin", "allow-same-origin"),
137
+ ("allow-scripts", "allow-scripts"),
138
+ ("allow-top-navigation", "allow-top-navigation"),
139
+ ],
140
+ default=None,
141
+ help_text="Enables an extra set of restrictions for the content in an &lt;iframe&gt;.",
142
+ max_length=50,
143
+ null=True,
144
+ ),
145
+ ),
146
+ (
147
+ "src_doc",
148
+ models.TextField(
149
+ blank=True,
150
+ help_text="Specifies the HTML content of the page to show in the &lt;iframe&gt;.",
151
+ null=True,
152
+ ),
153
+ ),
154
+ (
155
+ "attributes",
156
+ models.JSONField(
157
+ blank=True,
158
+ encoder=django.core.serializers.json.DjangoJSONEncoder,
159
+ help_text='More attributes as JSON data. E.g. {"data-name": "value", "id": 42}',
160
+ null=True,
161
+ ),
162
+ ),
163
+ ],
164
+ options={
165
+ "abstract": False,
166
+ },
167
+ bases=("cms.cmsplugin",),
168
+ ),
169
+ migrations.CreateModel(
170
+ name="Link",
171
+ fields=[
172
+ (
173
+ "cmsplugin_ptr",
174
+ models.OneToOneField(
175
+ auto_created=True,
176
+ on_delete=django.db.models.deletion.CASCADE,
177
+ parent_link=True,
178
+ primary_key=True,
179
+ related_name="%(app_label)s_%(class)s",
180
+ serialize=False,
181
+ to="cms.cmsplugin",
182
+ ),
183
+ ),
184
+ (
185
+ "href",
186
+ models.CharField(
187
+ help_text="Specifies the location of the linked document.",
188
+ max_length=255,
189
+ validators=[cms_qe_plugins.validators.validate_url_or_path],
190
+ ),
191
+ ),
192
+ (
193
+ "rel",
194
+ models.CharField(
195
+ choices=[
196
+ ("alternate", "alternate"),
197
+ ("author", "author"),
198
+ ("dns-prefetch", "dns-prefetch"),
199
+ ("help", "help"),
200
+ ("icon", "icon"),
201
+ ("license", "license"),
202
+ ("next", "next"),
203
+ ("pingback", "pingback"),
204
+ ("preconnect", "preconnect"),
205
+ ("prefetch", "prefetch"),
206
+ ("preload", "preload"),
207
+ ("prerender", "prerender"),
208
+ ("prev", "prev"),
209
+ ("search", "search"),
210
+ ("stylesheet", "stylesheet"),
211
+ ],
212
+ default="stylesheet",
213
+ help_text="Required. Specifies the relationship between the current document and the linked document.",
214
+ max_length=30,
215
+ ),
216
+ ),
217
+ (
218
+ "cross_origin",
219
+ models.CharField(
220
+ blank=True,
221
+ choices=[
222
+ ("", ""),
223
+ ("anonymous", "anonymous"),
224
+ ("use-credentials", "use-credentials"),
225
+ ],
226
+ default=None,
227
+ help_text="Specifies how the element handles cross-origin requests.",
228
+ max_length=15,
229
+ null=True,
230
+ ),
231
+ ),
232
+ (
233
+ "hreflang",
234
+ models.CharField(
235
+ blank=True,
236
+ help_text="Specifies the language of the text in the linked document.",
237
+ max_length=30,
238
+ null=True,
239
+ ),
240
+ ),
241
+ (
242
+ "media",
243
+ models.CharField(
244
+ blank=True,
245
+ help_text="Specifies on what device the linked document will be displayed.",
246
+ max_length=255,
247
+ null=True,
248
+ ),
249
+ ),
250
+ (
251
+ "referrer_policy",
252
+ models.CharField(
253
+ blank=True,
254
+ choices=[
255
+ ("", ""),
256
+ ("no-referrer", "no-referrer"),
257
+ (
258
+ "no-referrer-when-downgrade",
259
+ "no-referrer-when-downgrade",
260
+ ),
261
+ ("origin", "origin"),
262
+ ("origin-when-cross-origin", "origin-when-cross-origin"),
263
+ ("unsafe-url", "unsafe-url"),
264
+ ],
265
+ default=None,
266
+ help_text="Specifies which referrer to use when fetching the resource.",
267
+ max_length=50,
268
+ null=True,
269
+ ),
270
+ ),
271
+ (
272
+ "sizes",
273
+ models.CharField(
274
+ blank=True,
275
+ help_text='Specifies the size of the linked resource. Only for rel="icon".',
276
+ max_length=255,
277
+ null=True,
278
+ ),
279
+ ),
280
+ (
281
+ "title",
282
+ models.CharField(
283
+ blank=True,
284
+ help_text="Defines a preferred or an alternate stylesheet.",
285
+ max_length=255,
286
+ null=True,
287
+ ),
288
+ ),
289
+ (
290
+ "type",
291
+ models.CharField(
292
+ blank=True,
293
+ help_text="Specifies the media type of the linked document. E.g. 'text/css'.",
294
+ max_length=255,
295
+ null=True,
296
+ ),
297
+ ),
298
+ (
299
+ "attributes",
300
+ models.JSONField(
301
+ blank=True,
302
+ encoder=django.core.serializers.json.DjangoJSONEncoder,
303
+ help_text='More attributes as JSON data. E.g. {"data-name": "value", "id": 42}',
304
+ null=True,
305
+ ),
306
+ ),
307
+ ],
308
+ options={
309
+ "abstract": False,
310
+ },
311
+ bases=("cms.cmsplugin",),
312
+ ),
313
+ migrations.CreateModel(
314
+ name="Script",
315
+ fields=[
316
+ (
317
+ "cmsplugin_ptr",
318
+ models.OneToOneField(
319
+ auto_created=True,
320
+ on_delete=django.db.models.deletion.CASCADE,
321
+ parent_link=True,
322
+ primary_key=True,
323
+ related_name="%(app_label)s_%(class)s",
324
+ serialize=False,
325
+ to="cms.cmsplugin",
326
+ ),
327
+ ),
328
+ (
329
+ "src",
330
+ models.CharField(
331
+ help_text="Specifies the location of the linked script.",
332
+ max_length=255,
333
+ validators=[cms_qe_plugins.validators.validate_url_or_path],
334
+ ),
335
+ ),
336
+ (
337
+ "asyncf",
338
+ models.BooleanField(
339
+ default=False,
340
+ help_text="Specifies that the script is downloaded in parallel to parsing the page, and executed as soon as it is available (before parsing completes) (only for external scripts)",
341
+ verbose_name="async",
342
+ ),
343
+ ),
344
+ (
345
+ "cross_origin",
346
+ models.CharField(
347
+ blank=True,
348
+ choices=[
349
+ ("", ""),
350
+ ("anonymous", "anonymous"),
351
+ ("use-credentials", "use-credentials"),
352
+ ],
353
+ default=None,
354
+ help_text="Sets the mode of the request to an HTTP CORS Request.",
355
+ max_length=15,
356
+ null=True,
357
+ ),
358
+ ),
359
+ (
360
+ "defer",
361
+ models.BooleanField(
362
+ default=False,
363
+ help_text="Specifies that the script is downloaded in parallel to parsing the page, and executed after the page has finished parsing (only for external scripts)",
364
+ ),
365
+ ),
366
+ (
367
+ "integrity",
368
+ models.CharField(
369
+ blank=True,
370
+ help_text="Allows a browser to check the fetched script to ensure that the code is never loaded if the source has been manipulated",
371
+ max_length=255,
372
+ null=True,
373
+ ),
374
+ ),
375
+ (
376
+ "nomodule",
377
+ models.BooleanField(
378
+ default=False,
379
+ help_text="Specifies that the script should not be executed in browsers supporting ES2015 modules.",
380
+ ),
381
+ ),
382
+ (
383
+ "referrer_policy",
384
+ models.CharField(
385
+ blank=True,
386
+ choices=[
387
+ ("", ""),
388
+ ("no-referrer", "no-referrer"),
389
+ (
390
+ "no-referrer-when-downgrade",
391
+ "no-referrer-when-downgrade",
392
+ ),
393
+ ("origin", "origin"),
394
+ ("origin-when-cross-origin", "origin-when-cross-origin"),
395
+ ("unsafe-url", "unsafe-url"),
396
+ ("same-origin", "same-origin"),
397
+ ("strict-origin", "strict-origin"),
398
+ (
399
+ "strict-origin-when-cross-origin",
400
+ "strict-origin-when-cross-origin",
401
+ ),
402
+ ],
403
+ default=None,
404
+ help_text="Specifies which referrer to use when fetching the resource.",
405
+ max_length=50,
406
+ null=True,
407
+ ),
408
+ ),
409
+ (
410
+ "type",
411
+ models.CharField(
412
+ blank=True,
413
+ help_text="Specifies the media type of the script.",
414
+ max_length=255,
415
+ null=True,
416
+ ),
417
+ ),
418
+ (
419
+ "attributes",
420
+ models.JSONField(
421
+ blank=True,
422
+ encoder=django.core.serializers.json.DjangoJSONEncoder,
423
+ help_text='More attributes as JSON data. E.g. {"data-name": "value", "id": 42}',
424
+ null=True,
425
+ ),
426
+ ),
427
+ ],
428
+ options={
429
+ "abstract": False,
430
+ },
431
+ bases=("cms.cmsplugin",),
432
+ ),
433
+ ]
File without changes
@@ -0,0 +1,137 @@
1
+ from cms.models.pluginmodel import CMSPlugin
2
+ from django.core.serializers.json import DjangoJSONEncoder
3
+ from django.db import models
4
+
5
+ from .attributes import CROSS_ORIGINS, LOADING, REFERRER_POLICY, REL, SANDBOX, SCRIPT_REFERRER_POLICY
6
+ from .utils import make_choices, make_opt_choices
7
+ from .validators import validate_url_or_path
8
+
9
+
10
+ class Script(CMSPlugin):
11
+ """HTML Script tag."""
12
+
13
+ src = models.CharField(
14
+ max_length=255, validators=[validate_url_or_path],
15
+ help_text="Specifies the location of the linked script.")
16
+ asyncf = models.BooleanField(
17
+ default=False,
18
+ verbose_name='async',
19
+ help_text='Specifies that the script is downloaded in parallel to parsing the page, and executed as soon as it '
20
+ 'is available (before parsing completes) (only for external scripts)'
21
+ )
22
+ cross_origin = models.CharField(
23
+ max_length=15, null=True, blank=True, choices=make_opt_choices(CROSS_ORIGINS), default=None,
24
+ help_text="Sets the mode of the request to an HTTP CORS Request.")
25
+ defer = models.BooleanField(
26
+ default=False,
27
+ help_text='Specifies that the script is downloaded in parallel to parsing the page, and executed after '
28
+ 'the page has finished parsing (only for external scripts)'
29
+ )
30
+ integrity = models.CharField(
31
+ max_length=255, null=True, blank=True,
32
+ help_text='Allows a browser to check the fetched script to ensure that the code is never loaded '
33
+ 'if the source has been manipulated'
34
+ )
35
+ nomodule = models.BooleanField(
36
+ default=False,
37
+ help_text='Specifies that the script should not be executed in browsers supporting ES2015 modules.'
38
+ )
39
+ referrer_policy = models.CharField(
40
+ max_length=50, null=True, blank=True, choices=make_opt_choices(SCRIPT_REFERRER_POLICY), default=None,
41
+ help_text="Specifies which referrer to use when fetching the resource.")
42
+ type = models.CharField(
43
+ max_length=255, null=True, blank=True,
44
+ help_text="Specifies the media type of the script.")
45
+ attributes = models.JSONField(
46
+ null=True, blank=True, encoder=DjangoJSONEncoder,
47
+ help_text='More attributes as JSON data. E.g. {"data-name": "value", "id": 42}')
48
+
49
+ def __str__(self):
50
+ return self.src
51
+
52
+
53
+ class Link(CMSPlugin):
54
+ """HTML Link tag."""
55
+
56
+ href = models.CharField(
57
+ max_length=255, validators=[validate_url_or_path],
58
+ help_text="Specifies the location of the linked document.")
59
+ rel = models.CharField(
60
+ max_length=30, choices=make_choices(REL), default='stylesheet',
61
+ help_text="Required. Specifies the relationship between the current document and the linked document.")
62
+ cross_origin = models.CharField(
63
+ max_length=15, null=True, blank=True, choices=make_opt_choices(CROSS_ORIGINS), default=None,
64
+ help_text="Specifies how the element handles cross-origin requests.")
65
+ hreflang = models.CharField(
66
+ max_length=30, null=True, blank=True, help_text="Specifies the language of the text in the linked document.")
67
+ media = models.CharField(
68
+ max_length=255, null=True, blank=True,
69
+ help_text="Specifies on what device the linked document will be displayed.")
70
+ referrer_policy = models.CharField(
71
+ max_length=50, null=True, blank=True, choices=make_opt_choices(REFERRER_POLICY), default=None,
72
+ help_text="Specifies which referrer to use when fetching the resource.")
73
+ sizes = models.CharField(
74
+ max_length=255, null=True, blank=True,
75
+ help_text='Specifies the size of the linked resource. Only for rel="icon".')
76
+ title = models.CharField(
77
+ max_length=255, null=True, blank=True,
78
+ help_text="Defines a preferred or an alternate stylesheet.")
79
+ type = models.CharField(
80
+ max_length=255, null=True, blank=True,
81
+ help_text="Specifies the media type of the linked document. E.g. 'text/css'.")
82
+ attributes = models.JSONField(
83
+ null=True, blank=True, encoder=DjangoJSONEncoder,
84
+ help_text='More attributes as JSON data. E.g. {"data-name": "value", "id": 42}')
85
+
86
+ def __str__(self):
87
+ return self.href
88
+
89
+
90
+ class Iframe(CMSPlugin):
91
+ """HTML Iframe tag."""
92
+
93
+ src = models.CharField(
94
+ max_length=255, validators=[validate_url_or_path],
95
+ help_text="Specifies the address of the document to embed in the &lt;iframe&gt;.")
96
+ allow = models.CharField(
97
+ max_length=255, null=True, blank=True,
98
+ help_text="Specifies a feature policy for the &lt;iframe&gt;. Form more see "
99
+ "<a href='https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#allow' target='_blank'>"
100
+ "IFrame attributes.</a>.")
101
+ allow_full_screen = models.BooleanField(
102
+ null=True, blank=True,
103
+ help_text='Set to true if the &lt;iframe&gt; can activate fullscreen mode by calling the requestFullscreen() '
104
+ 'method.'
105
+ )
106
+ allow_payment_request = models.BooleanField(
107
+ null=True, blank=True,
108
+ help_text='Set to true if a cross-origin &lt;iframe&gt; should be allowed to invoke the Payment Request API.'
109
+ )
110
+ height = models.IntegerField(
111
+ null=True, blank=True,
112
+ help_text='Specifies the height of an &lt;iframe&gt;. Default height is 150 pixels.')
113
+ width = models.IntegerField(
114
+ null=True, blank=True,
115
+ help_text='Specifies the width of an &lt;iframe&gt;. Default width is 300 pixels.')
116
+ loading = models.CharField(
117
+ max_length=255, null=True, blank=True, choices=make_opt_choices(LOADING), default=None,
118
+ help_text="Specifies whether a browser should load an iframe immediately or to defer loading of iframes "
119
+ "until some conditions are met.")
120
+ name = models.CharField(
121
+ max_length=255, null=True, blank=True,
122
+ help_text="Specifies the name of an &lt;iframe&gt;.")
123
+ referrer_policy = models.CharField(
124
+ max_length=50, null=True, blank=True, choices=make_opt_choices(SCRIPT_REFERRER_POLICY), default=None,
125
+ help_text="Specifies which referrer information to send when fetching the iframe.")
126
+ sandbox = models.CharField(
127
+ max_length=50, null=True, blank=True, choices=make_opt_choices(SANDBOX), default=None,
128
+ help_text="Enables an extra set of restrictions for the content in an &lt;iframe&gt;.")
129
+ src_doc = models.TextField(
130
+ null=True, blank=True, help_text="Specifies the HTML content of the page to show in the &lt;iframe&gt;."
131
+ )
132
+ attributes = models.JSONField(
133
+ null=True, blank=True, encoder=DjangoJSONEncoder,
134
+ help_text='More attributes as JSON data. E.g. {"data-name": "value", "id": 42}')
135
+
136
+ def __str__(self):
137
+ return self.src
@@ -0,0 +1 @@
1
+ <iframe src="{{ instance.src }}"{% if instance.allow %} allow{% endif %}{% if instance.allow_full_screen is not None %} allowfullscreen="{% if instance.allow_full_screen %}1{% else %}0{% endif %}"{% endif %}{% if instance.allow_payment_request is not None %} allowpaymentrequest="{% if instance.allow_payment_request %}1{% else %}0{% endif %}"{% endif %}{% if instance.height %} height="{{ instance.height }}"{% endif %}{% if instance.width %} width="{{ instance.width }}"{% endif %}{% if instance.loading %} loading="{{ instance.loading }}"{% endif %}{% if instance.name %} name="{{ instance.name }}"{% endif %}{% if instance.referrer_policy %} referrerpolicy="{{ instance.referrer_policy }}"{% endif %}{% if instance.sandbox %} sandbox="{{ instance.sandbox }}"{% endif %}{% if instance.src_doc %} srcdoc="{{ instance.src_doc }}"{% endif %}{% if instance.attributes %}{% for name, value in instance.attributes.items %} {{ name }}="{{ value }}"{% endfor %}{% endif %}></iframe>
@@ -0,0 +1 @@
1
+ <link href="{{ instance.href }}"{% if instance.rel %} rel="{{ instance.rel }}"{% endif %}{% if instance.cross_origin %} crossorigin="{{ instance.cross_origin }}"{% endif %}{% if instance.hreflang %} hreflang="{{ instance.hreflang }}"{% endif %}{% if instance.media %} media="{{ instance.media }}"{% endif %}{% if instance.referrer_policy %} referrerpolicy="{{ instance.referrer_policy }}"{% endif %}{% if instance.sizes %} sizes="{{ instance.sizes }}"{% endif %}{% if instance.title %} title="{{ instance.title }}"{% endif %}{% if instance.type %} type="{{ instance.type }}"{% endif %}{% if instance.attributes %}{% for name, value in instance.attributes.items %} {{ name }}="{{ value }}"{% endfor %}{% endif %}>
@@ -0,0 +1 @@
1
+ <script src="{{ instance.src }}"{% if instance.asyncf %} async{% endif %}{% if instance.cross_origin %} crossorigin="{{ instance.cross_origin }}"{% endif %}{% if instance.defer %} defer{% endif %}{% if instance.integrity %} integrity="{{ instance.integrity }}"{% endif %}{% if instance.referrer_policy %} referrerpolicy="{{ instance.referrer_policy }}"{% endif %}{% if instance.nomodule %} nomodule{% endif %}{% if instance.title %} title="{{ instance.title }}"{% endif %}{% if instance.type %} type="{{ instance.type }}"{% endif %}{% if instance.attributes %}{% for name, value in instance.attributes.items %} {{ name }}="{{ value }}"{% endfor %}{% endif %}></script>
@@ -0,0 +1,8 @@
1
+ def make_choices(items: tuple[str, ...]) -> list[tuple[str, str]]:
2
+ """Make field required choices."""
3
+ return [(item, item) for item in items]
4
+
5
+
6
+ def make_opt_choices(items: tuple[str, ...]) -> list[tuple[str, str]]:
7
+ """Make field optional choices."""
8
+ return [('', '')] + make_choices(items)
@@ -0,0 +1,15 @@
1
+ from django.core.validators import RegexValidator, URLValidator, _lazy_re_compile
2
+ from django.utils.translation import gettext_lazy as _
3
+
4
+ path_validator = RegexValidator(
5
+ _lazy_re_compile(r"^/\w+"),
6
+ message=_("Enter a valid URL or path."),
7
+ code="invalid",
8
+ )
9
+
10
+
11
+ def validate_url_or_path(value: str) -> None:
12
+ """Validate URL or path."""
13
+ if value[:1] == '/':
14
+ return path_validator(value)
15
+ return URLValidator()(value)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-cms-qe
3
- Version: 3.1.1
3
+ Version: 3.3.0
4
4
  Summary: Django CMS Quick & Easy provides all important modules to run new page withouta lot of coding. Aims to do it very easily and securely.
5
5
  Home-page: https://websites.pages.nic.cz/django-cms-qe
6
6
  Author: CZ.NIC, z.s.p.o.
@@ -73,6 +73,7 @@ Requires-Dist: pytest-watch ==4.2.0 ; extra == 'test'
73
73
  Requires-Dist: PyVirtualDisplay ==1.3.2 ; extra == 'test'
74
74
  Requires-Dist: webdriverwrapper ==2.8.0 ; extra == 'test'
75
75
  Requires-Dist: django-simple-captcha ==0.5.14 ; extra == 'test'
76
+ Requires-Dist: testfixtures ; extra == 'test'
76
77
  Requires-Dist: tzdata ; extra == 'test'
77
78
 
78
79
  # Django CMS QE
@@ -1,11 +1,13 @@
1
1
  cms_qe/__init__.py,sha256=do1c4s8BgjukMZMMMhBHs_lG9j8ncnAjR3oTICWEq5w,684
2
2
  cms_qe/admin.py,sha256=eLqAF3UIDWWyA0xE0Ft5WP5_3HImSWk3EYL2cRfL_8A,171
3
+ cms_qe/apps.py,sha256=AeRcBWwGs7rKLlzHhnV8M_2BEnkoO9959VwesxfHaio,338
3
4
  cms_qe/constants.py,sha256=YWUWCIabSwcamGZynvkJ9i8OWGtfHf-wFirm8GtqQpI,90
4
5
  cms_qe/export.py,sha256=PyKzMoFGzMdwCTKIYP1zri0Pn8zR_2e0-IkbHYfAv3g,8138
5
6
  cms_qe/fixtures.py,sha256=cq_wnZnqBwPBOHpp_0bHk424iCXKvwmN6ZaKwDvguXk,755
6
7
  cms_qe/monitoring.py,sha256=5t_o7o0htmAAxVjkN2oz0O0v9XdzfePhSfPGcLNPmE8,769
8
+ cms_qe/signals.py,sha256=MbuLSxPlJA147LEg-lDWDoUNTV1y0OKjwoI3HzgR97g,1253
7
9
  cms_qe/staticfiles.py,sha256=OHkfDfpIxN0B-eCRagZzHDHyBgaulcyYgKhp_3mPZuk,1363
8
- cms_qe/urls.py,sha256=DoOO3xijoxLpdfJAfl4DNna0q0nwG-OKGOdhzpWV1Ss,2790
10
+ cms_qe/urls.py,sha256=vNiiN7NwFFT48SBNIg6lzjTRMN6BicwH_zz6p-EApsw,3042
9
11
  cms_qe/utils.py,sha256=gxsWZmS34ZC0Tv1VW8A7VeGlrPyDshodF1ZWXj7xyWE,3057
10
12
  cms_qe/boilerplates/bootstrap3/static/cms_qe/css/bootstrap-theme.css,sha256=xOpS-e_dER8z72w-qryCieOGysQI8cELAVt3MHG0phY,26132
11
13
  cms_qe/boilerplates/bootstrap3/static/cms_qe/css/bootstrap-theme.css.map,sha256=cZQbJTuJQjdM1XuKhdRzSJ8hMRQ4up491P76Iq2_D1M,47706
@@ -45,7 +47,7 @@ cms_qe/settings/__init__.py,sha256=GJwHXMHwMuGYE-3ZzePJ-26I2WwE8bAIMUDoiTFr0L8,9
45
47
  cms_qe/settings/dev.py,sha256=51CBwiclE8LLoNB2uioIK_L3JhM1yzukQ0gZimkcFqw,1487
46
48
  cms_qe/settings/unittest.py,sha256=folLIMJb1Arh60_Sn0eNQrvIlx0OsAs6v1tDfyRZVuQ,514
47
49
  cms_qe/settings/base/__init__.py,sha256=5yHfne9gPD_xuTaG3voZP23yzuCwROmif2mmKs-hG_A,446
48
- cms_qe/settings/base/app.py,sha256=vzHEMzjghEuBrF0iALbvV5KE3rq4lZ8CVoaY-I5JXnA,3839
50
+ cms_qe/settings/base/app.py,sha256=pmy_0ThkSDNXmEVrWwFpRQ-mjHCvYFoo8YxmFRLG5bM,3988
49
51
  cms_qe/settings/base/auth.py,sha256=axEmEG5UYxyY3EE0keQjGZzkTv2iwxwfULr4LcvZPns,207
50
52
  cms_qe/settings/base/cache.py,sha256=9p6C5lOz1pG-6k15PyvxlShUjBYIbU0ewpA8AX_YFus,297
51
53
  cms_qe/settings/base/cms.py,sha256=8icCNxcEp_KRDyP8-LXB21UurJL4wNysY39whAyt3I4,1855
@@ -78,6 +80,7 @@ cms_qe/templatetags/cms_qe_filters.py,sha256=ciYCXLuMVde-f_-B_7a5mHfAJPWPMxYEUMg
78
80
  cms_qe/templatetags/kwacros.py,sha256=r2oHLltu8BgKKlrpgzXgvLjbIqwcrH13Lww3zTF-nr8,6275
79
81
  cms_qe/views/__init__.py,sha256=3b5FCZ5MaqgiWglC7c5mfvP3WYLWTtNp3YpVb9BgYi8,106
80
82
  cms_qe/views/errors.py,sha256=zUbCoyXy_MPsQv3UV1mgq-q2bwqPw9G4KgKU2-oue4w,3169
83
+ cms_qe/views/maintenance.py,sha256=rBfovOGETlfGNF7jlv-vuTEfjZIb5x9dYCSIyFTgHgQ,1312
81
84
  cms_qe/views/monitoring.py,sha256=1r2s_jm6B6P0gEmiqjH9m3loUW3BmJivvpg6qcUOxVM,1909
82
85
  cms_qe/views/search_result.py,sha256=H1eMOmtONnWqBDsBvz3MartyHhh42vyi0jPoxWb0X8c,296
83
86
  cms_qe/views/security.py,sha256=SNdJe4NSm1vuOGchVF3VqlmFDopt9xYoO-b4Y0UxkFU,1108
@@ -3885,6 +3888,17 @@ cms_qe_newsletter/templates/admin/cms_qe_newsletter/mailinglist/change_list.html
3885
3888
  cms_qe_newsletter/templates/cms_qe/newsletter/form.html,sha256=5YsoZlQsxlBfMStROmK8V9T0-w5akjCu3a75GlpO9bk,385
3886
3889
  cms_qe_newsletter/templates/cms_qe/newsletter/newsletter.html,sha256=0YnNGr4hNhAlkM-7ISeE8y1vRhFi7DdjJI_QH_B_ME4,195
3887
3890
  cms_qe_newsletter/templates/cms_qe/newsletter/submitted.html,sha256=mOgMi56fuo3VFGC7P_uIA0XQQ7qAkG130s-WuvmF-n4,51
3891
+ cms_qe_plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3892
+ cms_qe_plugins/attributes.py,sha256=XWLp_Mo3ZQxqUXKOsv1ff-IAX8fnNi2FS_KqdoQT0hQ,760
3893
+ cms_qe_plugins/cms_plugins.py,sha256=MLuIUaeVWMjG8zMqUrM5yqdSlmhgJdJ2T1OMHrvhTHg,683
3894
+ cms_qe_plugins/models.py,sha256=rJ8qKD8q2gp_dfWyXnmbXk3hz71DKoVuvr0fztk_gQ0,6789
3895
+ cms_qe_plugins/utils.py,sha256=7xnvez79gmODczHv_GEiQgvvaeALO2xWzNSKK33HoPw,306
3896
+ cms_qe_plugins/validators.py,sha256=qqLsCB-2kqC5QvWoSG3yqaVJVKvwPgq0QMwTrR3LKoA,445
3897
+ cms_qe_plugins/migrations/0001_initial.py,sha256=O6dBegZWf6DwRKXTz5vx3lNZ-GcBrvGpq94QeVBgo2I,17673
3898
+ cms_qe_plugins/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3899
+ cms_qe_plugins/templates/cms_qe_plugins/iframe.html,sha256=rWJ7kj7lAjKAr2gJMRX1gRTUHBdVcuFJ-ajN2zZhZvE,978
3900
+ cms_qe_plugins/templates/cms_qe_plugins/link.html,sha256=zzAr1dAWckbPEw5RnkmFKdIZDnbKqVs_g47wbETusLs,713
3901
+ cms_qe_plugins/templates/cms_qe_plugins/script.html,sha256=PnOgFjXxtzOEKzGRQQnTdKiDTEzENl-_q14WYrbIYJ0,669
3888
3902
  cms_qe_table/__init__.py,sha256=0eiVSkglZ6A-QLBnGXQdTlysM6dj2kfTc6scFcUGGVA,1084
3889
3903
  cms_qe_table/cms_plugins.py,sha256=dR8b7h_xOJnQ4CDetxrfOiv1tfekhdnpe4KbKqasLi0,1065
3890
3904
  cms_qe_table/exceptions.py,sha256=D9jeyKebnha1lObxS-eJhDeIYLobqlQCLieqg5jPkEU,349
@@ -3957,8 +3971,8 @@ test_selenium/pages/cms/__init__.py,sha256=_qe4YZYaQbrXp7Szmmeo4TUSkXlE5Rozu8E3t
3957
3971
  test_selenium/pages/cms/login.py,sha256=UPzJQcYff8NUAT4nvmfQoJQxzOJyPrJ_cKtH35NVfNg,521
3958
3972
  test_selenium/pages/cms/page.py,sha256=YQnpZkopfVnhoyQKpRDGqjNeV6xUl-pEHjEcZ9HRiPk,489
3959
3973
  test_selenium/pages/cms/wizard.py,sha256=yatbXH-rf1ap4O1hY0I13WikM3zkm_NrAiSK6bqENIU,545
3960
- django_cms_qe-3.1.1.dist-info/LICENSE,sha256=5wLaeUil0gfU9p8C4zn2Yu_PvZBNieUoYl0z9FcFWdA,1521
3961
- django_cms_qe-3.1.1.dist-info/METADATA,sha256=ERYomAcNNB7jhK0mOZasWqZqPbvRlN8wraI7xrYZ5IU,4631
3962
- django_cms_qe-3.1.1.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
3963
- django_cms_qe-3.1.1.dist-info/top_level.txt,sha256=Gx3iR7ga_mwVi-a0WHznNn16KhciCv31fUrdh0sr8IM,157
3964
- django_cms_qe-3.1.1.dist-info/RECORD,,
3974
+ django_cms_qe-3.3.0.dist-info/LICENSE,sha256=5wLaeUil0gfU9p8C4zn2Yu_PvZBNieUoYl0z9FcFWdA,1521
3975
+ django_cms_qe-3.3.0.dist-info/METADATA,sha256=IqUIoCRDoEpA_bKnEWxPZK-8Fe2Phz-Ugxszg5B3Oco,4677
3976
+ django_cms_qe-3.3.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
3977
+ django_cms_qe-3.3.0.dist-info/top_level.txt,sha256=T4dauFwJy7FmxCy7WoQI3pPwiDessNB2LkfOAP76ssE,172
3978
+ django_cms_qe-3.3.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.3)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -5,6 +5,7 @@ cms_qe_breadcrumb
5
5
  cms_qe_i18n
6
6
  cms_qe_menu
7
7
  cms_qe_newsletter
8
+ cms_qe_plugins
8
9
  cms_qe_table
9
10
  cms_qe_test
10
11
  cms_qe_video