django-cms-qe 3.2.0__py3-none-any.whl → 3.4.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.
@@ -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',
@@ -9,3 +9,6 @@ LOGIN_REDIRECT_URL = '/'
9
9
  LOGIN_URL = '/auth/login'
10
10
 
11
11
  CMS_QE_AUTH_ENABLED = False # Enable cms_qe_auth registration.
12
+
13
+ ALDRYN_FORMS_EMAIL_AVAILABILITY_CHECKER_FNC = 'cms_qe_auth.utils.smtp_server_accepts_email_address'
14
+ ALDRYN_FORMS_EMAIL_AVAILABILITY_CHECKER_CLASS = 'cms_qe_auth.utils.SMTPCheckRecipient'
cms_qe_auth/admin.py CHANGED
@@ -1,6 +1,32 @@
1
+ from django import forms
1
2
  from django.contrib import admin
2
3
  from django.contrib.auth.admin import UserAdmin
4
+ from django.core.exceptions import ValidationError
5
+ from django.utils.translation import gettext_lazy as _
3
6
 
4
7
  from .models import User
8
+ from .utils import smtp_server_accepts_email_address
5
9
 
6
- admin.register(User)(UserAdmin)
10
+
11
+ class CmsQeAuthUserForm(forms.ModelForm):
12
+
13
+ def clean(self):
14
+ field_name = "email"
15
+ if self.cleaned_data[field_name] and self.cleaned_data["is_active"] and self.cleaned_data["is_staff"]:
16
+ try:
17
+ smtp_server_accepts_email_address(self.cleaned_data[field_name])
18
+ except ValidationError as err:
19
+ msg = _("Enter a valid email or don't enter any.")
20
+ if "email" in self._errors:
21
+ self._errors[field_name].append(err)
22
+ self._errors[field_name].append(msg)
23
+ else:
24
+ self._errors[field_name] = self.error_class([err, msg])
25
+ return self.cleaned_data
26
+
27
+
28
+ class CmsQeAuthUserAdmin(UserAdmin):
29
+ form = CmsQeAuthUserForm
30
+
31
+
32
+ admin.register(User)(CmsQeAuthUserAdmin)
Binary file
@@ -329,3 +329,6 @@ msgstr ""
329
329
  "Jste přihlášen jako <strong>%(username)s</strong>.\n"
330
330
  "Chcete se <a href=\"%(logout_url)s?next=%(register_url)s>i tak registrovat</"
331
331
  "a>?"
332
+
333
+ msgid "Enter a valid email or don't enter any."
334
+ msgstr "Zadejte platný e-mail nebo nedávejte žádný."
cms_qe_auth/utils.py CHANGED
@@ -1,7 +1,18 @@
1
+ import logging
2
+ import smtplib
3
+ import socket
4
+ from typing import Optional
5
+
6
+ from django.conf import settings
1
7
  from django.contrib.auth import get_user_model
8
+ from django.core.exceptions import ValidationError
2
9
  from django.utils.encoding import force_bytes, force_str
3
10
  from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
4
11
 
12
+ ACTION_OK = 250 # Requested mail action okay, completed.
13
+
14
+ logger = logging.getLogger(__name__)
15
+
5
16
  # pylint:disable=invalid-name
6
17
 
7
18
 
@@ -21,3 +32,57 @@ def get_user_by_uidb64(uidb64):
21
32
  except (TypeError, ValueError, OverflowError, User.DoesNotExist):
22
33
  user = None
23
34
  return user
