From 5ea3b2c7b71d4ee0d47b6993f050cb24b497e4f2 Mon Sep 17 00:00:00 2001 From: Anders <6058745+ddabble@users.noreply.github.com> Date: Sat, 21 Mar 2026 13:03:18 +0100 Subject: [PATCH 1/4] Added missing migration changes The changes originated in the following commits: * `checkin/0004_auto_20180906_1855.py`: b57c22afaabc5f81aaa16b77293b2b9413c2a8ef * `make_queue/0007_auto_20180914_1306.py`: b57c22afaabc5f81aaa16b77293b2b9413c2a8ef * `make_queue/0036_alter_coursepermission_short_name_and_more.py`: a744023d967933c74f1c53903ce9bced6a0f3c3d * `news/0022_abstract_newsbase.py`: b57c22afaabc5f81aaa16b77293b2b9413c2a8ef --- .../migrations/0004_auto_20180906_1855.py | 2 +- .../migrations/0007_auto_20180914_1306.py | 4 +-- ...er_coursepermission_short_name_and_more.py | 34 +++++++++++++++++++ src/news/migrations/0022_abstract_newsbase.py | 4 +-- 4 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 src/make_queue/migrations/0036_alter_coursepermission_short_name_and_more.py diff --git a/src/checkin/migrations/0004_auto_20180906_1855.py b/src/checkin/migrations/0004_auto_20180906_1855.py index 9d4a77168..0809b3665 100644 --- a/src/checkin/migrations/0004_auto_20180906_1855.py +++ b/src/checkin/migrations/0004_auto_20180906_1855.py @@ -13,7 +13,7 @@ class Migration(migrations.Migration): name="suggestskill", options={ "ordering": ("title",), - "permissions": (("can_force_suggestion", "Can force suggestion"),), + "permissions": [("can_force_suggestion", "Can force suggestion")], }, ), ] diff --git a/src/make_queue/migrations/0007_auto_20180914_1306.py b/src/make_queue/migrations/0007_auto_20180914_1306.py index 06bd9b149..097e3c03c 100644 --- a/src/make_queue/migrations/0007_auto_20180914_1306.py +++ b/src/make_queue/migrations/0007_auto_20180914_1306.py @@ -64,9 +64,9 @@ class Migration(migrations.Migration): ), ], options={ - "permissions": ( + "permissions": [ ("can_view_reservation_user", "Can view reservation user"), - ), + ], }, ), migrations.DeleteModel( diff --git a/src/make_queue/migrations/0036_alter_coursepermission_short_name_and_more.py b/src/make_queue/migrations/0036_alter_coursepermission_short_name_and_more.py new file mode 100644 index 000000000..91368e528 --- /dev/null +++ b/src/make_queue/migrations/0036_alter_coursepermission_short_name_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 5.0.2 on 2026-03-24 22:40 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ( + "make_queue", + "0035_coursepermission_printer3dcourse_course_permissions_and_more", + ), + ] + + operations = [ + migrations.AlterField( + model_name="coursepermission", + name="short_name", + field=models.CharField( + blank=True, max_length=4, unique=True, verbose_name="short name" + ), + ), + migrations.AlterField( + model_name="machinetype", + name="usage_requirement", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="make_queue.coursepermission", + verbose_name="usage requirement", + ), + ), + ] diff --git a/src/news/migrations/0022_abstract_newsbase.py b/src/news/migrations/0022_abstract_newsbase.py index 3c3882bea..bbdd2821d 100644 --- a/src/news/migrations/0022_abstract_newsbase.py +++ b/src/news/migrations/0022_abstract_newsbase.py @@ -106,7 +106,7 @@ class Migration(migrations.Migration): ], options={ "ordering": ("-publication_time",), - "permissions": (("can_view_private", "Can view private articles"),), + "permissions": [("can_view_private", "Can view private articles")], "abstract": False, }, ), @@ -176,7 +176,7 @@ class Migration(migrations.Migration): ), ], options={ - "permissions": (("can_view_private", "Can view private events"),), + "permissions": [("can_view_private", "Can view private events")], "abstract": False, }, ), From 9124f7efa032ec85bfdb9e94d1baa081a734d6e2 Mon Sep 17 00:00:00 2001 From: Anders <6058745+ddabble@users.noreply.github.com> Date: Sat, 21 Mar 2026 13:05:48 +0100 Subject: [PATCH 2/4] Upgraded django-decorator-include This also fixes the following warning: `UserWarning: pkg_resources is deprecated as an API.` --- pyproject.toml | 2 +- uv.lock | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9d56c4218..db2ecd161 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ dependencies = [ "django-ckeditor==6.7.0", "django-cleanup==8.1.0", "django-constance==3.1.0", - "django-decorator-include==3.0", + "django-decorator-include==3.3", # (See this page for a list of all management commands: # https://django-extensions.readthedocs.io/en/latest/command_extensions.html) "django-extensions==3.2.3", diff --git a/uv.lock b/uv.lock index 1b22a596c..9cec525de 100644 --- a/uv.lock +++ b/uv.lock @@ -339,15 +339,12 @@ wheels = [ [[package]] name = "django-decorator-include" -version = "3.0" +version = "3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/11/d13f07b11b37c7f0a4860e37c2f93387f83343e82376313a22f9dd3bda0c/django-decorator-include-3.0.tar.gz", hash = "sha256:18db98e0e48dbb3977ab79b5520dbf5ecbb35f0bced8e15ed62dc66762345856", size = 5192, upload-time = "2020-05-07T12:40:42.453Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/a9/d8e41e2b4dc42ae59a28e4f680909bee9831a848495679d5c1b46b5c1a8e/django_decorator_include-3.0-py3-none-any.whl", hash = "sha256:fe7a96afb23f66dcbc6227d3c48430e53cc5704eab30841ddb8bd940e5ea8a5a", size = 5410, upload-time = "2020-05-07T12:40:40.979Z" }, -] +sdist = { url = "https://files.pythonhosted.org/packages/c0/5c/117a3070f3e9f9b51916c4f38db163e7afe1bb14f1d670e280f62efaf641/django_decorator_include-3.3.tar.gz", hash = "sha256:336f73c403983a5d1bf4da5f19cd114db3a912b9bfc0e69ef1f20ce589828c3e", size = 7040, upload-time = "2025-01-16T15:55:18.496Z" } [[package]] name = "django-extensions" @@ -1067,7 +1064,7 @@ requires-dist = [ { name = "django-ckeditor", specifier = "==6.7.0" }, { name = "django-cleanup", specifier = "==8.1.0" }, { name = "django-constance", specifier = "==3.1.0" }, - { name = "django-decorator-include", specifier = "==3.0" }, + { name = "django-decorator-include", specifier = "==3.3" }, { name = "django-extensions", specifier = "==3.2.3" }, { name = "django-hosts", specifier = "==6.0" }, { name = "django-ical", specifier = "==1.9.2" }, From 62d75bbe571127908d6e4b0bb84b3030de7fe577 Mon Sep 17 00:00:00 2001 From: Anders <6058745+ddabble@users.noreply.github.com> Date: Sat, 21 Mar 2026 13:09:07 +0100 Subject: [PATCH 3/4] Added tests for some management commands This removes the need for the following checklist item in `pull_request_template.md`: "- [ ] I've run `makemigrations`, `makemessages` and `compilemessages`" --- .../actions/install-dependencies/action.yml | 3 +- .github/pull_request_template.md | 1 - src/locale/nb/LC_MESSAGES/django.po | 32 --------- src/web/tests/management/__init__.py | 0 src/web/tests/management/test_check.py | 15 ++++ .../tests/management/test_compilemessages.py | 69 +++++++++++++++++++ src/web/tests/management/test_makemessages.py | 53 ++++++++++++++ .../tests/management/test_makemigrations.py | 19 +++++ src/web/tests/management/test_shell_plus.py | 14 ++++ 9 files changed, 172 insertions(+), 34 deletions(-) create mode 100644 src/web/tests/management/__init__.py create mode 100644 src/web/tests/management/test_check.py create mode 100644 src/web/tests/management/test_compilemessages.py create mode 100644 src/web/tests/management/test_makemessages.py create mode 100644 src/web/tests/management/test_makemigrations.py create mode 100644 src/web/tests/management/test_shell_plus.py diff --git a/.github/actions/install-dependencies/action.yml b/.github/actions/install-dependencies/action.yml index e2c87c814..853ae46bc 100644 --- a/.github/actions/install-dependencies/action.yml +++ b/.github/actions/install-dependencies/action.yml @@ -23,7 +23,8 @@ runs: # API can be unreliable run: | sudo apt update - sudo apt install python3-dev libldap2-dev libsasl2-dev libssl-dev -o Acquire::Retries=3 + sudo apt install --yes --no-install-recommends -o Acquire::Retries=3 \ + python3-dev libldap2-dev libsasl2-dev libssl-dev gettext - name: Install uv uses: astral-sh/setup-uv@v7 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e72814f5d..ffed56710 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -14,7 +14,6 @@ _(If any of the points are not relevant, mark them as checked, so that it's easy to see which points you've handled or not)_ -- [ ] I've run `makemigrations`, `makemessages` and `compilemessages` - [ ] I've written tests that fail without these changes (if relevant/possible) - [ ] I've manually tested the website UI with different device layouts - _Most common is to test with typical screen sizes for mobile (320-425 px), tablet (768 px) and desktop (1024+ px), which can easily be done with your browser's dev tools_ diff --git a/src/locale/nb/LC_MESSAGES/django.po b/src/locale/nb/LC_MESSAGES/django.po index 405b4bb8a..624b4cef3 100644 --- a/src/locale/nb/LC_MESSAGES/django.po +++ b/src/locale/nb/LC_MESSAGES/django.po @@ -2283,35 +2283,3 @@ msgstr "Velg verdi" msgid "Search places" msgstr "Søk etter steder" - -#~ msgid "You have completed the Raise3D printer course" -#~ msgstr "Du har fullført Raise3D-printerkurset" - -#~ msgid "You have not taken the Raise3D printer course" -#~ msgstr "Du har ikke tatt Raise3D-printerkurset" - -#~ msgid "" -#~ "To use a Raise3D printer, make a reservation in the calendar of one of " -#~ "the Raise3D printers on the “Reservations” page." -#~ msgstr "" -#~ "For å bruke en Raise3D-printer, lag en reservasjon i kalenderen til en av " -#~ "Raise3D-printerne på «Reservasjoner»-siden." - -#~ msgid "You have completed the SLA 3D printer course" -#~ msgstr "Du har fullført SLA 3D-printerkurset" - -#~ msgid "You have not taken the SLA 3D printer course" -#~ msgstr "Du har ikke tatt SLA 3D-printerkurset" - -#~ msgid "" -#~ "To use an SLA 3D printer, make a reservation in the calendar of one of " -#~ "the SLA 3D printers on the “Reservations” page." -#~ msgstr "" -#~ "For å bruke en SLA 3D-printer, lag en reservasjon i kalenderen til en av " -#~ "SLA 3D-printerne på «Reservasjoner»-siden." - -#~ msgid "Raise3D course" -#~ msgstr "Raise3D-kurs" - -#~ msgid "SLA course" -#~ msgstr "SLA-kurs" diff --git a/src/web/tests/management/__init__.py b/src/web/tests/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/web/tests/management/test_check.py b/src/web/tests/management/test_check.py new file mode 100644 index 000000000..e255ffb5a --- /dev/null +++ b/src/web/tests/management/test_check.py @@ -0,0 +1,15 @@ +import io +from unittest import TestCase as StandardTestCase + +from django.core import management + + +class CheckTests(StandardTestCase): + def test__check__identifies_no_issues(self): + out = io.StringIO() + err = io.StringIO() + management.call_command("check", fail_level="WARNING", stdout=out, stderr=err) + self.assertEqual( + out.getvalue().strip(), "System check identified no issues (0 silenced)." + ) + self.assertEqual(err.getvalue().strip(), "") diff --git a/src/web/tests/management/test_compilemessages.py b/src/web/tests/management/test_compilemessages.py new file mode 100644 index 000000000..b69277e4a --- /dev/null +++ b/src/web/tests/management/test_compilemessages.py @@ -0,0 +1,69 @@ +import io +import re +from pathlib import Path +from unittest import TestCase as StandardTestCase + +from django.core import management + +from web.tests.management.test_makemessages import ( + LocaleFileType, + get_project_locale_files, +) + + +class CompilessagesTests(StandardTestCase): + def test_mo_files_are_up_to_date(self): + before_contents = self.get_mo_file_contents() + + # For some reason, changes in the `.po` files are often not reflected in + # the `.mo` files when running `compilemessages` - unless the `.mo` files + # are deleted first, in which case they seem to be consistently updated + self.delete_mo_files() + management.call_command("compilemessages") + + after_contents = self.get_mo_file_contents() + + if after_contents != before_contents: + self.fail( + f"\n{'#' * 52}" + "\n### One or more of the `.mo` files are outdated! ###" + "\n### Run `make compilemessages` to fix this. ###" + f"\n{'#' * 52}" + ) + + self._test_compilemessages_only_processes_our_mo_files() + + @staticmethod + def get_mo_file_contents() -> dict[Path, bytes]: + return { + path: path.read_bytes() + for path in get_project_locale_files(LocaleFileType.MO) + } + + @staticmethod + def delete_mo_files(): + for path in get_project_locale_files(LocaleFileType.MO): + path.unlink() + + def _test_compilemessages_only_processes_our_mo_files(self): + """Test that the ``compilemessages`` command only processes ``.mo`` files inside + our project apps (see the comment above + ``web.management.commands.makemessages.Command.DEFAULT_IGNORED_DIRS``). + + NOTE: This should only be called after confirming that the ``.mo`` files are up + to date, as that's assumed to be the case when checking the command + output. + """ + out = io.StringIO() + err = io.StringIO() + management.call_command("compilemessages", stdout=out, stderr=err) + output = out.getvalue().strip() + self.assertFalse(err.getvalue().strip()) + + # Example line: `File “path/to/file.po” is already compiled and up to date.` + file_paths = { + Path(path) for path in re.findall(r"[\w\s]*“(.+)”[\w\s]*", output) + } + self.assertSetEqual( + file_paths, set(get_project_locale_files(LocaleFileType.PO)) + ) diff --git a/src/web/tests/management/test_makemessages.py b/src/web/tests/management/test_makemessages.py new file mode 100644 index 000000000..456ea966a --- /dev/null +++ b/src/web/tests/management/test_makemessages.py @@ -0,0 +1,53 @@ +from enum import StrEnum +from pathlib import Path +from unittest import TestCase as StandardTestCase + +from django.conf import settings +from django.core import management + + +class LocaleFileType(StrEnum): + PO = ".po" + MO = ".mo" + + +def get_project_locale_files(file_type: LocaleFileType) -> list[Path]: + return [ + file_path + for dir_path in settings.LOCALE_PATHS + for file_path in Path(dir_path).rglob(f"*{file_type}") + ] + + +class MakemessagesTests(StandardTestCase): + def test_po_files_are_up_to_date(self): + before_contents = self.get_po_file_contents() + management.call_command("makemessages", all=True) + management.call_command("makemessages", all=True, domain="djangojs") + after_contents = self.get_po_file_contents() + + if after_contents != before_contents: + self.fail( + f"\n{'#' * 52}" + "\n### One or more of the `.po` files are outdated! ###" + "\n### Run `make makemessages-all` to fix this. ###" + f"\n{'#' * 52}" + ) + + @classmethod + def get_po_file_contents(cls) -> dict[Path, str]: + def get_contents(path: Path) -> str: + contents = path.read_text(encoding="utf-8") + # Remove the `POT-Creation-Date:` line, as it can change even if no other + # parts of the contents have changed, leading to false test failures + contents = "\n".join( + line + for line in contents.splitlines() + if not line.startswith('"POT-Creation-Date:') + ) + return contents + + return { + path: get_contents(path) + for path in get_project_locale_files(LocaleFileType.PO) + } diff --git a/src/web/tests/management/test_makemigrations.py b/src/web/tests/management/test_makemigrations.py new file mode 100644 index 000000000..803e5057a --- /dev/null +++ b/src/web/tests/management/test_makemigrations.py @@ -0,0 +1,19 @@ +import io +from unittest import TestCase as StandardTestCase + +from django.core import management + + +class MakemigrationsTests(StandardTestCase): + def test__makemigrations__detects_no_changes(self): + out = io.StringIO() + err = io.StringIO() + management.call_command("makemigrations", dry_run=True, stdout=out, stderr=err) + self.assertEqual(err.getvalue().strip(), "") + if out.getvalue().strip() != "No changes detected": + self.fail( + f"\n{'#' * 63}" + "\n### There are changes in the code that are not reflected in ###" + "\n### the migrations! Run `make makemigrations` to fix this. ###" + f"\n{'#' * 63}" + ) diff --git a/src/web/tests/management/test_shell_plus.py b/src/web/tests/management/test_shell_plus.py new file mode 100644 index 000000000..13d994fa5 --- /dev/null +++ b/src/web/tests/management/test_shell_plus.py @@ -0,0 +1,14 @@ +import subprocess +import sys +from unittest import TestCase as StandardTestCase + + +class ShellPlusTests(StandardTestCase): + def test__shell_plus__starts_successfully(self): + result = subprocess.run( # noqa: PLW1510 + [sys.executable, "manage.py", "shell_plus", "--command=pass"], + capture_output=True, + ) + error_output = result.stderr.decode().strip() + self.assertEqual(error_output, "") + result.check_returncode() From 942d044aaccdd49cb57c76d54e484983bab4d2f2 Mon Sep 17 00:00:00 2001 From: Anders <6058745+ddabble@users.noreply.github.com> Date: Wed, 25 Mar 2026 00:47:24 +0100 Subject: [PATCH 4/4] test --- src/web/tests/management/test_makemessages.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/web/tests/management/test_makemessages.py b/src/web/tests/management/test_makemessages.py index 456ea966a..a2abbdcc0 100644 --- a/src/web/tests/management/test_makemessages.py +++ b/src/web/tests/management/test_makemessages.py @@ -26,6 +26,8 @@ def test_po_files_are_up_to_date(self): management.call_command("makemessages", all=True, domain="djangojs") after_contents = self.get_po_file_contents() + self.maxDiff = None + self.assertEqual(after_contents, before_contents) if after_contents != before_contents: self.fail( f"\n{'#' * 52}"