django-cms-qe 3.7.2__py3-none-any.whl → 4.1.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 (38) hide show
  1. cms_qe/boilerplates/bootstrap3/templates/cms_qe/home.html +3 -3
  2. cms_qe/cms_menus.py +134 -0
  3. cms_qe/export.py +8 -7
  4. cms_qe/hooks.py +96 -0
  5. cms_qe/ldap.py +4 -0
  6. cms_qe/settings/base/app.py +12 -4
  7. cms_qe/settings/base/cache.py +6 -4
  8. cms_qe/settings/base/cms.py +4 -1
  9. cms_qe/settings/base/database.py +7 -10
  10. cms_qe/settings/base/email.py +9 -11
  11. cms_qe/settings/base/env.py +3 -0
  12. cms_qe/settings/base/logging.py +2 -1
  13. cms_qe/settings/base/search.py +5 -1
  14. cms_qe/settings/base/security.py +2 -1
  15. cms_qe/settings/dev.py +25 -19
  16. cms_qe/static/cms_qe/css/fix-djangocms-admin-style.css +33 -0
  17. cms_qe/templates/admin/inc/extrastyle.html +2 -0
  18. cms_qe/templates/base.html +7 -4
  19. cms_qe/templates/cms_qe/alias_content_preview.html +28 -0
  20. cms_qe/templates/cms_qe/home.html +0 -2
  21. cms_qe/templates/pg_is_in_recovery_login.html +4 -0
  22. cms_qe/utils.py +11 -17
  23. cms_qe/views/test_messages.py +32 -0
  24. cms_qe_auth/models.py +1 -1
  25. cms_qe_test/cms.py +22 -5
  26. cms_qe_test/mail_filebased_backend.py +20 -0
  27. {django_cms_qe-3.7.2.dist-info → django_cms_qe-4.1.0.dist-info}/METADATA +61 -22
  28. {django_cms_qe-3.7.2.dist-info → django_cms_qe-4.1.0.dist-info}/RECORD +31 -28
  29. {django_cms_qe-3.7.2.dist-info → django_cms_qe-4.1.0.dist-info}/top_level.txt +0 -1
  30. example/__init__.py +0 -0
  31. example/settings/__init__.py +0 -0
  32. example/settings/aldryn_newsblog.py +0 -14
  33. example/settings/dev.py +0 -29
  34. example/settings/selenium.py +0 -10
  35. example/urls.py +0 -16
  36. example/wsgi.py +0 -16
  37. {django_cms_qe-3.7.2.dist-info → django_cms_qe-4.1.0.dist-info}/WHEEL +0 -0
  38. {django_cms_qe-3.7.2.dist-info → django_cms_qe-4.1.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/cms_menus.py ADDED
@@ -0,0 +1,134 @@
1
+ from cms.cms_menus import CMSNavigationNode, get_menu_node_for_page, get_visible_nodes
2
+ from cms.models import EmptyPageContent, Page, PageContent, PageUrl
3
+ from cms.toolbar.utils import get_toolbar_from_request
4
+ from cms.utils.i18n import get_fallback_languages, get_public_languages, hide_untranslated, is_valid_site_language
5
+ from cms.utils.page import get_page_queryset
6
+ from django.db.models.query import Prefetch, prefetch_related_objects
7
+ from django.http import HttpRequest
8
+ from menus.base import Menu
9
+
10
+
11
+ # This class i a copy of https://github.com/django-cms/django-cms/blob/4.1.7/cms/cms_menus.py#L199
12
+ # with extra function custom_menu_node.
13
+ class CMSMenu(Menu):
14
+ """Subclass of :class:`menus.base.Menu`. Its :meth:`~menus.base.Menu.get_nodes()` creates a list of NavigationNodes
15
+ based on a site's :class:`cms.models.pagemodel.Page` objects.
16
+ """
17
+
18
+ def get_nodes(self, request):
19
+ site = self.renderer.site
20
+ lang = self.renderer.request_language
21
+ toolbar = get_toolbar_from_request(request)
22
+
23
+ pages = get_page_queryset(site)
24
+
25
+ if is_valid_site_language(lang, site_id=site.pk):
26
+ _valid_language = True
27
+ _hide_untranslated = hide_untranslated(lang, site.pk)
28
+ else:
29
+ _valid_language = False
30
+ _hide_untranslated = False
31
+
32
+ if _valid_language:
33
+ # The request language has been explicitly configured
34
+ # for the current site.
35
+ if _hide_untranslated:
36
+ fallbacks = []
37
+ else:
38
+ fallbacks = get_fallback_languages(lang, site_id=site.pk)
39
+ languages = [lang] + [_lang for _lang in fallbacks if _lang != lang]
40
+ else:
41
+ # The request language is not configured for the current site.
42
+ # Fallback to all configured public languages for the current site.
43
+ languages = get_public_languages(site.pk)
44
+ fallbacks = languages
45
+
46
+ pages = (
47
+ pages.filter(pagecontent_set__language__in=languages)
48
+ .select_related("node")
49
+ .order_by("node__path")
50
+ .distinct()
51
+ )
52
+ pages = get_visible_nodes(request, pages, site)
53
+
54
+ if not pages:
55
+ return []
56
+
57
+ try:
58
+ homepage = [page for page in pages if page.is_home][0]
59
+ except IndexError:
60
+ homepage = None
61
+
62
+ urls_lookup = Prefetch(
63
+ "urls",
64
+ to_attr="filtered_urls",
65
+ queryset=PageUrl.objects.filter(language__in=languages),
66
+ )
67
+ if toolbar.edit_mode_active or toolbar.preview_mode_active:
68
+ # Get all translations visible in the admin for the current page
69
+ translations_qs = PageContent.admin_manager.current_content(language__in=languages)
70
+ else:
71
+ # Only get public translations
72
+ translations_qs = PageContent.objects.filter(language__in=languages)
73
+ translations_lookup = Prefetch(
74
+ "pagecontent_set",
75
+ to_attr="filtered_translations",
76
+ queryset=translations_qs,
77
+ )
78
+ prefetch_related_objects(pages, urls_lookup, translations_lookup)
79
+ # Build the blank title instances only once
80
+ blank_page_content_cache = {language: EmptyPageContent(language=language) for language in languages}
81
+
82
+ # Maps a node id to its page id
83
+ node_id_to_page: dict[int, int] = {}
84
+
85
+ def _page_to_node(page):
86
+ # EmptyPageContent is used to prevent the cms from trying
87
+ # to find a translation in the database
88
+ page.page_content_cache = blank_page_content_cache.copy()
89
+
90
+ for page_url in page.filtered_urls:
91
+ page.urls_cache[page_url.language] = page_url
92
+
93
+ for trans in page.filtered_translations:
94
+ page.page_content_cache[trans.language] = trans
95
+
96
+ menu_node = get_menu_node_for_page(
97
+ self.renderer,
98
+ page,
99
+ language=lang,
100
+ fallbacks=fallbacks,
101
+ endpoint=toolbar.preview_mode_active or toolbar.edit_mode_active,
102
+ )
103
+ return menu_node
104
+
105
+ menu_nodes = []
106
+
107
+ for page in pages:
108
+ node = page.node
109
+ parent_id = node_id_to_page.get(node.parent_id)
110
+
111
+ if node.parent_id and not parent_id:
112
+ # If the parent page is not available (unpublished, etc..)
113
+ # don't bother creating menu nodes for its descendants.
114
+ continue
115
+
116
+ menu_node = _page_to_node(page)
117
+ if menu_node:
118
+ # Only add pages with at least one page content
119
+ cut_homepage = homepage and not homepage.get_in_navigation(lang)
120
+
121
+ if cut_homepage and parent_id == homepage.pk:
122
+ # When the homepage is hidden from navigation,
123
+ # we need to cut all its direct children from it.
124
+ menu_node.parent_id = None
125
+ else:
126
+ menu_node.parent_id = parent_id
127
+ menu_node = self.custom_menu_node(request, page, menu_node)
128
+ node_id_to_page[node.pk] = page.pk
129
+ menu_nodes.append(menu_node)
130
+ return menu_nodes
131
+
132
+ def custom_menu_node(self, request: HttpRequest, page: Page, menu_node: CMSNavigationNode) -> CMSNavigationNode:
133
+ menu_node.attr["page_description"] = page.get_meta_description()
134
+ return menu_node
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,96 @@
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
+ from .cms_menus import CMSMenu
16
+
17
+
18
+ def pg_is_in_recovery():
19
+ """Return True when database is slave or False when database is master."""
20
+ with connection.cursor() as cursor:
21
+ if cursor.db.vendor != 'postgresql':
22
+ return False
23
+ cursor.execute("SELECT pg_is_in_recovery()")
24
+ return cursor.fetchone()[0]
25
+
26
+
27
+ class PgIsInRecoveryLoginView(LoginView):
28
+ template_name = 'admin/login.html'
29
+ url_page_name = "login"
30
+
31
+ def get(self, request, *args, **kwargs):
32
+ if pg_is_in_recovery():
33
+ messages.add_message(request, messages.WARNING,
34
+ _('The database is in recovery mode. Unable to login. Try it later.'))
35
+ return super().get(request, *args, **kwargs)
36
+
37
+ def post(self, request, *args, **kwargs):
38
+ if pg_is_in_recovery():
39
+ messages.add_message(request, messages.ERROR, _('Login failed. The database is in recovery mode.'))
40
+ return HttpResponseRedirect(reverse(self.url_page_name))
41
+ return super().post(request, *args, **kwargs)
42
+
43
+ def get_template_names(self):
44
+ if pg_is_in_recovery():
45
+ return ['pg_is_in_recovery_login.html']
46
+ return self.template_name
47
+
48
+
49
+ class PgIsInRecoveryMenuRenderer(MenuRenderer):
50
+
51
+ def __init__(self, pool, request):
52
+ pool.menus['CMSMenu'] = CMSMenu
53
+ super().__init__(pool, request)
54
+
55
+ def get_nodes(self, namespace=None, root_id=None, breadcrumb=False):
56
+ try:
57
+ return super().get_nodes(namespace, root_id, breadcrumb)
58
+ except InternalError:
59
+ if pg_is_in_recovery():
60
+ return []
61
+ raise
62
+
63
+
64
+ def render_alias_content(request: HttpRequest, alias_content: AliasContent) -> TemplateResponse:
65
+ """Render alias content with additionad css class alias-$name.
66
+
67
+ This is the same function as on the url
68
+ https://github.com/django-cms/djangocms-alias/blob/master/djangocms_alias/rendering.py#L4,
69
+ it just uses a different template. In the template, a css class is added by the alias name.
70
+ This is necessary so that the appropriate styles can be linked to it.
71
+ """
72
+ template = "cms_qe/alias_content_preview.html"
73
+ context = {
74
+ "alias_content": alias_content,
75
+ "site_styles": settings.STYLES_FOR_ALIAS_ADMIN_PREVIEW,
76
+ }
77
+ return TemplateResponse(request, template, context)
78
+
79
+
80
+ def get_renderer(self, request: HttpRequest) -> PgIsInRecoveryMenuRenderer:
81
+ self.discover_menus()
82
+ return PgIsInRecoveryMenuRenderer(pool=self, request=request)
83
+
84
+
85
+ def patch_menu_pool_cachekey():
86
+ """Skip exception when MenuRenderer attempts to write to read only database."""
87
+ menu_pool.get_renderer = MethodType(get_renderer, menu_pool)
88
+
89
+
90
+ def patch_alias():
91
+ """Patch alias template preview."""
92
+ try:
93
+ extension = apps.get_app_config('cms').cms_extension
94
+ extension.toolbar_enabled_models[AliasContent] = render_alias_content
95
+ except KeyError:
96
+ 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}" # pylint: disable=C0103
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/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,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.7.2
3
+ Version: 4.1.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,36 +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: django-cms~=3.11
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
31
36
  Requires-Dist: easy-thumbnails[svg]~=2.10
