Refactor: Remove CLI menu loader and finalize admin-based JSON upload flow
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Ekaropolus 2025-05-16 01:26:43 -06:00
parent 69a7852f7a
commit 9046deeffa
14 changed files with 200 additions and 2 deletions

View File

@ -67,6 +67,7 @@ TEMPLATES = [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"pxy_dashboard.context_processors.sidebar_context",
],
},
},

View File

@ -1,3 +1,10 @@
from django.contrib import admin
from .models import SidebarMenuItem
# Register your models here.
@admin.register(SidebarMenuItem)
class SidebarMenuAdmin(admin.ModelAdmin):
list_display = ("label", "type", "url", "order", "parent")
list_filter = ("type", "parent")
search_fields = ("label", "url")
ordering = ("order",)

View File

@ -0,0 +1,21 @@
from .models import SidebarMenuItem
def build_menu_tree(parent=None):
items = SidebarMenuItem.objects.filter(parent=parent).order_by("order")
tree = []
for item in items:
tree.append({
"type": item.type,
"label": item.label,
"icon": item.icon,
"url": item.url,
"badge": item.badge,
"children": build_menu_tree(parent=item)
})
return tree
def sidebar_context(request):
sidebar_menu = build_menu_tree()
return {"sidebar_menu": sidebar_menu}

View File

View File

@ -0,0 +1,26 @@
import json
from django.core.management.base import BaseCommand
from pxy_dashboard.models import SidebarMenuItem
class Command(BaseCommand):
help = "Carga el menú lateral desde un archivo JSON"
def add_arguments(self, parser):
parser.add_argument("json_file", type=str, help="Ruta al archivo JSON")
def handle(self, *args, **kwargs):
json_path = kwargs["json_file"]
with open(json_path, "r", encoding="utf-8") as f:
data = json.load(f)
created = self.create_menu_items(data)
self.stdout.write(self.style.SUCCESS(f"{created} ítems de menú creados."))
def create_menu_items(self, items, parent=None):
count = 0
for item in items:
children = item.pop("children", [])
menu_item = SidebarMenuItem.objects.create(parent=parent, **item)
count += 1
if children:
count += self.create_menu_items(children, parent=menu_item)
return count

View File

@ -0,0 +1,31 @@
# Generated by Django 5.0.3 on 2025-05-15 20:58
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='SidebarMenuItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('type', models.CharField(choices=[('title', 'Título de sección'), ('link', 'Enlace simple'), ('submenu', 'Submenú (con hijos)')], max_length=10)),
('label', models.CharField(max_length=100)),
('icon', models.CharField(blank=True, max_length=100, null=True)),
('url', models.CharField(blank=True, max_length=200, null=True)),
('badge', models.CharField(blank=True, max_length=50, null=True)),
('order', models.PositiveIntegerField(default=0)),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='pxy_dashboard.sidebarmenuitem')),
],
options={
'ordering': ['order'],
},
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.3 on 2025-05-16 07:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pxy_dashboard', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='sidebarmenuitem',
name='open_in_new_tab',
field=models.BooleanField(default=False),
),
]

View File

@ -1,3 +1,24 @@
from django.db import models
# Create your models here.
class SidebarMenuItem(models.Model):
MENU_TYPES = [
("title", "Título de sección"),
("link", "Enlace simple"),
("submenu", "Submenú (con hijos)"),
]
type = models.CharField(max_length=10, choices=MENU_TYPES)
label = models.CharField(max_length=100)
icon = models.CharField(max_length=100, blank=True, null=True)
url = models.CharField(max_length=200, blank=True, null=True)
badge = models.CharField(max_length=50, blank=True, null=True)
parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.CASCADE, related_name="children")
order = models.PositiveIntegerField(default=0)
open_in_new_tab = models.BooleanField(default=False)
class Meta:
ordering = ["order"]
def __str__(self):
return self.label

View File

@ -50,6 +50,11 @@
</a>
</div>
{% load sidebar_menu %}
<ul class="side-nav">
{% render_sidebar_menu sidebar_menu %}
</ul>
<!--- Sidemenu -->
<ul class="side-nav">

View File

@ -0,0 +1,40 @@
{% for item in items %}
{% if item.type == "title" %}
<li class="side-nav-title mt-1">{{ item.label }}</li>
{% elif item.children %}
<li class="side-nav-item">
<a data-bs-toggle="collapse" href="#menu-{{ item.label|slugify }}-{{ level }}" class="side-nav-link">
<i class="{{ item.icon }}"></i>
<span>{{ item.label }}</span>
<span class="menu-arrow"></span>
</a>
<div class="collapse" id="menu-{{ item.label|slugify }}-{{ level }}">
<ul class="{% if level == 1 %}side-nav-second-level{% elif level == 2 %}side-nav-third-level{% else %}side-nav-forth-level{% endif %}">
{% for child in item.children %}
{% if child.children %}
{% include "pxy_dashboard/partials/sidebar_menu_node.html" with items=child.children level=level|add:"1" %}
{% else %}
<li>
<a href="{% url child.url %}">
{{ child.label }}
</a>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
</li>
{% else %}
<li class="side-nav-item">
<a href="{% url item.url %}" class="side-nav-link" {% if item.open_in_new_tab %}target="_blank"{% endif %}>
<i class="{{ item.icon }}"></i>
{% if item.badge %}
<span class="badge bg-success float-end">{{ item.badge }}</span>
{% endif %}
<span>{{ item.label }}</span>
</a>
</li>
{% endif %}
{% endfor %}

View File

View File

@ -0,0 +1,11 @@
from django import template
register = template.Library()
@register.inclusion_tag("pxy_dashboard/partials/sidebar_menu_node.html", takes_context=True)
def render_sidebar_menu(context, items, level=1):
return {
"items": items,
"level": level,
"request": context["request"],
}

17
sidebar_menu.json Normal file
View File

@ -0,0 +1,17 @@
[
{
"type": "group",
"label": "Layouts",
"icon": "ri-layout-3-fill",
"order": 140,
"children": [
{ "type": "link", "label": "Horizontal", "url": "layouts:horizontal", "order": 10 },
{ "type": "link", "label": "Detached", "url": "layouts:detached", "order": 20 },
{ "type": "link", "label": "Full View", "url": "layouts:full", "order": 30 },
{ "type": "link", "label": "Fullscreen View", "url": "layouts:fullscreen", "order": 40 },
{ "type": "link", "label": "Hover Menu", "url": "layouts:hover", "order": 50 },
{ "type": "link", "label": "Compact", "url": "layouts:compact", "order": 60 },
{ "type": "link", "label": "Icon View", "url": "layouts:icon-view", "order": 70 }
]
}
]