Skip to content

feat: GH-999 let CMS admin change header logo#1083

Open
wesleyboar wants to merge 48 commits into
mainfrom
feat/GH-999-let-cms-admin-edit-header
Open

feat: GH-999 let CMS admin change header logo#1083
wesleyboar wants to merge 48 commits into
mainfrom
feat/GH-999-let-cms-admin-edit-header

Conversation

@wesleyboar

@wesleyboar wesleyboar commented Feb 11, 2026

Copy link
Copy Markdown
Member

Overview

  • CMS admins edit header logo via new header-logo static placeholder.
  • Empty placeholder fallback is settings-driven logo.

Related

Changes

  • adds header-logo static placeholder and "Header logo" (Picture) plugin
  • updates header.html to wrap settings fallback in {% static_placeholder %}
  • updates add-form defaults1
  • limits header-logo to "Header logo"

Testing

Tip

Available to test on two CMS servers:

You're welcome to deploy and test on Core-Portal. Just delete the plugin instance when you're done (in case this PR is not merged).

  1. Empty header-logo: settings logo and nav unchanged.
  2. Add Header logo: default1 on new form only.
  3. Re-open plugin: saved values kept (defaults not re-applied).
  4. Header logo appears only in header-logo, not on page placeholders; one logo per slot.

UI

Note

UI, a little outdated: Header-Content → Header logo, restored id="header-logo".

Add.a.logo.mov

The "Plugin Importer" you might see in video is 3rd-party plugin cruft. The code to remove it is overkill.

the.Portal.logo.template.adds.portal-logo.class.mov
add.class.to.Picture.navbar-brand.is.always.present.mov
add.other.attribtues.to.Picture.still.works.and.migrates.to.container.mov
Only.header.content.allows.header.logo.mov

Footnotes

  1. id, link to homepage, use original-size image, alt "Project logo", Portal logo template when PORTAL_IS_TACC_CORE_PORTAL 2

@wesleyboar wesleyboar left a comment

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.

Before 53cfef6, I asked AI to review how to improve clarity:

AI Suggestions

1. header_logo.html — > Name the three sources