32
- Requires-Dist: djangocms-frontend~=1.1
33
- Requires-Dist: django-csp~=3.7
34
- Requires-Dist: djangocms-picture~=4.0
35
- Requires-Dist: django-axes~=6.0
36
- Requires-Dist: django-constance[database]~=2.9
37
- Requires-Dist: djangocms-file~=3.0
38
- Requires-Dist: django-import-export~=3.2
39
- Requires-Dist: django-mail-queue==3.2.5
40
- Requires-Dist: djangocms-icon~=2.0
41
- Requires-Dist: djangocms-googlemap~=2.0
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
42
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
43
52
  Requires-Dist: mailchimp3~=3.0
44
- Requires-Dist: argon2-cffi~=21.3
45
- Requires-Dist: djangocms-aldryn-forms[captcha]~=7.7
46
- Requires-Dist: djangocms-aldryn-search~=2.0
47
- Requires-Dist: django-haystack~=3.2
48
- Requires-Dist: pymemcache~=4.0
49
53
  Requires-Dist: whoosh~=2.7
50
- Requires-Dist: djangorestframework~=3.16
51
- Requires-Dist: markdown~=3.8
54
+ Requires-Dist: Markdown~=3.8
52
55
  Requires-Dist: django-filter~=25.1
53
56
  Requires-Dist: django-rest-knox~=5.0