35
+
36
+
37
+ class ActionIsNoOkResponse(Exception):
38
+ """Action si not OK response."""
39
+
40
+
41
+ def check_smtp_response(response: tuple[int, bytes]) -> None:
42
+ """Check if response is valid."""
43
+ if response[0] != ACTION_OK:
44
+ raise ActionIsNoOkResponse(force_str(response[1]))
45
+
46
+
47
+ class SMTPCheckRecipient:
48
+ """SMTP server checks recipient email if the server accepts it."""
49
+
50
+ def __init__(self, hostname: str):
51
+ self.smtp: Optional[smtplib.SMTP] = None
52
+ self.hostname = hostname
53
+
54
+ def connect(self) -> None:
55
+ if self.hostname and self.smtp is None:
56
+ try:
57
+ self.smtp = smtplib.SMTP(self.hostname, timeout=5)
58
+ check_smtp_response(self.smtp.helo())
59
+ check_smtp_response(self.smtp.docmd('MAIL FROM:""'))
60
+ except (ActionIsNoOkResponse, OSError, socket.gaierror, smtplib.SMTPException) as error:
61
+ logger.error(error)
62
+
63
+ def close(self) -> None:
64
+ if self.smtp is not None and self.smtp.sock is not None:
65
+ self.smtp.close()
66
+
67
+ def __enter__(self):
68
+ return self
69
+
70
+ def __exit__(self, exc_type, exc_value, exc_tb):
71
+ self.close()
72
+
73
+ def check(self, address: str) -> None:
74
+ """Check if SMTP server accepts email address. Raise ValidationError if not."""
75
+ self.connect()
76
+ if self.smtp is not None and self.smtp.sock is not None:
77
+ try:
78
+ code, message = self.smtp.docmd(f'RCPT TO:{address}')
79
+ if code != ACTION_OK:
80
+ raise ValidationError(force_str(message), code="invalid")
81
+ except (socket.gaierror, smtplib.SMTPException) as error:
82
+ logger.error(error)
83
+
84
+
85
+ def smtp_server_accepts_email_address(address: str) -> None:
86
+ """SMTP server accepts email address. Raise ValidationError if not."""
87
+ with SMTPCheckRecipient(settings.EMAIL_HOST) as checker:
88
+ checker.check(address)
@@ -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 &lt;iframe&gt;.",
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.2.0
3
+ Version: 3.4.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.
@@ -47,8 +47,8 @@ cms_qe/settings/__init__.py,sha256=GJwHXMHwMuGYE-3ZzePJ-26I2WwE8bAIMUDoiTFr0L8,9
47
47
  cms_qe/settings/dev.py,sha256=51CBwiclE8LLoNB2uioIK_L3JhM1yzukQ0gZimkcFqw,1487
48
48
  cms_qe/settings/unittest.py,sha256=folLIMJb1Arh60_Sn0eNQrvIlx0OsAs6v1tDfyRZVuQ,514
49
49
  cms_qe/settings/base/__init__.py,sha256=5yHfne9gPD_xuTaG3voZP23yzuCwROmif2mmKs-hG_A,446
50
- cms_qe/settings/base/app.py,sha256=vAq5Eo2LK6yU19cvbLrpLpWLV_REhdVRs9NfCZGEeu8,3966
51
- cms_qe/settings/base/auth.py,sha256=axEmEG5UYxyY3EE0keQjGZzkTv2iwxwfULr4LcvZPns,207
50
+ cms_qe/settings/base/app.py,sha256=pmy_0ThkSDNXmEVrWwFpRQ-mjHCvYFoo8YxmFRLG5bM,3988
51
+ cms_qe/settings/base/auth.py,sha256=OTr1LJ4RSMZm8STs4Q3pwPXmQoURax8OKLJ8eAj7PW4,395
52
52
  cms_qe/settings/base/cache.py,sha256=9p6C5lOz1pG-6k15PyvxlShUjBYIbU0ewpA8AX_YFus,297
53
53
  cms_qe/settings/base/cms.py,sha256=8icCNxcEp_KRDyP8-LXB21UurJL4wNysY39whAyt3I4,1855
54
54
  cms_qe/settings/base/constants.py,sha256=Rdq6ESg_J2B1Xm-ImEYM8pmsid-LqkSR7LXQdb3wlZU,7899
@@ -94,7 +94,7 @@ cms_qe_analytical/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
94
94
  cms_qe_analytical/templatetags/google_analytics.py,sha256=cN87jJzwVcjhqSS2JKTmPt9WntuBLnzxEcwgjrQMJSg,7370
95
95
  cms_qe_analytical/templatetags/piwik.py,sha256=EHOaojmewFpMqHzm_QCtCXGc_HGoiCJ_G-LPc8IfjM4,4131
96
96
  cms_qe_auth/__init__.py,sha256=SZS-CdmFLgIN4WGaUX_780L-vH0lqC7CdxKWjm_EuxE,867
97
- cms_qe_auth/admin.py,sha256=_Lj2OjIGRChal66ifTsin2iYW-UwLtd32WAT7__fLLY,140
97
+ cms_qe_auth/admin.py,sha256=rf8XPFKbGwTZGA7Po3j16LTj3sJkZ_IwFaipRqUN8Gc,1105
98
98
  cms_qe_auth/apps.py,sha256=YiIgOI3Bxj4-7IZdxUEvHmTK1xS9fR2qMS-fe5AZ-W0,191
99
99
  cms_qe_auth/cms_menus.py,sha256=UxzzuMfOJCC_EiCkV2__6R5JKV9q1WGbTEgO7yLy8rE,1675
100
100
  cms_qe_auth/cms_plugins.py,sha256=USiNHaWdIJqPFUMLOjhuVam4nwOckujg1uguXNIs798,1575
