feat: new dynamic pages in admin panel
This commit is contained in:
@@ -1,7 +1,20 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.utils.text import slugify
|
||||
|
||||
from apps.core.utils import CONTENT_FORMAT_CHOICES, FORMAT_MARKDOWN, FORMAT_PLAIN, render_content
|
||||
|
||||
RESERVED_PAGE_SLUGS = frozenset({
|
||||
"admin",
|
||||
"about",
|
||||
"contact",
|
||||
"faq",
|
||||
"home",
|
||||
"products",
|
||||
"static",
|
||||
"media",
|
||||
})
|
||||
|
||||
|
||||
class HeroSection(models.Model):
|
||||
badge = models.CharField(max_length=200, blank=True, help_text="Small label above the title (e.g. 'Developing since 2021').")
|
||||
@@ -204,3 +217,124 @@ class DownloadItem(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.get_platform_display()})"
|
||||
|
||||
|
||||
class CustomPage(models.Model):
|
||||
title = models.CharField(max_length=200)
|
||||
slug = models.SlugField(max_length=200, unique=True)
|
||||
menu_label = models.CharField(
|
||||
max_length=100,
|
||||
blank=True,
|
||||
help_text="Nav label when shown in menu. Defaults to title.",
|
||||
)
|
||||
meta_description = models.CharField(max_length=300, blank=True)
|
||||
show_in_nav = models.BooleanField(default=True)
|
||||
menu_order = models.PositiveIntegerField(default=0)
|
||||
is_published = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["menu_order", "title"]
|
||||
verbose_name = "Custom Page"
|
||||
verbose_name_plural = "Custom Pages"
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
@property
|
||||
def nav_label(self):
|
||||
return self.menu_label or self.title
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if self.slug in RESERVED_PAGE_SLUGS:
|
||||
raise ValidationError({"slug": f'"{self.slug}" is reserved and cannot be used.'})
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.title)
|
||||
self.full_clean()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class CustomPageSection(models.Model):
|
||||
TYPE_HERO = "hero"
|
||||
TYPE_INTRO = "intro"
|
||||
TYPE_GRID = "grid"
|
||||
TYPE_HISTORY = "history"
|
||||
TYPE_CUSTOM = "custom"
|
||||
TYPE_FEATURES = "features"
|
||||
TYPE_SCREENSHOTS = "screenshots"
|
||||
TYPE_PRODUCTS = "products"
|
||||
TYPE_PROBLEMS = "problems"
|
||||
TYPE_SUPPORTERS = "supporters"
|
||||
TYPE_ABOUT_STRIP = "about_strip"
|
||||
TYPE_FAQ = "faq"
|
||||
|
||||
TYPE_CHOICES = [
|
||||
(TYPE_HERO, "Hero"),
|
||||
(TYPE_INTRO, "Intro Card"),
|
||||
(TYPE_GRID, "Grid Cards"),
|
||||
(TYPE_HISTORY, "History Block"),
|
||||
(TYPE_CUSTOM, "Custom Content"),
|
||||
(TYPE_FEATURES, "Features Grid"),
|
||||
(TYPE_SCREENSHOTS, "Screenshots Gallery"),
|
||||
(TYPE_PRODUCTS, "Products Grid"),
|
||||
(TYPE_PROBLEMS, "Problems / Value Proposition"),
|
||||
(TYPE_SUPPORTERS, "Supporters"),
|
||||
(TYPE_ABOUT_STRIP, "About Strip"),
|
||||
(TYPE_FAQ, "FAQ Accordion"),
|
||||
]
|
||||
|
||||
page = models.ForeignKey(CustomPage, on_delete=models.CASCADE, related_name="sections")
|
||||
section_type = models.CharField(max_length=30, choices=TYPE_CHOICES, default=TYPE_CUSTOM)
|
||||
badge = models.CharField(max_length=100, blank=True)
|
||||
title = models.CharField(max_length=300, blank=True)
|
||||
subtitle = models.CharField(max_length=500, blank=True)
|
||||
description = models.TextField(blank=True, help_text="Short intro text (homepage-style sections).")
|
||||
content = models.TextField(blank=True)
|
||||
content_format = models.CharField(max_length=20, choices=CONTENT_FORMAT_CHOICES, default=FORMAT_MARKDOWN)
|
||||
link_text = models.CharField(max_length=100, blank=True)
|
||||
link_url = models.CharField(max_length=300, blank=True)
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["order"]
|
||||
verbose_name = "Custom Page Section"
|
||||
verbose_name_plural = "Custom Page Sections"
|
||||
|
||||
@property
|
||||
def rendered_content(self):
|
||||
return render_content(self.content, self.content_format)
|
||||
|
||||
def __str__(self):
|
||||
label = self.title or self.badge or self.get_section_type_display()
|
||||
return f"{self.page} › [{self.get_section_type_display()}] {label}"
|
||||
|
||||
|
||||
class CustomPageSectionItem(models.Model):
|
||||
section = models.ForeignKey(CustomPageSection, on_delete=models.CASCADE, related_name="items")
|
||||
icon = models.CharField(max_length=20, blank=True)
|
||||
badge = models.CharField(max_length=100, blank=True)
|
||||
title = models.CharField(max_length=300, blank=True)
|
||||
content = models.TextField(blank=True)
|
||||
content_format = models.CharField(max_length=20, choices=CONTENT_FORMAT_CHOICES, default=FORMAT_PLAIN)
|
||||
url = models.URLField(blank=True)
|
||||
image = models.ImageField(upload_to="pages/custom/", blank=True, null=True)
|
||||
image_alt = models.CharField(max_length=200, blank=True)
|
||||
is_featured = models.BooleanField(default=False)
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ["order"]
|
||||
verbose_name = "Custom Page Section Item"
|
||||
verbose_name_plural = "Custom Page Section Items"
|
||||
|
||||
@property
|
||||
def rendered_content(self):
|
||||
return render_content(self.content, self.content_format)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.section} › {self.title or self.icon or '(item)'}"
|
||||
|
||||
Reference in New Issue
Block a user