57
+ Requires-Dist: djangorestframework~=3.16
54
58
  Requires-Dist: drf-spectacular~=0.28
55
59
  Provides-Extra: dev
60
+ Requires-Dist: django-simple-captcha~=0.5; extra == "dev"
56
61
  Requires-Dist: django-debug-toolbar~=4.1; extra == "dev"
57
62
  Requires-Dist: django-extensions~=3.2; extra == "dev"
58
63
  Provides-Extra: test
@@ -62,6 +67,7 @@ Requires-Dist: mypy; extra == "test"
62
67
  Requires-Dist: pylint; extra == "test"
63
68
  Requires-Dist: pylint-django; extra == "test"
64
69
  Requires-Dist: pytest~=6.2; extra == "test"
70
+ Requires-Dist: pytest-cov~=6.2; extra == "test"
65
71
  Requires-Dist: pytest-data~=0.4; extra == "test"
66
72
  Requires-Dist: pytest-django~=3.9; extra == "test"
67
73
  Requires-Dist: pytest-env~=0.6; extra == "test"
@@ -81,7 +87,7 @@ Requires-Dist: psycopg2; extra == "psql"
81
87
  Provides-Extra: mysql
82
88
  Requires-Dist: mysqlclient~=2.2; extra == "mysql"
83
89
  Provides-Extra: newsblog
