Navigation system overhaul: replace hardcoded nav/footer with CMS-managed models #32

Closed
opened 2026-03-02 18:33:12 +00:00 by mark · 0 comments
Owner

Problem

The site's navigation (header nav, footer nav, footer branding/socials, copyright text) is almost entirely hardcoded in Django templates (nav.html, footer.html). This causes several issues:

  1. Dead links: Unpublishing a page (e.g. About) leaves a broken hardcoded link — the nav doesn't reflect page state.
  2. No CMS control over menus: Wagtail's show_in_menus field is not used; nav links are static HTML.
  3. No footer configurability: Site name, tagline, copyright text, and social links are all baked into templates.
  4. Duplication: Nav links are repeated in desktop nav, mobile nav, and footer — three places to maintain.

The only dynamic footer element is legal pages via {% get_legal_pages %}, which correctly queries LegalPage.objects.live().filter(show_in_footer=True).

Current State

Element Location Dynamic?
Header nav links (Home, Articles, About) nav.html lines 14-16, 36-38 Hardcoded
Subscribe button/form nav.html lines 17, 39-47 Hardcoded
Logo + site name nav.html lines 5-9, footer.html line 5 Hardcoded
Footer tagline footer.html lines 6-9 Hardcoded
Footer nav links footer.html lines 14-16 Hardcoded
Footer legal links footer.html lines 17-20 get_legal_pages tag
Footer social links footer.html lines 27-37 Hardcoded
Copyright line footer.html lines 42-43 (year is dynamic)
SiteSettings model apps/core/models.py OG image + privacy page only

Proposed Solution

1. Extend SiteSettings model

Add CMS-editable branding/footer fields:

  • site_name (CharField, default "NO HYPE AI")
  • tagline (CharField, default "Honest AI tool reviews for developers.")
  • footer_description (TextField — the paragraph under the logo in footer)
  • copyright_text (CharField, default "No Hype AI. All rights reserved.")

