diff --git a/README.md b/README.md
index ad87d3e02..2e4eadb85 100644
--- a/README.md
+++ b/README.md
@@ -194,6 +194,7 @@ To contribute, first read [How to Contirbute][Contributing].
| `python manage.py collectstatic` | [django](https://docs.djangoproject.com/en/3.2/howto/static-files/)
| `python manage.py createsuperuser` | [django cms](https://docs.django-cms.org/en/release-3.8.x/how_to/install.html#admin-user), [django](https://docs.djangoproject.com/en/3.2/ref/django-admin/#createsuperuser)
+
[Camino]: https://github.com/TACC/Camino
diff --git a/apps/search_page/__init__.py b/apps/search_page/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/search_page/apps.py b/apps/search_page/apps.py
new file mode 100644
index 000000000..548c4f279
--- /dev/null
+++ b/apps/search_page/apps.py
@@ -0,0 +1,10 @@
+from django.apps import AppConfig
+from django.conf import settings
+
+class SearchPageConfig(AppConfig):
+ name = 'apps.search_page'
+
+ def ready(self):
+ if settings.SEARCH_PAGE_AUTO_SETUP:
+ from .utils import create_page
+ create_page()
diff --git a/apps/search_page/cms_apps.py b/apps/search_page/cms_apps.py
new file mode 100644
index 000000000..236d4daa6
--- /dev/null
+++ b/apps/search_page/cms_apps.py
@@ -0,0 +1,11 @@
+from cms.app_base import CMSApp
+from cms.apphook_pool import apphook_pool
+
+
+@apphook_pool.register
+class SearchPageApphook(CMSApp):
+ app_name = 'apps.search_page'
+ name = 'SearchPage'
+
+ def get_urls(self, page=None, language=None, **kwargs):
+ return ['apps.search_page.urls']
diff --git a/apps/search_page/static/search_page/css/google-search.css b/apps/search_page/static/search_page/css/google-search.css
new file mode 100644
index 000000000..e93d6af86
--- /dev/null
+++ b/apps/search_page/static/search_page/css/google-search.css
@@ -0,0 +1,134 @@
+#google-search {
+
+ &:is(#cms-content-container > *:last-child) {
+ margin-bottom: var(--global-space--section-gap, 60px);
+ }
+
+ /* SEARCH CONTAINER */
+
+ /* To remove padding from search container */
+ & .gsc-control-cse {
+ padding: unset;
+ }
+
+
+
+ /* TABLE OF SEARCH STATS & SORTING OPTIONS */
+
+ /* To remove border from search analytics, add gray background */
+ & .gsc-above-wrapper-area {
+ --bkgd-color: var(--global-color-primary--x-light);
+
+ background-color: var(--bkgd-color);
+ box-shadow: 40vw 0 var(--bkgd-color), -40vw 0 var(--bkgd-color);
+ border-bottom: unset;
+ }
+
+ & .gsc-above-wrapper-area-container {
+ border-bottom: unset;
+ }
+
+ /* To override Core-Styles tables */
+ & tbody > tr:first-child > :is(td, th) {
+ border: unset;
+ padding-inline: unset;
+ background: unset;
+ vertical-align: middle;
+ }
+
+ & .gsc-selected-option-container {
+ background: var(--global-color-primary--xx-light);
+ border: var(--global-border--normal);
+ }
+
+ & .gsc-result-info {
+ padding: unset;
+ font-size: var(--global-font-size--medium);
+ }
+
+
+
+ /* SUGGESTION PHRASE (after "Did you mean:") */
+
+ & .gs-spelling {
+ padding: unset;
+ }
+ & .gs-spelling a {
+ color: var(--global-color-accent--normal);
+ }
+
+
+
+ /* SEARCH RESULTS */
+
+ /* (search result body text) */
+ & .gs-snippet {
+ color: var(--global-color-primary--dark);
+ }
+
+ /* (url under search result title) */
+ & .gs-webResult div.gs-visibleUrl {
+ color: var(--global-color-secondary--normal);
+ }
+
+ /* (search result titles) */
+ & a.gs-title:link {
+ color: var(--global-color-accent--normal);
+ text-decoration: none;
+ text-decoration-thickness: var(--global-border-width--normal);
+ text-underline-offset: 0.2em;
+ }
+ & a.gs-title:link:hover {
+ text-decoration-line: underline;
+ text-decoration-style: solid;
+ }
+
+ /* (push search-result description to right) */
+ & .gs-image-box {
+ margin-right: 10px;
+ }
+
+
+
+ /* GOOGLE PAGE NAVIGATION (at bottom) */
+
+ & .gsc-cursor-box {
+ display: flex;
+ justify-content: center;
+ margin-block: var(--global-space--large);
+ }
+
+ & .gsc-cursor-current-page {
+ color: var(--global-color-accent--normal);
+ text-decoration: none;
+ text-decoration-thickness: var(--global-border-width--normal);
+ text-underline-offset: 0.2em;
+ }
+
+ & .gsc-cursor-current-page:hover {
+ text-decoration-line: underline;
+ text-decoration-style: solid;
+ }
+
+
+
+ /* GOOGLE BRANDING */
+
+ & .gcsc-find-more-on-google {
+ color: var(--global-color-accent--normal);
+ text-decoration: none;
+ text-decoration-thickness: var(--global-border-width--normal);
+ text-underline-offset: 0.2em;
+ }
+
+ & .gcsc-find-more-on-google:hover {
+ text-decoration-line: underline;
+ text-decoration-style: solid;
+ }
+
+ & .gcsc-branding-img-noclear {
+ vertical-align: unset;
+ top: 1px;
+ }
+
+}
diff --git a/apps/search_page/templates/search_page.html b/apps/search_page/templates/search_page.html
new file mode 100644
index 000000000..db55e320d
--- /dev/null
+++ b/apps/search_page/templates/search_page.html
@@ -0,0 +1,25 @@
+{% extends "base.html" %}
+{% load static cms_tags %}
+
+{% block css %}
+ {{ block.super }}
+
+{% endblock css %}
+
+{% block app_content %}
+
+
+
{% page_attribute "page_title" %}
+
+
+
+ {% if settings.GOOGLE_SEARCH_ENGINE_ID %}
+
+ {% else %}
+ Please inform your website administrator to verify a GOOGLE_SEARCH_ENGINE_ID is set for this website.
+ {% endif %}
+{% endblock app_content %}
diff --git a/apps/search_page/urls.py b/apps/search_page/urls.py
new file mode 100644
index 000000000..5a4056342
--- /dev/null
+++ b/apps/search_page/urls.py
@@ -0,0 +1,8 @@
+from django.urls import path
+from . import views
+
+app_name = 'apps.search_page'
+
+urlpatterns = [
+ path('', views.SearchPageView, name='search'),
+]
diff --git a/apps/search_page/utils.py b/apps/search_page/utils.py
new file mode 100644
index 000000000..5cca4f09e
--- /dev/null
+++ b/apps/search_page/utils.py
@@ -0,0 +1,64 @@
+import logging
+
+from django.conf import settings
+from django.urls import reverse, NoReverseMatch
+
+from cms.api import create_page as create_cms_page
+from cms.models.pagemodel import Page
+
+from .cms_apps import SearchPageApphook
+
+
+logger = logging.getLogger(f'portal.{__name__}')
+
+TITLE = 'Search'
+REVERSE_ID = 'search_page'
+DEFAULT_SLUG = settings.PORTAL_SEARCH_PATH.strip('/')
+
+def get_page():
+ try:
+ return Page.objects.filter(reverse_id=REVERSE_ID).first()
+ except Page.DoesNotExist:
+ return None
+
+def get_slug(page=None):
+ if page:
+ return page.get_slug()
+ else:
+ page = get_page()
+ return get_slug(page) if page else DEFAULT_SLUG
+
+def get_page_url():
+ page = get_page()
+ if page:
+ return page.get_absolute_url()
+ else:
+ try:
+ return reverse('apps.search_page:search')
+ except NoReverseMatch:
+ return None
+
+def create_page():
+ page = get_page()
+ slug = get_slug(page)
+
+ if not page:
+ page = create_cms_page(
+ title=f'{TITLE} (Auto-Generated)',
+ menu_title=TITLE,
+ page_title=TITLE,
+ reverse_id=REVERSE_ID,
+ # Use a template from CMS_TEMPLATES setting
+ template='standard.html',
+ language='en',
+ published=True,
+ slug=slug,
+ in_navigation=False,
+ apphook=SearchPageApphook,
+ apphook_namespace=SearchPageApphook.name,
+ )
+ logger.info(f'Created search page "{TITLE}" at "{slug}"')
+ else:
+ logger.info(f'Found existing search page at "{slug}"')
+
+ return page
diff --git a/apps/search_page/views.py b/apps/search_page/views.py
new file mode 100644
index 000000000..cf5486460
--- /dev/null
+++ b/apps/search_page/views.py
@@ -0,0 +1,4 @@
+from django.shortcuts import render
+
+def SearchPageView(request):
+ return render(request, 'search_page.html')
diff --git a/package-lock.json b/package-lock.json
index a0472cd77..276eaf0c7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,7 @@
"minimist": "^1.2.6"
},
"devDependencies": {
- "@tacc/core-styles": "^2.45.0"
+ "@tacc/core-styles": "^2.46.2"
},
"engines": {
"node": ">=16",
@@ -1132,9 +1132,9 @@
}
},
"node_modules/@tacc/core-styles": {
- "version": "2.45.0",
- "resolved": "https://registry.npmjs.org/@tacc/core-styles/-/core-styles-2.45.0.tgz",
- "integrity": "sha512-ujck0rgUFb+vo42Qr5MRUfF8KHnIAEjHsRuh9dhBaeo6mrRhyjBCq+JgGryjQxOiCnFFM8yuYrJlUPsCaSEGCA==",
+ "version": "2.46.2",
+ "resolved": "https://registry.npmjs.org/@tacc/core-styles/-/core-styles-2.46.2.tgz",
+ "integrity": "sha512-b3MTccuLswbJM19ysX85vHYt6l/hHtvEgR3ZhKZZvoVXPBZjyfhl9zvnOrnQPB5tnLMt59nKdyHo+XcEj67lgA==",
"dev": true,
"license": "MIT",
"bin": {
@@ -4733,9 +4733,9 @@
}
},
"@tacc/core-styles": {
- "version": "2.45.0",
- "resolved": "https://registry.npmjs.org/@tacc/core-styles/-/core-styles-2.45.0.tgz",
- "integrity": "sha512-ujck0rgUFb+vo42Qr5MRUfF8KHnIAEjHsRuh9dhBaeo6mrRhyjBCq+JgGryjQxOiCnFFM8yuYrJlUPsCaSEGCA==",
+ "version": "2.46.2",
+ "resolved": "https://registry.npmjs.org/@tacc/core-styles/-/core-styles-2.46.2.tgz",
+ "integrity": "sha512-b3MTccuLswbJM19ysX85vHYt6l/hHtvEgR3ZhKZZvoVXPBZjyfhl9zvnOrnQPB5tnLMt59nKdyHo+XcEj67lgA==",
"dev": true,
"requires": {}
},
diff --git a/package.json b/package.json
index efa0c1242..23b32a020 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,6 @@
},
"homepage": "https://github.com/TACC/Core-CMS",
"devDependencies": {
- "@tacc/core-styles": "^2.45.0"
+ "@tacc/core-styles": "^2.46.2"
}
}
diff --git a/poetry.lock b/poetry.lock
index 4fd2870f6..9d86b1964 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -747,13 +747,15 @@ taggit-helpers = ["django-taggit-helpers"]
[[package]]
name = "djangocms-bootstrap4"
-version = "3.0.1"
+version = "3.0.2"
description = "Adds Bootstrap 4 components as plugins."
optional = false
python-versions = "*"
groups = ["main"]
-files = []
-develop = false
+files = [
+ {file = "djangocms_bootstrap4-3.0.2-py3-none-any.whl", hash = "sha256:3b7e7eb1b7551cf433bf2bb0f3601d47645203c3bb4ad75ec097eb4bd81a34cf"},
+ {file = "djangocms_bootstrap4-3.0.2.tar.gz", hash = "sha256:e931aefadbd22ab00e6cea8b7224da767afea125df15bf0c8a8d5f7872153e64"},
+]
[package.dependencies]
django-cms = ">=3.7,<4"
@@ -767,12 +769,6 @@ djangocms-text-ckeditor = ">=3.1.0"
[package.extras]
static-ace = ["djangocms-static-ace"]
-[package.source]
-type = "git"
-url = "https://github.com/django-cms/djangocms-bootstrap4.git"
-reference = "49983f4"
-resolved_reference = "49983f4175ec4a4e2b5076993a893cbdd79c4ab2"
-
[[package]]
name = "djangocms-column"
version = "2.0.0"
@@ -2502,4 +2498,4 @@ testing = ["func-timeout", "jaraco.itertools"]
[metadata]
lock-version = "2.1"
python-versions = ">=3.11,<4.0"
-content-hash = "077910c85e2fda3f3ff38d22d4275d876d2ecef54cd3e21ed2ee744c56771495"
+content-hash = "8ef7c041a1522e5e00995677cce04b851ebfd5852399795d29aa902b49526d21"
diff --git a/pyproject.toml b/pyproject.toml
index 7a00eb064..a686634eb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "tacc-core-cms-backend"
-version = "4.35.0"
+version = "4.36.3"
description = "DjangoCMS backend for the TACC ACI-WMA Core-CMS Codebase."
authors = ["TACC-WMA "]
@@ -45,9 +45,7 @@ djangocms-admin-style = "~3.2.6"
djangocms-apphook-setup = "0.4.1"
djangocms-attributes-field = "2.1.0"
djangocms-blog = "^1.2"
-# TO get a commit in main (since v3.0.1) to fix Container error
-# https://github.com/django-cms/djangocms-bootstrap4/pull/164
-djangocms-bootstrap4 = {git = "https://github.com/django-cms/djangocms-bootstrap4.git", rev = "49983f4"}
+djangocms-bootstrap4 = "^3.0"
djangocms-column = "^2.0"
djangocms-file = "3.0.0"
djangocms-forms-maintained = { git = "https://github.com/TACC/djangocms-forms", rev = "6b59ff366495915f06f4d6fac01a2f0aa9efecaf" }
diff --git a/taccsite_cms/_settings/search.py b/taccsite_cms/_settings/search.py
index ce19ba3d4..b73151536 100644
--- a/taccsite_cms/_settings/search.py
+++ b/taccsite_cms/_settings/search.py
@@ -5,16 +5,19 @@
########################
# To support any search
-PORTAL_SEARCH_PATH = '/search'
+PORTAL_SEARCH_PATH = '/search/'
# To support Google search
# PORTAL_SEARCH_QUERY_PARAM_NAME = 'q'
# PORTAL_SEARCH_INDEX_IS_AUTOMATIC = False
+GOOGLE_SEARCH_ENGINE_ID = ''
# (DEPRECATED) To support Elasticsearch
PORTAL_SEARCH_QUERY_PARAM_NAME = 'query_string'
PORTAL_SEARCH_INDEX_IS_AUTOMATIC = True
+SEARCH_PAGE_AUTO_SETUP = True
+
ES_AUTH = 'username:password'
ES_HOSTS = 'http://elasticsearch:9200'
ES_INDEX_PREFIX = 'cms-dev-{}'
@@ -34,5 +37,6 @@
ALDRYN_SEARCH_REGISTER_APPHOOK = True
_INSTALLED_APPS = [
- 'haystack', # search index
+ 'haystack', # ElasticSearch
+# 'search_page' # Google Search
]
diff --git a/taccsite_cms/apps.py b/taccsite_cms/apps.py
index f3bdc022a..64d8e4bde 100644
--- a/taccsite_cms/apps.py
+++ b/taccsite_cms/apps.py
@@ -1,7 +1,10 @@
import logging
+
from django.apps import AppConfig
from django.db.models.signals import post_migrate
+from .djangocms_picture.extend import extendPicturePlugin
+
logger = logging.getLogger(f"portal.{__name__}")
class TaccsiteCmsConfig(AppConfig):
@@ -10,6 +13,7 @@ class TaccsiteCmsConfig(AppConfig):
def ready(self):
post_migrate.connect(self.create_groups, sender=self)
+ extendPicturePlugin()
def create_groups(self, sender, **kwargs):
from django.contrib.auth.models import Group
diff --git a/taccsite_cms/contrib/bootstrap4_djangocms_picture/cms_plugins.py b/taccsite_cms/contrib/bootstrap4_djangocms_picture/cms_plugins.py
deleted file mode 100644
index 3edf41136..000000000
--- a/taccsite_cms/contrib/bootstrap4_djangocms_picture/cms_plugins.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# To support generic Image plugin without uninstalling Bootstrap's
-# FAQ: Bootstrap Image plugin has features not desirable in TACC plugins
-# FAQ: We must not break sites that already use Bootstrap Image plugin
-try:
- from django.utils.translation import gettext as _
-
- from cms.plugin_pool import plugin_pool
-
- from djangocms_picture.cms_plugins import PicturePlugin
- from djangocms_bootstrap4.contrib.bootstrap4_picture.cms_plugins import Bootstrap4PicturePlugin
-
- # To clairfy for users how this plugin differs from Generic > Image
- Bootstrap4PicturePlugin.name = _('Picture / Image (Responsive)')
-
- # To re-register generic Picture plugin
- # SEE: https://github.com/django-cms/djangocms-bootstrap4/blob/master/djangocms_bootstrap4/contrib/bootstrap4_picture/cms_plugins.py#L54
- plugin_pool.register_plugin(PicturePlugin)
-
-# To avoid server crash if Boostrap plugin is not registered
-# CAVEAT: If import statement fails for reason other than Bootstrap presence,
-# then that failure, and the failure of this plugin, is silent
-except ImportError:
- pass
diff --git a/taccsite_cms/djangocms_picture/extend.py b/taccsite_cms/djangocms_picture/extend.py
new file mode 100644
index 000000000..21798b481
--- /dev/null
+++ b/taccsite_cms/djangocms_picture/extend.py
@@ -0,0 +1,163 @@
+def extendPicturePlugin():
+ from django.utils.translation import gettext_lazy as _
+
+ from cms.plugin_pool import plugin_pool
+
+ ZOOM_TEMPLATE_NAME = 'zoom_effect'
+ ZOOM_TEMPLATE_LABEL = 'Zoom image on hover'
+ ZOOM_TEMPLATE_NOTE = _('The "%(zoom_template_label)s" templates only have effect if Image either has a Link or is within a Link.') % {"zoom_template_label": ZOOM_TEMPLATE_LABEL}
+ ZOOM_TEMPLATE_ERROR = _(' "%(zoom_template_label)s" templates require Image to either have a Link or be within a Link.') % {"zoom_template_label": ZOOM_TEMPLATE_LABEL}
+
+ LINK_TEMPLATE_NAME = 'no_link_to_ext_image'
+
+ def add_help_text(form_instance):
+ """Adds help text for: 'Template' field"""
+
+ if 'template' in form_instance.fields:
+ form_instance.fields['template'].help_text += _(' %(ZOOM_TEMPLATE_NOTE)s') % {"ZOOM_TEMPLATE_NOTE": ZOOM_TEMPLATE_NOTE}
+
+ def whether_to_render_link(instance):
+ has_explicit_link = bool(instance.link_url or instance.link_page_id)
+ # FAQ: The djangocms_picture has "feature" such that an image with
+ # "External image" URL will automatically link to that resource
+ has_implicit_link = bool(instance.get_link()) and not has_explicit_link
+
+ allow_implicit_link = not LINK_TEMPLATE_NAME in instance.template
+
+ if has_explicit_link or (has_implicit_link and allow_implicit_link):
+ return True
+
+ return False
+
+ def validate_zoom_template(instance):
+ """Validates: 'Template' field choice 'Zoom image …'"""
+ from django.core.exceptions import ValidationError
+ from djangocms_link.cms_plugins import LinkPlugin
+
+ errors = {}
+
+ would_render_link = whether_to_render_link(instance)
+ should_add_zoom_effect = ZOOM_TEMPLATE_NAME in instance.template
+ parent_plugin = instance.parent.get_plugin_instance()[0] if instance.parent else None
+ is_in_link = instance.parent.plugin_type == 'LinkPlugin' if instance.parent else False
+
+ if (should_add_zoom_effect and not would_render_link and not is_in_link):
+ errors['template'] = ZOOM_TEMPLATE_ERROR
+
+ if errors:
+ raise ValidationError(errors)
+
+ def get_more_context_variables(instance):
+ """
+ Calculate boolean context variables to simplify template logic.
+ Returns a dictionary of context variables.
+ """
+ # Figure/Caption
+ has_caption_text = bool(instance.caption_text)
+ has_child_plugins = bool(instance.child_plugin_instances)
+ has_figure_content = has_caption_text or has_child_plugins
+
+ # Template
+ is_zoom_template = ZOOM_TEMPLATE_NAME in instance.template
+
+ # Link
+ should_render_link = whether_to_render_link(instance)
+
+ # Zoom Effect
+ should_add_zoom_effect = is_zoom_template
+ should_wrap_image_for_zoom = (
+ should_add_zoom_effect and
+ (has_figure_content or not should_render_link)
+ )
+ should_add_zoom_class_to_link = (
+ should_add_zoom_effect and
+ should_render_link and
+ not has_figure_content
+ )
+
+ # Attributes
+ should_add_attributes_to_image = (
+ not has_figure_content and
+ not should_render_link
+ )
+
+ return {
+ 'should_render_link': should_render_link,
+ 'has_figure_content': has_figure_content,
+ 'should_wrap_image_for_zoom': should_wrap_image_for_zoom,
+ 'should_add_zoom_class_to_link': should_add_zoom_class_to_link,
+ 'should_add_attributes_to_image': should_add_attributes_to_image,
+ }
+
+
+ # djangocms_picture
+ from djangocms_picture.cms_plugins import PicturePlugin as OriginalPicturePlugin
+ from djangocms_picture.models import Picture as OriginalPicture
+
+ class PicturePluginForm(OriginalPicturePlugin.form):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ add_help_text(self)
+
+ class PicturePluginModel(OriginalPicture):
+ class Meta:
+ proxy = True
+
+ def clean(self):
+ super().clean()
+ validate_zoom_template(self)
+
+ class PicturePlugin(OriginalPicturePlugin):
+ model = PicturePluginModel
+ form = PicturePluginForm
+ name = 'Image'
+
+ def render(self, context, instance, placeholder):
+ context = super().render(context, instance, placeholder)
+
+ more_context = get_more_context_variables(instance)
+ context.update(more_context)
+
+ return context
+
+ # To support generic Image plugin without uninstalling Bootstrap's
+ # FAQ: Had been done cuz Image plugin had percieved use cases,
+ # but is since not regularly used, but is used, thus maintained
+ # FAQ: No need to unregister cuz Bootstrap4PicturePlugin does that
+ # https://github.com/django-cms/djangocms-bootstrap4/blob/3.0.0/djangocms_bootstrap4/contrib/bootstrap4_picture/cms_plugins.py#L54
+ # plugin_pool.unregister_plugin(OriginalPicturePlugin)
+ plugin_pool.register_plugin(PicturePlugin)
+
+
+ # djangocms_bootstrap4: bootstrap4_picture
+ from djangocms_bootstrap4.contrib.bootstrap4_picture.cms_plugins import Bootstrap4PicturePlugin as OriginalBootstrap4PicturePlugin
+ from djangocms_bootstrap4.contrib.bootstrap4_picture.models import Bootstrap4Picture as OriginalBootstrap4Picture
+
+ class Bootstrap4PictureForm(OriginalBootstrap4PicturePlugin.form):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ add_help_text(self)
+
+ class Bootstrap4PictureModel(OriginalBootstrap4Picture):
+ class Meta:
+ proxy = True
+
+ def clean(self):
+ super().clean()
+ validate_zoom_template(self)
+
+ class Bootstrap4PicturePlugin(OriginalBootstrap4PicturePlugin):
+ model = Bootstrap4PictureModel
+ form = Bootstrap4PictureForm
+ name = 'Picture / Image (Responsive)'
+
+ def render(self, context, instance, placeholder):
+ context = super().render(context, instance, placeholder)
+
+ more_context = get_more_context_variables(instance)
+ context.update(more_context)
+
+ return context
+
+ plugin_pool.unregister_plugin(OriginalBootstrap4PicturePlugin)
+ plugin_pool.register_plugin(Bootstrap4PicturePlugin)
diff --git a/taccsite_cms/settings.py b/taccsite_cms/settings.py
index ed6927309..d22e7dc30 100644
--- a/taccsite_cms/settings.py
+++ b/taccsite_cms/settings.py
@@ -349,8 +349,10 @@ def gettext(s): return s
# FAQ: List custom directory first, so custom templates take precedence
# SEE: https://docs.djangoproject.com/en/2.2/topics/templates/#configuration
'DIRS': glob(
+ # XXX: Strange and from my ignorant implementation
os.path.join(BASE_DIR, 'taccsite_custom')
) + [
+ os.path.join(BASE_DIR, 'taccsite_custom', 'templates'),
os.path.join(BASE_DIR, 'taccsite_cms', 'templates')
],
'OPTIONS': {
@@ -476,12 +478,12 @@ def gettext(s): return s
# core TACC CMS
# HELP: If this were top of list, would TACC/Core-CMS/pull/169 fix break?
'taccsite_cms',
+ 'apps.search_page',
'common_apps.email_management',
# django CMS Bootstrap
# IDEA: Extend Bootstrap apps instead of overwrite
'taccsite_cms.contrib.bootstrap4_djangocms_link',
- 'taccsite_cms.contrib.bootstrap4_djangocms_picture',
# TACC CMS Plugins
'djangocms_tacc_image_gallery',
@@ -580,7 +582,9 @@ def get_subdirs_as_module_names(path):
('center', _('Align center')),
]
DJANGOCMS_PICTURE_TEMPLATES = [
- ('no_link_to_ext_image', _('Do not link to external image')),
+ ('no_link_to_ext_image', _('Do not link to "External image"')),
+ ('zoom_effect', _('Zoom image on hover')),
+ ('zoom_effect_no_link_to_ext_image', _('Zoom image on hover & Do not link to "External image"')),
]
# FILE UPLOAD VALUES MUST BE SET!
@@ -874,4 +878,5 @@ def get_subdirs_as_module_names(path):
'PORTAL_SOCIAL_SHARE_PLATFORMS',
'PORTAL_SEARCH_PATH',
'PORTAL_SEARCH_QUERY_PARAM_NAME',
+ 'GOOGLE_SEARCH_ENGINE_ID',
]
diff --git a/taccsite_cms/settings_custom.example.py b/taccsite_cms/settings_custom.example.py
index c30612115..42b0a062f 100644
--- a/taccsite_cms/settings_custom.example.py
+++ b/taccsite_cms/settings_custom.example.py
@@ -91,10 +91,11 @@
########################
# To support Google search
+PORTAL_SEARCH_PATH = '/search/'
PORTAL_SEARCH_QUERY_PARAM_NAME = 'q'
-
-# To disable Elasticsearch
PORTAL_SEARCH_INDEX_IS_AUTOMATIC = False
+SEARCH_PAGE_AUTO_SETUP = True
+GOOGLE_SEARCH_ENGINE_ID = ''
########################
# DJANGOCMS_BLOG
diff --git a/taccsite_cms/static/site_cms/css/src/_imports/components/django.cms.blog.app.page.css b/taccsite_cms/static/site_cms/css/src/_imports/components/django.cms.blog.app.page.css
index 4a6c5ef4f..13dadc8da 100644
--- a/taccsite_cms/static/site_cms/css/src/_imports/components/django.cms.blog.app.page.css
+++ b/taccsite_cms/static/site_cms/css/src/_imports/components/django.cms.blog.app.page.css
@@ -170,6 +170,15 @@ Styleguide Components.DjangoCMS.Blog.App.Page
margin-inline: auto;
}
+/* To add space under aligned images */
+/* NOTE: All images should be aligned by news editor,
+ so missing space is considered news editor error */
+:--article-page .blog-content .align-right,
+:--article-page .blog-content .align-center,
+:--article-page .blog-content .align-left {
+ margin-bottom: var(--global-space--grid-gap);
+}
+
/* To always align Bootstrap blockquote text left */
/* FAQ: Boostrap, loaded in foundation layer, used !important */
/* TODO: When extending Core-Styles c-news, .blockquote... is only for CMS */
diff --git a/taccsite_cms/static/site_cms/css/test/djangocms-picture.css b/taccsite_cms/static/site_cms/css/test/djangocms-picture.css
new file mode 100644
index 000000000..0a91e3675
--- /dev/null
+++ b/taccsite_cms/static/site_cms/css/test/djangocms-picture.css
@@ -0,0 +1,72 @@
+main {
+ .col {
+ /* To support notes */
+
+ :is(a, figure, span) {
+ position: relative;
+ }
+ :is(a, figure, span)::before {
+ position: absolute;
+ inset: 0 auto auto 0;
+ font-size: smaller;
+ background: rgb(from black r g b / 50%);
+ z-index: 1;
+ padding-inline: 0.5em;
+ }
+
+
+ /* To make notes unique */
+
+ a::before {
+ top: 0;
+ color: aqua;
+ content: '';
+ }
+ a[class]::before {
+ content: '';
+ }
+ a[data-class]::before {
+ content: '';
+ }
+
+ figure::before {
+ top: 3lh;
+ color: fuchsia;
+ content: '';
+ }
+ figure[class]::before {
+ content: '';
+ }
+ figure[data-class]::before {
+ content: '';
+ }
+
+ span::before {
+ top: 6lh;
+ color: yellow;
+ content: '';
+ }
+
+
+ /* To align notes to mimic tag hierarchy */
+
+ a > span::before,
+ a > figure::before,
+ figure > span::before {
+ margin-left: 1em;
+ }
+ a > figure > span::before {
+ margin-left: 2em;
+ }
+
+
+ /* To disable unsupported combos */
+
+ .u-image-zoom--on-hover:is(
+ span:not(a > *, a > figure > *),
+ figure:not(a > *)
+ ) {
+ opacity: 0.5;
+ }
+ }
+}
diff --git a/taccsite_cms/templates/djangocms_picture/default/picture.html b/taccsite_cms/templates/djangocms_picture/default/picture.html
index 15f50ab31..463006bb3 100644
--- a/taccsite_cms/templates/djangocms_picture/default/picture.html
+++ b/taccsite_cms/templates/djangocms_picture/default/picture.html
@@ -1,45 +1,48 @@
{# https://github.com/django-cms/djangocms-picture/blob/3.0.0/djangocms_picture/templates/djangocms_picture/default/picture.html #}
-{# TACC (mimic v3.0.0 to v4.0.0 changes): #}
{# TACC (support children as caption content): #}
+{# TACC (mimic v3.0.0 to v4.0.0 changes): #}
+{# TACC (support image zoom): #}
{# {% load thumbnail %} #}
-{% load thumbnail l10n cms_tags %}
+{% load thumbnail l10n cms_tags strip_class_attribute %}
+{# /TACC #}
{# /TACC #}
{# /TACC #}
-{# TACC (allow link to be conditional): #}
-{% block picture_link_start %}
+{# TACC (support custom condition): #}
+{% if should_render_link %}
{# /TACC #}
-{% if picture_link %}
- {# TACC (allow link to be conditional): #}
- {% block picture_link %}
- {# /TACC #}
-{# TACC (allow link to be conditional): #}
-{% endblock %}
-{# /TACC #}
+ {# TACC (support image zoom): #}
+ class="
+ {{ instance.attributes.class|default:'' }}
+ {{ instance.link_attributes.class|default:'' }}
+ {{ className }}
+ {% if should_add_zoom_class_to_link %}u-image-zoom--on-hover{% endif %}
+ "
+ {# TACC (assign attributes to parent): #}
+ {{ instance.attributes_str|strip_class_attribute|safe }}
+ {{ instance.link_attributes_str|strip_class_attribute|safe }}
+ {# /TACC #}
+ {# /TACC #}
+ >
{% endif %}
-{# TACC (allow link to be conditional): #}
-{% endblock %}
-{# /TACC #}
-{# start render figure/figcaption #}
+
{# TACC (support children as caption content): #}
-{# {% if instance.caption_text %} #}
-{% if instance.caption_text or instance.child_plugin_instances %}
+{% if has_figure_content %}
+{# /TACC #}
{# TACC (assign attributes to parent): #}
- {# #}
{# /TACC #}
{% endif %}
-{# /TACC #}
-{# end render figure/figcaption #}
+{# TACC (support image zoom): #}
+{% if should_wrap_image_for_zoom %}
+
+{% endif %}
+{# /TACC #}
{# TACC (mimic v3.0.0 to v4.0.0 changes): #}
{% localize off %}
{# /TACC #}
@@ -64,7 +67,7 @@
{# TACC (allow link to be conditional): #}
{% block picture_attributes %}
{# TACC (assign attributes to parent): #}
- {% if not instance.caption_text and not picture_link and not instance.child_plugin_instances %}
+ {% if should_add_attributes_to_image %}
{{ instance.attributes_str }}
{% endif %}
{# /TACC #}
@@ -74,10 +77,15 @@
{# TACC (mimic v3.0.0 to v4.0.0 changes): #}
{% endlocalize %}
{# /TACC #}
+{# TACC (support image zoom): #}
+ {% if should_wrap_image_for_zoom %}
+
+ {% endif %}
+{# /TACC #}
-{# start render figure/figcaption #}
-{# {% if instance.caption_text %} #}
-{% if instance.caption_text or instance.child_plugin_instances %}
+{# TACC (support custom condition): #}
+{% if has_figure_content %}
+{# /TACC #}
{# TACC (support children as caption content): #}
{# {{ instance.caption_text }} #}
@@ -89,17 +97,12 @@
{# /TACC #}
{% endif %}
-{# end render figure/figcaption #}
-{# TACC (allow link to be conditional): #}
-{% block picture_link_end %}
+{# TACC (support custom condition): #}
+{% if should_render_link %}
{# /TACC #}
-{% if picture_link %}
{% endif %}
-{# TACC (allow link to be conditional): #}
-{% endblock %}
-{# /TACC #}
{% comment %}
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img
diff --git a/taccsite_cms/templates/djangocms_picture/no_link_to_ext_image/picture.html b/taccsite_cms/templates/djangocms_picture/no_link_to_ext_image/picture.html
index 479af2ce9..d9e697407 100644
--- a/taccsite_cms/templates/djangocms_picture/no_link_to_ext_image/picture.html
+++ b/taccsite_cms/templates/djangocms_picture/no_link_to_ext_image/picture.html
@@ -1,28 +1,6 @@
{% extends "djangocms_picture/default/picture.html" %}
-{# Do not let instance.external_picture trigger picture_link templating #}
-{% comment %}
-App djangocms_picture assumes external image must be linked to,
-but we want to allow external image to be displayed without link.
-{% endcomment %}
-{# FAQ: picture_link is instance link_url or link_page_id or external_picture #}
-{# https://github.com/django-cms/djangocms-picture/blob/3.0.0/djangocms_picture/models.py#L269-L276 #}
-{# So picture with external image is not wrapped in a link #}
-{% block picture_link_start %}
- {% if instance.link_url or instance.link_page_id %}
- {% block picture_link %}
- {{ block.super }}
- {% endblock %}
- {% endif %}
-{% endblock %}
-
-{# So picture attributes can be added to picture with external image #}
-{% block picture_attributes %}
- {% if not instance.caption_text and not instance.link_url and not instance.link_page_id and not instance.child_plugin_instances %}
- {{ instance.attributes_str }}
- {% endif %}
-{% endblock %}
-
-{# So picture with external image is not wrapped in a link #}
-{% block picture_link_end %}
-{% endblock %}
+{# To allow "External image" to be rendered without a link #}
+{# FAQ: Template exists to avoid adding a new model field #}
+{# FAQ: Default template uses custom boolean context variables for logic #}
+{# FAQ: App djangocms_picture assumes that an image, if not explicitely linked, must link to itself. But sometimes, we want an image to just be an image, unlinked. #}
diff --git a/taccsite_cms/templates/djangocms_picture/no_link_to_image/picture.html b/taccsite_cms/templates/djangocms_picture/no_link_to_image/picture.html
new file mode 100644
index 000000000..5ab79b9f9
--- /dev/null
+++ b/taccsite_cms/templates/djangocms_picture/no_link_to_image/picture.html
@@ -0,0 +1,2 @@
+{# backwards-compatibility for temporary name #}
+{% extends "djangocms_picture/no_link_to_ext_image/picture.html" %}
diff --git a/taccsite_cms/templates/djangocms_picture/zoom_effect/picture.html b/taccsite_cms/templates/djangocms_picture/zoom_effect/picture.html
new file mode 100644
index 000000000..f1f398d5c
--- /dev/null
+++ b/taccsite_cms/templates/djangocms_picture/zoom_effect/picture.html
@@ -0,0 +1,5 @@
+{% extends "djangocms_picture/default/picture.html" %}
+
+{# To zoom image on hover #}
+{# FAQ: Template exists to avoid adding a new model field #}
+{# FAQ: Default template uses custom boolean context variables for logic #}
diff --git a/taccsite_cms/templates/djangocms_picture/zoom_effect_no_link_to_ext_image/picture.html b/taccsite_cms/templates/djangocms_picture/zoom_effect_no_link_to_ext_image/picture.html
new file mode 100644
index 000000000..e94d37638
--- /dev/null
+++ b/taccsite_cms/templates/djangocms_picture/zoom_effect_no_link_to_ext_image/picture.html
@@ -0,0 +1,5 @@
+{% extends "djangocms_picture/default/picture.html" %}
+
+{# To combine `zoom_effect` and `no_link_to_image` %}
+{# SEE: taccsite_cms/templates/djangocms_picture/zoom_effect #}
+{# SEE: taccsite_cms/templates/djangocms_picture/no_link_to_image #}
diff --git a/taccsite_cms/templates/nav_search.raw.html b/taccsite_cms/templates/nav_search.raw.html
index c33f5fe68..73f896097 100644
--- a/taccsite_cms/templates/nav_search.raw.html
+++ b/taccsite_cms/templates/nav_search.raw.html
@@ -1,6 +1,6 @@
{# @var settings #}
-{% load static %}
+{% load static search_tags %}
@@ -10,7 +10,7 @@
-