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 %} -
+ diff --git a/taccsite_cms/templatetags/search_tags.py b/taccsite_cms/templatetags/search_tags.py new file mode 100644 index 000000000..2e8377367 --- /dev/null +++ b/taccsite_cms/templatetags/search_tags.py @@ -0,0 +1,10 @@ +from django import template +from cms.models import Page +from django.conf import settings +from apps.search_page.utils import get_page_url + +register = template.Library() + +@register.simple_tag +def search_page_url(): + return get_page_url() or settings.PORTAL_SEARCH_PATH