84
- Requires-Dist: djangocms-aldryn-newsblog~=3.3; extra == "newsblog"
90
+ Requires-Dist: djangocms-aldryn-newsblog~=4.0; extra == "newsblog"
85
91
  Dynamic: author
86
92
  Dynamic: author-email
87
93
  Dynamic: classifier
@@ -144,3 +150,36 @@ To find more useful commands, run just `make`.
144
150
  ## Upgrade
145
151
 
146
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
@@ -1,14 +1,17 @@
1
1
  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
+ cms_qe/cms_menus.py,sha256=KdgZgvzRZwkpqviv3exizo_Vf5qtJziC7Q2yFRslJSQ,5470
4
5
  cms_qe/constants.py,sha256=YWUWCIabSwcamGZynvkJ9i8OWGtfHf-wFirm8GtqQpI,90
5
- cms_qe/export.py,sha256=PyKzMoFGzMdwCTKIYP1zri0Pn8zR_2e0-IkbHYfAv3g,8138
6
+ cms_qe/export.py,sha256=3MflO_EmaCrlqqa-cTMOiRirsp3r4mxdQNt-Zh5FtzY,8242
6
7
  cms_qe/fixtures.py,sha256=cq_wnZnqBwPBOHpp_0bHk424iCXKvwmN6ZaKwDvguXk,755
8
+ cms_qe/hooks.py,sha256=Bu8eV2E8wTeOAPET8C_YocwMV6h5XLItPO3ItCkV-yI,3482
9
+ cms_qe/ldap.py,sha256=2mpFdLoIdT_pAiGJ6ADnE74YXjaC55aNANv0L8DVwqU,188
7
10
  cms_qe/monitoring.py,sha256=5t_o7o0htmAAxVjkN2oz0O0v9XdzfePhSfPGcLNPmE8,769
