Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: CMD-187 autocreate search page #885

Draft
wants to merge 37 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4515b85
feat: template & setting for Google search
wesleyboar Oct 22, 2024
2883e49
Merge branch 'main' into feat/template-and-setting-for-google-search
wesleyboar Oct 22, 2024
8607c90
fix: styles and fallback warning for Google Search
wesleyboar Oct 22, 2024
f3dc4dc
enhance: move sarch CSS to its own file
wesleyboar Oct 22, 2024
3947bc2
enhance: rename search stylesheet and ID
wesleyboar Oct 22, 2024
d0d5e00
refactor: move css to top of template
wesleyboar Oct 22, 2024
b74e2a0
fix: missing default GOOGLE_SEARCH_ENGINE_ID
wesleyboar Oct 22, 2024
7c93b7e
Merge branch 'main' into feat/template-and-setting-for-google-search
wesleyboar Oct 23, 2024
f80ebc8
feat: search template is standard
wesleyboar Oct 23, 2024
5208bde
Merge branch 'main' into feat/template-and-setting-for-google-search
wesleyboar Oct 23, 2024
9457519
feat: convert search template into a search app
wesleyboar Oct 23, 2024
16d34c3
feat(search_page): slimmer views.py
wesleyboar Oct 23, 2024
da3a73a
feat(search): create page if it does not exist
wesleyboar Oct 23, 2024
5fb5a32
fix(search): restore breadcrumbs
wesleyboar Oct 23, 2024
1fef4f8
chore(search_page): remove now-unnecessary file
wesleyboar Oct 23, 2024
e8072e6
enhance(search_page): flag page as auto-generated
wesleyboar Oct 23, 2024
3291015
enhance(search): attach apphook to manage urls
wesleyboar Oct 23, 2024
ca11f07
style(search): polish
wesleyboar Oct 24, 2024
445010c
fix(search): searchbar not using curr. search path
wesleyboar Oct 24, 2024
8d812c8
chore(search): remove cruft
wesleyboar Oct 24, 2024
3793bb9
chore(search): reduce silly complexity of tag
wesleyboar Oct 24, 2024
92e9ecf
chore(search): remove cruft 2
wesleyboar Oct 24, 2024
adf6ad2
feat(search): auto isntall and setup
wesleyboar Oct 24, 2024
d9db279
feat(search): simplify an isolate markup
wesleyboar Oct 24, 2024
fda65e5
core(search): remove cruft markup
wesleyboar Oct 24, 2024
9d88be6
chore(search): simlify utils.py
wesleyboar Oct 24, 2024
148f661
style(search): new line
wesleyboar Oct 24, 2024
7cbb96e
refactor(search): polish
wesleyboar Oct 24, 2024
f57d4e1
refactor(search): rename a function
wesleyboar Oct 24, 2024
55b84da
style(search): ending slash for example path
wesleyboar Oct 24, 2024
3ee4fe1
style(search): ending slash for example path
wesleyboar Oct 24, 2024
b4d6ac3
Merge branch 'main' into feat/template-and-setting-for-google-search
wesleyboar Oct 25, 2024
0f16d38
Merge branch 'main' into feat/template-and-setting-for-google-search
wesleyboar Oct 26, 2024
ba2b091
Merge branch 'main' into feat/template-and-setting-for-google-search
wesleyboar Nov 1, 2024
959ba8e
Merge branch 'main' into feat/template-and-setting-for-google-search
wesleyboar Nov 14, 2024
7dd0402
Merge branch 'main' into feat/template-and-setting-for-google-search
wesleyboar Nov 14, 2024
a12a70f
Merge branch 'main' into feat/template-and-setting-for-google-search
wesleyboar Nov 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added apps/search_page/__init__.py
Empty file.
10 changes: 10 additions & 0 deletions apps/search_page/apps.py
Original file line number Diff line number Diff line change
@@ -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()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

This create_page function is run twice on website startup.

