django-cms-qe 3.6.1__py3-none-any.whl → 3.7.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.
Files changed (39) hide show
  1. cms_qe/boilerplates/bootstrap3/templates/cms_qe/home.html +3 -3
  2. cms_qe/export.py +8 -7
  3. cms_qe/hooks.py +90 -0
  4. cms_qe/ldap.py +4 -0
  5. cms_qe/settings/base/app.py +12 -4
  6. cms_qe/settings/base/cache.py +6 -4
  7. cms_qe/settings/base/cms.py +4 -1
  8. cms_qe/settings/base/database.py +7 -10
  9. cms_qe/settings/base/email.py +9 -11
  10. cms_qe/settings/base/env.py +3 -0
  11. cms_qe/settings/base/logging.py +2 -1
  12. cms_qe/settings/base/search.py +5 -1
  13. cms_qe/settings/base/security.py +2 -1
  14. cms_qe/settings/dev.py +25 -19
  15. cms_qe/static/cms_qe/css/fix-djangocms-admin-style.css +33 -0
  16. cms_qe/templates/admin/inc/extrastyle.html +2 -0
  17. cms_qe/templates/base.html +7 -4
  18. cms_qe/templates/cms_qe/alias_content_preview.html +28 -0
  19. cms_qe/templates/cms_qe/home.html +0 -2
  20. cms_qe/templates/pg_is_in_recovery_login.html +4 -0
  21. cms_qe/urls.py +2 -0
  22. cms_qe/utils.py +11 -17
  23. cms_qe/views/redirect_to_page.py +38 -0
  24. cms_qe/views/test_messages.py +32 -0
  25. cms_qe_auth/models.py +1 -1
  26. cms_qe_test/cms.py +22 -5
  27. cms_qe_test/mail_filebased_backend.py +20 -0
  28. {django_cms_qe-3.6.1.dist-info → django_cms_qe-3.7.0.dist-info}/METADATA +74 -34
  29. {django_cms_qe-3.6.1.dist-info → django_cms_qe-3.7.0.dist-info}/RECORD +32 -29
  30. {django_cms_qe-3.6.1.dist-info → django_cms_qe-3.7.0.dist-info}/WHEEL +1 -1
  31. {django_cms_qe-3.6.1.dist-info → django_cms_qe-3.7.0.dist-info}/top_level.txt +0 -1
  32. example/__init__.py +0 -0
  33. example/settings/__init__.py +0 -0
  34. example/settings/aldryn_newsblog.py +0 -14
  35. example/settings/dev.py +0 -29
  36. example/settings/selenium.py +0 -10
  37. example/urls.py +0 -16
  38. example/wsgi.py +0 -16
  39. {django_cms_qe-3.6.1.dist-info → django_cms_qe-3.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
1
- {% load static i18n cms_tags sekizai_tags %}
1
+ {% load static i18n cms_tags sekizai_tags djangocms_alias_tags %}
2
2
 
3
3
  {% addtoblock "css" %}
4
4
  <link rel="stylesheet" href="{% static "cms_qe/css/bootstrap.min.css" %}" />
@@ -26,7 +26,7 @@
26
26
 
27
27
  {% block header %}
28
28
  <header>
29
- {% static_placeholder "header" %}
29
+ {% static_alias "header" %}
30
30
  </header>
31
31
  {% endblock %}
32
32
  {% block content %}
@@ -36,7 +36,7 @@
36
36
  {% endblock %}
37
37
  {% block footers %}
38
38
  <footer>
39
- {% static_placeholder "footer" %}
39
+ {% static_alias "footer" %}
40
40
  </footer>
41
41
  {% endblock %}
42
42
 
cms_qe/export.py CHANGED
@@ -137,7 +137,7 @@ def export_data(export_type, modeladmin, queryset):
137
137
  if not field.auto_created or field.name == 'id'
138
138
  ]
139
139
 
140
- def get_export_headers(self) -> list:
140
+ def get_export_headers(self, selected_fields=None) -> list:
141
141
  """
142
142
  As header use verbose name which is better than database name.
143
143
  """
@@ -153,8 +153,8 @@ def export_data(export_type, modeladmin, queryset):
153
153
  name = force_str(field.verbose_name)
154
154
  return name
155
155
 
156
- def export_field(self, field, obj):
157
- value = super().export_field(field, obj)
156
+ def export_field(self, field, instance, **kwargs):
157
+ value = super().export_field(field, instance, **kwargs)
158
158
  if '__proxy__' in value.__class__.__name__:
159
159
  value = force_str(value)
160
160
  return value
@@ -176,11 +176,11 @@ class AdminField(fields.Field):
176
176
  self.get_modeladmin = lambda: modeladmin
177
177
  super().__init__(*args, **kwds)
178
178
 
179
- def get_value(self, obj):
179
+ def get_value(self, instance):
180
180
  admin_property = getattr(self.get_modeladmin(), self.attribute, None)
181
181
  if admin_property:
182
- return admin_property(obj)
183
- return super().get_value(obj)
182
+ return admin_property(instance)
183
+ return super().get_value(instance)
184
184
 
185
185
 
186
186
  # Taken from https://github.com/django-import-export/django-import-export/issues/525#issuecomment-303046691
@@ -197,12 +197,13 @@ class ChoicesWidget(widgets.Widget):
197
197
  """
198
198
  self.choices = dict(choices)
199
199
  self.revert_choices = {v: k for k, v in self.choices.items()}
200
+ super().__init__()
200
201
 
201
202
  # pylint: disable=keyword-arg-before-vararg
202
203
  def clean(self, value, row=None, *args, **kwargs):
203
204
  """Returns the db value given the display value"""
204
205
  return self.revert_choices.get(value, value) if value else None
205
206
 
206
- def render(self, value, obj=None):
207
+ def render(self, value, obj=None, **kwargs):
207
208
  """Returns the display value given the db value"""
208
209
  return self.choices.get(value, '')
cms_qe/hooks.py ADDED
@@ -0,0 +1,90 @@
1
+ from types import MethodType
2
+
3
+ from django.apps import apps
4
+ from django.conf import settings
5
+ from django.contrib import messages
6
+ from django.contrib.auth.views import LoginView
7
+ from django.db import InternalError, connection
8
+ from django.http import HttpRequest, HttpResponseRedirect
9
+ from django.template.response import TemplateResponse
10
+ from django.urls import reverse
11
+ from django.utils.translation import gettext_lazy as _
12
+ from djangocms_alias.models import AliasContent
13
+ from menus.menu_pool import MenuRenderer, menu_pool
14
+
15
+
16
+ def pg_is_in_recovery():
17
+ """Return True when database is slave or False when database is master."""
18
+ with connection.cursor() as cursor:
19
+ if cursor.db.vendor != 'postgresql':
20
+ return False
21
+ cursor.execute("SELECT pg_is_in_recovery()")
22
+ return cursor.fetchone()[0]
23
+
24
+
25
+ class PgIsInRecoveryLoginView(LoginView):
26
+ template_name = 'admin/login.html'
27
+ url_page_name = "login"
28
+
29
+ def get(self, request, *args, **kwargs):
30
+ if pg_is_in_recovery():
31
+ messages.add_message(request, messages.WARNING,
32
+ _('The database is in recovery mode. Unable to login. Try it later.'))
33
+ return super().get(request, *args, **kwargs)
34
+
35
+ def post(self, request, *args, **kwargs):
36
+ if pg_is_in_recovery():
37
+ messages.add_message(request, messages.ERROR, _('Login failed. The database is in recovery mode.'))
38
+ return HttpResponseRedirect(reverse(self.url_page_name))
39
+ return super().post(request, *args, **kwargs)
40
+
41
+ def get_template_names(self):
42
+ if pg_is_in_recovery():
43
+ return ['pg_is_in_recovery_login.html']
44
+ return self.template_name
45
+
46
+
47
+ class PgIsInRecoveryMenuRenderer(MenuRenderer):
48
+
49
+ def get_nodes(self, namespace=None, root_id=None, breadcrumb=False):
50
+ try:
51
+ return super().get_nodes(namespace, root_id, breadcrumb)
52
+ except InternalError:
53
+ if pg_is_in_recovery():
54
+ return []
55
+ raise
56
+
57
+
58
+ def render_alias_content(request: HttpRequest, alias_content: AliasContent) -> TemplateResponse:
59
+ """Render alias content with additionad css class alias-$name.
60
+
61
+ This is the same function as on the url
62
+ https://github.com/django-cms/djangocms-alias/blob/master/djangocms_alias/rendering.py#L4,
63
+ it just uses a different template. In the template, a css class is added by the alias name.
64
+ This is necessary so that the appropriate styles can be linked to it.
65
+ """
66
+ template = "cms_qe/alias_content_preview.html"
67
+ context = {
68
+ "alias_content": alias_content,
69
+ "site_styles": settings.STYLES_FOR_ALIAS_ADMIN_PREVIEW,
70
+ }
71
+ return TemplateResponse(request, template, context)
72
+
73
+
74
+ def get_renderer(self, request: HttpRequest) -> PgIsInRecoveryMenuRenderer:
75
+ self.discover_menus()
76
+ return PgIsInRecoveryMenuRenderer(pool=self, request=request)
77
+
78
+
79
+ def patch_menu_pool_cachekey():
80
+ """Skip exception when MenuRenderer attempts to write to read only database."""
81
+ menu_pool.get_renderer = MethodType(get_renderer, menu_pool)
82
+
83
+
84
+ def patch_alias():
85
+ """Patch alias template preview."""
86
+ try:
87
+ extension = apps.get_app_config('cms').cms_extension
88
+ extension.toolbar_enabled_models[AliasContent] = render_alias_content
89
+ except KeyError:
90
+ pass
cms_qe/ldap.py ADDED
@@ -0,0 +1,4 @@
1
+ def clean_user_data(model_fields):
2
+ """Transform the user data loaded from LDAP into a form suitable for creating a user."""
3
+ model_fields['is_staff'] = True
4
+ return model_fields
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Base settings for Django app.
3
3
  """