8
11
  cms_qe/signals.py,sha256=MbuLSxPlJA147LEg-lDWDoUNTV1y0OKjwoI3HzgR97g,1253
9
12
  cms_qe/staticfiles.py,sha256=OHkfDfpIxN0B-eCRagZzHDHyBgaulcyYgKhp_3mPZuk,1363
10
13
  cms_qe/urls.py,sha256=npDzzW9SgLVMZECTyOUL5Cpw7RxeuuNssTKOXuXuxtM,3247
11
- cms_qe/utils.py,sha256=gxsWZmS34ZC0Tv1VW8A7VeGlrPyDshodF1ZWXj7xyWE,3057
14
+ cms_qe/utils.py,sha256=52ETz4NKv8xyBrMJ4yDUTfUBaO6Owp-5_IHfxDftQEE,2913
12
15
  cms_qe/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
16
  cms_qe/api/constants.py,sha256=pdSziATRm6yUaaPBYoD07JXcULMvKD0h5RdNTPpG0rM,64
14
17
  cms_qe/api/permissions.py,sha256=QKSll8wVOWKNbvX_FsC9CRKbPVcT2s5FM81_fnqeEQg,409
@@ -33,7 +36,7 @@ cms_qe/boilerplates/bootstrap3/static/cms_qe/js/bootstrap.min.js,sha256=U5ZEeKfG
33
36
  cms_qe/boilerplates/bootstrap3/static/cms_qe/js/jquery_3.2.1.min.js,sha256=hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4,86659
34
37
  cms_qe/boilerplates/bootstrap3/static/cms_qe/js/npm.js,sha256=x6qCoap9RSJKONkm0q2v9_5K71vNr6Kke9rAV_RCLC0,484
35
38
  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
39
+ cms_qe/boilerplates/bootstrap3/templates/cms_qe/home.html,sha256=itXnWbX_-Z9jIgdca25obWjwVCTT0Jy6FxWTD5aUvyU,1474
37
40
  cms_qe/boilerplates/bootstrap3/templates/cmsplugin_filer_folder/plugins/folder/gallery.html,sha256=ZAbkXcsJ6mwAJt0vQNd55ZWB-BhJ_gtvtbnKx8PysV8,3107
38
41
  cms_qe/haystack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
42
  cms_qe/haystack/forms.py,sha256=4FyieMfxfE6h2dcUaWAZJ18rEOAcnMVb2zLZ60iCbVA,912
@@ -52,26 +55,31 @@ cms_qe/middleware/page_status_code.py,sha256=J-Ezet9ban9rnjWaSuRss9gOz5h7uCCyL46
52
55
  cms_qe/migrations/0001_api_permissions.py,sha256=KPJYBdX3dWYEbUswSiIhkhDV6FcjXG-MPfC2aYwt7Wc,672
53
56
  cms_qe/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
57
  cms_qe/settings/__init__.py,sha256=GJwHXMHwMuGYE-3ZzePJ-26I2WwE8bAIMUDoiTFr0L8,982
55
- cms_qe/settings/dev.py,sha256=51CBwiclE8LLoNB2uioIK_L3JhM1yzukQ0gZimkcFqw,1487
58
+ cms_qe/settings/dev.py,sha256=dKLpdibotQELoMjU-_HJlcsY30U5xM9KSONdsDW_Dq0,2168
56
59
  cms_qe/settings/unittest.py,sha256=folLIMJb1Arh60_Sn0eNQrvIlx0OsAs6v1tDfyRZVuQ,514
57
60
  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
61
+ cms_qe/settings/base/app.py,sha256=rWkemCai4k6PBxcx9ga6ggF32lUUVyh30Vp_7QU22Rg,4469
59
62
  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
63
+ cms_qe/settings/base/cache.py,sha256=yBCvIIW25bSBD9nzVbPQvsPfw-pkOmcnLO7OPwzXPWo,337
64
+ cms_qe/settings/base/cms.py,sha256=hzl10CwswfQJkIfn4WoFJUmoumwC2smhSl8IR4VxkLM,1990
62
65
  cms_qe/settings/base/constants.py,sha256=2lggnUhHesx5HKaz8kJ983JwHEPj4ZLoMfhgOqERPZY,8248