Inline model so editors can add/remove/reorder social links:

  • platform_name (CharField)
  • url (URLField)
  • icon (CharField with choices mapping to SVG icon includes)
  • sort_order (via Wagtail's Orderable)

3. Create NavigationMenuItem orderable on SiteSettings

Inline model for header/footer nav links:

  • link_page (ForeignKey to Page, optional) — uses page URL/title, only renders if page is live
  • link_url (URLField, optional) — for external/custom links
  • link_title (CharField, optional) — override display text
  • open_in_new_tab (BooleanField)
  • show_in_header / show_in_footer (BooleanField)
  • sort_order (via Orderable)

Key behaviour: If link_page is set but the page is unpublished/deleted, the link is automatically excluded from rendering. This solves the dead link problem.

4. New template tags

  • {% get_nav_items "header" %} — menu items filtered by show_in_header=True, excluding dead pages
  • {% get_nav_items "footer" %} — same for footer
  • {% get_social_links %} — social link orderables

5. Update templates

  • nav.html: Replace hardcoded links with {% get_nav_items "header" %} loop + dynamic site name
  • footer.html: Replace hardcoded links, branding, socials with template tag loops

6. Data migration

Populate new fields with current hardcoded values so the site looks identical after deployment.

Testing Strategy

Unit Tests

  • NavigationMenuItem with live page → renders; unpublished page → excluded
  • NavigationMenuItem with external URL → renders correctly
  • SocialMediaLink ordering respected
  • SiteSettings branding fields have correct defaults
  • get_nav_items("header") / get_nav_items("footer") return correct filtered sets
  • get_social_links returns ordered links
  • Template rendering reflects model data; unpublished pages excluded from HTML

E2E Tests (Playwright)

  • All CMS nav items appear in header (desktop + mobile) and footer
  • Social links appear with correct URLs
  • Branding text matches CMS settings

Regression

  • Existing e2e tests (checking for "Home", "Articles", etc.) must still pass — data migration ensures visual parity.

Notes

  • Keep {% get_legal_pages %} unchanged — it works well and legal pages have their own show_in_footer field.
  • The Subscribe button/form is a special CTA — keep hardcoded or add a toggle on SiteSettings.
  • Social icons: use a choices field mapping platform names to SVG template includes (components/icons/{platform}.html), not raw SVG in the DB.
## Problem The site's navigation (header nav, footer nav, footer branding/socials, copyright text) is almost entirely **hardcoded in Django templates** (`nav.html`, `footer.html`). This causes several issues: 1. **Dead links**: Unpublishing a page (e.g. About) leaves a broken hardcoded link — the nav doesn't reflect page state. 2. **No CMS control over menus**: Wagtail's `show_in_menus` field is not used; nav links are static HTML. 3. **No footer configurability**: Site name, tagline, copyright text, and social links are all baked into templates. 4. **Duplication**: Nav links are repeated in desktop nav, mobile nav, and footer — three places to maintain. The only dynamic footer element is legal pages via `{% get_legal_pages %}`, which correctly queries `LegalPage.objects.live().filter(show_in_footer=True)`. ## Current State | Element | Location | Dynamic? | |---|---|---| | Header nav links (Home, Articles, About) | `nav.html` lines 14-16, 36-38 | ❌ Hardcoded | | Subscribe button/form | `nav.html` lines 17, 39-47 | ❌ Hardcoded | | Logo + site name | `nav.html` lines 5-9, `footer.html` line 5 | ❌ Hardcoded | | Footer tagline | `footer.html` lines 6-9 | ❌ Hardcoded | | Footer nav links | `footer.html` lines 14-16 | ❌ Hardcoded | | Footer legal links | `footer.html` lines 17-20 | ✅ `get_legal_pages` tag | | Footer social links | `footer.html` lines 27-37 | ❌ Hardcoded | | Copyright line | `footer.html` lines 42-43 | ❌ (year is dynamic) | | `SiteSettings` model | `apps/core/models.py` | ✅ OG image + privacy page only | ## Proposed Solution ### 1. Extend `SiteSettings` model Add CMS-editable branding/footer fields: - `site_name` (CharField, default "NO HYPE AI") - `tagline` (CharField, default "Honest AI tool reviews for developers.") - `footer_description` (TextField — the paragraph under the logo in footer) - `copyright_text` (CharField, default "No Hype AI. All rights reserved.") ### 2. Create `SocialMediaLink` orderable on `SiteSettings` Inline model so editors can add/remove/reorder social links: - `platform_name` (CharField) - `url` (URLField) - `icon` (CharField with choices mapping to SVG icon includes) - `sort_order` (via Wagtail's `Orderable`) ### 3. Create `NavigationMenuItem` orderable on `SiteSettings` Inline model for header/footer nav links: - `link_page` (ForeignKey to Page, optional) — uses page URL/title, **only renders if page is live** - `link_url` (URLField, optional) — for external/custom links - `link_title` (CharField, optional) — override display text - `open_in_new_tab` (BooleanField) - `show_in_header` / `show_in_footer` (BooleanField) - `sort_order` (via `Orderable`) **Key behaviour**: If `link_page` is set but the page is unpublished/deleted, the link is automatically excluded from rendering. This solves the dead link problem. ### 4. New template tags - `{% get_nav_items "header" %}` — menu items filtered by `show_in_header=True`, excluding dead pages - `{% get_nav_items "footer" %}` — same for footer - `{% get_social_links %}` — social link orderables ### 5. Update templates - **`nav.html`**: Replace hardcoded links with `{% get_nav_items "header" %}` loop + dynamic site name - **`footer.html`**: Replace hardcoded links, branding, socials with template tag loops ### 6. Data migration Populate new fields with current hardcoded values so the site looks identical after deployment. ## Testing Strategy ### Unit Tests - `NavigationMenuItem` with live page → renders; unpublished page → excluded - `NavigationMenuItem` with external URL → renders correctly - `SocialMediaLink` ordering respected - `SiteSettings` branding fields have correct defaults - `get_nav_items("header")` / `get_nav_items("footer")` return correct filtered sets - `get_social_links` returns ordered links - Template rendering reflects model data; unpublished pages excluded from HTML ### E2E Tests (Playwright) - All CMS nav items appear in header (desktop + mobile) and footer - Social links appear with correct URLs - Branding text matches CMS settings ### Regression - Existing e2e tests (checking for "Home", "Articles", etc.) must still pass — data migration ensures visual parity. ## Notes - Keep `{% get_legal_pages %}` unchanged — it works well and legal pages have their own `show_in_footer` field. - The Subscribe button/form is a special CTA — keep hardcoded or add a toggle on `SiteSettings`. - Social icons: use a choices field mapping platform names to SVG template includes (`components/icons/{platform}.html`), not raw SVG in the DB.
mark closed this issue 2026-03-02 19:52:00 +00:00
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: nohype/main-site#32