feat: changes for citation ad dynamic

This commit is contained in:
mohamad
2026-05-17 18:51:26 +03:30
parent f0a9d5832b
commit b8ab67049f
22 changed files with 1170 additions and 287 deletions
+49 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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