The @var and the three > branches don’t say where each > source comes from. A short > comment per branch helps:

  • After the load/var block, > add a single line describing > the three cases, e.g.
    {# Logo source: plugin_logo > (CMS Picture), settings.LOGO > (legacy), or settings.> PORTAL_LOGO. #}
  • Or add a one-line comment > above each of the three > blocks, e.g.
    {# 1. From CMS static > placeholder "header-logo" > (Picture plugin). #}
    {# 2. From settings.LOGO > (legacy tuple). #}
    {# 3. From settings.> PORTAL_LOGO (default). #}

That makes it obvious why > there are three branches and > what each represents.


2. header_logo.html — > Document expected shape of > plugin_logo

The first and third branches > use the same logo.* keys. To > make the contract explicit for > someone editing the tag or the > template, add a short comment > near the top, e.g.:

  • {# plugin_logo (when set) > must provide: link_href, > link_target, img_class, > img_file_src, is_remote, > img_crossorigin, img_alt_text > (same shape as PORTAL_LOGO). #}>

Then it’s clear that > plugin_logo is “a > PORTAL_LOGO-shaped dict.”


3. header_logo_tags.py > — Document the contract and > simplify alt

  • In the docstring, state that > the returned dict is > PORTAL_LOGO-shaped (or > “same keys as header_logo.html > expects for the plugin_logo / > PORTAL_LOGO branch”). That > ties the tag to the template > without reading both.

  • Build alt in two steps so > the fallback chain is easier > to read:

    • alt_from_attrs = (getattr> (instance, 'attributes', > None) or {}).get('alt')
    • alt = alt_from_attrs or > (getattr(picture, > 'default_alt_text', None) if > picture else None) or ''

    Same behavior, but the > intent (“attribute alt, then > picture default, then > empty”) is obvious.

  • Optionally add a one-line > comment above the return > { ... }: e.g. “Same keys as > header_logo.html expects > (PORTAL_LOGO shape).”


4. header_logo_tags.py > — Clarify “instance”

  • In the docstring, spell out > that instance is the > djangocms_picture plugin > instance (the one that has > picture, attributes, > link_target, and provides > img_src in context). That > way “instance” isn’t ambiguous.

5. picture.html — > Optional one-line “why”

The comment already says “pass > plugin_logo and use shared > header_logo.html.” If you want > the reason in one line, you > could add:

  • {# Single markup in > header_logo.html; we only > supply plugin_logo from the > Picture plugin. #}

Summary

  • Templates: Name the > three logo sources (plugin / > LOGO / PORTAL_LOGO) and > document the expected > plugin_logo shape (same as > PORTAL_LOGO).
  • Tag: Docstring = “builds > a PORTAL_LOGO-shaped dict from > the Picture plugin instance”; > simplify alt with one or two > intermediate variables; > optionally a one-line comment > above the returned dict.

That should make the flow (Picture → tag → plugin_logo → header_logo.html’s first branch) and the data contract clear for the next reader.

@wesleyboar

wesleyboar commented Feb 11, 2026

Copy link
Copy Markdown
Member Author

I do not yet unite all three ways to build a header logo, because this PR is clear and clean and isolated without refactor. Refactor can be done in isolation after this PR is tested and merged.

Update: Since 53cfef6, the new placeholder can take more than a logo (but doesn't do anything special besides add them to end of header).

Update: Since 053fdd7, placeholder only accepts "Header logo" (Picture) plugin.

@wesleyboar wesleyboar marked this pull request as ready for review February 11, 2026 18:55
@wesleyboar wesleyboar marked this pull request as draft February 11, 2026 18:55
Changed `header-…` static placeholder to accept more than just a logo, and render intelligently.

WARNING: Introduces magic…
- Enhanced the `_get_header_content_parsed` function to clarify the handling of logo and remaining plugins.
- Updated comments for better understanding of the logic flow.
- Fixed a typo in the variable name `is_picture_plugin`.
- Adjusted return statements to ensure consistent output structure.
@wesleyboar wesleyboar changed the title feat: #999 CMS admin can change header logo feat: GH-999 CMS admin can change header logo Apr 6, 2026
wesleyboar and others added 3 commits April 21, 2026 15:00
Fix misc. bugs in #1083.

| default logo | custom logo |
| - | - |
| <img width="1280" height="400" alt="edit header - default image"
src="https://github.com/user-attachments/assets/ef119dcc-e49a-460e-9853-06ef9a1521ee"
/> | <img width="1280" height="400" alt="edit header - custom logo"
src="https://github.com/user-attachments/assets/1759ecf7-458d-4ca2-bc26-1f3957973a54"
/> |

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Wesley B <wesleyboar@users.noreply.github.com>
Document header-content architecture, PR stack, template layout, and
testing for #1083 and follow-up orchestrator/branding PRs.

Relates-to #999

Co-authored-by: Cursor <cursoragent@cursor.com>
## Overview

(1) Adds **Header logo** as a thin Picture plugin on the
`header-content` static placeholder so superusers can change the portal
logo in CMS. (2) Empty placeholder still uses settings. (3) Core-Portal
`/cms/header/logo/markup/` returns the same published plugin output or
settings fallback.

## Related

- improves #1083
- supports #999

## Changes

- **added** `HeaderLogoPlugin`
- **added** logo-only render helper and template tag (Core-Portal markup
URL)
- **updated** header logo slot and Core-Portal markup template
- **renamed** settings fallback template
- **removed** custom header logo Picture/markup cruft
- **updated** GH-999 plan doc

## Testing

1. `make start` (or existing stack).
2. Empty `header-content`: settings logo on site nav and on
http://localhost:8000/cms/header/logo/markup/
3. Superuser: add **TACC Header → Header logo**, publish; verify Picture
output (link, srcset, etc. as configured) and default `#header-logo`
when Attributes have no `id`; placeholder editable in `?edit`
4. Core-Portal (or fetch markup URL with CMS session): published plugin
HTML matches site logo for that user
5. Non-superuser cannot edit static placeholders; `footer-content`
unchanged

## UI


https://github.com/user-attachments/assets/167d9579-13ca-4c7d-a5d2-e0c0074235ac


https://github.com/user-attachments/assets/c609d7cf-ef54-4cea-92fd-57a38d7e5ac4

## Notes

- Custom Core-CMS overrides of `header_logo_settings.html` must switch
to `header_logo_via_settings.html`.
- When `header-content` holds more than a logo (PR 2), site uses full
placeholder; markup URL stays logo-only via `render.py`.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread taccsite_cms/settings/settings.py Outdated
Comment thread taccsite_cms/settings/settings.py Outdated
Co-authored-by: Wesley B <62723358+wesleyboar@users.noreply.github.com>
## Overview

Pre-fills the Header logo plugin admin form when adding a new instance
so editors see home link, height, original-size cropping, and margin
defaults (PR #1083 to-do).

## Related

- supports #1083
- supports #999

## Changes

- **added** `HeaderLogoForm` with add-only defaults
- **updated** `HeaderLogoPlugin` to use the form
- **updated** GH-999 plan follow-up note (settings / Core-Styles parity
deferred)

## Testing

1. Delete Header logo from `header-content` if present.
2. Add **TACC Header → Header logo**; confirm form shows home page link,
height 50, use original image, Attributes class `mr-5`.
3. Save, publish; confirm logo renders with link and expected markup.
4. Edit existing plugin; confirm defaults are not re-applied.

## UI


https://github.com/user-attachments/assets/1679b110-7d9c-4dc4-88f6-aca81a74d52c


https://github.com/user-attachments/assets/37265f24-6808-44cb-b8f0-ff1f4762a67d
## Overview

Header logo plugin and settings fallback match Core-Portal link/img
classes. Portal logo picture template puts `portal-logo` on the image
(even if a link is set).

## Related

- supports #1083
- supports #999
- requires TACC/Core-Styles#648
- requires TACC/Core-Styles#649

## Changes

- **updated** header logo plugin render and settings fallback markup
- **added** Portal logo picture template and `picture_attributes_img` on
default Picture
- **updated** Header logo form defaults (Portal template when Core
Portal)
- **updated** `@tacc/core-styles` dev dep (temporary pin)

## Testing

1. Empty `header-content`: settings logo uses `navbar-brand` on `<a>`,
`portal-logo` on `<img>`.
2. With `PORTAL_IS_TACC_CORE_PORTAL`: add Header logo defaults to Portal
logo; published output has `portal-logo` on `<img>` and `navbar-brand`
on the link.
3. Core-Portal `/cms/header/logo/markup/` matches site logo for plugin
vs settings paths.

## UI


https://github.com/user-attachments/assets/4a186b0b-3625-4314-9154-2d6de6ffadc8


https://github.com/user-attachments/assets/dc629e0e-b4ec-4955-94a7-7c0b1cf91f84


https://github.com/user-attachments/assets/ec99e5e5-5d68-4db7-8e25-21ced4609bdf


https://github.com/user-attachments/assets/39368ab6-8995-4aec-83c7-ccf8b2d31962

## Notes

Bumped `@tacc/core-styles` to a published version after Core-Styles PRs
merge.
Set Attributes alt to "Project logo" for new Header logo plugins.
Exclude HeaderLogoPlugin from default placeholders; allow only in header-content CMS_PLACEHOLDER_CONF.
@wesleyboar wesleyboar changed the title feat: GH-999 CMS admin can change header logo feat: GH-999 let CMS admin change header logo Jun 17, 2026
wesleyboar added a commit to TACC/Core-Styles that referenced this pull request Jun 18, 2026
## Overview

If header logo is `.svg` image (no wrapper link `<a>`), it got giant.

## Related

- fixes edge case of TACC/Core-CMS#1083

## Testing & UI

Skipped. I did it. Promise. Sorry. Getting this PR done.

@wesleyboar wesleyboar left a comment

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.

Notes for reviewers, and one bug report for myself.

]
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).

Comment on lines +75 to +77
{# TACC (allow attributes to be force-added to <img>): #}
{% block picture_attributes_img %}{% endblock %}
{# /TACC #}

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.

If an image has a caption or a link, the class attribute is hoisted to the parent <a> or <figure>1, so this lets us force the class on <img>, that typically has no class attribute…

Caution

…Crap! If <img> has no link (<a>) then class is not hoisted, and… yup — tested it — something doesn't work: the class="portal-logo" is not added.

Footnotes

  1. This old template logic came from me, so class-based behavior added via a CMS editor (e.g. align-right) apply to the element that either is or has the image.

@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. I document this bug for now.

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.

Not as big a change a it looks. I basically moved it to ./header_logo_via_settings.html which this conditionally calls.

what file looks like, sans diff
{% load header_tags %}

{% render_header_logo as header_logo %}
{% if header_logo %}
  {{ header_logo }}
{% else %}
  {% include "header_logo_via_settings.html" %}
{% endif %}

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.

Just copied over taccsite_cms/templates/header_logo.html which itself conditionally calls this.

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.

Just a tag lib with one tag that render header logo, if any.

Comment thread package.json
"homepage": "https://github.com/TACC/Core-CMS",
"devDependencies": {
"@tacc/core-styles": "^2.57.0"
"@tacc/core-styles": "^2.57.2"

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.

Tweaked header .navbar-brand styles to account for edge cases a CMS editor can create.

HeaderLogoPlugin mirrors picture template hoist rules so portal-logo
lands on img with a single class attribute when attrs are not hoisted,
and via picture_img_class when they are.

Closes #1185
wesleyboar added a commit that referenced this pull request Jun 18, 2026
)

## Overview

Content editor groups should not be able to change sitewide static
placeholders (footer, future header slots). This removes that permission
when group setup runs, so existing sites drop it on the next deploy that
runs the helper.

## Related

- Relates-to #1171
- Split from #1083

## Changes

- **added** `del_perm` helper (mirror of `add_perm`)
- **updated** `let_view_page_and_structure` to revoke `Can change static
placeholder` instead of granting it

## Testing

1. On a dev DB, grant a content group `Can change static placeholder`
(or use a site that still has it).
2. Run the management command path that calls
`let_view_page_and_structure` for that group (same as existing group
setup).
3. Confirm the group no longer has `Can change static placeholder` in
Django admin.
4. Confirm superusers can still edit static placeholders.

## UI

No UI change.

## Notes

Includes a TODO to delete the revoke block after all sites have deployed
once. Open question in code: whether “Sitewide Content Manager” should
retain this permission.
wesleyboar added a commit that referenced this pull request Jun 18, 2026
## Overview

The footer already uses the `footer-content` static placeholder; this
adds a display name so Structure mode shows “Footer content” instead of
the raw code.

## Related

- Split from #1083

## Changes

- **updated** `CMS_PLACEHOLDER_CONF` with a `footer-content` entry and
translated name only

## Testing

1. `make start`, log in as a user who can use Structure mode.
2. Open any page, enter Structure mode.
3. Confirm the footer static placeholder is labeled “Footer content”.

## UI

Structure sidebar label only; no front-end markup change.

@wesleyboar wesleyboar left a comment

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.

More notes for reviewers…

Warning

Overcomplicated. Simplifying. See #1189.

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" plugin) so we can set defaults1.

Footnotes

  1. Defaults are set in another file, 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.

Comment thread djangocms_tacc_header_logo/render.py Outdated

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

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.

Comment on lines +571 to +582
None: {
'excluded_plugins': ['HeaderLogoPlugin'],
},
HEADER_LOGO_PLACEHOLDER_NAME: {
'name': _('Header logo'),
'plugins': ['HeaderLogoPlugin'],
'excluded_plugins': [],
'limits': {
'global': 1,
'HeaderLogoPlugin': 1,
},
},

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.

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)

## Overview

Stacks on #1083. Drops the extra ContentRenderer path and duplicate
settings template; `header.html` owns the static placeholder,
`header_logo.html` stays settings markup as on `main`.

## Related

- requires #1083
- footer Structure label: #1188 (merged to `main`)
- static-placeholder perm: #1187 (merged to `main`)

## Changes

- **deleted** `render.py`, `constants.py`, `header_tags`, and
`header_logo_via_settings.html`
- **updated** `header.html`: `{% static_placeholder "header-logo" or %}`
→ `header_logo.html`
- **updated** `CMS_PLACEHOLDER_CONF` for `header-logo` (keeps
`footer-content` with #1188)
- **updated** `docs/gh-999-editable-header-plan.md` (minimal deltas)

## Testing

Same as #1083 steps 1–4 after this branch is merged into
`feat/GH-999-let-cms-admin-edit-header`.

## UI

No new UI; structure only.
Matches settings fallback anchor id for tests and cross-repo selectors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Let CMS Admin Edit Header

1 participant