From 935c4390848669bdf960f98f5c16e19a0e487029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniele=20Vigan=C3=B2?= Date: Wed, 3 Dec 2025 23:07:56 +0100 Subject: [PATCH 1/6] Introduce compatibility with Django 6.0 --- .../0024_alter_basebook_language.py | 123 ++++++++++++++++++ ram/portal/templatetags/dcc.py | 16 ++- ram/ram/__init__.py | 2 +- 3 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 ram/bookshelf/migrations/0024_alter_basebook_language.py diff --git a/ram/bookshelf/migrations/0024_alter_basebook_language.py b/ram/bookshelf/migrations/0024_alter_basebook_language.py new file mode 100644 index 0000000..b7f5c5c --- /dev/null +++ b/ram/bookshelf/migrations/0024_alter_basebook_language.py @@ -0,0 +1,123 @@ +# Generated by Django 6.0 on 2025-12-03 22:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookshelf", "0023_delete_basebookdocument"), + ] + + operations = [ + migrations.AlterField( + model_name="basebook", + name="language", + field=models.CharField( + choices=[ + ("af", "Afrikaans"), + ("ar", "Arabic"), + ("ar-dz", "Algerian Arabic"), + ("ast", "Asturian"), + ("az", "Azerbaijani"), + ("bg", "Bulgarian"), + ("be", "Belarusian"), + ("bn", "Bengali"), + ("br", "Breton"), + ("bs", "Bosnian"), + ("ca", "Catalan"), + ("ckb", "Central Kurdish (Sorani)"), + ("cs", "Czech"), + ("cy", "Welsh"), + ("da", "Danish"), + ("de", "German"), + ("dsb", "Lower Sorbian"), + ("el", "Greek"), + ("en", "English"), + ("en-au", "Australian English"), + ("en-gb", "British English"), + ("eo", "Esperanto"), + ("es", "Spanish"), + ("es-ar", "Argentinian Spanish"), + ("es-co", "Colombian Spanish"), + ("es-mx", "Mexican Spanish"), + ("es-ni", "Nicaraguan Spanish"), + ("es-ve", "Venezuelan Spanish"), + ("et", "Estonian"), + ("eu", "Basque"), + ("fa", "Persian"), + ("fi", "Finnish"), + ("fr", "French"), + ("fy", "Frisian"), + ("ga", "Irish"), + ("gd", "Scottish Gaelic"), + ("gl", "Galician"), + ("he", "Hebrew"), + ("hi", "Hindi"), + ("hr", "Croatian"), + ("hsb", "Upper Sorbian"), + ("ht", "Haitian Creole"), + ("hu", "Hungarian"), + ("hy", "Armenian"), + ("ia", "Interlingua"), + ("id", "Indonesian"), + ("ig", "Igbo"), + ("io", "Ido"), + ("is", "Icelandic"), + ("it", "Italian"), + ("ja", "Japanese"), + ("ka", "Georgian"), + ("kab", "Kabyle"), + ("kk", "Kazakh"), + ("km", "Khmer"), + ("kn", "Kannada"), + ("ko", "Korean"), + ("ky", "Kyrgyz"), + ("lb", "Luxembourgish"), + ("lt", "Lithuanian"), + ("lv", "Latvian"), + ("mk", "Macedonian"), + ("ml", "Malayalam"), + ("mn", "Mongolian"), + ("mr", "Marathi"), + ("ms", "Malay"), + ("my", "Burmese"), + ("nb", "Norwegian Bokmål"), + ("ne", "Nepali"), + ("nl", "Dutch"), + ("nn", "Norwegian Nynorsk"), + ("os", "Ossetic"), + ("pa", "Punjabi"), + ("pl", "Polish"), + ("pt", "Portuguese"), + ("pt-br", "Brazilian Portuguese"), + ("ro", "Romanian"), + ("ru", "Russian"), + ("sk", "Slovak"), + ("sl", "Slovenian"), + ("sq", "Albanian"), + ("sr", "Serbian"), + ("sr-latn", "Serbian Latin"), + ("sv", "Swedish"), + ("sw", "Swahili"), + ("ta", "Tamil"), + ("te", "Telugu"), + ("tg", "Tajik"), + ("th", "Thai"), + ("tk", "Turkmen"), + ("tr", "Turkish"), + ("tt", "Tatar"), + ("udm", "Udmurt"), + ("ug", "Uyghur"), + ("uk", "Ukrainian"), + ("ur", "Urdu"), + ("uz", "Uzbek"), + ("vi", "Vietnamese"), + ("zh-hans", "Simplified Chinese"), + ("zh-hant", "Traditional Chinese"), + ], + default="en", + max_length=7, + ), + ), + ] diff --git a/ram/portal/templatetags/dcc.py b/ram/portal/templatetags/dcc.py index c1ff7e9..ab3c1c0 100644 --- a/ram/portal/templatetags/dcc.py +++ b/ram/portal/templatetags/dcc.py @@ -1,36 +1,38 @@ from django import template from django.utils.html import format_html +from django.utils.safestring import mark_safe register = template.Library() @register.simple_tag def dcc(object): - socket = ( + socket = mark_safe( '' ) decoder = '' if object.decoder_interface is not None: - socket = ( + socket = mark_safe( f'' f'' ) if object.decoder: if object.decoder.sound: - decoder = ( + decoder = mark_safe( f'' '' ) else: - decoder = ( + decoder = mark_safe( f'' '' ) - if decoder: return format_html( - f'{socket} {decoder}' + '{} {}', + socket, + decoder, ) - return format_html(socket) + return socket diff --git a/ram/ram/__init__.py b/ram/ram/__init__.py index ff3fbc5..14731ee 100644 --- a/ram/ram/__init__.py +++ b/ram/ram/__init__.py @@ -1,4 +1,4 @@ from ram.utils import git_suffix -__version__ = "0.17.14" +__version__ = "0.17.15" __version__ += git_suffix(__file__) From 66c3c3f51c7ef072eb0a14bfcd03b6cb59984e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniele=20Vigan=C3=B2?= Date: Wed, 3 Dec 2025 23:24:53 +0100 Subject: [PATCH 2/6] Extend compatibility with Django 6.0 --- ram/bookshelf/admin.py | 26 ++++++++++++++------------ ram/consist/admin.py | 2 +- ram/metadata/admin.py | 4 ++-- ram/ram/utils.py | 5 +++-- ram/roster/admin.py | 17 +++++++++-------- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/ram/bookshelf/admin.py b/ram/bookshelf/admin.py index 0e8a88b..a5bf51c 100644 --- a/ram/bookshelf/admin.py +++ b/ram/bookshelf/admin.py @@ -2,7 +2,7 @@ import html from django.conf import settings from django.contrib import admin -from django.utils.html import format_html, strip_tags +from django.utils.html import format_html, format_html_join, strip_tags from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin from ram.admin import publish, unpublish @@ -123,13 +123,14 @@ class BookAdmin(SortableAdminBase, admin.ModelAdmin): @admin.display(description="Invoices") def invoices(self, obj): if obj.invoice.exists(): - html = "
".join( - "{}".format( - i.file.url, i - ) for i in obj.invoice.all()) + html = format_html_join( + "
", + "{}", + ((i.file.url, i) for i in obj.invoice.all()) + ) else: html = "-" - return format_html(html) + return html @admin.display(description="Publisher") def get_publisher(self, obj): @@ -207,7 +208,7 @@ class PublisherAdmin(admin.ModelAdmin): @admin.display(description="Country") def country_flag(self, obj): return format_html( - ' {}'.format(obj.country.flag, obj.country.name) + ' {}', obj.country.flag, obj.country.name ) @@ -285,13 +286,14 @@ class CatalogAdmin(SortableAdminBase, admin.ModelAdmin): @admin.display(description="Invoices") def invoices(self, obj): if obj.invoice.exists(): - html = "
".join( - "{}".format( - i.file.url, i - ) for i in obj.invoice.all()) + html = format_html_join( + "
", + "{}", + ((i.file.url, i) for i in obj.invoice.all()) + ) else: html = "-" - return format_html(html) + return html def download_csv(modeladmin, request, queryset): header = [ diff --git a/ram/consist/admin.py b/ram/consist/admin.py index 472b6a0..135760a 100644 --- a/ram/consist/admin.py +++ b/ram/consist/admin.py @@ -54,7 +54,7 @@ class ConsistAdmin(SortableAdminBase, admin.ModelAdmin): @admin.display(description="Country") def country_flag(self, obj): return format_html( - ' {}'.format(obj.country.flag, obj.country) + ' {}', obj.country.flag, obj.country ) fieldsets = ( diff --git a/ram/metadata/admin.py b/ram/metadata/admin.py index c017394..d3f30b5 100644 --- a/ram/metadata/admin.py +++ b/ram/metadata/admin.py @@ -54,7 +54,7 @@ class CompanyAdmin(admin.ModelAdmin): @admin.display(description="Country") def country_flag(self, obj): return format_html( - ' {}'.format(obj.country.flag, obj.country.name) + ' {}', obj.country.flag, obj.country.name ) @@ -68,7 +68,7 @@ class ManufacturerAdmin(admin.ModelAdmin): @admin.display(description="Country") def country_flag(self, obj): return format_html( - ' {}'.format(obj.country.flag, obj.country.name) + ' {}', obj.country.flag, obj.country.name ) diff --git a/ram/ram/utils.py b/ram/ram/utils.py index b5c5e94..f60b1f3 100644 --- a/ram/ram/utils.py +++ b/ram/ram/utils.py @@ -48,8 +48,9 @@ def git_suffix(fname): def get_image_preview(url, max_size=150): return format_html( - ''.format(src=url, size=max_size) + '', # noqa: E501 + src=url, + size=max_size, ) diff --git a/ram/roster/admin.py b/ram/roster/admin.py index e7908fa..9d291e2 100644 --- a/ram/roster/admin.py +++ b/ram/roster/admin.py @@ -2,7 +2,7 @@ import html from django.conf import settings from django.contrib import admin -from django.utils.html import format_html, strip_tags +from django.utils.html import format_html, format_html_join, strip_tags from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin @@ -44,7 +44,7 @@ class RollingClass(admin.ModelAdmin): @admin.display(description="Country") def country_flag(self, obj): return format_html( - ' {}'.format(obj.country.flag, obj.country) + ' {}', obj.country.flag, obj.country.name ) @@ -152,7 +152,7 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin): @admin.display(description="Country") def country_flag(self, obj): return format_html( - ' {}'.format(obj.country.flag, obj.country) + ' {}', obj.country.flag, obj.country.name ) fieldsets = ( @@ -222,13 +222,14 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin): @admin.display(description="Invoices") def invoices(self, obj): if obj.invoice.exists(): - html = "
".join( - "{}".format( - i.file.url, i - ) for i in obj.invoice.all()) + html = format_html_join( + "
", + "{}", + ((i.file.url, i) for i in obj.invoice.all()) + ) else: html = "-" - return format_html(html) + return html def download_csv(modeladmin, request, queryset): header = [ From 3e69b9ae6e0c66866ef644a11d2ea35b5a74b3ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniele=20Vigan=C3=B2?= Date: Thu, 4 Dec 2025 00:07:05 +0100 Subject: [PATCH 3/6] Remove a migration operation failing on Django 6.0 (safe) --- ram/bookshelf/migrations/0016_basebook_book_catalogue.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ram/bookshelf/migrations/0016_basebook_book_catalogue.py b/ram/bookshelf/migrations/0016_basebook_book_catalogue.py index 5e44540..4a1072b 100644 --- a/ram/bookshelf/migrations/0016_basebook_book_catalogue.py +++ b/ram/bookshelf/migrations/0016_basebook_book_catalogue.py @@ -101,10 +101,10 @@ class Migration(migrations.Migration): model_name="basebook", name="old_title", ), - migrations.RemoveField( - model_name="basebook", - name="old_authors", - ), + # migrations.RemoveField( + # model_name="basebook", + # name="old_authors", + # ), migrations.RemoveField( model_name="basebook", name="old_publisher", From f655900411fdc42e8cf05df73d6ef18c0df10c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniele=20Vigan=C3=B2?= Date: Sun, 7 Dec 2025 23:05:44 +0100 Subject: [PATCH 4/6] Better migration fix for Django 6.0 --- .../0016_basebook_book_catalogue.py | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/ram/bookshelf/migrations/0016_basebook_book_catalogue.py b/ram/bookshelf/migrations/0016_basebook_book_catalogue.py index 4a1072b..e465782 100644 --- a/ram/bookshelf/migrations/0016_basebook_book_catalogue.py +++ b/ram/bookshelf/migrations/0016_basebook_book_catalogue.py @@ -1,7 +1,8 @@ # Generated by Django 5.1.2 on 2024-11-27 16:35 import django.db.models.deletion -from django.db import migrations, models +from django.db import migrations, models, connection +from django.db.utils import ProgrammingError, OperationalError def basebook_to_book(apps, schema_editor): @@ -16,6 +17,19 @@ def basebook_to_book(apps, schema_editor): b.authors.set(row.old_authors.all()) +def drop_temporary_tables(apps, schema_editor): + try: + with connection.cursor() as cursor: + cursor.execute( + 'DROP TABLE IF EXISTS bookshelf_basebook_old_authors' + ) + cursor.execute( + 'DROP TABLE IF EXISTS bookshelf_basebook_authors' + ) + except (ProgrammingError, OperationalError): + pass + + class Migration(migrations.Migration): dependencies = [ @@ -101,10 +115,6 @@ class Migration(migrations.Migration): model_name="basebook", name="old_title", ), - # migrations.RemoveField( - # model_name="basebook", - # name="old_authors", - # ), migrations.RemoveField( model_name="basebook", name="old_publisher", @@ -138,4 +148,16 @@ class Migration(migrations.Migration): }, bases=("bookshelf.basebook",), ), + # Required by Dajngo 6.0 on SQLite + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name="basebook", + name="old_authors", + ), + ], + database_operations=[ + migrations.RunPython(drop_temporary_tables) + ] + ), ] From 1b769da5537cc94594d93375da296033c05ecc61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniele=20Vigan=C3=B2?= Date: Sun, 7 Dec 2025 23:08:18 +0100 Subject: [PATCH 5/6] Update python versions in pipeline --- .github/workflows/django.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index 4c3e7ee..059f4c9 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -13,7 +13,7 @@ jobs: strategy: max-parallel: 2 matrix: - python-version: ['3.12', '3.13'] + python-version: ['3.13', '3.14'] steps: - uses: actions/checkout@v3 From 3804c3379b29c037722832b44fa08a7e720637fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniele=20Vigan=C3=B2?= Date: Mon, 8 Dec 2025 14:06:17 +0100 Subject: [PATCH 6/6] Add 'repository' to the login menu --- ram/portal/templates/includes/login.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ram/portal/templates/includes/login.html b/ram/portal/templates/includes/login.html index f9d69a7..6001cbc 100644 --- a/ram/portal/templates/includes/login.html +++ b/ram/portal/templates/includes/login.html @@ -11,9 +11,10 @@