From cd06d9237984ae7a0914c4f9d6a6755945ec84d2 Mon Sep 17 00:00:00 2001 From: Wesley B <62723358+wesleyboar@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:28:32 -0500 Subject: [PATCH 1/3] refactor(search): rebuild feature branch on main via cherry-pick (#1215) ## Overview Rebuilds the search feature on current `main` instead of stale `release/v4.36.X`. > [!WARNING] > First run on a fresh database will crash `manage.py migrate` itself, because `apps/search_page/apps.py`'s `ready()` hook queries `cms_page` before it exists. Workaround: set `SEARCH_PAGE_AUTO_SETUP = False`, migrate, then set it back to `True` and restart. [Details.](https://github.com/TACC/Core-CMS/pull/885#pullrequestreview-2395629276) > [!IMPORTANT] > Should instead: > - [Use a custom migration.](https://github.com/TACC/Core-CMS/pull/885#pullrequestreview-2408907691) > - (and/or) Create a [Core-CMS plugin](https://github.com/search?q=org%3ATACC+%22Core-CMS-Plugin%22&type=repositories) (from [TACC/Django-App](https://github.com/TACC/Django-App)). ## Related - replaces #1129 ## Changes - **rebuilt** branch from `main` + cherry-picked search-only commits - **dropped** unrelated inherited commits (favicons, breadcrumbs, image-zoom, etc.) ## Testing / UI See #1129. --- apps/search_page/__init__.py | 0 apps/search_page/apps.py | 10 ++ apps/search_page/cms_apps.py | 11 ++ .../static/search_page/css/google-search.css | 152 ++++++++++++++++++ apps/search_page/templates/search_page.html | 25 +++ apps/search_page/urls.py | 8 + apps/search_page/utils.py | 64 ++++++++ apps/search_page/views.py | 4 + taccsite_cms/_settings/search.py | 8 +- taccsite_cms/settings/settings.py | 2 + .../settings/settings_custom.example.py | 5 +- taccsite_cms/templates/nav_search.raw.html | 4 +- taccsite_cms/templatetags/search_tags.py | 10 ++ taccsite_cms/urls_custom.example.py | 1 + 14 files changed, 298 insertions(+), 6 deletions(-) create mode 100644 apps/search_page/__init__.py create mode 100644 apps/search_page/apps.py create mode 100644 apps/search_page/cms_apps.py create mode 100644 apps/search_page/static/search_page/css/google-search.css create mode 100644 apps/search_page/templates/search_page.html create mode 100644 apps/search_page/urls.py create mode 100644 apps/search_page/utils.py create mode 100644 apps/search_page/views.py create mode 100644 taccsite_cms/templatetags/search_tags.py 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..6d9bec42d --- /dev/null +++ b/apps/search_page/static/search_page/css/google-search.css @@ -0,0 +1,152 @@ +#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--light); + + /* to undo core-styles.base */ + /* https://github.com/TACC/Core-Styles/blob/8ab89d3/src/lib/_imports/elements/links.css */ + &:not([href]) { + opacity: 1; + pointer-events: auto; + } + } + + + + /* 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--light); + 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; + } + & a.gs-title b { + color: currentColor; + } + & .gs-result .gs-title:not(a):hover { + /* to remove duplicate underline */ + /* (fix Google's default_v6+en.css bug `.gs-result .gs-title:hover`) */ + text-decoration: auto; + } + + /* (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--light); + 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--light); + text-decoration: none; + text-decoration-thickness: var(--global-border-width--normal); + text-underline-offset: 0.2em; + } + svg.gcsc-find-more-on-google-magnifier { + fill: currentColor; + } + + & .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/settings.py b/taccsite_cms/settings/settings.py index cc1eed190..7db5962e6 100644 --- a/taccsite_cms/settings/settings.py +++ b/taccsite_cms/settings/settings.py @@ -483,6 +483,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 @@ -762,4 +763,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/settings_custom.example.py b/taccsite_cms/settings/settings_custom.example.py index 2cb9f05ae..24c0ca696 100644 --- a/taccsite_cms/settings/settings_custom.example.py +++ b/taccsite_cms/settings/settings_custom.example.py @@ -100,10 +100,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 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 @@ -
+ 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 diff --git a/taccsite_cms/urls_custom.example.py b/taccsite_cms/urls_custom.example.py index d42426372..f1c15bcb1 100644 --- a/taccsite_cms/urls_custom.example.py +++ b/taccsite_cms/urls_custom.example.py @@ -3,6 +3,7 @@ custom_urls = [ # CUSTOM APP re_path(r'^custom_test/', include('apps.custom_example.urls', namespace='custom_test')), + # DJANGOCMS_BLOG re_path(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')), ] From e5b17086d43624193dc2ed0e5b031d097f33c476 Mon Sep 17 00:00:00 2001 From: Wesley B <62723358+wesleyboar@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:10:55 -0500 Subject: [PATCH 2/3] fix(search): gray header background gaps (#1218) ## Overview Fixes gray background gaps on wide screens beside the search stats/sorting bar, and prevents that bar from shrinking the content width. ## Related - [WP-1313 comment's bug #1](https://tacc-main.atlassian.net/browse/WP-1313?focusedCommentId=40743) - required to later undo https://github.com/TACC/Core-CMS-Custom/pull/553 ## Changes - **fixed** `.gsc-wrapper` / `.gsc-above-wrapper-area` shrinking content width by unsetting `max-width` - **fixed** fake background gaps on wide screens by using a full `box-shadow` spread with `clip-path` to contain overflow ## Testing 1. `make start` 2. Visit the search page at a wide viewport (e.g. 1920px) 3. Verify the gray background behind the stats/sorting row has no gaps and content width is unaffected ## UI | before | after | | - | - | | before | after | --- .../static/search_page/css/google-search.css | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/search_page/static/search_page/css/google-search.css b/apps/search_page/static/search_page/css/google-search.css index 6d9bec42d..d2f91f199 100644 --- a/apps/search_page/static/search_page/css/google-search.css +++ b/apps/search_page/static/search_page/css/google-search.css @@ -13,14 +13,29 @@ + /* SEARCH WRAPPERS */ + + /* To not shrink content width */ + /* To avoid gaps in fake background on wide screen */ + .gsc-wrapper, + .gsc-above-wrapper-area { + max-width: unset; + } + + + /* TABLE OF SEARCH STATS & SORTING OPTIONS */ - /* To remove border from search analytics, add gray background */ + /* To replace border with fake 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); + background-color: var(--bkgd-color); /* the real bkgd */ + box-shadow: + 100vw 0 0 100vw var(--bkgd-color), + -100vw 0 0 100vw var(--bkgd-color); /* the extended bkgd */ + clip-path: inset(0 -100vw); /* to hide vertical overflow of extended bkgd */ + border-bottom: unset; } From 4ec5549e997aba11a260dffac84e4aad546dfb7b Mon Sep 17 00:00:00 2001 From: Wesley B <62723358+wesleyboar@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:57:16 -0500 Subject: [PATCH 3/3] refactor(search): import Google search styles via CDN (#1219) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Overview Replaces the local Google search styles with a CDN `@import` back to Core-CMS-Custom, now that it holds the actual styles. ## Related - [TACC/Core-CMS-Custom#555](https://github.com/TACC/Core-CMS-Custom/pull/555) ## Changes - **replaced** `apps/search_page/static/search_page/css/google-search.css` contents with a CDN `@import` ## Testing Skipped. ## UI No visual change — styles are byte-identical to before, just sourced via CDN. --- .../static/search_page/css/google-search.css | 168 +----------------- 1 file changed, 1 insertion(+), 167 deletions(-) diff --git a/apps/search_page/static/search_page/css/google-search.css b/apps/search_page/static/search_page/css/google-search.css index d2f91f199..6b0715fda 100644 --- a/apps/search_page/static/search_page/css/google-search.css +++ b/apps/search_page/static/search_page/css/google-search.css @@ -1,167 +1 @@ -#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; - } - - - - /* SEARCH WRAPPERS */ - - /* To not shrink content width */ - /* To avoid gaps in fake background on wide screen */ - .gsc-wrapper, - .gsc-above-wrapper-area { - max-width: unset; - } - - - - /* TABLE OF SEARCH STATS & SORTING OPTIONS */ - - /* To replace border with fake background */ - & .gsc-above-wrapper-area { - --bkgd-color: var(--global-color-primary--x-light); - - background-color: var(--bkgd-color); /* the real bkgd */ - box-shadow: - 100vw 0 0 100vw var(--bkgd-color), - -100vw 0 0 100vw var(--bkgd-color); /* the extended bkgd */ - clip-path: inset(0 -100vw); /* to hide vertical overflow of extended bkgd */ - - 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--light); - - /* to undo core-styles.base */ - /* https://github.com/TACC/Core-Styles/blob/8ab89d3/src/lib/_imports/elements/links.css */ - &:not([href]) { - opacity: 1; - pointer-events: auto; - } - } - - - - /* 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--light); - 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; - } - & a.gs-title b { - color: currentColor; - } - & .gs-result .gs-title:not(a):hover { - /* to remove duplicate underline */ - /* (fix Google's default_v6+en.css bug `.gs-result .gs-title:hover`) */ - text-decoration: auto; - } - - /* (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--light); - 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--light); - text-decoration: none; - text-decoration-thickness: var(--global-border-width--normal); - text-underline-offset: 0.2em; - } - svg.gcsc-find-more-on-google-magnifier { - fill: currentColor; - } - - & .gcsc-find-more-on-google:hover { - text-decoration-line: underline; - text-decoration-style: solid; - } - - & .gcsc-branding-img-noclear { - vertical-align: unset; - top: 1px; - } - -} +@import url("https://cdn.jsdelivr.net/gh/TACC/Core-CMS-Custom@f2ff5062/generic_assets/css/google-search.css");