63
- cms_qe/settings/base/database.py,sha256=qT7ePr2lg4CVJcHnG2nXFPPvzIjBOLB4auvRiWxaDPY,408
64
- cms_qe/settings/base/email.py,sha256=agT6ZBAyT29TUEQIRYObAdgOSETLlZEi7KizNhr9dVc,357
66
+ cms_qe/settings/base/database.py,sha256=_i3OJMRcrcI17VJeqY66JNOpAOXkhq570lRi0RVpzGw,354
67
+ cms_qe/settings/base/email.py,sha256=WkTj4V4lMZ3BG5AvHGPqulZ8T_cQMrvXiLj_NhLmpZs,576
68
+ cms_qe/settings/base/env.py,sha256=Oe10oSrA7QlD9TBDnEup8mtNTd1pXyaO5OThtUW8UVQ,36
65
69
  cms_qe/settings/base/i18n.py,sha256=n_7esPYSbf9Wj-T23uWds7tCvQ0ol9MfyMtKzy009sM,355
66
- cms_qe/settings/base/logging.py,sha256=GFVyQ_DcdKl9iu9C8fCshrAdrIKY0T-OD6lst5bb8qQ,1319
70
+ cms_qe/settings/base/logging.py,sha256=NnhPZnPX6zqobi4Ekx5euOnCD3Rw_Sb7jOl0tl80Vv0,1368
67
71
  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
72
+ cms_qe/settings/base/search.py,sha256=Vp6ROmo2x3ZaMrnHhNBjR3dX_r7BvIkcG9CPXmlZhiE,725
73
+ cms_qe/settings/base/security.py,sha256=fLaVfmqdF6-IZ0oppnV3Xs6KGARx7u9fYvWMwRJA3LM,4557
70
74
  cms_qe/settings/base/template.py,sha256=bITmA7XkoqbDpefWWOBsEiPtCREzFfHkUuFvGxJVLK4,1082
71
- cms_qe/templates/base.html,sha256=BMd8MbubDB8m1ZzBWfAfzTs4EBQn0oBQUw1GVRA4z6A,972
75
+ cms_qe/static/cms_qe/css/fix-djangocms-admin-style.css,sha256=sf_9yH_rKSts9L0OQ1vN5t32PTyjBuwfHZnDCN0GTX4,805
76
+ cms_qe/templates/base.html,sha256=Hb6MWA_IpBWlCyJ2NiPPQv0nSKOJgXSXS7y62-cHGUA,1094
77
+ cms_qe/templates/pg_is_in_recovery_login.html,sha256=ng0snZ-rriwFRKVvG0ZCY5QsQbod7jex2pVJ3hVonc0,116
72
78
  cms_qe/templates/admin/index.html,sha256=6CjuqOPQnEYXa7zwyoLyDHt-zzfBwLAf45B0F80ryZ0,1812
79
+ cms_qe/templates/admin/inc/extrastyle.html,sha256=vgsAzeKxV8Meu5j60vo9hMzlsKnRagkyI7icucfQpEg,105
80
+ cms_qe/templates/cms_qe/alias_content_preview.html,sha256=2klC7206SDOCf-sWYgeEcBiG2fIA8H2OML2fwEcewXc,956
73
81
  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
82
+ cms_qe/templates/cms_qe/home.html,sha256=zT4fnq9V6CsVkGXs_iFMGE4RvcMAeWvs4r0XTiHcLwA,114
75
83
  cms_qe/templates/cms_qe/internal_error.html,sha256=n4JJ80KNHyhiSxGLQadCn9KmctnFABcwLU4KuZly8A4,251
76
84
  cms_qe/templates/cms_qe/search_result.html,sha256=5XDX4nhRDkSJq_EdW95p8CuEN1Yy14OG9UC3WDz7TXI,310
77
85
  cms_qe/templates/cms_qe/include/body_bottom.html,sha256=t-B_SA2AFPmTC5hnDANyR9O9tmH3-hzGTLoRPi9lgIY,29
