feat: a link in download section

This commit is contained in:
mohamad
2026-06-07 17:41:41 +03:30
parent cbc49cd8ae
commit 33b3477176
17 changed files with 215 additions and 17 deletions
@@ -0,0 +1,23 @@
# Generated by Django 5.2.13 on 2026-06-07 13:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pages', '0013_contact_submission_attachments'),
]
operations = [
migrations.AlterField(
model_name='aboutsection',
name='section_type',
field=models.CharField(choices=[('hero', 'Hero'), ('intro', 'Intro Card'), ('grid', 'Grid Cards'), ('history', 'History Block'), ('custom', 'Custom Content'), ('supporters', 'Supporters')], default='custom', max_length=30),
),
migrations.AlterField(
model_name='aboutsectionitem',
name='image',
field=models.ImageField(blank=True, help_text='Logo or image (used for Supporters cards).', null=True, upload_to='about/items/'),
),
]
+4
View File
@@ -95,6 +95,8 @@ class MainProductAdmin(admin.ModelAdmin):
"distribution",
"package_resource_button_text",
"package_source_button_text",
"downloads_section_link_text",
"downloads_section_link_url",
)
},
),
@@ -135,6 +137,8 @@ class SubProductAdmin(admin.ModelAdmin):
"short_description",
"package_resource_button_text",
"package_source_button_text",
"downloads_section_link_text",
"downloads_section_link_url",
)
},
),
@@ -0,0 +1,45 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("products", "0015_release_original_filenames"),
]
operations = [
migrations.AddField(
model_name="mainproduct",
name="downloads_section_link_text",
field=models.CharField(
blank=True,
help_text="Label for the optional downloads section link.",
max_length=100,
),
),
migrations.AddField(
model_name="mainproduct",
name="downloads_section_link_url",
field=models.URLField(
blank=True,
help_text="Optional link shown in the top-right corner of the downloads section.",
),
),
migrations.AddField(
model_name="subproduct",
name="downloads_section_link_text",
field=models.CharField(
blank=True,
help_text="Label for the optional downloads section link.",
max_length=100,
),
),
migrations.AddField(
model_name="subproduct",
name="downloads_section_link_url",
field=models.URLField(
blank=True,
help_text="Optional link shown in the top-right corner of the downloads section.",
),
),
]
@@ -0,0 +1,34 @@
# Generated by Django 5.2.13 on 2026-06-07 13:49
import apps.products.release_assets
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('products', '0016_downloads_section_link'),
]
operations = [
migrations.AlterField(
model_name='subproductversion',
name='linux_download_file',
field=models.FileField(blank=True, help_text='Optional. Served from this site when the matching URL above is empty. To remove an uploaded file, check Clear, then Save — the file is deleted from the server. Any file type and size are allowed; large uploads may require web server limits.', storage=apps.products.release_assets.ReleaseFileStorage(), upload_to=apps.products.release_assets.release_linux_file_upload_to),
),
migrations.AlterField(
model_name='subproductversion',
name='macos_download_file',
field=models.FileField(blank=True, help_text='Optional. Served from this site when the matching URL above is empty. To remove an uploaded file, check Clear, then Save — the file is deleted from the server. Any file type and size are allowed; large uploads may require web server limits.', storage=apps.products.release_assets.ReleaseFileStorage(), upload_to=apps.products.release_assets.release_macos_file_upload_to),
),
migrations.AlterField(
model_name='subproductversion',
name='source_code_file',
field=models.FileField(blank=True, help_text='Optional. Served from this site when the matching URL above is empty. To remove an uploaded file, check Clear, then Save — the file is deleted from the server. Any file type and size are allowed; large uploads may require web server limits.', storage=apps.products.release_assets.ReleaseFileStorage(), upload_to=apps.products.release_assets.release_source_file_upload_to),
),
migrations.AlterField(
model_name='subproductversion',
name='windows_download_file',
field=models.FileField(blank=True, help_text='Optional. Served from this site when the matching URL above is empty. To remove an uploaded file, check Clear, then Save — the file is deleted from the server. Any file type and size are allowed; large uploads may require web server limits.', storage=apps.products.release_assets.ReleaseFileStorage(), upload_to=apps.products.release_assets.release_windows_file_upload_to),
),
]
+18
View File
@@ -62,6 +62,15 @@ class MainProduct(models.Model):
default="Source",
help_text="Label for the source code button on package releases.",
)
downloads_section_link_url = models.URLField(
blank=True,
help_text="Optional link shown in the top-right corner of the downloads section.",
)
downloads_section_link_text = models.CharField(
max_length=100,
blank=True,
help_text="Label for the optional downloads section link.",
)
order = models.PositiveIntegerField(default=0)
is_active = models.BooleanField(default=True)
show_on_homepage = models.BooleanField(default=False, help_text="Display this product in the homepage Products section.")
@@ -135,6 +144,15 @@ class SubProduct(models.Model):
default="Source",
help_text="Label for the source code button on package releases.",
)
downloads_section_link_url = models.URLField(
blank=True,
help_text="Optional link shown in the top-right corner of the downloads section.",
)
downloads_section_link_text = models.CharField(
max_length=100,
blank=True,
help_text="Label for the optional downloads section link.",
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
+4
View File
@@ -5,6 +5,10 @@ from django.utils.text import get_valid_filename
class ReleaseFileStorage(FileSystemStorage):
def __init__(self, **kwargs):
kwargs.setdefault("allow_overwrite", True)
super().__init__(**kwargs)
def get_available_name(self, name, max_length=None):
return name
+9
View File
@@ -13,6 +13,14 @@ def package_button_labels(product):
}
def downloads_section_link(product):
url = (getattr(product, "downloads_section_link_url", "") or "").strip()
text = (getattr(product, "downloads_section_link_text", "") or "").strip()
if url and text:
return {"url": url, "text": text}
return None
def featured_version(qs):
ordered = qs.order_by("order", "pk")
cand = ordered.filter(is_featured_stable=True).first()
@@ -38,6 +46,7 @@ def build_release_context(distribution, active_versions, product):
"show_older_versions_link": False,
"package_versions": [],
**package_button_labels(product),
"downloads_section_link": downloads_section_link(product),
}
if distribution == DISTRIBUTION_INSTALLABLE:
+27
View File
@@ -155,6 +155,33 @@ class ArticleModelTest(TestCase):
article.full_clean()
class ReleaseFileStorageTest(TestCase):
def test_save_overwrites_existing_release_file(self):
from django.core.files.uploadedfile import SimpleUploadedFile
from apps.products.models import MainProduct, SubProductVersion
main = MainProduct.objects.create(
name="Storage Test",
short_description="s",
description="d",
)
version = SubProductVersion.objects.create(
main_product=main,
version="1.0",
windows_download_file=SimpleUploadedFile("setup.exe", b"v1"),
is_active=True,
)
version.windows_download_file.save(
"setup.exe",
SimpleUploadedFile("setup.exe", b"v2"),
save=True,
)
version.refresh_from_db()
with version.windows_download_file.open("rb") as handle:
self.assertEqual(handle.read(), b"v2")
class SubProductVersionModelTest(TestCase):
def setUp(self):
self.main_product = MainProduct.objects.create(
+1 -2
View File
@@ -216,6 +216,7 @@ class SubProductOlderVersionsViewTest(ProductViewsSetup):
self.assertTrue(response.context["show_releases_section"])
@override_settings(MEDIA_ROOT=settings.BASE_DIR / "test_media_releases")
class ReleaseAssetDownloadViewTest(ProductViewsSetup):
def test_uploaded_file_download(self):
release_file = SimpleUploadedFile("setup.exe", b"binary-payload")
@@ -277,7 +278,6 @@ class ReleaseAssetDownloadViewTest(ProductViewsSetup):
)
self.assertEqual(self.client.get(url).status_code, 404)
@override_settings(MEDIA_ROOT=settings.BASE_DIR / "test_media_releases")
def test_clearing_file_removes_from_disk(self):
version = SubProductVersion.objects.create(
main_product=self.main_product,
@@ -294,7 +294,6 @@ class ReleaseAssetDownloadViewTest(ProductViewsSetup):
self.assertEqual(version.windows_download_filename, "")
self.assertFalse(os.path.exists(stored_path))
@override_settings(MEDIA_ROOT=settings.BASE_DIR / "test_media_releases")
def test_deleting_version_removes_files_from_disk(self):
version = SubProductVersion.objects.create(
main_product=self.main_product,