1. Initial Deploy on Existing Site (When App Has Not Yet Been Installed).

Internal Server Error. Re-deploy required.

Log

Note: treebeard is an application that manages pages.

Traceback (most recent call last):
  File "/usr/local/lib/python3.11/site-packages/django/db/backends/utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/psycopg/cursor.py", line 732, in execute
    raise ex.with_traceback(None)
psycopg.errors.UniqueViolation: duplicate key value violates unique constraint "cms_treenode_path_key"
DETAIL:  Key (path)=(0004) already exists.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/code/taccsite_cms/wsgi.py", line 16, in <module>
    application = get_wsgi_application()
                  ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/core/wsgi.py", line 12, in get_wsgi_application
    django.setup(set_prefix=False)
  File "/usr/local/lib/python3.11/site-packages/django/__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/usr/local/lib/python3.11/site-packages/django/apps/registry.py", line 124, in populate
    app_config.ready()
  File "/code/apps/search_page/apps.py", line 10, in ready
    create_page()
  File "/code/apps/search_page/utils.py", line 46, in create_page
    page = create_cms_page(
           ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/contextlib.py", line 81, in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/cms/api.py", line 183, in create_page
    page.set_tree_node(site=site, target=target_node, position=position)
  File "/usr/local/lib/python3.11/site-packages/cms/models/pagemodel.py", line 474, in set_tree_node
    self.node = TreeNode.add_root(instance=new_node)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/treebeard/mp_tree.py", line 626, in add_root
    return MP_AddRootHandler(cls, **kwargs).process()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/treebeard/mp_tree.py", line 344, in process
    newobj.save()
  File "/usr/local/lib/python3.11/site-packages/django/db/models/base.py", line 814, in save
    self.save_base(
  File "/usr/local/lib/python3.11/site-packages/django/db/models/base.py", line 877, in save_base
    updated = self._save_table(
              ^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/base.py", line 1020, in _save_table
    results = self._do_insert(
              ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/base.py", line 1061, in _do_insert
    return manager._insert(
           ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/manager.py", line 87, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/query.py", line 1805, in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 1822, in execute_sql
    cursor.execute(sql, params)
  File "/usr/local/lib/python3.11/site-packages/django/db/backends/utils.py", line 67, in execute
    return self._execute_with_wrappers(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/backends/utils.py", line 80, in _execute_with_wrappers
    return executor(sql, params, many, context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/backends/utils.py", line 84, in _execute
    with self.db.wrap_database_errors:
  File "/usr/local/lib/python3.11/site-packages/django/db/utils.py", line 91, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/usr/local/lib/python3.11/site-packages/django/db/backends/utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/psycopg/cursor.py", line 732, in execute
    raise ex.with_traceback(None)
django.db.utils.IntegrityError: duplicate key value violates unique constraint "cms_treenode_path_key"
DETAIL:  Key (path)=(0004) already exists.
unable to load app 0 (mountpoint='') (callable not found or import error)
*** no app loaded. going in full dynamic mode ***
uWSGI running as root, you can use --uid/--gid/--chroot options
*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** 
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 1)
spawned uWSGI worker 1 (pid: 16, cores: 20)
spawned uWSGI worker 2 (pid: 24, cores: 20)
spawned uWSGI worker 3 (pid: 37, cores: 20)
spawned uWSGI worker 4 (pid: 66, cores: 20)
--- no python application found, check your startup logs for errors ---
[pid: 66|app: -1|req: -1/1] 129.114.111.125 () {68 vars in 2061 bytes} [Fri Oct 25 10:01:21 2024] GET /admin/cms/page/?language=en => generated 21 bytes in 0 msecs (HTTP/2.0 500) 2 headers in 83 bytes (0 switches on core 2)
--- no python application found, check your startup logs for errors ---
[pid: 16|app: -1|req: -1/2] 129.114.111.125 () {66 vars in 1951 bytes} [Fri Oct 25 10:01:26 2024] GET / => generated 21 bytes in 0 msecs (HTTP/2.0 500) 2 headers in 83 bytes (0 switches on core 1)

2. Re-Deploy.

Page is created and works. Logs note existing page.

Log
[DJANGO] INFO 2024-10-25 10:03:53,120 UTC utils portal.apps.search_page.utils.create_page:62: Found existing search page at "search"

3. Delete Search Page. Deploy Again.

Page is created and works. Logs note existing page.

Log
[DJANGO] INFO 2024-10-25 10:03:53,120 UTC utils portal.apps.search_page.utils.create_page:62: Found existing search page at "search"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 changes: 11 additions & 0 deletions apps/search_page/cms_apps.py
Original file line number Diff line number Diff line change
@@ -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']
134 changes: 134 additions & 0 deletions apps/search_page/static/search_page/css/google-search.css
Original file line number Diff line number Diff line change
@@ -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;
}

}
25 changes: 25 additions & 0 deletions apps/search_page/templates/search_page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{% extends "base.html" %}
{% load static cms_tags %}

{% block css %}
{{ block.super }}
<link id="css-search" rel="stylesheet" href="{% static 'search_page/css/google-search.css' %}">
{% endblock css %}

{% block app_content %}
<div class="row">
<div class="col col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
<h1>{% page_attribute "page_title" %}</h1>
</div>
</div>

{% if settings.GOOGLE_SEARCH_ENGINE_ID %}
<div id="google-search">
<script async src="https://cse.google.com/cse.js?cx={{ settings.GOOGLE_SEARCH_ENGINE_ID }}">
</script>
<div class="gcse-searchresults-only"></div>
</div>
{% else %}
<p class="c-message c-message--scope-section c-message--type-warning">Please inform your website administrator to verify a <code>GOOGLE_SEARCH_ENGINE_ID</code> is set for this website.</p>
{% endif %}
{% endblock app_content %}
8 changes: 8 additions & 0 deletions apps/search_page/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.urls import path
from . import views

app_name = 'apps.search_page'

urlpatterns = [
path('', views.SearchPageView, name='search'),
]
64 changes: 64 additions & 0 deletions apps/search_page/utils.py
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions apps/search_page/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from django.shortcuts import render

def SearchPageView(request):
return render(request, 'search_page.html')
8 changes: 6 additions & 2 deletions taccsite_cms/_settings/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -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-{}'
Expand All @@ -34,5 +37,6 @@
ALDRYN_SEARCH_REGISTER_APPHOOK = True

_INSTALLED_APPS = [
'haystack', # search index
'haystack', # ElasticSearch
# 'search_page' # Google Search
]
2 changes: 2 additions & 0 deletions taccsite_cms/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -852,4 +853,5 @@ def get_subdirs_as_module_names(path):
'PORTAL_SOCIAL_SHARE_PLATFORMS',
'PORTAL_SEARCH_PATH',
'PORTAL_SEARCH_QUERY_PARAM_NAME',
'GOOGLE_SEARCH_ENGINE_ID',
]
5 changes: 3 additions & 2 deletions taccsite_cms/settings_custom.example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions taccsite_cms/templates/nav_search.raw.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{# @var settings #}

{% load static %}
{% load static search_tags %}
<!-- FAQ: This template loads independently at a unique url (see `urls.py`)
so Portal and User Guide can render this markup into their markup. -->

Expand All @@ -14,7 +14,7 @@
<link rel="stylesheet" href="{% static 'site_cms/css/build/element.tacc-search-bar.css' %}">
{% endif %}

<form part="form" method="get" action="{{ settings.PORTAL_SEARCH_PATH }}">
<form part="form" method="get" action="{% search_page_url %}">
<label for="header-search" class="u-hide--visually" part="label">
Search
</label>
Expand Down
10 changes: 10 additions & 0 deletions taccsite_cms/templatetags/search_tags.py
Original file line number Diff line number Diff line change
@@ -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