From 752c07c4cfdf79aab51b161f9d48addbd9b75f47 Mon Sep 17 00:00:00 2001 From: Marcus Furlong Date: Tue, 31 Mar 2026 23:27:24 -0400 Subject: [PATCH] django 5.2 compatibility updates - replace unique_together with UniqueConstraint across all models - remove deprecated USE_L10N setting (default since django 4.0) - import include from django.urls instead of django.conf.urls - use request.headers instead of request.META for http headers - use @admin.register decorator style - update INSTALL.md for ubuntu 26.04 (resolute) --- INSTALL.md | 8 ++--- errata/admin.py | 4 +-- hosts/admin.py | 2 +- ...alter_hostrepo_unique_together_and_more.py | 21 ++++++++++++++ hosts/models.py | 7 ++++- ...le_unique_together_module_unique_module.py | 21 ++++++++++++++ modules/models.py | 7 ++++- operatingsystems/admin.py | 2 +- ...lter_osrelease_unique_together_and_more.py | 21 ++++++++++++++ operatingsystems/models.py | 7 ++++- packages/admin.py | 4 +-- ..._alter_package_unique_together_and_more.py | 29 +++++++++++++++++++ packages/models.py | 14 +++++++-- patchman/settings.py | 1 - patchman/urls.py | 2 +- reports/admin.py | 4 +-- reports/views.py | 6 ++-- repos/admin.py | 4 +-- ...011_alter_cvss_unique_together_and_more.py | 29 +++++++++++++++++++ security/models.py | 14 +++++++-- security/tests/test_models.py | 2 +- 21 files changed, 180 insertions(+), 29 deletions(-) create mode 100644 hosts/migrations/0013_alter_hostrepo_unique_together_and_more.py create mode 100644 modules/migrations/0007_alter_module_unique_together_module_unique_module.py create mode 100644 operatingsystems/migrations/0012_alter_osrelease_unique_together_and_more.py create mode 100644 packages/migrations/0008_alter_package_unique_together_and_more.py create mode 100644 security/migrations/0011_alter_cvss_unique_together_and_more.py diff --git a/INSTALL.md b/INSTALL.md index 0d94733b..c0ec8f73 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -5,18 +5,18 @@ mysql or postgresql instead, see the database configuration section. ## Supported Server Installation Options - - [Ubuntu 24.04](#ubuntu-2404-noble) + - [Ubuntu 26.04](#ubuntu-2604-noble) - [Debian 13](#debian-13-trixie) - [Rocky 10](#rocky-10) - [virtualenv + pip](#virtualenv--pip) - [Source](#source) -### Ubuntu 24.04 (noble) +### Ubuntu 26.04 (resolute) ```shell curl -sS https://repo.openbytes.ie/openbytes.gpg > /usr/share/keyrings/openbytes.gpg -echo "deb [signed-by=/usr/share/keyrings/openbytes.gpg] https://repo.openbytes.ie/patchman/ubuntu noble-backports main" > /etc/apt/sources.list.d/patchman.list +echo "deb [signed-by=/usr/share/keyrings/openbytes.gpg] https://repo.openbytes.ie/patchman/ubuntu resolute main" > /etc/apt/sources.list.d/patchman.list apt update apt -y install python3-patchman patchman-client patchman-manage createsuperuser @@ -74,7 +74,7 @@ gunicorn patchman.wsgi -b 0.0.0.0:80 ### Source -#### Ubuntu 24.04 (noble) +#### Ubuntu 26.04 (resolute) 1. Install dependencies diff --git a/errata/admin.py b/errata/admin.py index ac4b8a50..0323a2cd 100644 --- a/errata/admin.py +++ b/errata/admin.py @@ -19,8 +19,6 @@ from errata.models import Erratum +@admin.register(Erratum) class ErratumAdmin(admin.ModelAdmin): readonly_fields = ('affected_packages', 'fixed_packages', 'references') - - -admin.site.register(Erratum, ErratumAdmin) diff --git a/hosts/admin.py b/hosts/admin.py index 43bf31da..b65d30e1 100644 --- a/hosts/admin.py +++ b/hosts/admin.py @@ -20,9 +20,9 @@ from hosts.models import Host, HostRepo +@admin.register(Host) class HostAdmin(admin.ModelAdmin): readonly_fields = ('packages', 'updates') -admin.site.register(Host, HostAdmin) admin.site.register(HostRepo) diff --git a/hosts/migrations/0013_alter_hostrepo_unique_together_and_more.py b/hosts/migrations/0013_alter_hostrepo_unique_together_and_more.py new file mode 100644 index 00000000..bde135a0 --- /dev/null +++ b/hosts/migrations/0013_alter_hostrepo_unique_together_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.28 on 2026-04-01 03:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosts', '0012_backfill_cached_counts'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='hostrepo', + unique_together=set(), + ), + migrations.AddConstraint( + model_name='hostrepo', + constraint=models.UniqueConstraint(fields=('host', 'repo'), name='unique_hostrepo'), + ), + ] diff --git a/hosts/models.py b/hosts/models.py index 37e67779..e997760e 100644 --- a/hosts/models.py +++ b/hosts/models.py @@ -646,7 +646,12 @@ class HostRepo(models.Model): priority = models.IntegerField(default=0) class Meta: - unique_together = ['host', 'repo'] + constraints = [ + models.UniqueConstraint( + fields=['host', 'repo'], + name='unique_hostrepo', + ), + ] ordering = ['host', 'repo'] def __str__(self): diff --git a/modules/migrations/0007_alter_module_unique_together_module_unique_module.py b/modules/migrations/0007_alter_module_unique_together_module_unique_module.py new file mode 100644 index 00000000..bbe8585b --- /dev/null +++ b/modules/migrations/0007_alter_module_unique_together_module_unique_module.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.28 on 2026-04-01 03:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('modules', '0006_alter_module_context_alter_module_name_and_more'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='module', + unique_together=set(), + ), + migrations.AddConstraint( + model_name='module', + constraint=models.UniqueConstraint(fields=('name', 'stream', 'version', 'context', 'arch', 'repo'), name='unique_module'), + ), + ] diff --git a/modules/models.py b/modules/models.py index aa06a41b..bbad4c09 100644 --- a/modules/models.py +++ b/modules/models.py @@ -35,7 +35,12 @@ class Module(models.Model): class Meta: verbose_name = 'Module' verbose_name_plural = 'Modules' - unique_together = ['name', 'stream', 'version', 'context', 'arch', 'repo'] + constraints = [ + models.UniqueConstraint( + fields=['name', 'stream', 'version', 'context', 'arch', 'repo'], + name='unique_module', + ), + ] ordering = ['name', 'stream'] def __str__(self): diff --git a/operatingsystems/admin.py b/operatingsystems/admin.py index 4884b5eb..fc3538db 100644 --- a/operatingsystems/admin.py +++ b/operatingsystems/admin.py @@ -20,9 +20,9 @@ from operatingsystems.models import OSRelease, OSVariant +@admin.register(OSRelease) class OSReleaseAdmin(admin.ModelAdmin): filter_horizontal = ('repos',) admin.site.register(OSVariant) -admin.site.register(OSRelease, OSReleaseAdmin) diff --git a/operatingsystems/migrations/0012_alter_osrelease_unique_together_and_more.py b/operatingsystems/migrations/0012_alter_osrelease_unique_together_and_more.py new file mode 100644 index 00000000..8f7a5e11 --- /dev/null +++ b/operatingsystems/migrations/0012_alter_osrelease_unique_together_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.28 on 2026-04-01 03:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('operatingsystems', '0011_alter_osrelease_codename_alter_osrelease_cpe_name_and_more'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='osrelease', + unique_together=set(), + ), + migrations.AddConstraint( + model_name='osrelease', + constraint=models.UniqueConstraint(fields=('name', 'codename', 'cpe_name'), name='unique_osrelease'), + ), + ] diff --git a/operatingsystems/models.py b/operatingsystems/models.py index 220cbdb3..5e472170 100644 --- a/operatingsystems/models.py +++ b/operatingsystems/models.py @@ -35,7 +35,12 @@ class OSRelease(models.Model): class Meta: verbose_name = 'Operating System Release' verbose_name_plural = 'Operating System Releases' - unique_together = ['name', 'codename', 'cpe_name'] + constraints = [ + models.UniqueConstraint( + fields=['name', 'codename', 'cpe_name'], + name='unique_osrelease', + ), + ] ordering = ['name'] def __str__(self): diff --git a/packages/admin.py b/packages/admin.py index bc4b1aaa..140e01b4 100644 --- a/packages/admin.py +++ b/packages/admin.py @@ -20,14 +20,14 @@ from packages.models import Package, PackageName, PackageUpdate +@admin.register(Package) class PackageAdmin(admin.ModelAdmin): readonly_fields = ('name',) +@admin.register(PackageUpdate) class PackageUpdateAdmin(admin.ModelAdmin): readonly_fields = ('oldpackage', 'newpackage') -admin.site.register(Package, PackageAdmin) admin.site.register(PackageName) -admin.site.register(PackageUpdate, PackageUpdateAdmin) diff --git a/packages/migrations/0008_alter_package_unique_together_and_more.py b/packages/migrations/0008_alter_package_unique_together_and_more.py new file mode 100644 index 00000000..7a03ee2c --- /dev/null +++ b/packages/migrations/0008_alter_package_unique_together_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.28 on 2026-04-01 03:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('packages', '0007_alter_package_epoch_alter_package_release_and_more'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='package', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='packageupdate', + unique_together=set(), + ), + migrations.AddConstraint( + model_name='package', + constraint=models.UniqueConstraint(fields=('name', 'epoch', 'version', 'release', 'arch', 'packagetype', 'category'), name='unique_package'), + ), + migrations.AddConstraint( + model_name='packageupdate', + constraint=models.UniqueConstraint(fields=('oldpackage', 'newpackage', 'security'), name='unique_packageupdate'), + ), + ] diff --git a/packages/models.py b/packages/models.py index 99ceaec3..e3446c95 100644 --- a/packages/models.py +++ b/packages/models.py @@ -87,7 +87,12 @@ class Package(models.Model): class Meta: ordering = ['name', 'epoch', 'version', 'release', 'arch'] - unique_together = ['name', 'epoch', 'version', 'release', 'arch', 'packagetype', 'category'] + constraints = [ + models.UniqueConstraint( + fields=['name', 'epoch', 'version', 'release', 'arch', 'packagetype', 'category'], + name='unique_package', + ), + ] def __str__(self): if self.epoch: @@ -226,7 +231,12 @@ class PackageUpdate(models.Model): security = models.BooleanField(default=False) class Meta: - unique_together = ['oldpackage', 'newpackage', 'security'] + constraints = [ + models.UniqueConstraint( + fields=['oldpackage', 'newpackage', 'security'], + name='unique_packageupdate', + ), + ] ordering = ['oldpackage', 'newpackage'] def __str__(self): diff --git a/patchman/settings.py b/patchman/settings.py index 9fe4b84f..cb86644d 100644 --- a/patchman/settings.py +++ b/patchman/settings.py @@ -67,7 +67,6 @@ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'America/NewYork' USE_I18N = True -USE_L10N = True USE_TZ = True DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' diff --git a/patchman/urls.py b/patchman/urls.py index 9f5065fd..b12d5a2b 100644 --- a/patchman/urls.py +++ b/patchman/urls.py @@ -16,7 +16,7 @@ # along with If not, see from django.conf import settings -from django.conf.urls import handler404, handler500, include # noqa +from django.urls import include from django.contrib import admin from django.urls import path from django.views import static diff --git a/reports/admin.py b/reports/admin.py index 66e0bf5b..0ad970f4 100644 --- a/reports/admin.py +++ b/reports/admin.py @@ -20,8 +20,6 @@ from reports.models import Report +@admin.register(Report) class ReportAdmin(admin.ModelAdmin): readonly_fields = ('packages',) - - -admin.site.register(Report, ReportAdmin) diff --git a/reports/views.py b/reports/views.py index 2aab3e54..44cf65b9 100644 --- a/reports/views.py +++ b/reports/views.py @@ -304,8 +304,8 @@ def create(self, request): data = serializer.validated_data # Extract client IP - x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') - x_real_ip = request.META.get('HTTP_X_REAL_IP') + x_forwarded_for = request.headers.get('x-forwarded-for') + x_real_ip = request.headers.get('x-real-ip') if x_forwarded_for: report_ip = x_forwarded_for.split(',')[0] elif x_real_ip: @@ -336,7 +336,7 @@ def create(self, request): os=data['os'], report_ip=report_ip, protocol='2', - useragent=request.META.get('HTTP_USER_AGENT', ''), + useragent=request.headers.get('user-agent', ''), packages=json.dumps(data.get('packages', [])), repos=json.dumps(data.get('repos', [])), modules=json.dumps(data.get('modules', [])), diff --git a/repos/admin.py b/repos/admin.py index a516ff8a..20695e86 100644 --- a/repos/admin.py +++ b/repos/admin.py @@ -20,14 +20,14 @@ from repos.models import Mirror, MirrorPackage, Repository +@admin.register(Mirror) class MirrorAdmin(admin.ModelAdmin): readonly_fields = ('packages',) +@admin.register(MirrorPackage) class MirrorPackageAdmin(admin.ModelAdmin): readonly_fields = ('package',) admin.site.register(Repository) -admin.site.register(Mirror, MirrorAdmin) -admin.site.register(MirrorPackage, MirrorPackageAdmin) diff --git a/security/migrations/0011_alter_cvss_unique_together_and_more.py b/security/migrations/0011_alter_cvss_unique_together_and_more.py new file mode 100644 index 00000000..6051bd5d --- /dev/null +++ b/security/migrations/0011_alter_cvss_unique_together_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.28 on 2026-04-01 03:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('security', '0010_fix_cvss_vector_string_length'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='cvss', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='reference', + unique_together=set(), + ), + migrations.AddConstraint( + model_name='cvss', + constraint=models.UniqueConstraint(fields=('score', 'severity', 'version', 'vector_string'), name='unique_cvss'), + ), + migrations.AddConstraint( + model_name='reference', + constraint=models.UniqueConstraint(fields=('ref_type', 'url'), name='unique_reference'), + ), + ] diff --git a/security/models.py b/security/models.py index aab22882..9b7dd5b3 100644 --- a/security/models.py +++ b/security/models.py @@ -32,7 +32,12 @@ class Reference(models.Model): url = models.URLField(max_length=512) class Meta: - unique_together = ['ref_type', 'url'] + constraints = [ + models.UniqueConstraint( + fields=['ref_type', 'url'], + name='unique_reference', + ), + ] ordering = ['ref_type', 'url'] def __str__(self): @@ -87,7 +92,12 @@ class CVSS(models.Model): vector_string = models.CharField(max_length=255, blank=True, null=True) class Meta: - unique_together = ['score', 'severity', 'version', 'vector_string'] + constraints = [ + models.UniqueConstraint( + fields=['score', 'severity', 'version', 'vector_string'], + name='unique_cvss', + ), + ] def __str__(self): return f'{self.score} ({self.severity}) [{self.vector_string}]' diff --git a/security/tests/test_models.py b/security/tests/test_models.py index c04acf89..f2285392 100644 --- a/security/tests/test_models.py +++ b/security/tests/test_models.py @@ -155,7 +155,7 @@ def test_reference_str(self): self.assertIn('example.com', str_repr) def test_reference_unique_together(self): - """Test Reference unique_together constraint.""" + """Test Reference unique constraint.""" Reference.objects.create( url='https://example.com/advisory', ref_type='VENDOR',