@@ -103,15 +103,15 @@ cms_qe_auth/forms.py,sha256=x7sdFoOrKBLTJXqESedpIh6Kc1k5zZhL4vwnmhj1gH8,1137
103
103
  cms_qe_auth/models.py,sha256=Aro43D9y1zrS-3eKHVZEuSkchusDZAcj15B2vYcdt0Q,2713
104
104
  cms_qe_auth/token.py,sha256=DG4Bu8AVV-d1ayL4Oc9DXNnERt1sstrll80RBGrplx0,224
105
105
  cms_qe_auth/urls.py,sha256=RCgr9t1YonE0yR_8gXiXZIGESvQfrwVwlKhBOWmgxkw,2040
106
- cms_qe_auth/utils.py,sha256=d5K5i_YOahd1qCTc9gqRYQ3-docIKusDMS_Az0-cVT0,614
106
+ cms_qe_auth/utils.py,sha256=94aELOVicanApvKkZ3xw5sFjd2C8AEzyCrHdgpFW8Pc,2790
107
107
  cms_qe_auth/views.py,sha256=TG7kwG2xtRUt9S8nG3rUlNOU_BsfvRVybNexGpF_9uI,2233
108
108
  cms_qe_auth/boilerplates/bootstrap3/templates/cms_qe/auth/login_form.html,sha256=nG9X-nvfXA4_9h4lVJGIHdcYFhf7sNEg_rVPa6ze9a4,884
109
109
  cms_qe_auth/boilerplates/bootstrap3/templates/cms_qe/auth/password_change_form.html,sha256=1khb3qibf_YpVV2GyT6W9sgTMtFetNmjs2mbCdCFtIo,599
110
110
  cms_qe_auth/boilerplates/bootstrap3/templates/cms_qe/auth/password_reset_confirm.html,sha256=5x4jB2vKZIRG5CSUxsKKHnSF0laVsUALR4Vqo7ZgQTU,920
111
111
  cms_qe_auth/boilerplates/bootstrap3/templates/cms_qe/auth/password_reset_form.html,sha256=RforEA_H8W7EOIIFqeJtJ3H1FE8dsWgUW5vDVjzTIw4,571
112
112
  cms_qe_auth/boilerplates/bootstrap3/templates/cms_qe/auth/register_form.html,sha256=oyq6uHRx2DHzSO1j-AlV99xgaoRgF9zXdetfOUz0JQs,653
113
- cms_qe_auth/locale/cs/LC_MESSAGES/django.mo,sha256=0_J63-IStzcNYEQHC-9R75pILWbRc4Yz0QFMtqtTo2A,6609
114
- cms_qe_auth/locale/cs/LC_MESSAGES/django.po,sha256=WTbW4z7vgmywNG1sKsA5TKSGQPLFN9IKgFgDCPD2S7Y,11891
113
+ cms_qe_auth/locale/cs/LC_MESSAGES/django.mo,sha256=YhgQa3TxE9VvULZaMt4YJfC3HC8wU8pp_s4m2RMiXWU,6722
114
+ cms_qe_auth/locale/cs/LC_MESSAGES/django.po,sha256=pBsbpADrzEImaS-Ib4hCUGsW6sWC9MlbdXrpZsF5ZiM,11998
115
115
  cms_qe_auth/locale/en/LC_MESSAGES/django.mo,sha256=1Oza0kFTJYv1uLpu51-XbZShcMmsgRMPdKVmSFB0mZk,378
116
116
  cms_qe_auth/locale/en/LC_MESSAGES/django.po,sha256=oKfD16eQBOFiMYTJ_nMrhWiHUmTOqMkOEExuFfbZlb4,9205
117
117
  cms_qe_auth/migrations/0001_initial.py,sha256=6cJuHqIgIqa2G5jhCJfGFh6tWdAShujT5PHK9S38MDI,3403
@@ -3888,6 +3888,17 @@ cms_qe_newsletter/templates/admin/cms_qe_newsletter/mailinglist/change_list.html
3888
3888
  cms_qe_newsletter/templates/cms_qe/newsletter/form.html,sha256=5YsoZlQsxlBfMStROmK8V9T0-w5akjCu3a75GlpO9bk,385
3889
3889
  cms_qe_newsletter/templates/cms_qe/newsletter/newsletter.html,sha256=0YnNGr4hNhAlkM-7ISeE8y1vRhFi7DdjJI_QH_B_ME4,195
3890
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
3891
3902
  cms_qe_table/__init__.py,sha256=0eiVSkglZ6A-QLBnGXQdTlysM6dj2kfTc6scFcUGGVA,1084