@@ -92,6 +100,7 @@ cms_qe/views/monitoring.py,sha256=1r2s_jm6B6P0gEmiqjH9m3loUW3BmJivvpg6qcUOxVM,19
92
100
  cms_qe/views/redirect_to_page.py,sha256=2HeGxTIKQQcfXsafdDz9F1fDHKGAbpiq1qKBKMvDL70,806
93
101
  cms_qe/views/search_result.py,sha256=H1eMOmtONnWqBDsBvz3MartyHhh42vyi0jPoxWb0X8c,296
94
102
  cms_qe/views/security.py,sha256=SNdJe4NSm1vuOGchVF3VqlmFDopt9xYoO-b4Y0UxkFU,1108
103
+ cms_qe/views/test_messages.py,sha256=ULHdXwjSmQxCxFF1hzBzpDa5MltGwyIF8DjQPb7N-7M,1281
95
104
  cms_qe/whoosh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
96
105
  cms_qe/whoosh/backend.py,sha256=YOVJGz3htWdcSUbYtX2a9VqywAU3EJP_EvxXHCY0QVA,5418
97
106
  cms_qe_analytical/LICENSE.txt,sha256=ptQIrnsiWFFf2LZ60DTAO6XA7CQYFuwhX1m4kzhv5_8,1072
@@ -108,7 +117,7 @@ cms_qe_auth/cms_menus.py,sha256=UxzzuMfOJCC_EiCkV2__6R5JKV9q1WGbTEgO7yLy8rE,1675
108
117
  cms_qe_auth/cms_plugins.py,sha256=USiNHaWdIJqPFUMLOjhuVam4nwOckujg1uguXNIs798,1575
109
118
  cms_qe_auth/fixtures.py,sha256=hQO75OnBmU4NiT_GF-oW4lU50FrLDgGF-gfouYeLfRI,784
110
119
  cms_qe_auth/forms.py,sha256=x7sdFoOrKBLTJXqESedpIh6Kc1k5zZhL4vwnmhj1gH8,1137
111
- cms_qe_auth/models.py,sha256=Aro43D9y1zrS-3eKHVZEuSkchusDZAcj15B2vYcdt0Q,2713
120
+ cms_qe_auth/models.py,sha256=ytcbh-rT36IQmOXxk4yef1xRq2RVvTzneORAC7pvWds,2713
112
121
  cms_qe_auth/token.py,sha256=DG4Bu8AVV-d1ayL4Oc9DXNnERt1sstrll80RBGrplx0,224
113
122
  cms_qe_auth/urls.py,sha256=RCgr9t1YonE0yR_8gXiXZIGESvQfrwVwlKhBOWmgxkw,2040
114
123
  cms_qe_auth/utils.py,sha256=JYZUzQhUE_kycVBRBNi-fmGy5WtjIwc3_qsGxOVrNR0,2972
@@ -3941,7 +3950,8 @@ cms_qe_table/templates/cms_qe/table/table_widget.html,sha256=tsjlS5Mc_6iALFk0QIe
3941
3950
  cms_qe_table/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3942
3951
  cms_qe_table/templatetags/cms_qe_table_filters.py,sha256=eFBB2FoCcpQRAknMIJLac0ts18w8XIODbouuJlP0ty4,782
3943
3952
  cms_qe_test/__init__.py,sha256=-Vc3K2g4JFSE2qw5AvuTGi4rwQGMOXAEycrjgFWk1BQ,121
3944
- cms_qe_test/cms.py,sha256=pspLQxbnwG71PuZKtwfWDu6uVk7RYO86Od1cDXBF108,1796
3953
+ cms_qe_test/cms.py,sha256=cJs5ZHbyPamTBuIMOhT1R2DR6teKYwhbZeuY-M-7i8I,2660
3954
+ cms_qe_test/mail_filebased_backend.py,sha256=9q3YuR-WcfhNOJc6hWclJmwRs949gzGFm6Eb3W7CjeI,645
3945
3955
  cms_qe_video/__init__.py,sha256=2iOdITrw_UvFcQpFA0rhUWBCRe2qvTuDvltp5Q233cc,1070
3946
3956
  cms_qe_video/cms_plugins.py,sha256=kqJX5eb-pYutxO-_0UnO784QpTwOb5Eudo6bW2NXaZg,3020