4
+ from .env import ENV
4
5
 
5
6
  # Default primary key field type
6
7
  # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
@@ -8,8 +9,10 @@ Base settings for Django app.
8
9
  DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
9
10
 
10
11
  SITE_ID = 1
12
+ CMS_CONFIRM_VERSION4 = True
13
+ DJANGOCMS_VERSIONING_ALLOW_DELETING_VERSIONS = True
11
14
 
12
- INTERNAL_IPS = ['127.0.0.1']
15
+ INTERNAL_IPS = ENV.list("INTERNAL_IPS", default=[])
13
16
 
14
17
  META_USE_SITES = True
15
18
  META_SITE_PROTOCOL = 'https'
@@ -47,8 +50,12 @@ INSTALLED_APPS = [
47
50
  'treebeard', # Tree structure of pages and plugins.
48
51
  'sekizai', # Static file management.
49
52
 
53
+ 'djangocms_text',
54
+ 'djangocms_link',
55
+ 'djangocms_alias',
56
+ 'djangocms_versioning',
57
+
50
58
  # Other Django CMS's useful modules.
51
- 'djangocms_text_ckeditor',
52
59
  'djangocms_googlemap',
53
60
 
54
61
  # Django Filer's modules.
@@ -70,11 +77,13 @@ INSTALLED_APPS = [
70
77
  'djangocms_frontend.contrib.collapse',
71
78
  'djangocms_frontend.contrib.content',
72
79
  'djangocms_frontend.contrib.grid',
80
+ 'djangocms_frontend.contrib.icon',
81
+ 'djangocms_frontend.contrib.image',
73
82
  'djangocms_frontend.contrib.jumbotron',
74
83
  'djangocms_frontend.contrib.link',
75
84
  'djangocms_frontend.contrib.listgroup',
76
85
  'djangocms_frontend.contrib.media',
77
- 'djangocms_frontend.contrib.image',
86
+ 'djangocms_frontend.contrib.navigation',
78
87
  'djangocms_frontend.contrib.tabs',
79
88
  'djangocms_frontend.contrib.utilities',
80
89
 
@@ -83,7 +92,6 @@ INSTALLED_APPS = [
83
92
  'constance',
84
93
  'constance.backends.database',
85
94
  'import_export',
86
- 'mailqueue',
87
95
 
88
96
  # Aldryn forms
89
97
  'aldryn_forms',
@@ -5,9 +5,11 @@ Caching setting by default in-memory without need to configure anything.
5
5
  # Caching
6
6
  # https://docs.djangoproject.com/en/4.2/topics/cache/
7
7
 
8
+ # https://pypi.org/project/python-environ/
9
+ # Supported types / cache_url
10
+
11
+ from .env import ENV
12
+
8
13
  CACHES = {
9
- 'default': {
10
- 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
11
- 'LOCATION': '127.0.0.1:11211',
12
- }
14
+ "default": ENV.cache("CACHE_URL", default="pymemcache://127.0.0.1:11211"),
13
15
  }
@@ -41,7 +41,7 @@ THUMBNAIL_PROCESSORS = (
41
41
  'easy_thumbnails.processors.filters'
42
42
  )
43
43
 
44
- TEXT_ADDITIONAL_TAGS = ('iframe',)
44
+ TEXT_ADDITIONAL_ATTRIBUTES: dict[str, set] = {"iframe": set()}
45
45
 
46
46
  # cmsplugin_filer_folder
47
47
  CMSPLUGIN_FILER_FOLDER_STYLE_CHOICES = (
@@ -65,3 +65,6 @@ DJANGOCMS_FRONTEND_GRID_CONTAINERS = (
65
65
  ("container-full", _("Full container")),
66
66
  (" ", "----"),
67
67
  )
68
+
69
+ # For example: ["css/bootstrap.min.css", "css/screen.css"]
70
+ STYLES_FOR_ALIAS_ADMIN_PREVIEW: list[str] = []
@@ -5,15 +5,12 @@ Database settings, used PostgreSQL without auth by default.
5
5
  # Database
6
6
  # https://docs.djangoproject.com/en/1.11/ref/settings/#databases
7
7
 
8
+ # https://pypi.org/project/python-environ/
9
+ # Supported types / db_url
10
+
11
+ from .env import ENV
12
+
13
+ # Database
8
14
  DATABASES = {
9
- 'default': {
10
- 'ENGINE': 'django.db.backends.postgresql_psycopg2',
11
- 'NAME': 'cms_qe',
12
- 'USER': '',
13
- 'PASSWORD': '',
14
- 'HOST': '',
15
- 'OPTIONS': {
16
- 'application_name': 'cms_qe',
17
- }
18
- }
15
+ "default": ENV.db("DATABASE_URL", default='postgres://qe_user:password@/cms_qe'),
19
16
  }
@@ -1,14 +1,12 @@
1
1
  """
2
2
  Mailing settings, by default app looks for smtp server.
3
3
  """
4
-
5
- EMAIL_HOST = 'localhost'
6
- EMAIL_PORT = 587 # TLS uses usually 587, not 22
7
-
8
- EMAIL_HOST_USER = ''
9
- EMAIL_HOST_PASSWORD = ''
10
-
11
- EMAIL_USE_TLS = True # Prefer to use secure mailing by default
12
- EMAIL_SUBJECT_PREFIX = '' # Remove Django default prefix
13
-
14
- DEFAULT_FROM_EMAIL = 'django_cms_qe@localhost'
4
+ from .env import ENV
5
+
6
+ EMAIL_HOST = ENV.str("EMAIL_HOST", default="localhost")
7
+ EMAIL_HOST_USER = ENV.str("EMAIL_USER", default="")
8
+ EMAIL_HOST_PASSWORD = ENV.str("EMAIL_PASSWORD", default="")
9
+ EMAIL_PORT = ENV.int("EMAIL_PORT", default=587) # TLS uses usually 587, not 22
10
+ EMAIL_USE_TLS = ENV.bool("EMAIL_USE_TLS", default=False)
11
+ EMAIL_SUBJECT_PREFIX = ENV.str("EMAIL_SUBJECT_PREFIX", default="") # Remove Django default prefix
12
+ DEFAULT_FROM_EMAIL = ENV.str("DEFAULT_FROM_EMAIL", default="django_cms_qe@localhost")
@@ -0,0 +1,3 @@
1
+ import environ
2
+
3
+ ENV = environ.Env()
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Logging settings with base formatters and handlers.
3
3
  """
4
+ from .env import ENV
4
5
 
5
6
  # Logging
6
7
  # https://docs.djangoproject.com/en/1.11/topics/logging/
@@ -49,7 +50,7 @@ LOGGING = {
49
50
  'propagate': True,
50
51
  },
51
52
  '': {
52
- 'level': 'INFO',
53
+ 'level': ENV.str("LOGGER", default="ERROR"),
53
54
  'handlers': ['console'],
54
55
  },
55
56
  }
@@ -1,15 +1,19 @@
1
1
  import os
2
2
  from pathlib import Path
3
3
 
4
+ from .env import ENV
5
+
4
6
  site_resolver = Path(__file__).resolve()
5
7
 
6
8
  PROJECT_DIR = site_resolver.parent.parent.parent.parent
7
9
 
8
10
  HAYSTACK_ROUTERS = ['aldryn_search.router.LanguageRouter']
9
11
  HAYSTACK_ENGINE = 'cms_qe.whoosh.backend.AnalyzerWhooshEngine'
10
- _HAYSTACK_PATH = os.path.normpath(os.path.join(PROJECT_DIR, 'whoosh_index'))
12
+ _HAYSTACK_PATH = ENV.str('HAYSTACK_PATH', default=os.path.normpath(os.path.join(PROJECT_DIR, 'whoosh_index')))
11
13
  HAYSTACK_CONNECTIONS = {
12
14
  'default': {'ENGINE': HAYSTACK_ENGINE, 'PATH': os.path.join(_HAYSTACK_PATH, 'default')},
13
15
  'en': {'ENGINE': HAYSTACK_ENGINE, 'PATH': os.path.join(_HAYSTACK_PATH, 'en')},
14
16
  }
15
17
  HAYSTACK_CUSTOM_HIGHLIGHTER = "cms_qe.haystack.highlighting.HaystackHighlighter"
18
+
19
+ ALDRYN_NEWSBLOG_UPDATE_SEARCH_DATA_ON_SAVE = True
@@ -41,10 +41,11 @@ authorization by one of those options (more about that in `documentation
41
41
  AXES_NUM_PROXIES = 1
42
42
 
43
43
  """
44
+ from .env import ENV
44
45
 
45
46
  # Cookies.
46
47
 
47
- SESSION_COOKIE_NAME = 'sessionid'
48
+ SESSION_COOKIE_NAME = ENV.str("SESSION_COOKIE_NAME", default="sessionid")
48
49
  SESSION_COOKIE_SECURE = True
49
50
 
50
51
  # Secure headers.
cms_qe/settings/dev.py CHANGED
@@ -8,6 +8,7 @@ import os
8
8
  from pathlib import Path
9
9
 
10
10
  from .base import * # noqa: F401,F403 pylint: disable=wildcard-import,unused-wildcard-import
11
+ from .base.env import ENV
11
12
 
12
13
  # Quick-start development settings - unsuitable for production
13
14
  # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
@@ -16,7 +17,7 @@ DEBUG = True
16
17
 
17
18
  META_SITE_PROTOCOL = 'http'
18
19
 
19
- SECRET_KEY = '^xzhq0*q1+t0*ihq^^1wuyj3i%y#(38b7d-vlpkm-d(=!^uk6x'
20
+ SECRET_KEY = ENV.str("SECRET_KEY", default='secret')
20
21
 
21
22
  SESSION_COOKIE_SECURE = False
22
23
 
@@ -38,30 +39,35 @@ MIDDLEWARE += [ # noqa: F405
38
39
  site_resolver = Path(__file__).resolve()
39
40
 
40
41
  PROJECT_DIR = site_resolver.parent.parent.parent
42
+ RUN_SITE_DIR = os.environ.get("VENV_PATH", PROJECT_DIR)
41
43
 
42
- STATIC_ROOT = os.path.join(PROJECT_DIR, 'staticfiles')
44
+ STATIC_ROOT = ENV.str("STATIC_ROOT", default=os.path.join(PROJECT_DIR, 'staticfiles'))
45
+ MEDIA_ROOT = ENV.str("MEDIA_ROOT", default=os.path.join(RUN_SITE_DIR, 'media'))
43
46
 
44
47
  # Caching
45
- # https://docs.djangoproject.com/en/1.11/topics/cache/
46
-
47
48
  CACHES = {
48
- 'default': {
49
- 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
50
- 'LOCATION': os.path.join(PROJECT_DIR, 'django_cache'),
51
- }
49
+ "default": ENV.cache("CACHE_URL", default=f'filecache://{os.path.join(RUN_SITE_DIR, "django_cache")}')
52
50
  }
53
51
 
54
52
  # Database
55
- # https://docs.djangoproject.com/en/1.11/ref/settings/#databases
56
-
57
- DATABASES = {
58
- 'default': {
59
- 'ENGINE': 'django.db.backends.sqlite3',
60
- 'NAME': os.path.join(PROJECT_DIR, 'db.sqlite3'),
61
- 'TEST': {
62
- 'NAME': ':memory:',
63
- },
64
- }
53
+ database_path = os.path.join(RUN_SITE_DIR, 'db.sqlite3')
54
+ database_url_default = f"sqlite:///{database_path}"
55
+ DATABASES = {"default": ENV.db("DATABASE_URL", default=database_url_default)}
56
+
57
+ EMAIL_BACKEND = 'cms_qe_test.mail_filebased_backend.EmlEmailBackend'
58
+ EMAIL_FILE_PATH = ENV.str("EMAIL_FILE_PATH", default=os.path.join(RUN_SITE_DIR, 'django_mails'))
59
+
60
+ ALDRYN_FORMS_SUBMISSION_LIST_DISPLAY_FIELD = "aldryn_forms.admin.display_form_submission_data"
61
+
62
+ SITE_API_ROOT = "/api/v1/"
63
+
64
+ REST_FRAMEWORK = {
65
+ 'EXCEPTION_HANDLER': 'cms_qe.api.utils.exception_handler',
66
+ 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
65
67
  }
66
68
 
67
- EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
69
+ # API views: [("path/", "module.api.views.RecordViewSet", "api-records"), ...]
70
+ API_VIEWS = [
71
+ ('aldryn-forms/forms', 'aldryn_forms.api.views.FormViewSet', 'aldryn-forms-form'),
72
+ ('aldryn-forms/submitssions', 'aldryn_forms.api.views.SubmissionsViewSet', 'aldryn-forms-submitssions'),
73
+ ]
@@ -0,0 +1,33 @@
1
+ form .selector-chosen-title {
2
+ background-color: #00bbff;
3
+ }
4
+
5
+ fieldset .fieldset-heading,
6
+ fieldset .inline-heading,
7
+ :not(.inline-related) .collapse summary {
8
+ background-color: #00bbff !important;
9
+ }
10
+
11
+ fieldset h2.fieldset-heading {
12
+ color: white !important;
13
+ background-color: #00bbff !important;
14
+ font-weight: bold !important;
15
+ padding-left: 1em !important;
16
+ }
17
+
18
+ .selector .selector-chooser button.selector-add,
19
+ .selector .selector-chooser button.selector-remove {
20
+ width: 38px !important;
21
+ height: 32px !important;
22
+ padding: 0.4em 1em 1em 0.5em !important;
23
+ }
24
+
25
+ :enabled.selector-add,
26
+ :enabled.selector-remove {
27
+ opacity: 1 !important;
28
+ }
29
+
30
+ .selector .selector-available button.selector-chooseall,
31
+ .selector .selector-chosen button.selector-clearall {
32
+ height: 32px !important;
33
+ }
@@ -0,0 +1,2 @@
1
+ {% load static %}
2
+ <link rel="stylesheet" href="{% static "cms_qe/css/fix-djangocms-admin-style.css" %}">
@@ -1,4 +1,4 @@
1
- {% load i18n static cms_tags sekizai_tags %}
1
+ {% load i18n static cms_tags djangocms_alias_tags menu_tags sekizai_tags %}
2
2
  {# Doctype is important for Django CMS so it can correctly detect height of window. #}
3
3
  <!DOCTYPE html>
4
4
  <html lang="{{ LANGUAGE_CODE }}">
@@ -9,17 +9,20 @@
9
9
  {% include 'cms_qe/include/head.html' %}
10
10
  </head>
11
11
  <body>
12
- {% include 'cms_qe/include/body_top.html' %}
13
12
  {% cms_toolbar %}
13
+ {% block menu %}
14
+ <menu>{% show_menu 0 100 100 100 %}</menu>
15
+ {% endblock %}
16
+ {% include 'cms_qe/include/body_top.html' %}
14
17
  {% block header %}
15
18
  <header>
16
- {% static_placeholder "header" %}
19
+ {% static_alias "header" %}
17
20
  </header>
18
21
  {% endblock %}
19
22
  {% block content %}{% endblock %}
20
23
  {% block footers %}
21
24
  <footer>
22
- {% static_placeholder "footer" %}
25
+ {% static_alias "footer" %}
23
26
  </footer>
24
27
  {% endblock %}
25
28
  {% include "cms_qe/include/body_bottom.html" %}
@@ -0,0 +1,28 @@
1
+ {% extends "djangocms_alias/base.html" %}
2
+ {% load i18n cms_tags static %}
3
+
4
+ {% if site_styles %}
5
+ {% block extrastyle %}
6
+ {{ block.super }}
7
+ {% for path in site_styles %}
8
+ <link rel="stylesheet" href="{% static path %}">
9
+ {% endfor %}
10
+ {% endblock %}
11
+ {% block base_css %}
12
+ {{ block.super }}
13
+ {% for path in site_styles %}
14
+ <link rel="stylesheet" href="{% static path %}">
15
+ {% endfor %}
16
+ {% endblock %}
17
+ {% endif %}
18
+
19
+ {% block aliases_content %}
20
+ <div class="cms-aliases-page container alias-{{ alias_content.name|slugify }}">
21
+ <h2 class="cms-aliases-page-heading" id="{{ alias_content.name|slugify }}">
22
+ <span class="cms-aliases-page-heading-inner">Alias: {{ alias_content.name }}</span>
23
+ </h2>
24
+ <div class="cms-aliases-detail">
25
+ {% render_placeholder alias_content.placeholder %}
26
+ </div>
27
+ </div>
28
+ {% endblock aliases_content %}
@@ -2,7 +2,5 @@
2
2
  {% load cms_tags %}
3
3
 
4
4
  {% block content %}
5
- {% placeholder "page header" %}
6
5
  {% placeholder "content" %}
7
- {% placeholder "page footer" %}
8
6
  {% endblock %}
@@ -0,0 +1,4 @@
1
+ {% extends "admin/login.html" %}
2
+ {% load i18n %}
3
+
4
+ {% block content %}{# Do not display login form. #}{% endblock %}
cms_qe/urls.py CHANGED
@@ -14,6 +14,7 @@ from django.urls import include, path
14
14
  from django.views.i18n import JavaScriptCatalog
15
15
 
16
16
  from cms_qe.views.maintenance import HealthCheckView, ReloadSiteView
17
+ from cms_qe.views.redirect_to_page import RedirectToPage
17
18
  from cms_qe.views.search_result import SiteSearchView
18
19
  from cms_qe.views.security import SecurityTxtView
19
20
 
@@ -45,6 +46,7 @@ urlpatterns = [
45
46
  path('site-search-result/', SiteSearchView.as_view(), name='site-search-result'),
46
47
  path("healthcheck/", HealthCheckView.as_view(), name='healthcheck'), # Used by uwsgi in docker.
47
48
  path("superuser/reload-site/", ReloadSiteView.as_view(), name='reload-site'),
49
+ path('redirect-to-page/', RedirectToPage.as_view(), name='redirect-to-page'),
48
50
  ]
49
51
 
50
52
  # django-simple-captcha
cms_qe/utils.py CHANGED
@@ -5,41 +5,35 @@ from typing import Optional, Union
5
5
  from django.apps import apps
6
6
  from django.conf import settings
7
7
  from django.contrib.sites.shortcuts import get_current_site
8
+ from django.core.mail import EmailMultiAlternatives
8
9
  from django.template import TemplateDoesNotExist
9
10
  from django.template.loader import get_template
10
- from mailqueue.models import MailerMessage
11
11
 
12
12
 
13
13
  # pylint:disable=invalid-name
14
14
  def get_email(template: str, subject: str, to: Union[str, Iterable[str]], from_email: Optional[str] = None, **kwargs):
15
15
  """
16
- Returns a ``MailerMessage`` instance from ``mailqueue``. Use ``save()`` method instead of ``send()`` to send
17
- message or put it to a mailqueue.
16
+ Returns a ``EmailMultiAlternatives`` instance from ``django.core.mail``.
18
17
 
19
18
  Template should be without extension and you should create both ``.txt`` and ``.html`` version.
20
19
  Second one is not mandatory but is good to provide it as well.
21
20
 
22
21
  """
23
-
24
- email = MailerMessage()
25
- email.subject = subject
26
- email.from_address = from_email or settings.DEFAULT_FROM_EMAIL
27
-
28
- if isinstance(to, str):
29
- to = [to]
30
- email.to_address = ', '.join(to)
31
-
32
22
  template_txt = get_template(template + '.txt')
33
- content = template_txt.render(kwargs)
34
- email.content = content
35
23
 
24
+ msg = EmailMultiAlternatives(
25
+ subject,
26
+ template_txt.render(kwargs),
27
+ from_email or settings.DEFAULT_FROM_EMAIL,
28
+ [to] if isinstance(to, str) else to,
29
+ )
36
30
  try:
37
31
  template_html = get_template(template + '.html')
32
+ msg.attach_alternative(template_html.render(kwargs), "text/html")
38
33
  except TemplateDoesNotExist:
39
- return email
40
- email.html_content = template_html.render(kwargs)
34
+ pass
41
35
 
42
- return email
36
+ return msg
43
37
 
44
38
 
45
39
  def get_base_url(request) -> str:
@@ -0,0 +1,38 @@
1
+ """
2
+ from django.utils.safestring import mark_safe
3
+
4
+ REDIRECT_TO_PAGE = (
5
+ ('REDIRECT_TO_PAGE', (
6
+ '',
7
+ mark_safe('Redirect to page. Example: <pre>/path/one/\n/path/two/ localhost:8000</pre>'),
8
+ )),
9
+ )
10
+
11
+ EXTRA_CONSTANCE_CONFIG = ... + REDIRECT_TO_PAGE
12
+ """
13
+ import re
14
+ from django.views.generic import RedirectView
15
+
16
+
17
+ class RedirectToPage(RedirectView):
18
+ """Redirect to page according to settings in constance."""
19
+
20
+ url = "/"
21
+
22
+ def get_redirect_url(self, *args, **kwargs):
23
+ try:
24
+ from constance import config
25
+ redirect_to_page = config.REDIRECT_TO_PAGE
26
+ except (ModuleNotFoundError, AttributeError):
27
+ return self.url
28
+ for line in re.split("\n+", redirect_to_page):
29
+ line = line.strip()
30
+ groups = re.match(r"(?P<path>\S+)(\s+(?P<host>\S+))?", line.strip())
31
+ if groups is None:
32
+ continue
33
+ if groups['host'] is None:
34
+ return groups['path']
35
+ if groups['host'] == self.request.META.get("HTTP_HOST", self.request.META.get("SERVER_NAME")):
36
+ return groups['path']
37
+ continue
38
+ return self.url
@@ -0,0 +1,32 @@
1
+ """
2
+ from cms_qe.views.test_messages import TestMessagesView
3
+
4
+ urlpatterns = [
5
+ path("test-messages/", TestMessagesView.as_view(), name='test-messages'),
6
+ ]
7
+ """
8
+ from typing import Any
9
+
10
+ from django.contrib import messages
11
+ from django.http import HttpResponseRedirect
12
+ from django.views.generic import RedirectView
13
+
14
+
15
+ class TestMessagesView(RedirectView):
16
+ """Test messages view."""
17
+
18
+ def get_redirect_url(self, *args: Any, **kwargs: Any) -> HttpResponseRedirect:
19
+ """Prepare message and redirect to the next."""
20
+ msg = self.request.GET.get("msg", "Test message. ?type=all / debug / info / success / warning / error")
21
+ messages.set_level(self.request, messages.DEBUG)
22
+ if self.request.GET.get("type") in ("debug", "all"):
23
+ messages.debug(self.request, msg)
24
+ if self.request.GET.get("type") in ("info", "all"):
25
+ messages.info(self.request, msg)
26
+ if self.request.GET.get("type") in ("success", "all"):
27
+ messages.success(self.request, msg)
28
+ if self.request.GET.get("type") in ("warning", "all"):
29
+ messages.warning(self.request, msg)
30
+ if self.request.GET.get("type") in ("error", "all"):
31
+ messages.error(self.request, msg)
32
+ return self.request.GET.get("next", "/")
cms_qe_auth/models.py CHANGED
@@ -64,7 +64,7 @@ class User(AbstractUser):
64
64
  username=self.username,
65
65
  activation_url=activation_url,
66
66
  )
67
- email.save()
67
+ email.send()
68
68
 
69
69
  def _generate_activation_token(self):
70
70
  return TokenGenerator().make_token(self)
cms_qe_test/cms.py CHANGED
@@ -1,9 +1,13 @@
1
1
  from cms import api
2
- from cms.models import Placeholder
2
+ from cms.models import PageContent, Placeholder
3
3
  from cms.plugin_rendering import ContentRenderer
4
+ from cms.toolbar.toolbar import CMSToolbar
5
+ from django.contrib.auth import get_user_model
4
6
  from django.contrib.auth.models import AnonymousUser
5
7
  from django.contrib.messages.storage.fallback import FallbackStorage
6
8
  from django.test import RequestFactory
9
+ from djangocms_versioning.constants import DRAFT, PUBLISHED
10
+ from djangocms_versioning.models import Version
7
11
  from sekizai.context import SekizaiContext
8
12
 
9
13
 
@@ -11,6 +15,7 @@ def render_plugin(plugin, path='/', **data):
11
15
  placeholder = Placeholder.objects.create(slot='test')
12
16
  model_instance = api.add_plugin(placeholder, plugin, 'en', **data)
13
17
  request = generate_get_request(path)
18
+ request.toolbar = CMSToolbar(request)
14
19
  renderer = ContentRenderer(request=request)
15
20
  context = SekizaiContext()
16
21
  context.update({'request': request, })
@@ -35,16 +40,28 @@ def generate_post_request(path='', body=None):
35
40
 
36
41
 
37
42
  # pylint: disable=dangerous-default-value
38
- def create_page(title, language='en', page_params={}):
39
- page_params.setdefault('published', True)
43
+ def create_page(title, language='en', page_params={}, state="publish"):
40
44
  page_params.setdefault('overwrite_url', page_params.get('slug'))
41
- return api.create_page(title, 'cms_qe/home.html', language, **page_params)
45
+ page = api.create_page(title, 'cms_qe/home.html', language, **page_params)
46
+ content = PageContent.admin_manager.get(page=page)
47
+ user, _ = get_user_model().objects.get_or_create(username="tester")
48
+ version = content.versions.last()
49
+ if version is None:
50
+ version_state = PUBLISHED if state == "publish" else DRAFT
51
+ Version.objects.create(content=content, created_by=user, state=version_state)
52
+ else:
53
+ getattr(version, state)(user) # version.publish(user) / version.unpublish(user)
54
+ return page
42
55
 
43
56
 
44
57
  # pylint: disable=dangerous-default-value
45
58
  def create_text_page(title, language='en', page_params={}, plugin_params={}):
46
59
  plugin_params.setdefault('body', 'shello')
47
60
  page = create_page(title, language, page_params)
48
- placeholder = page.placeholders.get(slot='content')
61
+ placeholder = page.get_placeholders(language).filter(slot="content").get()
49
62
  api.add_plugin(placeholder, 'TextPlugin', language, **plugin_params)
50
63
  return page
64
+
65
+
66
+ def create_draft_page(title, language='en', page_params={}, state="unpublish"):
67
+ return create_page(title, language, page_params=page_params, state=state)
@@ -0,0 +1,20 @@
1
+ # Same as django.core.mail.backends.filebased.EmailBackend, but save logs with .eml extension.
2
+ import datetime
3
+ import os
4
+ from typing import Optional
5
+
6
+ from django.core.mail.backends.filebased import EmailBackend
7
+
8
+
9
+ class EmlEmailBackend(EmailBackend):
10
+ """Save logs with .eml extension."""
11
+
12
+ _fname: Optional[str]
13
+
14
+ def _get_filename(self):
15
+ """Return a unique file name."""
16
+ if self._fname is None:
17
+ timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
18
+ fname = "%s-%s.eml" % (timestamp, abs(id(self)))
19
+ self._fname = os.path.join(self.file_path, fname)
20
+ return self._fname
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-cms-qe
3
- Version: 3.6.1
3
+ Version: 3.7.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.
@@ -23,35 +23,41 @@ Classifier: Framework :: Django :: 4.0
23
23
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
24
24
  Classifier: Topic :: Software Development
25
25
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
26
- Requires-Python: >=3.9
26
+ Requires-Python: >=3.10
27
27
  Description-Content-Type: text/markdown
28
28
  License-File: LICENSE
29
- Requires-Dist: Django~=4.2
30
- Requires-Dist: easy-thumbnails[svg]
31
- Requires-Dist: djangocms-frontend~=1.1
32
- Requires-Dist: django-csp~=3.7
33
- Requires-Dist: djangocms-picture~=4.0
34
- Requires-Dist: django-axes~=6.0
35
- Requires-Dist: django-constance[database]~=2.9
36
- Requires-Dist: djangocms-file~=3.0
37
- Requires-Dist: django-import-export~=3.2
38
- Requires-Dist: django-mail-queue==3.2.5
39
- Requires-Dist: djangocms-icon~=2.0
40
- Requires-Dist: djangocms-googlemap~=2.0
29
+ Requires-Dist: django-cms~=4.1
30
+ Requires-Dist: django-filer~=3.3
31
+ Requires-Dist: djangocms-admin-style~=3.3
32
+ Requires-Dist: djangocms-alias~=2.0
33
+ Requires-Dist: djangocms-frontend~=2.1
34
+ Requires-Dist: djangocms-text~=0.8
35
+ Requires-Dist: djangocms-versioning~=2.3
36
+ Requires-Dist: easy-thumbnails[svg]~=2.10
37
+ Requires-Dist: argon2-cffi~=23.1
38
+ Requires-Dist: django-axes~=8.0
39
+ Requires-Dist: django-constance~=4.3
40
+ Requires-Dist: django-csp~=4.0
41
+ Requires-Dist: django-import-export~=4.3
41
42
  Requires-Dist: django-tablib~=3.2
43
+ Requires-Dist: djangocms-file~=3.0
44
+ Requires-Dist: djangocms-googlemap~=2.2
45
+ Requires-Dist: djangocms-icon~=2.1
46
+ Requires-Dist: djangocms-link~=5.0
47
+ Requires-Dist: djangocms-picture~=4.1
48
+ Requires-Dist: python-environ~=0.4
49
+ Requires-Dist: django-haystack~=3.3
50
+ Requires-Dist: djangocms-aldryn-forms[captcha]~=8.0
51
+ Requires-Dist: djangocms-aldryn-search~=3.0
42
52
  Requires-Dist: mailchimp3~=3.0
43
- Requires-Dist: argon2-cffi~=21.3
44
- Requires-Dist: djangocms-aldryn-forms[captcha]
45
- Requires-Dist: djangocms-aldryn-search
46
- Requires-Dist: django-haystack~=3.2
47
- Requires-Dist: pymemcache~=4.0
48
53
  Requires-Dist: whoosh~=2.7
49
- Requires-Dist: djangorestframework
50
- Requires-Dist: markdown
51
- Requires-Dist: django-filter
52
- Requires-Dist: django-rest-knox
53
- Requires-Dist: drf-spectacular
54
+ Requires-Dist: Markdown~=3.8
55
+ Requires-Dist: django-filter~=25.1
56
+ Requires-Dist: django-rest-knox~=5.0
57
+ Requires-Dist: djangorestframework~=3.16
58
+ Requires-Dist: drf-spectacular~=0.28
54
59
  Provides-Extra: dev
60
+ Requires-Dist: django-simple-captcha~=0.5; extra == "dev"
55
61
  Requires-Dist: django-debug-toolbar~=4.1; extra == "dev"
56
62
  Requires-Dist: django-extensions~=3.2; extra == "dev"
57
63
  Provides-Extra: test
@@ -61,15 +67,16 @@ Requires-Dist: mypy; extra == "test"
61
67
  Requires-Dist: pylint; extra == "test"
62
68
  Requires-Dist: pylint-django; extra == "test"
63
69
  Requires-Dist: pytest~=6.2; extra == "test"
64
- Requires-Dist: pytest-data==0.4; extra == "test"
65
- Requires-Dist: pytest-django==3.9.0; extra == "test"
66
- Requires-Dist: pytest-env==0.6.2; extra == "test"
67
- Requires-Dist: pytest-pythonpath==0.7.3; extra == "test"
68
- Requires-Dist: pytest-sugar==0.9.3; extra == "test"
69
- Requires-Dist: pytest-watch==4.2.0; extra == "test"
70
- Requires-Dist: PyVirtualDisplay==1.3.2; extra == "test"
71
- Requires-Dist: webdriverwrapper==2.8.0; extra == "test"
72
- Requires-Dist: django-simple-captcha==0.5.14; extra == "test"
70
+ Requires-Dist: pytest-cov~=6.2; extra == "test"
71
+ Requires-Dist: pytest-data~=0.4; extra == "test"
72
+ Requires-Dist: pytest-django~=3.9; extra == "test"
73
+ Requires-Dist: pytest-env~=0.6; extra == "test"
74
+ Requires-Dist: pytest-pythonpath~=0.7; extra == "test"
75
+ Requires-Dist: pytest-sugar~=0.9; extra == "test"
76
+ Requires-Dist: pytest-watch~=4.2; extra == "test"
77
+ Requires-Dist: PyVirtualDisplay~=1.3; extra == "test"
78
+ Requires-Dist: webdriverwrapper~=2.8; extra == "test"
79
+ Requires-Dist: django-simple-captcha~=0.5; extra == "test"
73
80
  Requires-Dist: testfixtures; extra == "test"
74
81
  Requires-Dist: tzdata; extra == "test"
75
82
  Provides-Extra: build
@@ -80,7 +87,7 @@ Requires-Dist: psycopg2; extra == "psql"
80
87
  Provides-Extra: mysql
81
88
  Requires-Dist: mysqlclient~=2.2; extra == "mysql"
82
89
  Provides-Extra: newsblog
83
- Requires-Dist: djangocms-aldryn-newsblog; extra == "newsblog"
90
+ Requires-Dist: djangocms-aldryn-newsblog~=4.0; extra == "newsblog"
84
91
  Dynamic: author
85
92
  Dynamic: author-email
86
93
  Dynamic: classifier
@@ -143,3 +150,36 @@ To find more useful commands, run just `make`.
143
150
  ## Upgrade
144
151
 
145
152
  To upgrade from version `2.2` to version >= `3.0.0`, you can use the [DjangoCMS upgrade plugins](https://gitlab.nic.cz/utils/djangocms-upgrade-plugins) tool.
153
+
154
+ ## Example in Docker
155
+
156
+ Download example:
157
+
158
+ curl https://gitlab.nic.cz/websites/django-cms-qe/-/archive/master/django-cms-qe.zip?path=example --output example.zip
159
+
160
+ Unzip and go to the example folder:
161
+
162
+ unzip example.zip
163
+ cd django-cms-qe*/example/
164
+
165
+ Build the site image:
166
+
167
+ docker compose build
168
+
169
+ Run website in docker:
170
+
171
+ docker compose up -d
172
+
173
+ See the website at http://localhost:8000/. Login into http://localhost:8000/admin/ as username ``admin`` with password ``admin``.
174
+ To run on a different port, specify the PORT parameter:
175
+
176
+ PORT=8008 docker compose up -d
177
+
178
+ Please wait a moment before browsing the website. It takes a while for all migrations to be completed and data to be loaded.
179
+ You can monitor the status of this process in the log:
180
+
181
+ docker compose logs -f web
182
+
183
+ Stop the website:
184
+
185
+ docker compose down
@@ -2,13 +2,15 @@ cms_qe/__init__.py,sha256=do1c4s8BgjukMZMMMhBHs_lG9j8ncnAjR3oTICWEq5w,684
2
2
  cms_qe/admin.py,sha256=eLqAF3UIDWWyA0xE0Ft5WP5_3HImSWk3EYL2cRfL_8A,171
3
3
  cms_qe/apps.py,sha256=AeRcBWwGs7rKLlzHhnV8M_2BEnkoO9959VwesxfHaio,338
4
4
  cms_qe/constants.py,sha256=YWUWCIabSwcamGZynvkJ9i8OWGtfHf-wFirm8GtqQpI,90
5
- cms_qe/export.py,sha256=PyKzMoFGzMdwCTKIYP1zri0Pn8zR_2e0-IkbHYfAv3g,8138
5
+ cms_qe/export.py,sha256=3MflO_EmaCrlqqa-cTMOiRirsp3r4mxdQNt-Zh5FtzY,8242
6
6
  cms_qe/fixtures.py,sha256=cq_wnZnqBwPBOHpp_0bHk424iCXKvwmN6ZaKwDvguXk,755
7
+ cms_qe/hooks.py,sha256=L9gLppl74Gj8euTJ1vWuSlc553ZAFAkj6mZaGw7iaW8,3330
8
+ cms_qe/ldap.py,sha256=2mpFdLoIdT_pAiGJ6ADnE74YXjaC55aNANv0L8DVwqU,188
7
9
  cms_qe/monitoring.py,sha256=5t_o7o0htmAAxVjkN2oz0O0v9XdzfePhSfPGcLNPmE8,769
8
10
  cms_qe/signals.py,sha256=MbuLSxPlJA147LEg-lDWDoUNTV1y0OKjwoI3HzgR97g,1253
9
11
  cms_qe/staticfiles.py,sha256=OHkfDfpIxN0B-eCRagZzHDHyBgaulcyYgKhp_3mPZuk,1363
10
- cms_qe/urls.py,sha256=qikE6wfP-JwJ-FiLCw6DRhhA46N0ajI8kABdv0GDjMk,3108
11
- cms_qe/utils.py,sha256=gxsWZmS34ZC0Tv1VW8A7VeGlrPyDshodF1ZWXj7xyWE,3057
12
+ cms_qe/urls.py,sha256=npDzzW9SgLVMZECTyOUL5Cpw7RxeuuNssTKOXuXuxtM,3247
13
+ cms_qe/utils.py,sha256=52ETz4NKv8xyBrMJ4yDUTfUBaO6Owp-5_IHfxDftQEE,2913
12
14
  cms_qe/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
15
  cms_qe/api/constants.py,sha256=pdSziATRm6yUaaPBYoD07JXcULMvKD0h5RdNTPpG0rM,64
14
16
  cms_qe/api/permissions.py,sha256=QKSll8wVOWKNbvX_FsC9CRKbPVcT2s5FM81_fnqeEQg,409
@@ -33,7 +35,7 @@ cms_qe/boilerplates/bootstrap3/static/cms_qe/js/bootstrap.min.js,sha256=U5ZEeKfG
33
35
  cms_qe/boilerplates/bootstrap3/static/cms_qe/js/jquery_3.2.1.min.js,sha256=hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4,86659
34
36
  cms_qe/boilerplates/bootstrap3/static/cms_qe/js/npm.js,sha256=x6qCoap9RSJKONkm0q2v9_5K71vNr6Kke9rAV_RCLC0,484
35
37
  cms_qe/boilerplates/bootstrap3/templates/cms_qe/error.html,sha256=gHFQbT2KPAwnhos6uMk4gvijDjubwiOA-j_q9fASBZM,375
36
- cms_qe/boilerplates/bootstrap3/templates/cms_qe/home.html,sha256=swbpKbL2KJhDdnuLRUelfQn7xPxolzYye4fLrLjFVOM,1465
38
+ cms_qe/boilerplates/bootstrap3/templates/cms_qe/home.html,sha256=itXnWbX_-Z9jIgdca25obWjwVCTT0Jy6FxWTD5aUvyU,1474
37
39
  cms_qe/boilerplates/bootstrap3/templates/cmsplugin_filer_folder/plugins/folder/gallery.html,sha256=ZAbkXcsJ6mwAJt0vQNd55ZWB-BhJ_gtvtbnKx8PysV8,3107
38
40
  cms_qe/haystack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
41
  cms_qe/haystack/forms.py,sha256=4FyieMfxfE6h2dcUaWAZJ18rEOAcnMVb2zLZ60iCbVA,912
@@ -52,26 +54,31 @@ cms_qe/middleware/page_status_code.py,sha256=J-Ezet9ban9rnjWaSuRss9gOz5h7uCCyL46
52
54
  cms_qe/migrations/0001_api_permissions.py,sha256=KPJYBdX3dWYEbUswSiIhkhDV6FcjXG-MPfC2aYwt7Wc,672
53
55
  cms_qe/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
56
  cms_qe/settings/__init__.py,sha256=GJwHXMHwMuGYE-3ZzePJ-26I2WwE8bAIMUDoiTFr0L8,982
55
- cms_qe/settings/dev.py,sha256=51CBwiclE8LLoNB2uioIK_L3JhM1yzukQ0gZimkcFqw,1487
57
+ cms_qe/settings/dev.py,sha256=A0LJ1cnoGqq7p7KInfqI-DNL8NqKaHbe5dByRIptknc,2143
56
58
  cms_qe/settings/unittest.py,sha256=folLIMJb1Arh60_Sn0eNQrvIlx0OsAs6v1tDfyRZVuQ,514
57
59
  cms_qe/settings/base/__init__.py,sha256=5yHfne9gPD_xuTaG3voZP23yzuCwROmif2mmKs-hG_A,446
58
- cms_qe/settings/base/app.py,sha256=RGxe4deN_qLGc_aRNKGjmBgsWYYpd-wdm6V47a0VR5I,4213
60
+ cms_qe/settings/base/app.py,sha256=rWkemCai4k6PBxcx9ga6ggF32lUUVyh30Vp_7QU22Rg,4469
59
61
  cms_qe/settings/base/auth.py,sha256=OTr1LJ4RSMZm8STs4Q3pwPXmQoURax8OKLJ8eAj7PW4,395
60
- cms_qe/settings/base/cache.py,sha256=9p6C5lOz1pG-6k15PyvxlShUjBYIbU0ewpA8AX_YFus,297
61
- cms_qe/settings/base/cms.py,sha256=8icCNxcEp_KRDyP8-LXB21UurJL4wNysY39whAyt3I4,1855
62
+ cms_qe/settings/base/cache.py,sha256=yBCvIIW25bSBD9nzVbPQvsPfw-pkOmcnLO7OPwzXPWo,337
63
+ cms_qe/settings/base/cms.py,sha256=hzl10CwswfQJkIfn4WoFJUmoumwC2smhSl8IR4VxkLM,1990
62
64
  cms_qe/settings/base/constants.py,sha256=Rdq6ESg_J2B1Xm-ImEYM8pmsid-LqkSR7LXQdb3wlZU,7899
63
- cms_qe/settings/base/database.py,sha256=qT7ePr2lg4CVJcHnG2nXFPPvzIjBOLB4auvRiWxaDPY,408
64
- cms_qe/settings/base/email.py,sha256=agT6ZBAyT29TUEQIRYObAdgOSETLlZEi7KizNhr9dVc,357
65
+ cms_qe/settings/base/database.py,sha256=_i3OJMRcrcI17VJeqY66JNOpAOXkhq570lRi0RVpzGw,354
66
+ cms_qe/settings/base/email.py,sha256=WkTj4V4lMZ3BG5AvHGPqulZ8T_cQMrvXiLj_NhLmpZs,576
67
+ cms_qe/settings/base/env.py,sha256=Oe10oSrA7QlD9TBDnEup8mtNTd1pXyaO5OThtUW8UVQ,36
65
68
  cms_qe/settings/base/i18n.py,sha256=n_7esPYSbf9Wj-T23uWds7tCvQ0ol9MfyMtKzy009sM,355
66
- cms_qe/settings/base/logging.py,sha256=GFVyQ_DcdKl9iu9C8fCshrAdrIKY0T-OD6lst5bb8qQ,1319
69
+ cms_qe/settings/base/logging.py,sha256=NnhPZnPX6zqobi4Ekx5euOnCD3Rw_Sb7jOl0tl80Vv0,1368
67
70
  cms_qe/settings/base/path.py,sha256=s0eOmSDOWfjjI5onp28y2S2UKwCYFRDGeoUsZla6-og,410
68
- cms_qe/settings/base/search.py,sha256=xbO9OFFGLi8PZut_Ngb-27BUI6HPG0ZM4lrO1HXHW-c,618
69
- cms_qe/settings/base/security.py,sha256=i6mHb8gv6XPthShL1kFLTwa_vrfoaivzqC9MXLE_YBw,4496
71
+ cms_qe/settings/base/search.py,sha256=Vp6ROmo2x3ZaMrnHhNBjR3dX_r7BvIkcG9CPXmlZhiE,725
72
+ cms_qe/settings/base/security.py,sha256=fLaVfmqdF6-IZ0oppnV3Xs6KGARx7u9fYvWMwRJA3LM,4557
70
73
  cms_qe/settings/base/template.py,sha256=bITmA7XkoqbDpefWWOBsEiPtCREzFfHkUuFvGxJVLK4,1082
71
- cms_qe/templates/base.html,sha256=BMd8MbubDB8m1ZzBWfAfzTs4EBQn0oBQUw1GVRA4z6A,972
74
+ cms_qe/static/cms_qe/css/fix-djangocms-admin-style.css,sha256=sf_9yH_rKSts9L0OQ1vN5t32PTyjBuwfHZnDCN0GTX4,805
75
+ cms_qe/templates/base.html,sha256=Hb6MWA_IpBWlCyJ2NiPPQv0nSKOJgXSXS7y62-cHGUA,1094
76
+ cms_qe/templates/pg_is_in_recovery_login.html,sha256=ng0snZ-rriwFRKVvG0ZCY5QsQbod7jex2pVJ3hVonc0,116
72
77
  cms_qe/templates/admin/index.html,sha256=6CjuqOPQnEYXa7zwyoLyDHt-zzfBwLAf45B0F80ryZ0,1812
78
+ cms_qe/templates/admin/inc/extrastyle.html,sha256=vgsAzeKxV8Meu5j60vo9hMzlsKnRagkyI7icucfQpEg,105
79
+ cms_qe/templates/cms_qe/alias_content_preview.html,sha256=2klC7206SDOCf-sWYgeEcBiG2fIA8H2OML2fwEcewXc,956
73
80
  cms_qe/templates/cms_qe/error.html,sha256=1wNCO-ToNoM-HBnfq0Id_W8m_epmOEYcoozRhhHth5U,322
74
- cms_qe/templates/cms_qe/home.html,sha256=XSyChEdMnxcw-OWrm_d_3h2lmXfPyfpCpyYKH6QNG2E,186
81
+ cms_qe/templates/cms_qe/home.html,sha256=zT4fnq9V6CsVkGXs_iFMGE4RvcMAeWvs4r0XTiHcLwA,114
75
82
  cms_qe/templates/cms_qe/internal_error.html,sha256=n4JJ80KNHyhiSxGLQadCn9KmctnFABcwLU4KuZly8A4,251
76
83
  cms_qe/templates/cms_qe/search_result.html,sha256=5XDX4nhRDkSJq_EdW95p8CuEN1Yy14OG9UC3WDz7TXI,310
77
84
  cms_qe/templates/cms_qe/include/body_bottom.html,sha256=t-B_SA2AFPmTC5hnDANyR9O9tmH3-hzGTLoRPi9lgIY,29
@@ -89,8 +96,10 @@ cms_qe/views/__init__.py,sha256=3b5FCZ5MaqgiWglC7c5mfvP3WYLWTtNp3YpVb9BgYi8,106
89
96
  cms_qe/views/errors.py,sha256=zUbCoyXy_MPsQv3UV1mgq-q2bwqPw9G4KgKU2-oue4w,3169
90
97
  cms_qe/views/maintenance.py,sha256=Q410LCeeihRWhIJ-zzRpFSjfvA6xhgr6NJlNAoTNO2U,1658
91
98
  cms_qe/views/monitoring.py,sha256=1r2s_jm6B6P0gEmiqjH9m3loUW3BmJivvpg6qcUOxVM,1909
99
+ cms_qe/views/redirect_to_page.py,sha256=MEgwDfjRYbMhCduimvIz_Do6NnKFRzjuSnuTBZmLnYI,1176
92
100
  cms_qe/views/search_result.py,sha256=H1eMOmtONnWqBDsBvz3MartyHhh42vyi0jPoxWb0X8c,296
93
101
  cms_qe/views/security.py,sha256=SNdJe4NSm1vuOGchVF3VqlmFDopt9xYoO-b4Y0UxkFU,1108
102
+ cms_qe/views/test_messages.py,sha256=ULHdXwjSmQxCxFF1hzBzpDa5MltGwyIF8DjQPb7N-7M,1281
94
103
  cms_qe/whoosh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
95
104
  cms_qe/whoosh/backend.py,sha256=YOVJGz3htWdcSUbYtX2a9VqywAU3EJP_EvxXHCY0QVA,5418
96
105
  cms_qe_analytical/LICENSE.txt,sha256=ptQIrnsiWFFf2LZ60DTAO6XA7CQYFuwhX1m4kzhv5_8,1072
@@ -107,7 +116,7 @@ cms_qe_auth/cms_menus.py,sha256=UxzzuMfOJCC_EiCkV2__6R5JKV9q1WGbTEgO7yLy8rE,1675
107
116
  cms_qe_auth/cms_plugins.py,sha256=USiNHaWdIJqPFUMLOjhuVam4nwOckujg1uguXNIs798,1575
108
117
  cms_qe_auth/fixtures.py,sha256=hQO75OnBmU4NiT_GF-oW4lU50FrLDgGF-gfouYeLfRI,784
109
118
  cms_qe_auth/forms.py,sha256=x7sdFoOrKBLTJXqESedpIh6Kc1k5zZhL4vwnmhj1gH8,1137
110
- cms_qe_auth/models.py,sha256=Aro43D9y1zrS-3eKHVZEuSkchusDZAcj15B2vYcdt0Q,2713
119
+ cms_qe_auth/models.py,sha256=ytcbh-rT36IQmOXxk4yef1xRq2RVvTzneORAC7pvWds,2713
111
120
  cms_qe_auth/token.py,sha256=DG4Bu8AVV-d1ayL4Oc9DXNnERt1sstrll80RBGrplx0,224
112
121
  cms_qe_auth/urls.py,sha256=RCgr9t1YonE0yR_8gXiXZIGESvQfrwVwlKhBOWmgxkw,2040
113
122
  cms_qe_auth/utils.py,sha256=JYZUzQhUE_kycVBRBNi-fmGy5WtjIwc3_qsGxOVrNR0,2972
@@ -3940,7 +3949,8 @@ cms_qe_table/templates/cms_qe/table/table_widget.html,sha256=tsjlS5Mc_6iALFk0QIe
3940
3949
  cms_qe_table/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3941
3950
  cms_qe_table/templatetags/cms_qe_table_filters.py,sha256=eFBB2FoCcpQRAknMIJLac0ts18w8XIODbouuJlP0ty4,782
3942
3951
  cms_qe_test/__init__.py,sha256=-Vc3K2g4JFSE2qw5AvuTGi4rwQGMOXAEycrjgFWk1BQ,121
3943
- cms_qe_test/cms.py,sha256=pspLQxbnwG71PuZKtwfWDu6uVk7RYO86Od1cDXBF108,1796
3952
+ cms_qe_test/cms.py,sha256=cJs5ZHbyPamTBuIMOhT1R2DR6teKYwhbZeuY-M-7i8I,2660
3953
+ cms_qe_test/mail_filebased_backend.py,sha256=9q3YuR-WcfhNOJc6hWclJmwRs949gzGFm6Eb3W7CjeI,645
3944
3954
  cms_qe_video/__init__.py,sha256=2iOdITrw_UvFcQpFA0rhUWBCRe2qvTuDvltp5Q233cc,1070
3945
3955
  cms_qe_video/cms_plugins.py,sha256=kqJX5eb-pYutxO-_0UnO784QpTwOb5Eudo6bW2NXaZg,3020
3946
3956
  cms_qe_video/fixtures.py,sha256=0oGo7Ufh3XwaLaMjcGN7CFao_BetcQ6xtikNeAHdqvs,701
@@ -3959,14 +3969,7 @@ cms_qe_video/templates/cms_qe/video/video_source_file.html,sha256=QJF5fs88s9Fznp
3959
3969
  cms_qe_video/templates/cms_qe/video/video_widget.html,sha256=Yumciq6bGlAYI1lYx5j9V6IF8QYrncNYygPTkXEz6Wk,925
3960
3970
  cms_qe_video/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3961
3971
  cms_qe_video/templatetags/cms_qe_video.py,sha256=NR_mGv91J0rEreZrQjCzaaXSrZsKvrSas12wMJ-Dg24,1168
3962
- django_cms_qe-3.6.1.dist-info/licenses/LICENSE,sha256=5wLaeUil0gfU9p8C4zn2Yu_PvZBNieUoYl0z9FcFWdA,1521
3963
- example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3964
- example/urls.py,sha256=H-IJsRGFVZGw6FD9gvK-0B0dLeSOsduziHvDvcHCQZ0,399
3965
- example/wsgi.py,sha256=lCKhvtFZlorSIA8qYEqc3pZ1Oflrz_Tc696MWJ62ue4,396
3966
- example/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3967
- example/settings/aldryn_newsblog.py,sha256=iQ5idy9rZR_N5IJzogc7R3PK3RIN7OMw7ksSIIIxPDk,276
3968
- example/settings/dev.py,sha256=TGGlSzh9ZDh54Z2X5i6mz7Ja3lT_qBqw9MHX3EnMs9o,676
3969
- example/settings/selenium.py,sha256=pRi8pIQIiYA0Y_yZsg7M63npHMRcUsFF7gZTr602yPY,215
3972
+ django_cms_qe-3.7.0.dist-info/licenses/LICENSE,sha256=5wLaeUil0gfU9p8C4zn2Yu_PvZBNieUoYl0z9FcFWdA,1521
3970
3973
  test_selenium/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3971
3974
  test_selenium/browser.py,sha256=OcfqxDa9OtL7M5CSwfIxtzToMUEhqGLvditemPeEUNo,1437
3972
3975
  test_selenium/conftest.py,sha256=mAptaAyj7a1hbUPDRWBBs1qL0TJ8Fma7Mch6PZwgtNo,220
@@ -3980,7 +3983,7 @@ test_selenium/pages/cms/__init__.py,sha256=_qe4YZYaQbrXp7Szmmeo4TUSkXlE5Rozu8E3t
3980
3983
  test_selenium/pages/cms/login.py,sha256=UPzJQcYff8NUAT4nvmfQoJQxzOJyPrJ_cKtH35NVfNg,521
3981
3984
  test_selenium/pages/cms/page.py,sha256=YQnpZkopfVnhoyQKpRDGqjNeV6xUl-pEHjEcZ9HRiPk,489
3982
3985
  test_selenium/pages/cms/wizard.py,sha256=yatbXH-rf1ap4O1hY0I13WikM3zkm_NrAiSK6bqENIU,545
3983
- django_cms_qe-3.6.1.dist-info/METADATA,sha256=1mPFRXB8pDwQu4galhjV6rJGrWldsksUZSK1KcZ1bkQ,5080
3984
- django_cms_qe-3.6.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
3985
- django_cms_qe-3.6.1.dist-info/top_level.txt,sha256=T4dauFwJy7FmxCy7WoQI3pPwiDessNB2LkfOAP76ssE,172
3986
- django_cms_qe-3.6.1.dist-info/RECORD,,
3986
+ django_cms_qe-3.7.0.dist-info/METADATA,sha256=JPaPrE76CgXO2VeTxyt0VNbQQWn0arzmOgJwuaRdjv4,6234
3987
+ django_cms_qe-3.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
3988
+ django_cms_qe-3.7.0.dist-info/top_level.txt,sha256=fQYSfQoprw1NXhYY-I8AzsPk-Rgst1REh3iOUvwAbkM,164
3989
+ django_cms_qe-3.7.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -9,5 +9,4 @@ cms_qe_plugins
9
9
  cms_qe_table
10
10
  cms_qe_test
11
11
  cms_qe_video
12
- example
13
12
  test_selenium
example/__init__.py DELETED
File without changes
File without changes
@@ -1,14 +0,0 @@
1
- from .dev import *
2
-
3
- INSTALLED_APPS += [ # noqa: F405
4
- # Aldryn News&Blog
5
- 'aldryn_apphooks_config',
6
- 'aldryn_common',
7
- 'aldryn_categories',
8
- 'aldryn_newsblog',
9
- 'aldryn_people',
10
- 'aldryn_translation_tools',
11
- 'parler',
12
- 'sortedm2m',
13
- 'taggit',
14
- ]
example/settings/dev.py DELETED
@@ -1,29 +0,0 @@
1
- import os
2
-
3
- from cms_qe.settings.dev import * # noqa: F403
4
-
5
- INSTALLED_APPS += [ # noqa: F405
6
- 'example',
7
- ]
8
-
9
- AUTHENTICATION_BACKENDS = [
10
- 'axes.backends.AxesBackend',
11
- 'django.contrib.auth.backends.ModelBackend',
12
- ]
13
-
14
- ROOT_URLCONF = 'example.urls'
15
- WSGI_APPLICATION = 'example.wsgi.application'
16
-
17
- BASE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
18
- STATIC_ROOT = os.path.join(BASE_DIR, 'static')
19
- MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
20
-
21
- DATABASES = {
22
- 'default': {
23
- 'ENGINE': 'django.db.backends.sqlite3',
24
- 'NAME': os.path.join(BASE_DIR, '..', 'db.sqlite3'),
25
- 'TEST': {
26
- 'NAME': ':memory:',
27
- }
28
- }
29
- }
@@ -1,10 +0,0 @@
1
- import os
2
-
3
- from .dev import * # noqa: F403
4
-
5
- DATABASES = {
6
- 'default': {
7
- 'ENGINE': 'django.db.backends.sqlite3',
8
- 'NAME': os.path.join(BASE_DIR, '..', 'db_selenium.sqlite3'), # noqa: F405
9
- }
10
- }
example/urls.py DELETED
@@ -1,16 +0,0 @@
1
- from django.conf import settings
2
- from django.urls import path
3
- from django.views.static import serve
4
-
5
- from cms_qe.urls import handler403, handler404, handler500, handler503, urlpatterns
6
-
7
-
8
- def serve_favicon(request):
9
- """Serve favicon.ico."""
10
- return serve(request, 'favicon.ico', settings.STATIC_ROOT)
11
-
12
-
13
- if settings.DEBUG:
14
- urlpatterns += [
15
- path('favicon.ico', serve_favicon),
16
- ]
example/wsgi.py DELETED
@@ -1,16 +0,0 @@
1
- """
2
- WSGI config for example project.
3
-
4
- It exposes the WSGI callable as a module-level variable named ``application``.
5
-
6
- For more information on this file, see
7
- https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
8
- """
9
-
10
- import os
11
-
12
- from django.core.wsgi import get_wsgi_application
13
-
14
- os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings.dev')
15
-
16
- application = get_wsgi_application()