Skip to content
Open
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
e2f7c53
feat: #999 only cms admin edit static placeholder
wesleyboar Feb 11, 2026
f52b8b2
feat: #999 add 'header-logo' placeholder
wesleyboar Feb 11, 2026
ead8569
refactor: simplify code and format docstring
wesleyboar Feb 11, 2026
53cfef6
refactor!: header-logo → header-content
wesleyboar Mar 5, 2026
e1d19d9
Merge branch 'main' into feat/GH-999-let-cms-admin-edit-header
wesleyboar Mar 5, 2026
9fef1c3
Merge branch 'main' into feat/GH-999-let-cms-admin-edit-header
wesleyboar Apr 2, 2026
7459528
docs: heaader_logo comments
wesleyboar Apr 2, 2026
d156af6
refactor: legible header content parsing logic
wesleyboar Apr 2, 2026
d5b2a36
fix: GH-999 CMS admin can change header logo (#1142)
wesleyboar Apr 21, 2026
c7f4454
Merge branch 'main' into feat/GH-999-let-cms-admin-edit-header
wesleyboar Jun 4, 2026
15a1c2d
docs(gh-999): add editable header implementation plan
wesleyboar Jun 4, 2026
053fdd7
feat: GH-999 CMS admin can change header logo - via plugin (#1171)
wesleyboar Jun 11, 2026
79f14b3
chore: delete cruft
wesleyboar Jun 11, 2026
c42f211
Merge branch 'main' into feat/GH-999-let-cms-admin-edit-header
wesleyboar Jun 11, 2026
15fc05c
feat(gh-999): default Header logo form values on add (#1183)
wesleyboar Jun 16, 2026
07c0c2a
fix(gh-999): align header logo markup with settings path (#1184)
wesleyboar Jun 17, 2026
27fa19d
feat(gh-999): default Header logo alt text on add
wesleyboar Jun 17, 2026
96ed5cd
fix(gh-999): restrict Header logo plugin to header-content
wesleyboar Jun 17, 2026
7070552
Merge branch 'main' into feat/GH-999-let-cms-admin-edit-header
wesleyboar Jun 17, 2026
3ee0438
refactor(gh-999): centralize header-content placeholder slot
wesleyboar Jun 17, 2026
fc6bb82
chore: whitespace
wesleyboar Jun 17, 2026
1937592
chore: plugin order (logo plugin not deprecated)
wesleyboar Jun 17, 2026
fc7eb38
fix: revert settings test change
wesleyboar Jun 17, 2026
0d4fd91
refactor: rename a constant
wesleyboar Jun 17, 2026
42a801a
docs: gh-999
wesleyboar Jun 17, 2026
c85cc71
feat(gh-999): name static placeholders in Structure sidebar
wesleyboar Jun 17, 2026
2cabc1c
chore: whitespace
wesleyboar Jun 17, 2026
e56e434
Merge branch 'main' into feat/GH-999-let-cms-admin-edit-header
wesleyboar Jun 17, 2026
b737a87
Merge branches 'feat/GH-999-let-cms-admin-edit-header' and 'feat/GH-9…
wesleyboar Jun 17, 2026
34988a7
deps: @tacc/core-styles v2.57.1
wesleyboar Jun 17, 2026
440ceba
deps: @tacc/core-styles v2.57.2
wesleyboar Jun 18, 2026
606a0eb
docs(GH-999): document img-only header logo CMS limitation
wesleyboar Jun 18, 2026
559dbf7
fix(GH-1185): apply portal-logo class for img-only header logos
wesleyboar Jun 18, 2026
9beaf61
docs: clean up "Known Limitations"
wesleyboar Jun 18, 2026
a4f1a43
Merge branch 'fix/GH-1185-header-logo-portal-logo-class' of github.co…
wesleyboar Jun 18, 2026
1d01d08
chore: reduce diff
wesleyboar Jun 18, 2026
9ff3bc2
fix: expected plugins in header logo placeholder
wesleyboar Jun 18, 2026
958e340
fix: lost `portal-logo` class
wesleyboar Jun 18, 2026
74241ec
chore: delete outdated tests
wesleyboar Jun 18, 2026
56931f1
refactor: rename plugin
wesleyboar Jun 18, 2026
f878b60
chore(test): revert unused _FakePicture attributes_str helper
wesleyboar Jun 18, 2026
97d3382
Merge branch 'main' into feat/GH-999-let-cms-admin-edit-header
wesleyboar Jun 18, 2026
d8d6114
refactor(gh-999): simplify header logo rendering (#1189)
wesleyboar Jun 19, 2026
7450f3f
refactor: rename a const
wesleyboar Jun 19, 2026
a6b9209
refactor: rename a const
wesleyboar Jun 19, 2026
4c79ddf
docs: clarity
wesleyboar Jun 19, 2026
cead748
docs: remove cruft
wesleyboar Jun 19, 2026
950ed99
fix(header-logo): always set id on Header logo plugin markup
wesleyboar Jun 19, 2026
d361e90
Merge branch 'main' into feat/GH-999-let-cms-admin-edit-header
wesleyboar Jul 1, 2026
1d96a54
Merge branch 'main' into feat/GH-999-let-cms-admin-edit-header
wesleyboar Jul 1, 2026
39a8386
Merge branch 'main' into feat/GH-999-let-cms-admin-edit-header
wesleyboar Jul 3, 2026
b41b1a6
Merge branch 'main' into feat/GH-999-let-cms-admin-edit-header
wesleyboar Jul 21, 2026
791d412
Merge branch 'main' into feat/GH-999-let-cms-admin-edit-header
wesleyboar Jul 27, 2026
0d31361
Merge branch 'main' into feat/GH-999-let-cms-admin-edit-header
wesleyboar Jul 28, 2026
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.
6 changes: 6 additions & 0 deletions djangocms_tacc_header_logo/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class TaccsiteHeaderLogoConfig(AppConfig):
name = 'djangocms_tacc_header_logo'
verbose_name = 'Header logo'
29 changes: 29 additions & 0 deletions djangocms_tacc_header_logo/cms_plugins.py

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Create header logo plugin (extends Picture) so we can control:

  • requirements (via this file)
  • defaults (via forms.py)

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from cms.plugin_pool import plugin_pool
from django.utils.translation import gettext_lazy as _

from djangocms_picture.cms_plugins import PicturePlugin
from djangocms_picture.models import Picture

from .forms import TaccsiteHeaderLogoForm

HEADER_LOGO_LINK_CLASS = 'navbar-brand'


@plugin_pool.register_plugin
class TaccsiteHeaderLogoPlugin(PicturePlugin):
"""
Components > "Header logo" Plugin
"""
model = Picture
form = TaccsiteHeaderLogoForm
module = 'TACC Site'
name = _('Header logo')

def render(self, context, instance, placeholder):
if not instance.attributes:
instance.attributes = {}

existing_class = instance.attributes.get('class', '')
instance.attributes['class'] = f'{HEADER_LOGO_LINK_CLASS} {existing_class}'.strip()

return super().render(context, instance, placeholder)
1 change: 1 addition & 0 deletions djangocms_tacc_header_logo/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
HEADER_LOGO_PLACEHOLDER_NAME = 'header-logo'
38 changes: 38 additions & 0 deletions djangocms_tacc_header_logo/forms.py

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Set default configuration of a Picture-as-Header-logo.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from cms.models import Page
from django.conf import settings
from django.contrib.sites.models import Site
from djangocms_picture.forms import PictureForm

HEADER_LOGO_PICTURE_TEMPLATE = 'portal_logo'


def default_home_page():
site = Site.objects.get_current()
return Page.objects.filter(is_home=True, node__site=site).first()


class TaccsiteHeaderLogoForm(PictureForm):
"""
Defaults for new Header logo plugins only (admin add form).

On a TACC/Core-Portal instance, defaults to Portal logo picture template.
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
return

if settings.PORTAL_IS_TACC_CORE_PORTAL:
self.initial.setdefault('template', HEADER_LOGO_PICTURE_TEMPLATE)

home = default_home_page()
if home is not None:
self.initial.setdefault('link_page', home.pk)

self.initial.setdefault('use_no_cropping', True)
self.initial.setdefault('use_automatic_scaling', False)

attributes = dict(self.initial.get('attributes') or {})
attributes.setdefault('alt', 'Project logo')
self.initial['attributes'] = attributes
30 changes: 30 additions & 0 deletions djangocms_tacc_header_logo/render.py

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Allow us to capture logo output so we can:

  • see if logo is present in placeholder
  • render logo only if it is available

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from cms.models import StaticPlaceholder
from cms.plugin_rendering import ContentRenderer

from djangocms_tacc_header_logo.cms_plugins import TaccsiteHeaderLogoPlugin
from djangocms_tacc_header_logo.constants import HEADER_LOGO_PLACEHOLDER_NAME


def render(request, context):
"""
Render published TaccsiteHeaderLogoPlugin from header-logo (logo-only fragment).
"""
static_ph = StaticPlaceholder.objects.filter(code=HEADER_LOGO_PLACEHOLDER_NAME).first()
if not static_ph:
return ''

placeholder = static_ph.public
placeholder.is_static = True

renderer = ContentRenderer(request)
language = renderer.request_language

for plugin in renderer.get_plugins_to_render(placeholder, language, template=None):
if plugin.plugin_type == TaccsiteHeaderLogoPlugin.__name__:
return renderer.render_plugin(
plugin,
context,
placeholder,
editable=False,
)
return ''
67 changes: 67 additions & 0 deletions docs/gh-999-editable-header-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# GH-999: Editable header

Related:

- [#999](https://github.com/TACC/Core-CMS/issues/999)
- [#1083](https://github.com/TACC/Core-CMS/pull/1083)

## Status as of 2026-06

| State | Description |
| ------------------- | --------------------------------- |
| one placeholder | `header-logo` static placeholder |
| if plugin is added | `TaccsiteHeaderLogoPlugin` is only option |
| if plugin not added | renders settings-based logo |

The branding and navs and search are still settings-based.

## Known Limitations

| Issue | Summary | Why Allow This? |
| --- | --- | --- |
| [#1185](https://github.com/TACC/Core-CMS/issues/1185) | Header logo without a link is **missing** `class="portal-logo"` on `<img>`. | Default form links logo to home, so typical sites are unaffected. |

## How to Add Other Header Plugins

Requirements:

- (for [CMS editor UX](#cms-editor-ux)) plugins render where `{% static_placeholder %}` sits
- (to skip [Failed Ideas](#failed-ideas)) pick one option from below

### A. Wrapper Plugin

| Pros | Cons |
| ------------------------------ | ---------------------------------------------- |
| One placeholder | Extra container plugin |
| Layout management consolidated | Build/Maintain wrapper |
| | Must deprecate or migrate legacy `header-logo` |

### B. Multiple Placeholders

| Pros | Cons |
| ---------------------------------- | ---------------------------------- |
| Typical CMS usage | Several placeholders to configure |
| | Layout management not consolidated |
| Can keep using `header-logo` as is | |

## CMS Editor UX

Requirements:

- Static placeholder always visible in Structure mode.
- Edit plugin: auto-refreshes page preview.
- Add/Delete plugin: auto-refreshes page preview.
- Structure mode button toggles render of Structure sidebar.

## Failed Ideas

| Idea | Problem |
| -------------------------------------------- | --------------------------------------------------- |
| One slot + custom `render_`* in many regions | Preview/Structure break; Picture edits need refresh |
| Nest plugins under logo but render elsewhere | Preview/Structure break; Picture edits need refresh |
| `static_placeholder` away from visible logo | Bad WYSIWYG; toolbar/Structure bugs |
| Per-region render registry without A or B | Publish-only; bad edit UX |
| Prematurely code Option A | Two plugins for one logo |
| `cache = False` only | Doesn’t fix render vs placeholder split |
| Draft in `render_`*, placeholder elsewhere | Image swap lag; duplicate surfaces |
| Edit-only placeholder at top of header | Structure JS errors; no real preview |
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
},
"homepage": "https://github.com/TACC/Core-CMS",
"devDependencies": {
"@tacc/core-styles": "^2.57.0"
"@tacc/core-styles": "^2.57.2"

@wesleyboar wesleyboar Jun 18, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

To fix .navbar-brand edge cases which a CMS editor can create.

}
}
1 change: 1 addition & 0 deletions taccsite_cms/_settings/djangocms_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
]
DJANGOCMS_PICTURE_TEMPLATES = [
('no_link_to_ext_image', _('Do not link to external image')),
('portal_logo', _('Portal logo')),

@wesleyboar wesleyboar Jun 17, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

To load taccsite_cms/templates/djangocms_picture/portal_logo/picture.html, so .portal-logo class is used (which triggers a style that is only relevant for Core-Portal).

]

########################
Expand Down
31 changes: 28 additions & 3 deletions taccsite_cms/management/commands/util.py

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

To no longer let basic CMS editor edit placeholders.1

Footnotes

  1. I auto-added this permission before I realized I should not.

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,25 @@ def add_perm(group, app_label, model_name, perm_name):
else:
group.permissions.add( Permission.objects.get( name=perm_name ))

def del_perm(group, app_label, model_name, perm_name):
"""
Delete specific permission from a given group
"""
logger.debug(f'Removing permission ({app_label}.{model_name}) "{perm_name}"')
if app_label and model_name:
model = model_name.lower().replace(' ', '')
content_type = ContentType.objects.get(
app_label=app_label,
model=model
)
group.permissions.remove(
Permission.objects.get(
name=perm_name,
content_type=content_type
)
)
else:
group.permissions.remove(Permission.objects.get(name=perm_name))


# Page
Expand All @@ -44,9 +63,15 @@ def let_view_page_and_structure(group):
add_perm(group, 'cms', 'page', 'Can change page')

add_perm(group, 'cms', 'placeholder', 'Can use Structure mode')
# HELP: Not necessary on TACC (as of Core-CMS v4.17.1)
# Is necessary on WTCS (as of Core-CMS v4.20.2)
add_perm(group, 'cms', 'static placeholder', 'Can change static placeholder')
# To delete undesired permission from sites that still have it:
# ```py
# add_perm(group, 'cms', 'static placeholder', 'Can change static placeholder')
# ```
# HELP: Should "Sitewide Content Manager" keep this perm?
# SEE: https://weteachcs.org/admin/auth/group/9/change/
# FAQ: Only superuser may edit static placeholders (e.g. footer-content)
# TODO: After this is deployed on all sites once, delete this code
del_perm(group, 'cms', 'static placeholder', 'Can change static placeholder')



Expand Down
24 changes: 22 additions & 2 deletions taccsite_cms/settings/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@ def gettext(s): return s
# TACC CMS Plugins
'djangocms_tacc_image_gallery',
'djangocms_tacc_system_monitor',
'djangocms_tacc_header_logo',

# TACC CMS Plugins - DECPRECATED
'taccsite_cms.contrib.taccsite_blockquote',
Expand Down Expand Up @@ -563,8 +564,26 @@ def get_subdirs_as_module_names(path):
},
}

from djangocms_tacc_header_logo.constants import HEADER_LOGO_PLACEHOLDER_NAME

CMS_PERMISSION = True
CMS_PLACEHOLDER_CONF = {}
CMS_PLACEHOLDER_CONF = {
None: {
'excluded_plugins': ['TaccsiteHeaderLogoPlugin'],
},
HEADER_LOGO_PLACEHOLDER_NAME: {
'name': _('Header logo'),
'plugins': ['TaccsiteHeaderLogoPlugin'],
'excluded_plugins': [],
'limits': {
'global': 1,
'TaccsiteHeaderLogoPlugin': 1,
},
},
Comment on lines +571 to +581

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Name the placeholder and limit its content to a header logo.

'footer-content': {
'name': _('Footer content'),
},
}

THUMBNAIL_HIGH_RESOLUTION = True
THUMBNAIL_PROCESSORS = (
Expand Down Expand Up @@ -673,7 +692,7 @@ def get_subdirs_as_module_names(path):
if 'BRANDING' not in locals():
BRANDING = False

# For header_logo.html
# For header_logo_via_settings.html
deprecated_SETTINGS_EXPORT += ['LOGO']
if 'LOGO' not in locals():
LOGO = False
Expand Down Expand Up @@ -726,6 +745,7 @@ def get_subdirs_as_module_names(path):
'DEBUG',
'GOOGLE_ANALYTICS_PROPERTY_ID',
'GOOGLE_ANALYTICS_PRELOAD',
'HEADER_LOGO_PLACEHOLDER_NAME',
'TACC_CORE_STYLES_VERSION',
'PORTAL_BRANDING',
'PORTAL_LOGO',
Expand Down
3 changes: 3 additions & 0 deletions taccsite_cms/templates/djangocms_picture/default/picture.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@
{# /TACC #}
{% endblock %}
{# /TACC #}
{# TACC (allow attributes to be force-added to <img>): #}
{% block picture_attributes_img %}{% if picture_img_class %}class="{{ picture_img_class }}"{% endif %}{% endblock %}
{# /TACC #}
Comment on lines +62 to +64

@wesleyboar wesleyboar Jun 18, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This lets us force the class on <img> tag, because (as on any Picture with a link or caption) the class attribute is hoisted to the parent <a> or <figure>1.

Caution

If <img> has no link (<a>) then class is not hoisted, thus class="portal-logo" is not added. UI breaks.

Footnotes

  1. A long time ago, I wrote the hoisting logic to allow class-driven layout change (added via a CMS editor e.g. via "Align right"/.align-right) to apply to the Picture element (<img>) or its wrapper (<a>, <figure>).

@wesleyboar wesleyboar Jun 18, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Important

I accept this bug for now.1 I document this bug for now.

Footnotes

  1. Because the header logo always links to a home page.

>
{% endlocalize %}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{% extends "djangocms_picture/default/picture.html" %}
{% block picture_attributes_img %}class="portal-logo"{% endblock %}
6 changes: 4 additions & 2 deletions taccsite_cms/templates/header.html

@wesleyboar wesleyboar Jun 23, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The keystone code is in this file.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{# @var settings #}
{% load cms_tags %}

{# WARNING: Some markup is duplicated in other repositories #}
{# SEE: https://confluence.tacc.utexas.edu/x/LoCnCQ #}
Expand All @@ -22,7 +23,9 @@
{% endif %}
">
<!-- Portal Logo -->
{% include "header_logo.html" %}
{% static_placeholder settings.HEADER_LOGO_PLACEHOLDER_NAME or %}
{% include "header_logo_via_settings.html" %}
{% endstatic_placeholder %}

<!-- Navbar Accordian Toggle on Small Screens -->
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExpandTarget" aria-controls="navbarsExpandTarget" aria-expanded="false" aria-label="Toggle navigation">
Expand All @@ -44,7 +47,6 @@
{% endif %}
</div>
</nav>

{% endwith %}
{% endwith %}
{% endwith %}
Loading