3947
3957
  cms_qe_video/fixtures.py,sha256=0oGo7Ufh3XwaLaMjcGN7CFao_BetcQ6xtikNeAHdqvs,701
@@ -3960,14 +3970,7 @@ cms_qe_video/templates/cms_qe/video/video_source_file.html,sha256=QJF5fs88s9Fznp
3960
3970
  cms_qe_video/templates/cms_qe/video/video_widget.html,sha256=Yumciq6bGlAYI1lYx5j9V6IF8QYrncNYygPTkXEz6Wk,925
3961
3971
  cms_qe_video/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3962
3972
  cms_qe_video/templatetags/cms_qe_video.py,sha256=NR_mGv91J0rEreZrQjCzaaXSrZsKvrSas12wMJ-Dg24,1168
3963
- django_cms_qe-3.7.2.dist-info/licenses/LICENSE,sha256=5wLaeUil0gfU9p8C4zn2Yu_PvZBNieUoYl0z9FcFWdA,1521
3964
- example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3965
- example/urls.py,sha256=H-IJsRGFVZGw6FD9gvK-0B0dLeSOsduziHvDvcHCQZ0,399
3966
- example/wsgi.py,sha256=lCKhvtFZlorSIA8qYEqc3pZ1Oflrz_Tc696MWJ62ue4,396
3967
- example/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3968
- example/settings/aldryn_newsblog.py,sha256=iQ5idy9rZR_N5IJzogc7R3PK3RIN7OMw7ksSIIIxPDk,276
3969
- example/settings/dev.py,sha256=TGGlSzh9ZDh54Z2X5i6mz7Ja3lT_qBqw9MHX3EnMs9o,676
3970
- example/settings/selenium.py,sha256=pRi8pIQIiYA0Y_yZsg7M63npHMRcUsFF7gZTr602yPY,215
3973
+ django_cms_qe-4.1.0.dist-info/licenses/LICENSE,sha256=5wLaeUil0gfU9p8C4zn2Yu_PvZBNieUoYl0z9FcFWdA,1521
3971
3974
  test_selenium/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3972
3975
  test_selenium/browser.py,sha256=OcfqxDa9OtL7M5CSwfIxtzToMUEhqGLvditemPeEUNo,1437
3973
3976
  test_selenium/conftest.py,sha256=mAptaAyj7a1hbUPDRWBBs1qL0TJ8Fma7Mch6PZwgtNo,220
@@ -3981,7 +3984,7 @@ test_selenium/pages/cms/__init__.py,sha256=_qe4YZYaQbrXp7Szmmeo4TUSkXlE5Rozu8E3t
3981
3984
  test_selenium/pages/cms/login.py,sha256=UPzJQcYff8NUAT4nvmfQoJQxzOJyPrJ_cKtH35NVfNg,521
3982
3985
  test_selenium/pages/cms/page.py,sha256=YQnpZkopfVnhoyQKpRDGqjNeV6xUl-pEHjEcZ9HRiPk,489
3983
3986
  test_selenium/pages/cms/wizard.py,sha256=yatbXH-rf1ap4O1hY0I13WikM3zkm_NrAiSK6bqENIU,545
3984
- django_cms_qe-3.7.2.dist-info/METADATA,sha256=hgRfyIxLQM7jkrybpP0Mmfv3Q8p7MDmbI1o6mZhxoM8,5144
3985
- django_cms_qe-3.7.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
3986
- django_cms_qe-3.7.2.dist-info/top_level.txt,sha256=T4dauFwJy7FmxCy7WoQI3pPwiDessNB2LkfOAP76ssE,172
3987
- django_cms_qe-3.7.2.dist-info/RECORD,,
3987
+ django_cms_qe-4.1.0.dist-info/METADATA,sha256=Jh_kHri1S4t76H1AAFtTMjvzx56EFXELL2HgwBEtzNI,6234
3988
+ django_cms_qe-4.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
3989
+ django_cms_qe-4.1.0.dist-info/top_level.txt,sha256=fQYSfQoprw1NXhYY-I8AzsPk-Rgst1REh3iOUvwAbkM,164
3990
+ django_cms_qe-4.1.0.dist-info/RECORD,,
@@ -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()