initial commit
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
# Generated by Django 5.2.13 on 2026-04-27 09:07
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Article',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=300)),
|
||||
('description', models.TextField()),
|
||||
('order', models.PositiveIntegerField(default=0)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Article',
|
||||
'verbose_name_plural': 'Articles',
|
||||
'ordering': ['order', 'title'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MainProduct',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('slug', models.SlugField(blank=True, unique=True)),
|
||||
('short_description', models.CharField(max_length=300)),
|
||||
('description', models.TextField()),
|
||||
('image', models.ImageField(blank=True, null=True, upload_to='products/main/')),
|
||||
('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': 'Main Product',
|
||||
'verbose_name_plural': 'Main Products',
|
||||
'ordering': ['order', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ArticleSection',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=200)),
|
||||
('value', models.TextField()),
|
||||
('order', models.PositiveIntegerField(default=0)),
|
||||
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sections', to='products.article')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Article Section',
|
||||
'verbose_name_plural': 'Article Sections',
|
||||
'ordering': ['order'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SubProduct',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('slug', models.SlugField(blank=True)),
|
||||
('short_description', models.CharField(max_length=300)),
|
||||
('description', models.TextField()),
|
||||
('image', models.ImageField(blank=True, null=True, upload_to='products/sub/')),
|
||||
('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)),
|
||||
('main_product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sub_products', to='products.mainproduct')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Sub Product',
|
||||
'verbose_name_plural': 'Sub Products',
|
||||
'ordering': ['order', 'name'],
|
||||
'unique_together': {('main_product', 'slug')},
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='sub_product',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='articles', to='products.subproduct'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.0.2 on 2026-05-14 04:53
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SubProductRelease',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('platform', models.CharField(choices=[('windows', 'Windows'), ('macos', 'macOS'), ('linux', 'Linux')], max_length=20)),
|
||||
('release_type', models.CharField(choices=[('stable', 'Stable'), ('previous', 'Previous')], default='stable', max_length=20)),
|
||||
('version', models.CharField(help_text='e.g. 2.1.0', max_length=50)),
|
||||
('download_url', models.URLField()),
|
||||
('release_notes', models.TextField(blank=True)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('order', models.PositiveIntegerField(default=0)),
|
||||
('sub_product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='releases', to='products.subproduct')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Release',
|
||||
'verbose_name_plural': 'Releases',
|
||||
'ordering': ['release_type', 'platform', 'order'],
|
||||
'unique_together': {('sub_product', 'platform', 'release_type')},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 5.0.2 on 2026-05-14 05:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0002_subproductrelease'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='description_format',
|
||||
field=models.CharField(choices=[('plain', 'Plain Text'), ('markdown', 'Markdown'), ('html', 'HTML')], default='plain', max_length=20),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='articlesection',
|
||||
name='value_format',
|
||||
field=models.CharField(choices=[('plain', 'Plain Text'), ('markdown', 'Markdown'), ('html', 'HTML')], default='plain', max_length=20),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mainproduct',
|
||||
name='description_format',
|
||||
field=models.CharField(choices=[('plain', 'Plain Text'), ('markdown', 'Markdown'), ('html', 'HTML')], default='plain', max_length=20),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='subproduct',
|
||||
name='description_format',
|
||||
field=models.CharField(choices=[('plain', 'Plain Text'), ('markdown', 'Markdown'), ('html', 'HTML')], default='plain', max_length=20),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,145 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
def migrate_legacy_releases_to_versions(apps, schema_editor):
|
||||
OldRelease = apps.get_model("products", "SubProductRelease")
|
||||
Version = apps.get_model("products", "SubProductVersion")
|
||||
|
||||
PLATFORM_MAP = {
|
||||
"windows": "windows_download_url",
|
||||
"macos": "macos_download_url",
|
||||
"linux": "linux_download_url",
|
||||
}
|
||||
|
||||
sub_ids = (
|
||||
OldRelease.objects.values_list("sub_product_id", flat=True).distinct().order_by()
|
||||
)
|
||||
|
||||
for sub_id in sub_ids:
|
||||
used_labels = set()
|
||||
for release_type in ("stable", "previous"):
|
||||
slab = OldRelease.objects.filter(
|
||||
sub_product_id=sub_id, release_type=release_type
|
||||
).order_by("order", "pk")
|
||||
if not slab.exists():
|
||||
continue
|
||||
urls = {}
|
||||
labels = []
|
||||
orders = []
|
||||
notes = []
|
||||
any_active = False
|
||||
for r in slab:
|
||||
orders.append(r.order)
|
||||
if r.is_active:
|
||||
any_active = True
|
||||
f = PLATFORM_MAP.get(r.platform)
|
||||
if f and r.download_url:
|
||||
urls[f] = r.download_url
|
||||
labels.append(r.version or "")
|
||||
if (r.release_notes or "").strip():
|
||||
notes.append((r.release_notes or "").strip())
|
||||
vn = next((x for x in labels if x), None) or "1.0"
|
||||
if vn in used_labels:
|
||||
suffix = "older" if release_type == "previous" else "alternate"
|
||||
candidate = f"{vn} ({suffix})"
|
||||
n = 2
|
||||
while candidate in used_labels:
|
||||
candidate = f"{vn} ({suffix} {n})"
|
||||
n += 1
|
||||
vn = candidate
|
||||
used_labels.add(vn)
|
||||
Version.objects.create(
|
||||
sub_product_id=sub_id,
|
||||
version=vn,
|
||||
is_featured_stable=(release_type == "stable"),
|
||||
windows_download_url=urls.get("windows_download_url", ""),
|
||||
macos_download_url=urls.get("macos_download_url", ""),
|
||||
linux_download_url=urls.get("linux_download_url", ""),
|
||||
source_code_url="",
|
||||
package_resource_url="",
|
||||
release_notes="\n\n".join(dict.fromkeys(notes)),
|
||||
is_active=any_active,
|
||||
order=min(orders) if orders else 0,
|
||||
)
|
||||
|
||||
|
||||
def noop_reverse(apps, schema_editor):
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("products", "0003_article_description_format_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="subproduct",
|
||||
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.CreateModel(
|
||||
name="SubProductVersion",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("version", models.CharField(max_length=80)),
|
||||
(
|
||||
"is_featured_stable",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Highlighted as the main release on the product page.",
|
||||
),
|
||||
),
|
||||
("windows_download_url", models.URLField(blank=True)),
|
||||
("macos_download_url", models.URLField(blank=True)),
|
||||
("linux_download_url", models.URLField(blank=True)),
|
||||
("source_code_url", models.URLField(blank=True)),
|
||||
(
|
||||
"package_resource_url",
|
||||
models.URLField(
|
||||
blank=True,
|
||||
help_text="For package-type modules: external link for this release.",
|
||||
),
|
||||
),
|
||||
("release_notes", models.TextField(blank=True)),
|
||||
("is_active", models.BooleanField(default=True)),
|
||||
("order", models.PositiveIntegerField(default=0)),
|
||||
(
|
||||
"sub_product",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="versions",
|
||||
to="products.subproduct",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Release version",
|
||||
"verbose_name_plural": "Release versions",
|
||||
"ordering": ["order", "version", "pk"],
|
||||
"unique_together": {("sub_product", "version")},
|
||||
},
|
||||
),
|
||||
migrations.RunPython(migrate_legacy_releases_to_versions, noop_reverse),
|
||||
migrations.DeleteModel(
|
||||
name="SubProductRelease",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("products", "0004_distribution_and_versions"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="article",
|
||||
name="badge",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
default="",
|
||||
help_text="Optional label shown above the article title (e.g. 'Overview', 'Note').",
|
||||
max_length=100,
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,49 @@
|
||||
# Generated by Django 5.0.2 on 2026-05-17 14:59
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0005_article_badge'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='subproduct',
|
||||
name='homepage_order',
|
||||
field=models.PositiveIntegerField(default=0, help_text='Order within the homepage Products section.'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='subproduct',
|
||||
name='logo',
|
||||
field=models.ImageField(blank=True, help_text='Small logo shown as a corner badge on homepage product cards.', null=True, upload_to='products/sub_logos/'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='subproduct',
|
||||
name='show_on_homepage',
|
||||
field=models.BooleanField(default=False, help_text='Display this sub-product in the homepage Products section.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='subproductversion',
|
||||
name='package_resource_url',
|
||||
field=models.URLField(blank=True, help_text='Shown on the site when set. For Resources it is the main Open link; for Installable it appears under the platform grid.'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ArticleCitation',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('text', models.TextField(help_text='Full citation text.')),
|
||||
('url', models.URLField(blank=True, help_text='Optional link to the cited source.')),
|
||||
('order', models.PositiveIntegerField(default=0)),
|
||||
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='citations', to='products.article')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Article Citation',
|
||||
'verbose_name_plural': 'Article Citations',
|
||||
'ordering': ['order', 'pk'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.2 on 2026-05-17 15:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0006_subproduct_logo_homepage_citations'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='citation_count_display',
|
||||
field=models.PositiveSmallIntegerField(blank=True, help_text='Optional number shown in the dashed citation circle badge at the bottom of the article card. Leave blank to hide the badge.', null=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.0.2 on 2026-05-20 12:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0007_article_citation_count_display'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='citation_count_label',
|
||||
field=models.CharField(blank=True, default='', help_text="Optional label shown below the number in the citation circle badge (e.g. 'cited'). Leave blank to show no label.", max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='citation_count_display',
|
||||
field=models.PositiveSmallIntegerField(blank=True, help_text='Optional number shown in the dashed citation circle badge. Leave blank to show an empty circle (if a label is set) or hide the badge entirely.', null=True),
|
||||
),
|
||||
]
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.0.2 on 2026-05-26 12:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0010_release_version_main_product'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='mainproduct',
|
||||
name='homepage_order',
|
||||
field=models.PositiveIntegerField(default=0, help_text='Order within the homepage Products section.'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mainproduct',
|
||||
name='show_on_homepage',
|
||||
field=models.BooleanField(default=False, help_text='Display this product in the homepage Products section.'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def copy_subproduct_homepage_flags(apps, schema_editor):
|
||||
SubProduct = apps.get_model("products", "SubProduct")
|
||||
for sub in (
|
||||
SubProduct.objects.filter(show_on_homepage=True)
|
||||
.select_related("main_product")
|
||||
.order_by("homepage_order", "order")
|
||||
):
|
||||
main = sub.main_product
|
||||
if not main.show_on_homepage:
|
||||
main.show_on_homepage = True
|
||||
main.homepage_order = sub.homepage_order
|
||||
main.save(update_fields=["show_on_homepage", "homepage_order"])
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("products", "0011_mainproduct_homepage_order_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(copy_subproduct_homepage_flags, migrations.RunPython.noop),
|
||||
]
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 5.0.2 on 2026-05-31 09:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0012_migrate_homepage_visibility_to_mainproduct'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='mainproduct',
|
||||
name='package_resource_button_text',
|
||||
field=models.CharField(default='PyPI', help_text='Label for the package resource button on package releases.', max_length=100),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mainproduct',
|
||||
name='package_source_button_text',
|
||||
field=models.CharField(default='Source', help_text='Label for the source code button on package releases.', max_length=100),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='subproduct',
|
||||
name='package_resource_button_text',
|
||||
field=models.CharField(default='PyPI', help_text='Label for the package resource button on package releases.', max_length=100),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='subproduct',
|
||||
name='package_source_button_text',
|
||||
field=models.CharField(default='Source', help_text='Label for the source code button on package releases.', max_length=100),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.0.2 on 2026-05-31 09:42
|
||||
|
||||
import apps.products.release_assets
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0013_package_release_button_labels'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
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. Any file type and size are allowed; large uploads may require web server limits.', upload_to=apps.products.release_assets.release_file_upload_to),
|
||||
),
|
||||
migrations.AddField(
|
||||
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. Any file type and size are allowed; large uploads may require web server limits.', upload_to=apps.products.release_assets.release_file_upload_to),
|
||||
),
|
||||
migrations.AddField(
|
||||
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. Any file type and size are allowed; large uploads may require web server limits.', upload_to=apps.products.release_assets.release_file_upload_to),
|
||||
),
|
||||
migrations.AddField(
|
||||
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. Any file type and size are allowed; large uploads may require web server limits.', upload_to=apps.products.release_assets.release_file_upload_to),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,54 @@
|
||||
# Generated by Django 5.0.2 on 2026-05-31 09:52
|
||||
|
||||
import apps.products.release_assets
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0014_release_download_files'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='subproductversion',
|
||||
name='linux_download_filename',
|
||||
field=models.CharField(blank=True, max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='subproductversion',
|
||||
name='macos_download_filename',
|
||||
field=models.CharField(blank=True, max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='subproductversion',
|
||||
name='source_code_filename',
|
||||
field=models.CharField(blank=True, max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='subproductversion',
|
||||
name='windows_download_filename',
|
||||
field=models.CharField(blank=True, max_length=255),
|
||||
),
|
||||
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. 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. 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. 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. 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),
|
||||
),
|
||||
]
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,39 @@
|
||||
# Generated by Django 5.2.13 on 2026-06-08 12:42
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0017_alter_subproductversion_linux_download_file_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ProductVideo',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('video_source', models.CharField(blank=True, choices=[('youtube', 'YouTube / external link'), ('upload', 'Uploaded file')], default='youtube', max_length=20)),
|
||||
('video_url', models.CharField(blank=True, help_text='YouTube watch, embed, or youtu.be link.', max_length=500)),
|
||||
('video_file', models.FileField(blank=True, help_text='MP4, WebM, OGG, or MOV file.', upload_to='videos/')),
|
||||
('video_poster', models.ImageField(blank=True, help_text='Optional thumbnail shown before an uploaded video plays.', null=True, upload_to='videos/posters/')),
|
||||
('video_size', models.CharField(choices=[('sm', 'Small (480px)'), ('md', 'Medium (720px)'), ('lg', 'Large (960px)'), ('full', 'Full width')], default='md', max_length=10)),
|
||||
('video_aspect_ratio', models.CharField(choices=[('16/9', '16:9 (widescreen)'), ('4/3', '4:3 (standard)'), ('1/1', '1:1 (square)')], default='16/9', max_length=10)),
|
||||
('badge', models.CharField(blank=True, max_length=100)),
|
||||
('title', models.CharField(blank=True, max_length=300)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('order', models.PositiveIntegerField(default=0)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('main_product', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='videos', to='products.mainproduct')),
|
||||
('sub_product', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='videos', to='products.subproduct')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Product Video',
|
||||
'verbose_name_plural': 'Product Videos',
|
||||
'ordering': ['order', 'pk'],
|
||||
'constraints': [models.CheckConstraint(condition=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='product_video_exactly_one_parent')],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.13 on 2026-06-08 13:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0018_video_support'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='productvideo',
|
||||
name='video_styled_background',
|
||||
field=models.BooleanField(default=False, help_text='Glass panel with ambient glow (similar to the Downloads section).'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.13 on 2026-06-08 13:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0019_video_styled_background'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='productvideo',
|
||||
name='description_format',
|
||||
field=models.CharField(choices=[('plain', 'Plain Text'), ('markdown', 'Markdown'), ('html', 'HTML')], default='plain', max_length=20),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,70 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def migrate_featured_stable_to_channels(apps, schema_editor):
|
||||
Version = apps.get_model("products", "SubProductVersion")
|
||||
for version in Version.objects.filter(is_featured_stable=True):
|
||||
version.is_featured = True
|
||||
if not version.release_channel:
|
||||
version.release_channel = "stable"
|
||||
version.save(update_fields=["is_featured", "release_channel"])
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("products", "0020_video_description_format"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="subproductversion",
|
||||
name="channel_label",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
help_text="Optional custom badge text. Overrides the preset channel label when set.",
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="subproductversion",
|
||||
name="is_featured",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="Primary release shown at the top of the downloads section.",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="subproductversion",
|
||||
name="release_channel",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("", "None"),
|
||||
("stable", "Stable"),
|
||||
("beta", "Beta"),
|
||||
("rc", "Release candidate"),
|
||||
("preview", "Preview"),
|
||||
("nightly", "Nightly"),
|
||||
("current", "Current"),
|
||||
("previous", "Previous"),
|
||||
],
|
||||
default="",
|
||||
help_text="Preset badge for this release (Stable, Beta, Previous, etc.).",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="subproductversion",
|
||||
name="show_on_product_page",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="Also show this release on the product page (e.g. beta or previous version).",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(migrate_featured_stable_to_channels, migrations.RunPython.noop),
|
||||
migrations.RemoveField(
|
||||
model_name="subproductversion",
|
||||
name="is_featured_stable",
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user