feat: dynamic main icon

This commit is contained in:
mohamad
2026-05-25 15:57:24 +03:30
parent 4875d8b6c3
commit f08d0aacc0
38 changed files with 1565 additions and 540 deletions
+64 -25
View File
@@ -17,30 +17,52 @@ class ArticleSectionInline(admin.TabularInline):
ordering = ("order",)
class ArticleInline(admin.StackedInline):
class SubProductArticleInline(admin.StackedInline):
model = Article
fk_name = "sub_product"
extra = 0
fields = ("badge", "title", "description", "order")
ordering = ("order",)
show_change_link = True
class MainProductArticleInline(admin.StackedInline):
model = Article
fk_name = "main_product"
extra = 0
fields = ("badge", "title", "description", "order")
ordering = ("order",)
show_change_link = True
RELEASE_VERSION_INLINE_FIELDS = (
"version",
"is_featured_stable",
"windows_download_url",
"macos_download_url",
"linux_download_url",
"source_code_url",
"package_resource_url",
"release_notes",
"is_active",
"order",
)
class SubProductVersionInline(admin.TabularInline):
model = SubProductVersion
fk_name = "sub_product"
extra = 0
ordering = ("order", "version")
fields = (
"version",
"is_featured_stable",
"windows_download_url",
"macos_download_url",
"linux_download_url",
"source_code_url",
"package_resource_url",
"release_notes",
"is_active",
"order",
)
fields = RELEASE_VERSION_INLINE_FIELDS
class MainProductVersionInline(admin.TabularInline):
model = SubProductVersion
fk_name = "main_product"
extra = 0
ordering = ("order", "version")
fields = RELEASE_VERSION_INLINE_FIELDS
class SubProductInline(admin.StackedInline):
@@ -59,9 +81,9 @@ class MainProductAdmin(admin.ModelAdmin):
search_fields = ("name", "description")
prepopulated_fields = {"slug": ("name",)}
list_editable = ("order", "is_active")
inlines = [SubProductInline]
inlines = [MainProductArticleInline, MainProductVersionInline, SubProductInline]
fieldsets = (
(None, {"fields": ("name", "slug", "short_description")}),
(None, {"fields": ("name", "slug", "short_description", "distribution")}),
("Description", {"fields": ("description_format", "description")}),
("Media", {"fields": ("image",)}),
("Settings", {"fields": ("order", "is_active")}),
@@ -85,7 +107,7 @@ class SubProductAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("name",)}
list_editable = ("order", "is_active", "show_on_homepage", "homepage_order")
raw_id_fields = ("main_product",)
inlines = [ArticleInline, SubProductVersionInline]
inlines = [SubProductArticleInline, SubProductVersionInline]
fieldsets = (
(None, {"fields": ("main_product", "name", "slug", "distribution", "short_description")}),
("Description", {"fields": ("description_format", "description")}),
@@ -97,19 +119,27 @@ class SubProductAdmin(admin.ModelAdmin):
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ("title", "sub_product", "order", "created_at")
list_filter = ("sub_product__main_product",)
search_fields = ("title", "description")
list_display = ("title", "parent", "order", "created_at")
list_filter = ("main_product", "sub_product__main_product")
search_fields = ("title", "description", "main_product__name", "sub_product__name")
list_editable = ("order",)
raw_id_fields = ("sub_product",)
raw_id_fields = ("main_product", "sub_product")
inlines = [ArticleSectionInline, ArticleCitationInline]
fieldsets = (
(None, {"fields": ("sub_product", "badge", "title")}),
(None, {"fields": ("main_product", "sub_product", "badge", "title")}),
("Description", {"fields": ("description_format", "description")}),
("Citation Badge", {"fields": ("citation_count_display", "citation_count_label"), "description": "Number and optional label for the dashed citation circle badge. Both are optional — a label without a number shows an empty circle with the label; neither hides the badge entirely."}),
("Settings", {"fields": ("order",)}),
)
@admin.display(description="Parent")
def parent(self, obj):
if obj.main_product_id:
return obj.main_product
if obj.sub_product_id:
return obj.sub_product
return ""
@admin.register(ArticleSection)
class ArticleSectionAdmin(admin.ModelAdmin):
@@ -127,21 +157,22 @@ class ArticleSectionAdmin(admin.ModelAdmin):
@admin.register(SubProductVersion)
class SubProductVersionAdmin(admin.ModelAdmin):
list_display = (
"sub_product",
"parent",
"version",
"is_featured_stable",
"is_active",
"order",
)
list_filter = ("is_active", "is_featured_stable", "sub_product__distribution")
search_fields = ("sub_product__name", "version")
list_filter = ("is_active", "is_featured_stable", "main_product", "sub_product__main_product")
search_fields = ("main_product__name", "sub_product__name", "version")
list_editable = ("is_active", "order")
raw_id_fields = ("sub_product",)
raw_id_fields = ("main_product", "sub_product")
fieldsets = (
(
None,
{
"fields": (
"main_product",
"sub_product",
"version",
"is_featured_stable",
@@ -163,3 +194,11 @@ class SubProductVersionAdmin(admin.ModelAdmin):
("Details", {"fields": ("release_notes",)}),
("Settings", {"fields": ("is_active", "order")}),
)
@admin.display(description="Parent")
def parent(self, obj):
if obj.main_product_id:
return obj.main_product
if obj.sub_product_id:
return obj.sub_product
return ""
@@ -0,0 +1,28 @@
# Generated by Django 5.0.2 on 2026-05-23 10:11
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('products', '0008_article_citation_count_label'),
]
operations = [
migrations.AddField(
model_name='article',
name='main_product',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='articles', to='products.mainproduct'),
),
migrations.AlterField(
model_name='article',
name='sub_product',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='articles', to='products.subproduct'),
),
migrations.AddConstraint(
model_name='article',
constraint=models.CheckConstraint(check=models.Q(models.Q(('main_product__isnull', True), ('sub_product__isnull', False)), models.Q(('main_product__isnull', False), ('sub_product__isnull', True)), _connector='OR'), name='article_exactly_one_parent'),
),
]
@@ -0,0 +1,45 @@
# Generated by Django 5.0.2 on 2026-05-23 10:31
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('products', '0009_article_main_product'),
]
operations = [
migrations.AlterUniqueTogether(
name='subproductversion',
unique_together=set(),
),
migrations.AddField(
model_name='mainproduct',
name='distribution',
field=models.CharField(choices=[('installable', 'Installable application'), ('package', 'Package (external / non-installable)')], default='installable', help_text='Only affects how releases appear on the public site.', max_length=20),
),
migrations.AddField(
model_name='subproductversion',
name='main_product',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='versions', to='products.mainproduct'),
),
migrations.AlterField(
model_name='subproductversion',
name='sub_product',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='versions', to='products.subproduct'),
),
migrations.AddConstraint(
model_name='subproductversion',
constraint=models.CheckConstraint(check=models.Q(models.Q(('main_product__isnull', True), ('sub_product__isnull', False)), models.Q(('main_product__isnull', False), ('sub_product__isnull', True)), _connector='OR'), name='release_version_exactly_one_parent'),
),
migrations.AddConstraint(
model_name='subproductversion',
constraint=models.UniqueConstraint(condition=models.Q(('sub_product__isnull', False)), fields=('sub_product', 'version'), name='release_version_unique_sub_product_version'),
),
migrations.AddConstraint(
model_name='subproductversion',
constraint=models.UniqueConstraint(condition=models.Q(('main_product__isnull', False)), fields=('main_product', 'version'), name='release_version_unique_main_product_version'),
),
]
+89 -8
View File
@@ -1,3 +1,4 @@
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils.text import slugify
@@ -12,6 +13,13 @@ INSTALLABLE_PLATFORM_SPECS = (
("source_code_url", "Source code", None),
)
DISTRIBUTION_INSTALLABLE = "installable"
DISTRIBUTION_PACKAGE = "package"
DISTRIBUTION_CHOICES = [
(DISTRIBUTION_INSTALLABLE, "Installable application"),
(DISTRIBUTION_PACKAGE, "Package (external / non-installable)"),
]
class MainProduct(models.Model):
name = models.CharField(max_length=200)
@@ -22,6 +30,12 @@ class MainProduct(models.Model):
max_length=20, choices=CONTENT_FORMAT_CHOICES, default=FORMAT_PLAIN
)
image = models.ImageField(upload_to="products/main/", blank=True, null=True)
distribution = models.CharField(
max_length=20,
choices=DISTRIBUTION_CHOICES,
default=DISTRIBUTION_INSTALLABLE,
help_text="Only affects how releases appear on the public site.",
)
order = models.PositiveIntegerField(default=0)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
@@ -47,14 +61,17 @@ class MainProduct(models.Model):
def get_absolute_url(self):
return reverse("products:main_product_detail", kwargs={"main_slug": self.slug})
def get_versions_archive_url(self):
return reverse(
"products:main_product_versions",
kwargs={"main_slug": self.slug},
)
class SubProduct(models.Model):
DISTRIBUTION_INSTALLABLE = "installable"
DISTRIBUTION_PACKAGE = "package"
DISTRIBUTION_CHOICES = [
(DISTRIBUTION_INSTALLABLE, "Installable application"),
(DISTRIBUTION_PACKAGE, "Package (external / non-installable)"),
]
DISTRIBUTION_INSTALLABLE = DISTRIBUTION_INSTALLABLE
DISTRIBUTION_PACKAGE = DISTRIBUTION_PACKAGE
DISTRIBUTION_CHOICES = DISTRIBUTION_CHOICES
main_product = models.ForeignKey(
MainProduct,
@@ -121,10 +138,19 @@ class SubProduct(models.Model):
class Article(models.Model):
main_product = models.ForeignKey(
MainProduct,
on_delete=models.CASCADE,
related_name="articles",
null=True,
blank=True,
)
sub_product = models.ForeignKey(
SubProduct,
on_delete=models.CASCADE,
related_name="articles",
null=True,
blank=True,
)
badge = models.CharField(
max_length=100,
@@ -155,20 +181,47 @@ class Article(models.Model):
ordering = ["order", "title"]
verbose_name = "Article"
verbose_name_plural = "Articles"
constraints = [
models.CheckConstraint(
check=(
models.Q(sub_product__isnull=False, main_product__isnull=True)
| models.Q(sub_product__isnull=True, main_product__isnull=False)
),
name="article_exactly_one_parent",
),
]
@property
def rendered_description(self):
return render_content(self.description, self.description_format)
def clean(self):
super().clean()
has_main = self.main_product_id is not None
has_sub = self.sub_product_id is not None
if has_main == has_sub:
raise ValidationError(
"An article must belong to exactly one main product or sub-product."
)
def __str__(self):
return self.title
class SubProductVersion(models.Model):
main_product = models.ForeignKey(
MainProduct,
on_delete=models.CASCADE,
related_name="versions",
null=True,
blank=True,
)
sub_product = models.ForeignKey(
SubProduct,
on_delete=models.CASCADE,
related_name="versions",
null=True,
blank=True,
)
version = models.CharField(max_length=80)
is_featured_stable = models.BooleanField(
@@ -189,12 +242,40 @@ class SubProductVersion(models.Model):
class Meta:
ordering = ["order", "version", "pk"]
unique_together = [["sub_product", "version"]]
verbose_name = "Release version"
verbose_name_plural = "Release versions"
constraints = [
models.CheckConstraint(
check=(
models.Q(sub_product__isnull=False, main_product__isnull=True)
| models.Q(sub_product__isnull=True, main_product__isnull=False)
),
name="release_version_exactly_one_parent",
),
models.UniqueConstraint(
fields=["sub_product", "version"],
condition=models.Q(sub_product__isnull=False),
name="release_version_unique_sub_product_version",
),
models.UniqueConstraint(
fields=["main_product", "version"],
condition=models.Q(main_product__isnull=False),
name="release_version_unique_main_product_version",
),
]
def clean(self):
super().clean()
has_main = self.main_product_id is not None
has_sub = self.sub_product_id is not None
if has_main == has_sub:
raise ValidationError(
"A release version must belong to exactly one main product or sub-product."
)
def __str__(self):
return f"{self.sub_product.name} v{self.version}"
parent = self.sub_product or self.main_product
return f"{parent.name} v{self.version}"
def install_urls_for_specs(self, specs):
out = []
+100
View File
@@ -0,0 +1,100 @@
from .models import DISTRIBUTION_INSTALLABLE, DISTRIBUTION_PACKAGE, INSTALLABLE_PLATFORM_SPECS
def featured_version(qs):
ordered = qs.order_by("order", "pk")
cand = ordered.filter(is_featured_stable=True).first()
return cand if cand else ordered.first()
def install_specs_from_versions(version_list):
present = set()
for ver in version_list:
for field_name, *_ in INSTALLABLE_PLATFORM_SPECS:
if (getattr(ver, field_name) or "").strip():
present.add(field_name)
return tuple(s for s in INSTALLABLE_PLATFORM_SPECS if s[0] in present)
def build_release_context(distribution, active_versions):
context = {
"distribution_installable": distribution == DISTRIBUTION_INSTALLABLE,
"distribution_package": distribution == DISTRIBUTION_PACKAGE,
"show_releases_section": False,
"featured_version": None,
"featured_install_cells": [],
"show_older_versions_link": False,
"package_versions": [],
}
if distribution == DISTRIBUTION_INSTALLABLE:
featured = featured_version(active_versions)
if (
featured
and not featured.has_any_install_asset()
and not (featured.package_resource_url or "").strip()
):
for cand in active_versions.exclude(pk=featured.pk).order_by("order", "pk"):
if cand.has_any_install_asset() or (cand.package_resource_url or "").strip():
featured = cand
break
context["featured_version"] = featured
if featured and (
featured.has_any_install_asset()
or (featured.package_resource_url or "").strip()
):
if featured.has_any_install_asset():
specs = install_specs_from_versions([featured])
context["featured_install_cells"] = featured.install_urls_for_specs(specs)
context["show_releases_section"] = True
older_list = list(
active_versions.exclude(pk=featured.pk).order_by("order", "pk")
if featured
else active_versions.order_by("order", "pk")
)
context["show_older_versions_link"] = len(older_list) > 0
elif distribution == DISTRIBUTION_PACKAGE:
pkg_versions = list(active_versions.order_by("order", "pk"))
context["package_versions"] = pkg_versions
context["show_releases_section"] = len(pkg_versions) > 0
return context
def build_archive_context(distribution, active_versions):
featured = featured_version(active_versions)
archive = list(
active_versions.exclude(pk=featured.pk).order_by("order", "pk")
if featured
else active_versions.order_by("order", "pk")
)
context = {
"featured_version": featured,
"archive_versions": archive,
}
if distribution == DISTRIBUTION_INSTALLABLE:
specs = install_specs_from_versions(archive)
context["archive_specs"] = specs
archive_rows_installable = []
for ver in archive:
cells = []
for field_name, label, icon in specs:
u = (getattr(ver, field_name) or "").strip()
cells.append(
{"field": field_name, "label": label, "icon": icon, "url": u}
)
archive_rows_installable.append({"version_obj": ver, "cells": cells})
context["archive_rows_installable"] = archive_rows_installable
context["archive_rows_package"] = False
context["distribution_installable"] = True
context["distribution_package"] = False
else:
context["archive_specs"] = ()
context["archive_rows_installable"] = []
context["archive_rows_package"] = True
context["distribution_installable"] = False
context["distribution_package"] = True
return context
+75 -1
View File
@@ -1,7 +1,15 @@
from django.test import TestCase
from django.urls import reverse
from apps.products.models import Article, ArticleSection, MainProduct, SubProduct
from django.core.exceptions import ValidationError
from apps.products.models import (
Article,
ArticleSection,
MainProduct,
SubProduct,
SubProductVersion,
)
class MainProductModelTest(TestCase):
@@ -112,6 +120,72 @@ class ArticleModelTest(TestCase):
self.sub_product.delete()
self.assertFalse(Article.objects.filter(pk=article_pk).exists())
def test_main_product_article(self):
article = Article.objects.create(
main_product=self.main_product,
title="Product Article",
description="Body.",
)
self.assertEqual(article.main_product, self.main_product)
self.assertIsNone(article.sub_product_id)
def test_cascade_delete_with_main_product(self):
article = Article.objects.create(
main_product=self.main_product,
title="Product Article",
description="Body.",
)
article_pk = article.pk
self.main_product.delete()
self.assertFalse(Article.objects.filter(pk=article_pk).exists())
def test_requires_exactly_one_parent(self):
article = Article(
main_product=self.main_product,
sub_product=self.sub_product,
title="Invalid",
description="Body.",
)
with self.assertRaises(ValidationError):
article.full_clean()
def test_requires_a_parent(self):
article = Article(title="Orphan", description="Body.")
with self.assertRaises(ValidationError):
article.full_clean()
class SubProductVersionModelTest(TestCase):
def setUp(self):
self.main_product = MainProduct.objects.create(
name="Main",
short_description="Short",
description="Desc",
)
self.sub_product = SubProduct.objects.create(
main_product=self.main_product,
name="Sub",
short_description="Short",
description="Desc",
)
def test_main_product_version(self):
version = SubProductVersion.objects.create(
main_product=self.main_product,
version="1.0.0",
)
self.assertEqual(version.main_product, self.main_product)
self.assertIsNone(version.sub_product_id)
def test_requires_exactly_one_parent(self):
version = SubProductVersion(
main_product=self.main_product,
sub_product=self.sub_product,
version="1.0.0",
)
with self.assertRaises(ValidationError):
version.full_clean()
class ArticleSectionModelTest(TestCase):
def setUp(self):
+49 -1
View File
@@ -83,6 +83,18 @@ class MainProductDetailViewTest(ProductViewsSetup):
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
def test_detail_articles_in_context(self):
main_article = Article.objects.create(
main_product=self.main_product,
title="Overview",
description="Main product article.",
)
url = reverse("products:main_product_detail", kwargs={"main_slug": "radiuma"})
response = self.client.get(url)
self.assertIn("articles", response.context)
self.assertIn(main_article, list(response.context["articles"]))
self.assertNotIn(self.article, list(response.context["articles"]))
class SubProductDetailViewTest(ProductViewsSetup):
def test_sub_detail_returns_200(self):
@@ -161,4 +173,40 @@ class SubProductOlderVersionsViewTest(ProductViewsSetup):
)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "products/sub_versions.html")
self.assertTemplateUsed(response, "products/versions_archive.html")
def test_main_versions_returns_200(self):
from apps.products.models import SubProductVersion
SubProductVersion.objects.create(
main_product=self.main_product,
version="9.9",
is_featured_stable=True,
windows_download_url="https://example.com/win.exe",
is_active=True,
)
SubProductVersion.objects.create(
main_product=self.main_product,
version="9.8",
is_active=True,
)
url = reverse(
"products:main_product_versions",
kwargs={"main_slug": "radiuma"},
)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "products/versions_archive.html")
def test_main_detail_releases_in_context(self):
from apps.products.models import SubProductVersion
SubProductVersion.objects.create(
main_product=self.main_product,
version="1.0",
windows_download_url="https://example.com/win.exe",
is_active=True,
)
url = reverse("products:main_product_detail", kwargs={"main_slug": "radiuma"})
response = self.client.get(url)
self.assertTrue(response.context["show_releases_section"])
+5
View File
@@ -11,6 +11,11 @@ urlpatterns = [
views.MainProductDetailView.as_view(),
name="main_product_detail",
),
path(
"<slug:main_slug>/versions/",
views.MainProductOlderVersionsView.as_view(),
name="main_product_versions",
),
path(
"<slug:main_slug>/<slug:sub_slug>/versions/",
views.SubProductOlderVersionsView.as_view(),
+47 -96
View File
@@ -1,22 +1,8 @@
from django.shortcuts import get_object_or_404
from django.views.generic import DetailView, ListView, TemplateView
from .models import INSTALLABLE_PLATFORM_SPECS, MainProduct, SubProduct
def _featured_version(qs):
ordered = qs.order_by("order", "pk")
cand = ordered.filter(is_featured_stable=True).first()
return cand if cand else ordered.first()
def _install_specs_from_versions(version_list):
present = set()
for ver in version_list:
for field_name, *_ in INSTALLABLE_PLATFORM_SPECS:
if (getattr(ver, field_name) or "").strip():
present.add(field_name)
return tuple(s for s in INSTALLABLE_PLATFORM_SPECS if s[0] in present)
from .models import MainProduct, SubProduct
from .release_context import build_archive_context, build_release_context
class ProductOverviewView(ListView):
@@ -37,6 +23,39 @@ class MainProductDetailView(DetailView):
"sub_products"
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["articles"] = self.object.articles.prefetch_related(
"sections", "citations"
).all()
release_context = build_release_context(
self.object.distribution,
self.object.versions.filter(is_active=True),
)
release_context["versions_archive_url"] = self.object.get_versions_archive_url()
context.update(release_context)
return context
class MainProductOlderVersionsView(TemplateView):
template_name = "products/versions_archive.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
main_product = get_object_or_404(
MainProduct,
slug=self.kwargs["main_slug"],
is_active=True,
)
active_versions = main_product.versions.filter(is_active=True)
context["main_product"] = main_product
context["sub_product"] = None
context["product_detail_url"] = main_product.get_absolute_url()
context.update(
build_archive_context(main_product.distribution, active_versions)
)
return context
class SubProductDetailView(TemplateView):
template_name = "products/sub_detail.html"
@@ -56,63 +75,25 @@ class SubProductDetailView(TemplateView):
)
context["main_product"] = main_product
context["sub_product"] = sub_product
context["articles"] = sub_product.articles.prefetch_related("sections").all()
context["articles"] = sub_product.articles.prefetch_related(
"sections", "citations"
).all()
context["siblings"] = (
SubProduct.objects.filter(main_product=main_product, is_active=True)
.exclude(pk=sub_product.pk)
.order_by("order", "name")
)
active_versions = sub_product.versions.filter(is_active=True)
context["distribution_installable"] = (
sub_product.distribution == SubProduct.DISTRIBUTION_INSTALLABLE
release_context = build_release_context(
sub_product.distribution, active_versions
)
context["distribution_package"] = (
sub_product.distribution == SubProduct.DISTRIBUTION_PACKAGE
)
context["show_releases_section"] = False
context["featured_version"] = None
context["featured_install_cells"] = []
context["show_older_versions_link"] = False
context["package_versions"] = []
if sub_product.distribution == SubProduct.DISTRIBUTION_INSTALLABLE:
featured = _featured_version(active_versions)
if (
featured
and not featured.has_any_install_asset()
and not (featured.package_resource_url or "").strip()
):
for cand in active_versions.exclude(pk=featured.pk).order_by("order", "pk"):
if cand.has_any_install_asset() or (cand.package_resource_url or "").strip():
featured = cand
break
context["featured_version"] = featured
if featured and (
featured.has_any_install_asset()
or (featured.package_resource_url or "").strip()
):
if featured.has_any_install_asset():
specs = _install_specs_from_versions([featured])
context["featured_install_cells"] = featured.install_urls_for_specs(specs)
context["show_releases_section"] = True
older_list = list(
active_versions.exclude(pk=featured.pk).order_by("order", "pk")
if featured else active_versions.order_by("order", "pk")
)
context["show_older_versions_link"] = len(older_list) > 0
elif sub_product.distribution == SubProduct.DISTRIBUTION_PACKAGE:
pkg_versions = list(active_versions.order_by("order", "pk"))
context["package_versions"] = pkg_versions
context["show_releases_section"] = len(pkg_versions) > 0
release_context["versions_archive_url"] = sub_product.get_versions_archive_url()
context.update(release_context)
return context
class SubProductOlderVersionsView(TemplateView):
template_name = "products/sub_versions.html"
template_name = "products/versions_archive.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@@ -128,40 +109,10 @@ class SubProductOlderVersionsView(TemplateView):
is_active=True,
)
active_versions = sub_product.versions.filter(is_active=True)
featured = _featured_version(active_versions)
archive = list(
active_versions.exclude(pk=featured.pk).order_by("order", "pk")
if featured
else active_versions.order_by("order", "pk")
)
context["main_product"] = main_product
context["sub_product"] = sub_product
context["featured_version"] = featured
context["archive_versions"] = archive
if sub_product.distribution == SubProduct.DISTRIBUTION_INSTALLABLE:
specs = _install_specs_from_versions(archive)
context["archive_specs"] = specs
archive_rows_installable = []
for ver in archive:
cells = []
for field_name, label, icon in specs:
u = getattr(ver, field_name, "") or ""
u = u.strip()
cells.append(
{"field": field_name, "label": label, "icon": icon, "url": u}
)
archive_rows_installable.append(
{"version_obj": ver, "cells": cells}
)
context["archive_rows_installable"] = archive_rows_installable
context["archive_rows_package"] = False
context["distribution_installable"] = True
context["distribution_package"] = False
else:
context["archive_specs"] = ()
context["archive_rows_installable"] = []
context["archive_rows_package"] = True
context["distribution_installable"] = False
context["distribution_package"] = True
context["product_detail_url"] = sub_product.get_absolute_url()
context.update(
build_archive_context(sub_product.distribution, active_versions)
)
return context