Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 28 additions & 0 deletions app/home/templates/home/components/navbar.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -252,6 +252,34 @@
</ul>
</li>

{% for item in navigation_items %}
<li class="relative group">
<button class="menu-nav font-bantayog text-xl text-brown-1 font-light hover:text-orange-2 cursor-pointer">
{{item }}
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M6 9L12 15L18 9"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"/>
</svg>
</button>
<ul class="absolute left-0 top-full p-1 mt-2 w-52 bg-base-100 rounded-box shadow opacity-0 invisible
group-hover:opacity-100 group-hover:visible transition-all duration-150 z-[1]">
{% for child in item.children.all %}
<li>
<a href="{{child.page.get_absolute_url }}"
class="menu-subnav font-bantayog hover:text-orange-2 hover:bg-transparent">
{{child.title }}
</a>
</li>
{% endfor %}
</ul>

</li>
{% endfor %}

</ul>
</div>
<!-- Ticket Button (desktop only) -->
Expand Down
21 changes: 19 additions & 2 deletions app/pages/admin.py
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
from django.contrib import admin

from .models import Page
from .models import NavigationItem, Page


class NavigationItemAdmin(admin.ModelAdmin):
list_display = ["title", "parent", "page", "external_url", "order", "is_active"]
list_filter = ["is_active", "parent"]
search_fields = ["title", "page__title"]
list_editable = ["order", "is_active"]
ordering = ["parent__order", "parent__title", "order", "title"]

fieldsets = (
(None,{"fields": ("title", "parent", "order", "is_active")}),
(
"Link To",
{"fields": ("page", "external_url"), "description": "Choose either a page or external URL (not both)"},
),
)


# Register your models here.
admin.site.register(Page)
admin.site.register(NavigationItem, NavigationItemAdmin)
11 changes: 11 additions & 0 deletions app/pages/context_processors.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
from .models import NavigationItem


def navigation(request):
"""
Context processor to make navigation items available in all templates.
Returns only top-level active navigation items with their active children.
"""
nav_items = NavigationItem.objects.filter(is_active=True, parent=None).prefetch_related("children")

return{"navigation_items": nav_items}
33 changes: 33 additions & 0 deletions app/pages/migrations/0003_navigationitem.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
# Generated by Django 5.2.7 on 2025-12-10 13:30

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('pages', '0002_alter_page_slug'),
]

operations = [
migrations.CreateModel(
name='NavigationItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100)),
('external_url', models.URLField(blank=True, help_text='External URL (optional, used if no page is selected)', null=True)),
('order', models.IntegerField(default=0, help_text='Lower numbers appear first')),
('is_active', models.BooleanField(default=True, help_text='Show this item in navigation')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('page', models.ForeignKey(blank=True, help_text='Link to a page (optional if this is a dropdown parent)', null=True, on_delete=django.db.models.deletion.SET_NULL, to='pages.page')),
('parent', models.ForeignKey(blank=True, help_text='Parent navigation item (leave empty for top-level items)', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='pages.navigationitem')),
],
options={
'verbose_name': 'Navigation Item',
'verbose_name_plural': 'Navigation Items',
'ordering': ['order', 'title'],
},
),
]
64 changes: 64 additions & 0 deletions app/pages/models.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,71 @@ def save(self, *args, **kwargs):
def __str__(self):
return self.title

def get_absolute_url(self):
from django.urls import reverse

return reverse("pages:detail", kwargs={"slug": self.slug})

class Meta:
ordering = ["-created_at"]
verbose_name = "Page"
verbose_name_plural = "Pages"


class NavigationItem(models.Model):
"""Navigation item that can be top-level or a child (supports 1 level nesting only)"""

title = models.CharField(max_length=100)
parent = models.ForeignKey(
"self",
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="children",
help_text="Parent navigation item (leave empty for top-level items)",
)
page = models.ForeignKey(
Page,
on_delete=models.SET_NULL,
null=True,
blank=True,
help_text="Link to a page (optional if this is a dropdown parent)",
)
external_url = models.URLField(
blank=True, null=True, help_text="External URL (optional, used if no page is selected)"
)
order = models.IntegerField(default=0, help_text="Lower numbers appear first")
is_active = models.BooleanField(default=True, help_text="Show this item in navigation")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def save(self, *args, **kwargs):
if self.parent and self.parent.parent:
raise ValueError("Navigation items can only be nested 1 level deep")
super().save(*args, **kwargs)

def get_url(self):
"""Returns the URL for this nav item"""
if self.page:
return self.page.get_absolute_url()
if self.external_url:
return self.external_url
return "#"

def has_children(self):
"""Check if this nav item has any active children"""
return self.children.filter(is_active=True).exists()

def is_top_level(self):
"""Check if this is a top-level navigation item"""
return self.parent is None

def __str__(self):
if self.parent:
return f"{self.parent.title} >{self.title}"
return self.title

class Meta:
ordering = ["order", "title"]
verbose_name = "Navigation Item"
verbose_name_plural = "Navigation Items"
6 changes: 3 additions & 3 deletions app/pages/templates/pages/detail.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,11 +3,11 @@
{% block content %}
<main>
<div class="min-h-screen bg-contain bg-center" style="background-image: url('{% static 'img/bg-conference-at-glance.png' %}');">
<!-- Navbar -->

{% include "home/components/navbar.html" %}
<!-- Content Block (specific page content) -->

<div class="max-w-3xl mx-auto px-6 pt-40 pb-18">
<article class="max-w-4xl mx-auto bg-white rounded-lg shadow-lg p-8">
<article class="max-w-4xl mx-auto rounded-lg shadow-lg p-8">
<h1 class="text-4xl font-bold mb-4">{{page.title }}</h1>
<div class="prose prose-lg max-w-none">
{{page.content|safe }}
Expand Down
1 change: 1 addition & 0 deletions config/settings.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -113,6 +113,7 @@
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"app.pages.context_processors.navigation",
],
},
},
Expand Down