feat: changes for citation ad dynamic
This commit is contained in:
+49
-1
@@ -1,6 +1,54 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import ContactSubmission, DownloadItem, FAQEntry
|
||||
from .models import (
|
||||
AboutSection,
|
||||
AboutSectionItem,
|
||||
ContactSubmission,
|
||||
DownloadItem,
|
||||
FAQEntry,
|
||||
HomepageSection,
|
||||
HomepageSectionItem,
|
||||
)
|
||||
|
||||
|
||||
class HomepageSectionItemInline(admin.TabularInline):
|
||||
model = HomepageSectionItem
|
||||
extra = 1
|
||||
fields = ("icon", "title", "content", "image", "order")
|
||||
ordering = ("order",)
|
||||
|
||||
|
||||
@admin.register(HomepageSection)
|
||||
class HomepageSectionAdmin(admin.ModelAdmin):
|
||||
list_display = ("section_type", "badge", "title", "order", "is_active")
|
||||
list_filter = ("section_type", "is_active")
|
||||
list_editable = ("order", "is_active")
|
||||
inlines = [HomepageSectionItemInline]
|
||||
fieldsets = (
|
||||
(None, {"fields": ("section_type", "badge", "title", "description")}),
|
||||
("CTA Link (About Strip)", {"fields": ("link_text", "link_url"), "classes": ("collapse",)}),
|
||||
("Settings", {"fields": ("order", "is_active")}),
|
||||
)
|
||||
|
||||
|
||||
class AboutSectionItemInline(admin.StackedInline):
|
||||
model = AboutSectionItem
|
||||
extra = 1
|
||||
fields = ("badge", "title", "content", "url", "image", "image_alt", "is_featured", "order")
|
||||
ordering = ("order",)
|
||||
|
||||
|
||||
@admin.register(AboutSection)
|
||||
class AboutSectionAdmin(admin.ModelAdmin):
|
||||
list_display = ("section_type", "badge", "title", "order", "is_active")
|
||||
list_filter = ("section_type", "is_active")
|
||||
list_editable = ("order", "is_active")
|
||||
inlines = [AboutSectionItemInline]
|
||||
fieldsets = (
|
||||
(None, {"fields": ("section_type", "badge", "title", "subtitle")}),
|
||||
("Content", {"fields": ("content_format", "content")}),
|
||||
("Settings", {"fields": ("order", "is_active")}),
|
||||
)
|
||||
|
||||
|
||||
@admin.register(ContactSubmission)
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("pages", "0003_faqentry_answer_format"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="AboutSection",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
(
|
||||
"section_type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("hero", "Hero"),
|
||||
("intro", "Intro Card"),
|
||||
("grid", "Grid Cards"),
|
||||
("history", "History Block"),
|
||||
("custom", "Custom Content"),
|
||||
],
|
||||
default="custom",
|
||||
max_length=30,
|
||||
),
|
||||
),
|
||||
("badge", models.CharField(blank=True, max_length=100)),
|
||||
("title", models.CharField(blank=True, max_length=300)),
|
||||
(
|
||||
"subtitle",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
help_text="Used as subtitle in Hero and year in History.",
|
||||
max_length=500,
|
||||
),
|
||||
),
|
||||
("content", models.TextField(blank=True)),
|
||||
(
|
||||
"content_format",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("plain", "Plain Text"),
|
||||
("markdown", "Markdown"),
|
||||
("html", "HTML"),
|
||||
],
|
||||
default="markdown",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
("order", models.PositiveIntegerField(default=0)),
|
||||
("is_active", models.BooleanField(default=True)),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "About Section",
|
||||
"verbose_name_plural": "About Sections",
|
||||
"ordering": ["order"],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="AboutSectionItem",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
(
|
||||
"section",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="items",
|
||||
to="pages.aboutsection",
|
||||
),
|
||||
),
|
||||
("badge", models.CharField(blank=True, max_length=100)),
|
||||
("title", models.CharField(blank=True, max_length=300)),
|
||||
(
|
||||
"content",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
help_text="Description text or link label for History links.",
|
||||
),
|
||||
),
|
||||
("url", models.URLField(blank=True, help_text="Used for History block links.")),
|
||||
("image", models.ImageField(blank=True, null=True, upload_to="about/")),
|
||||
("image_alt", models.CharField(blank=True, max_length=200)),
|
||||
(
|
||||
"is_featured",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Mark as featured item (e.g. large screenshot).",
|
||||
),
|
||||
),
|
||||
("order", models.PositiveIntegerField(default=0)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "About Section Item",
|
||||
"verbose_name_plural": "About Section Items",
|
||||
"ordering": ["order"],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,49 @@
|
||||
# Generated by Django 5.0.2 on 2026-05-17 14:39
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pages', '0004_aboutsection_aboutsectionitem'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='HomepageSection',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('section_type', models.CharField(choices=[('features', 'Features'), ('screenshots', 'Screenshots Gallery'), ('products', 'Products'), ('problems', 'Problems / Value Proposition'), ('about_strip', 'About Strip')], max_length=30, unique=True)),
|
||||
('badge', models.CharField(blank=True, max_length=100)),
|
||||
('title', models.CharField(blank=True, max_length=300)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('link_text', models.CharField(blank=True, help_text='CTA button label (About Strip).', max_length=100)),
|
||||
('link_url', models.CharField(blank=True, help_text='CTA button URL (About Strip).', max_length=300)),
|
||||
('order', models.PositiveIntegerField(default=0)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Homepage Section',
|
||||
'verbose_name_plural': 'Homepage Sections',
|
||||
'ordering': ['order'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HomepageSectionItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('icon', models.CharField(blank=True, help_text='Emoji or short symbol (e.g. ⚗️).', max_length=20)),
|
||||
('title', models.CharField(blank=True, max_length=300)),
|
||||
('content', models.TextField(blank=True)),
|
||||
('order', models.PositiveIntegerField(default=0)),
|
||||
('section', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='pages.homepagesection')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Homepage Section Item',
|
||||
'verbose_name_plural': 'Homepage Section Items',
|
||||
'ordering': ['order'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.0.2 on 2026-05-17 14:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pages', '0005_homepage_sections'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='homepagesectionitem',
|
||||
name='image',
|
||||
field=models.ImageField(blank=True, help_text='Logo or image (used for Supporters cards).', null=True, upload_to='homepage/items/'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='homepagesection',
|
||||
name='section_type',
|
||||
field=models.CharField(choices=[('features', 'Features'), ('screenshots', 'Screenshots Gallery'), ('products', 'Products'), ('problems', 'Problems / Value Proposition'), ('about_strip', 'About Strip'), ('supporters', 'Supporters')], max_length=30, unique=True),
|
||||
),
|
||||
]
|
||||
+113
-1
@@ -1,6 +1,118 @@
|
||||
from django.db import models
|
||||
|
||||
from apps.core.utils import CONTENT_FORMAT_CHOICES, FORMAT_PLAIN, render_content
|
||||
from apps.core.utils import CONTENT_FORMAT_CHOICES, FORMAT_MARKDOWN, FORMAT_PLAIN, render_content
|
||||
|
||||
|
||||
class HomepageSection(models.Model):
|
||||
TYPE_FEATURES = "features"
|
||||
TYPE_SCREENSHOTS = "screenshots"
|
||||
TYPE_PRODUCTS = "products"
|
||||
TYPE_PROBLEMS = "problems"
|
||||
TYPE_ABOUT_STRIP = "about_strip"
|
||||
TYPE_SUPPORTERS = "supporters"
|
||||
|
||||
TYPE_CHOICES = [
|
||||
(TYPE_FEATURES, "Features"),
|
||||
(TYPE_SCREENSHOTS, "Screenshots Gallery"),
|
||||
(TYPE_PRODUCTS, "Products"),
|
||||
(TYPE_PROBLEMS, "Problems / Value Proposition"),
|
||||
(TYPE_ABOUT_STRIP, "About Strip"),
|
||||
(TYPE_SUPPORTERS, "Supporters"),
|
||||
]
|
||||
|
||||
section_type = models.CharField(max_length=30, choices=TYPE_CHOICES, unique=True)
|
||||
badge = models.CharField(max_length=100, blank=True)
|
||||
title = models.CharField(max_length=300, blank=True)
|
||||
description = models.TextField(blank=True)
|
||||
link_text = models.CharField(max_length=100, blank=True, help_text="CTA button label (About Strip).")
|
||||
link_url = models.CharField(max_length=300, blank=True, help_text="CTA button URL (About Strip).")
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["order"]
|
||||
verbose_name = "Homepage Section"
|
||||
verbose_name_plural = "Homepage Sections"
|
||||
|
||||
def __str__(self):
|
||||
return f"[{self.get_section_type_display()}] {self.title or self.badge}"
|
||||
|
||||
|
||||
class HomepageSectionItem(models.Model):
|
||||
section = models.ForeignKey(HomepageSection, on_delete=models.CASCADE, related_name="items")
|
||||
icon = models.CharField(max_length=20, blank=True, help_text="Emoji or short symbol (e.g. ⚗️).")
|
||||
title = models.CharField(max_length=300, blank=True)
|
||||
content = models.TextField(blank=True)
|
||||
image = models.ImageField(upload_to="homepage/items/", blank=True, null=True, help_text="Logo or image (used for Supporters cards).")
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ["order"]
|
||||
verbose_name = "Homepage Section Item"
|
||||
verbose_name_plural = "Homepage Section Items"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.section} › {self.title or self.icon or '(item)'}"
|
||||
|
||||
|
||||
class AboutSection(models.Model):
|
||||
TYPE_HERO = "hero"
|
||||
TYPE_INTRO = "intro"
|
||||
TYPE_GRID = "grid"
|
||||
TYPE_HISTORY = "history"
|
||||
TYPE_CUSTOM = "custom"
|
||||
|
||||
TYPE_CHOICES = [
|
||||
(TYPE_HERO, "Hero"),
|
||||
(TYPE_INTRO, "Intro Card"),
|
||||
(TYPE_GRID, "Grid Cards"),
|
||||
(TYPE_HISTORY, "History Block"),
|
||||
(TYPE_CUSTOM, "Custom Content"),
|
||||
]
|
||||
|
||||
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, help_text="Used as subtitle in Hero and year in History.")
|
||||
content = models.TextField(blank=True)
|
||||
content_format = models.CharField(max_length=20, choices=CONTENT_FORMAT_CHOICES, default=FORMAT_MARKDOWN)
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
is_active = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["order"]
|
||||
verbose_name = "About Section"
|
||||
verbose_name_plural = "About 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.get_section_type_display()}] {label}"
|
||||
|
||||
|
||||
class AboutSectionItem(models.Model):
|
||||
section = models.ForeignKey(AboutSection, on_delete=models.CASCADE, related_name="items")
|
||||
badge = models.CharField(max_length=100, blank=True)
|
||||
title = models.CharField(max_length=300, blank=True)
|
||||
content = models.TextField(blank=True, help_text="Description text or link label for History links.")
|
||||
url = models.URLField(blank=True, help_text="Used for History block links.")
|
||||
image = models.ImageField(upload_to="about/", blank=True, null=True)
|
||||
image_alt = models.CharField(max_length=200, blank=True)
|
||||
is_featured = models.BooleanField(default=False, help_text="Mark as featured item (e.g. large screenshot).")
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ["order"]
|
||||
verbose_name = "About Section Item"
|
||||
verbose_name_plural = "About Section Items"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.section} › {self.title or self.badge or '(item)'}"
|
||||
|
||||
|
||||
class ContactSubmission(models.Model):
|
||||
|
||||
+26
-1
@@ -5,17 +5,42 @@ from django.shortcuts import render
|
||||
from django.views import View
|
||||
from django.views.generic import ListView, TemplateView
|
||||
|
||||
from apps.products.models import SubProduct
|
||||
|
||||
from .forms import ContactForm
|
||||
from .models import ContactSubmission, FAQEntry
|
||||
from .models import AboutSection, ContactSubmission, FAQEntry, HomepageSection
|
||||
|
||||
|
||||
class HomeView(TemplateView):
|
||||
template_name = "pages/home.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx["homepage_sections"] = (
|
||||
HomepageSection.objects.filter(is_active=True)
|
||||
.prefetch_related("items")
|
||||
.order_by("order")
|
||||
)
|
||||
ctx["sub_products"] = (
|
||||
SubProduct.objects.filter(is_active=True, show_on_homepage=True)
|
||||
.select_related("main_product")
|
||||
.order_by("homepage_order", "order")
|
||||
)
|
||||
return ctx
|
||||
|
||||
|
||||
class AboutView(TemplateView):
|
||||
template_name = "pages/about.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx["about_sections"] = (
|
||||
AboutSection.objects.filter(is_active=True)
|
||||
.prefetch_related("items")
|
||||
.order_by("order")
|
||||
)
|
||||
return ctx
|
||||
|
||||
|
||||
class FAQView(ListView):
|
||||
model = FAQEntry
|
||||
|
||||
Reference in New Issue
Block a user