3892
3903
  cms_qe_table/cms_plugins.py,sha256=dR8b7h_xOJnQ4CDetxrfOiv1tfekhdnpe4KbKqasLi0,1065
3893
3904
  cms_qe_table/exceptions.py,sha256=D9jeyKebnha1lObxS-eJhDeIYLobqlQCLieqg5jPkEU,349
@@ -3940,8 +3951,6 @@ cms_qe_video/templates/cms_qe/video/video_source_file.html,sha256=QJF5fs88s9Fznp
3940
3951
  cms_qe_video/templates/cms_qe/video/video_widget.html,sha256=Yumciq6bGlAYI1lYx5j9V6IF8QYrncNYygPTkXEz6Wk,925
3941
3952
  cms_qe_video/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3942
3953
  cms_qe_video/templatetags/cms_qe_video.py,sha256=NR_mGv91J0rEreZrQjCzaaXSrZsKvrSas12wMJ-Dg24,1168
3943
- django_mail_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3944
- django_mail_backends/filebased.py,sha256=tsvMFFlm7-U5krIly53DArYZmrDrNZwQowKHScE8qQ4,612
3945
3954
  example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3946
3955
  example/urls.py,sha256=H-IJsRGFVZGw6FD9gvK-0B0dLeSOsduziHvDvcHCQZ0,399
3947
3956
  example/wsgi.py,sha256=lCKhvtFZlorSIA8qYEqc3pZ1Oflrz_Tc696MWJ62ue4,396
@@ -3962,8 +3971,8 @@ test_selenium/pages/cms/__init__.py,sha256=_qe4YZYaQbrXp7Szmmeo4TUSkXlE5Rozu8E3t
3962
3971
  test_selenium/pages/cms/login.py,sha256=UPzJQcYff8NUAT4nvmfQoJQxzOJyPrJ_cKtH35NVfNg,521
3963
3972
  test_selenium/pages/cms/page.py,sha256=YQnpZkopfVnhoyQKpRDGqjNeV6xUl-pEHjEcZ9HRiPk,489
3964
3973
  test_selenium/pages/cms/wizard.py,sha256=yatbXH-rf1ap4O1hY0I13WikM3zkm_NrAiSK6bqENIU,545
3965
- django_cms_qe-3.2.0.dist-info/LICENSE,sha256=5wLaeUil0gfU9p8C4zn2Yu_PvZBNieUoYl0z9FcFWdA,1521
3966
- django_cms_qe-3.2.0.dist-info/METADATA,sha256=6MBxVvDvwZR8z0sS-gYogsECKcYxogAu9YMsCNReG1o,4677
3967
- django_cms_qe-3.2.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
3968
- django_cms_qe-3.2.0.dist-info/top_level.txt,sha256=sns93tUvzAUoyPpolDHe0Orka1l44A4BJnYFko7EIKU,178
3969
- django_cms_qe-3.2.0.dist-info/RECORD,,
3974
+ django_cms_qe-3.4.0.dist-info/LICENSE,sha256=5wLaeUil0gfU9p8C4zn2Yu_PvZBNieUoYl0z9FcFWdA,1521
3975
+ django_cms_qe-3.4.0.dist-info/METADATA,sha256=8DDjXi8Wz4QPcH8O-Md8iC07CWZPk6sQE85vTf2M6dI,4677
3976
+ django_cms_qe-3.4.0.dist-info/WHEEL,sha256=cpQTJ5IWu9CdaPViMhC9YzF8gZuS5-vlfoFihTBC86A,91
3977
+ django_cms_qe-3.4.0.dist-info/top_level.txt,sha256=T4dauFwJy7FmxCy7WoQI3pPwiDessNB2LkfOAP76ssE,172
3978
+ django_cms_qe-3.4.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (70.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -5,9 +5,9 @@ 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
11
- django_mail_backends
12
12
  example
13
13
  test_selenium
@@ -1,17 +0,0 @@
1
- # EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
2
- # EMAIL_BACKEND = 'django_mail_backends.filebased.EmailBackend'
3
- import datetime
4
- import os
5
-
6
- from django.core.mail.backends.filebased import EmailBackend as DjangoEmailBackend
7
-
8
-
9
- class EmailBackend(DjangoEmailBackend):
10
-
11
- def _get_filename(self):
12
- """Return a unique file name."""
13
- if self._fname is None:
14
- timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
15
- fname = "%s-%s.eml" % (timestamp, abs(id(self)))
16
- self._fname = os.path.join(self.file_path, fname)
17
- return self._fname
File without changes