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/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/settings.py b/taccsite_cms/settings.py
index af5eb49c4..dd133ffa5 100644
--- a/taccsite_cms/settings.py
+++ b/taccsite_cms/settings.py
@@ -481,6 +481,7 @@ 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
@@ -862,4 +863,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 70082e72f..590d53a4b 100644
--- a/taccsite_cms/settings_custom.example.py
+++ b/taccsite_cms/settings_custom.example.py
@@ -96,10 +96,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/templates/nav_search.raw.html b/taccsite_cms/templates/nav_search.raw.html
index 5b7163864..95cb8b5f2 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 %}
@@ -14,7 +14,7 @@
{% endif %}
-