summaryrefslogtreecommitdiff
path: root/releases
diff options
context:
space:
mode:
authorNatalia <124304+nessita@users.noreply.github.com>2026-01-20 17:32:09 -0300
committernessita <124304+nessita@users.noreply.github.com>2026-01-26 11:42:58 -0300
commit29ef283090668efab79b8547750297c4071dca92 (patch)
treeee281b064149b989014270df79b9be69b3606988 /releases
parent1e23a7c2cd6cd2798df1d6ba7833b7bb53d5487d (diff)
Extended Release model with properties in preparation to incorporate the checklist-generator code.
Diffstat (limited to 'releases')
-rw-r--r--releases/admin.py1
-rw-r--r--releases/migrations/0006_alter_release_date.py26
-rw-r--r--releases/models.py49
-rw-r--r--releases/tests.py155
4 files changed, 228 insertions, 3 deletions
diff --git a/releases/admin.py b/releases/admin.py
index b12b3659..aae877a0 100644
--- a/releases/admin.py
+++ b/releases/admin.py
@@ -51,6 +51,7 @@ class ReleaseAdmin(admin.ModelAdmin):
)
list_filter = ("status", "is_lts", "is_active")
ordering = ("-major", "-minor", "-micro", "-status", "-iteration")
+ search_fields = ("version",)
@admin.display(
description="status",
diff --git a/releases/migrations/0006_alter_release_date.py b/releases/migrations/0006_alter_release_date.py
new file mode 100644
index 00000000..a2eedbcd
--- /dev/null
+++ b/releases/migrations/0006_alter_release_date.py
@@ -0,0 +1,26 @@
+# Generated by Django 6.0 on 2026-01-20 14:31
+
+import datetime
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("releases", "0005_release_artifacts"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="release",
+ name="date",
+ field=models.DateField(
+ blank=True,
+ default=datetime.date.today,
+ help_text="Leave blank if the release date isn't known yet, typically if you're creating the final release just after the alpha because you want to build docs for the upcoming version.",
+ null=True,
+ verbose_name="Release date",
+ ),
+ ),
+ ]
diff --git a/releases/models.py b/releases/models.py
index ac9bf577..e4f746d4 100644
--- a/releases/models.py
+++ b/releases/models.py
@@ -1,5 +1,6 @@
import datetime
import re
+from functools import total_ordering
from pathlib import Path
from django.conf import settings
@@ -160,6 +161,7 @@ def upload_to_checksum(release, filename):
return f"pgp/Django-{version}.checksum.txt"
+@total_ordering
class Release(models.Model):
DEFAULT_CACHE_KEY = "%s_django_version" % settings.CACHE_MIDDLEWARE_KEY_PREFIX
STATUS_CHOICES = (
@@ -189,7 +191,7 @@ class Release(models.Model):
null=True,
blank=True,
default=datetime.date.today,
- help_text="Leave blank if the release date isn't know yet, typically "
+ help_text="Leave blank if the release date isn't known yet, typically "
"if you're creating the final release just after the alpha "
"because you want to build docs for the upcoming version.",
)
@@ -210,8 +212,8 @@ class Release(models.Model):
is_lts = models.BooleanField(
"Long Term Support",
help_text=(
- 'Is this a release for an <abbr title="Long Term Support">LTS</abbr> Django '
- "version (e.g. 5.2a1, 5.2, 5.2.4)?"
+ 'Is this a release for an <abbr title="Long Term Support">LTS</abbr> '
+ "Django version (e.g. 5.2a1, 5.2, 5.2.4)?"
),
default=False,
)
@@ -273,6 +275,47 @@ class Release(models.Model):
version.append(0)
return tuple(version)
+ @cached_property
+ def version_verbose(self):
+ return (
+ f"{self.feature_version} {self.get_status_display()} {self.iteration}"
+ if self.is_pre_release
+ else self.version
+ )
+
+ @cached_property
+ def feature_version(self):
+ return f"{self.major}.{self.minor}"
+
+ @cached_property
+ def feature_release(self):
+ return Release.objects.get(version=self.feature_version)
+
+ @cached_property
+ def series(self):
+ return f"{self.major}.x"
+
+ @cached_property
+ def stable_branch(self):
+ return f"stable/{self.feature_version}.x"
+
+ @cached_property
+ def commit_prefix(self):
+ return f"[{self.feature_version}.x]"
+
+ @cached_property
+ def is_pre_release(self):
+ """Return True if this is an alpha, beta, or rc release."""
+ return self.status != "f"
+
+ @cached_property
+ def is_dot_zero(self):
+ """Return True if this is a final X.Y.0 release."""
+ return self.status == "f" and self.micro == 0
+
+ def __lt__(self, other):
+ return self.version_tuple < other.version_tuple
+
def clean(self):
if self.is_published and not self.tarball:
raise ValidationError(
diff --git a/releases/tests.py b/releases/tests.py
index a3553d1f..a9170faa 100644
--- a/releases/tests.py
+++ b/releases/tests.py
@@ -245,6 +245,161 @@ class ReleaseTestCase(TestCase):
self.assertIsNone(next_release.eol_date)
self.assertIsNone(other_release.eol_date)
+ def test_version_tuple(self):
+ cases = [
+ ("1.0", (1, 0, 0, "final", 0)),
+ ("1.8", (1, 8, 0, "final", 0)),
+ ("1.8.1", (1, 8, 1, "final", 0)),
+ ("1.8a1", (1, 8, 0, "alpha", 1)),
+ ("1.8b1", (1, 8, 0, "beta", 1)),
+ ("1.8rc1", (1, 8, 0, "rc", 1)),
+ ("5.2", (5, 2, 0, "final", 0)),
+ ("5.2a1", (5, 2, 0, "alpha", 1)),
+ ]
+ for version, expected in cases:
+ with self.subTest(version=version):
+ release = Release.objects.create(version=version)
+ self.assertEqual(release.version_tuple, expected)
+
+ def test_version_verbose(self):
+ cases = [
+ ("5.2a1", "5.2 alpha 1"),
+ ("5.2a2", "5.2 alpha 2"),
+ ("5.2b1", "5.2 beta 1"),
+ ("5.2rc1", "5.2 release candidate 1"),
+ ("5.2", "5.2"),
+ ("5.2.1", "5.2.1"),
+ ]
+ for version, expected in cases:
+ with self.subTest(version=version):
+ release = Release.objects.create(version=version)
+ self.assertEqual(release.version_verbose, expected)
+
+ def test_feature_version(self):
+ cases = [
+ ("5.2", "5.2"),
+ ("5.2a1", "5.2"),
+ ("5.2.1", "5.2"),
+ ("5.2.15", "5.2"),
+ ("4.1rc1", "4.1"),
+ ]
+ for version, expected in cases:
+ with self.subTest(version=version):
+ release = Release.objects.create(version=version)
+ self.assertEqual(release.feature_version, expected)
+
+ def test_feature_release(self):
+ feature = Release.objects.create(version="5.2")
+
+ # Feature release itself should return itself.
+ self.assertEqual(feature.feature_release, feature)
+ self.assertEqual(feature.feature_release.version, "5.2")
+
+ # All other versions in the series should return the feature release
+ cases = ["5.2a1", "5.2b1", "5.2rc1", "5.2.1", "5.2.2"]
+ for version in cases:
+ with self.subTest(version=version):
+ release = Release.objects.create(version=version)
+ self.assertEqual(release.feature_release, feature)
+ self.assertEqual(release.feature_release.version, "5.2")
+
+ def test_series(self):
+ cases = [
+ ("5.2", "5.x"),
+ ("5.2.1", "5.x"),
+ ("4.1", "4.x"),
+ ("3.2rc1", "3.x"),
+ ]
+ for version, expected in cases:
+ with self.subTest(version=version):
+ release = Release.objects.create(version=version)
+ self.assertEqual(release.series, expected)
+
+ def test_stable_branch(self):
+ cases = [
+ ("5.2", "stable/5.2.x"),
+ ("5.2.1", "stable/5.2.x"),
+ ("4.1rc1", "stable/4.1.x"),
+ ]
+ for version, expected in cases:
+ with self.subTest(version=version):
+ release = Release.objects.create(version=version)
+ self.assertEqual(release.stable_branch, expected)
+
+ def test_commit_prefix(self):
+ cases = [
+ ("5.2", "[5.2.x]"),
+ ("5.2.1", "[5.2.x]"),
+ ("4.1rc1", "[4.1.x]"),
+ ]
+ for version, expected in cases:
+ with self.subTest(version=version):
+ release = Release.objects.create(version=version)
+ self.assertEqual(release.commit_prefix, expected)
+
+ def test_is_pre_release(self):
+ cases = [
+ ("5.2a1", True),
+ ("5.2b1", True),
+ ("5.2rc1", True),
+ ("5.2", False),
+ ("5.2.1", False),
+ ]
+ for version, expected in cases:
+ with self.subTest(version=version):
+ release = Release.objects.create(version=version)
+ self.assertIs(release.is_pre_release, expected)
+
+ def test_is_dot_zero(self):
+ cases = [
+ ("5.2", True),
+ ("4.1", True),
+ ("5.2.1", False),
+ ("5.2.15", False),
+ ("5.2a1", False),
+ ("5.2rc1", False),
+ ]
+ for version, expected in cases:
+ with self.subTest(version=version):
+ release = Release.objects.create(version=version)
+ self.assertIs(release.is_dot_zero, expected)
+
+ def test_ordering(self):
+ r1 = Release.objects.create(version="5.2")
+ r2 = Release.objects.create(version="5.2.1")
+ r3 = Release.objects.create(version="6.0a1")
+ r4 = Release.objects.create(version="6.0")
+
+ # Comparison.
+ self.assertLessEqual(r1, r1)
+ self.assertLess(r1, r2)
+ self.assertLess(r2, r3)
+ self.assertLess(r1, r3)
+ self.assertLess(r3, r4)
+ self.assertGreater(r2, r1)
+ self.assertGreaterEqual(r1, r1)
+
+ # Sorting.
+ releases = [r3, r1, r2, r4]
+ self.assertEqual(sorted(releases), [r1, r2, r3, r4])
+
+ def test_equality(self):
+ r1 = Release(version="5.2")
+ r2 = Release(version="5.2")
+
+ self.assertLessEqual(r1, r2)
+ self.assertLessEqual(r2, r1)
+ # If r1 <= r2 and r2 <= r1, then r1 == r2 should be True.
+ self.assertEqual(r1, r2)
+
+ def test_release_hash(self):
+ r1 = Release.objects.create(version="5.2")
+ r2 = Release.objects.create(version="5.2.1")
+
+ self.assertEqual({r1, r2, r1}, {r1, r2})
+ self.assertNotEqual(hash(r1), hash(r2))
+ self.assertEqual(hash(r1), hash(r1))
+
class ReleaseUploadToTestCase(SimpleTestCase):
def test_upload_to_artifact(self):