diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27d6e035d..f68f5ecad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: run: cat Aptfile | sudo xargs apt-get install - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eacc7b7c9..fc4f3468a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -51,7 +51,7 @@ repos: - --exclude-files - "_test.js$" - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.15.11" + rev: "v0.15.12" hooks: - id: ruff-format - id: ruff diff --git a/.secrets.baseline b/.secrets.baseline index fd49503d1..1e565c0e2 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -116,7 +116,7 @@ ".yarn/", "_test.py$", "test_.*.py", - "poetry.lock", + "uv.lock", "yarn.lock", "compliance/test_data/cybersource/", "_test.js$" @@ -189,14 +189,14 @@ "filename": "pytest.ini", "hashed_secret": "c0d7ae39ad8e0e46cdbac4c3fb44a6bc1a17105d", "is_verified": false, - "line_number": 19 + "line_number": 18 }, { "type": "Secret Keyword", "filename": "pytest.ini", "hashed_secret": "b235838f76594bf21886c6eec9c06a207e9ec5ce", "is_verified": false, - "line_number": 35 + "line_number": 34 } ], "sheets/dev-setup.md": [ @@ -245,5 +245,5 @@ } ] }, - "generated_at": "2025-06-19T09:18:19Z" + "generated_at": "2026-04-13T13:24:33Z" } diff --git a/RELEASE.rst b/RELEASE.rst index 8acb1998e..12e3c4312 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -1,6 +1,14 @@ Release Notes ============= +Version 0.194.0 +--------------- + +- chore(deps): update astral-sh/setup-uv action to v8 (#3896) +- fix(deps): update dependency beautifulsoup4 to v4.14.3 (#3510) +- Wagtail and django upgrade (#3876) +- [pre-commit.ci] pre-commit autoupdate (#3898) + Version 0.193.2 (Released April 30, 2026) --------------- diff --git a/b2b_ecommerce/api.py b/b2b_ecommerce/api.py index 99d71ee1d..2112dde6a 100644 --- a/b2b_ecommerce/api.py +++ b/b2b_ecommerce/api.py @@ -75,7 +75,8 @@ def _generate_b2b_cybersource_sa_payload(*, order, receipt_url, cancel_url): product_version = order.product_version content_object = product_version.product.content_object - content_type = str(product_version.product.content_type) + _ct = product_version.product.content_type + content_type = f"{_ct.app_label} | {_ct.name}" price = order.total_price return { diff --git a/b2b_ecommerce/api_test.py b/b2b_ecommerce/api_test.py index e6b099798..1c8b46c9c 100644 --- a/b2b_ecommerce/api_test.py +++ b/b2b_ecommerce/api_test.py @@ -88,7 +88,7 @@ def test_signed_payload(mocker, contract_number): "item_0_code": "enrollment_code", "item_0_name": f"Enrollment codes for {product_version.description}"[:254], "item_0_quantity": order.num_seats, - "item_0_sku": f"enrollment_code-{str(product.content_type)}-{product.content_object.id}", # noqa: RUF010 + "item_0_sku": f"enrollment_code-{product.content_type.app_label} | {product.content_type.name}-{product.content_object.id}", "item_0_tax_amount": "0", "item_0_unit_price": str(total_price), "line_item_count": 1, diff --git a/b2b_ecommerce/models.py b/b2b_ecommerce/models.py index d3bb9b548..02974ddaa 100644 --- a/b2b_ecommerce/models.py +++ b/b2b_ecommerce/models.py @@ -188,8 +188,9 @@ def to_dict(self): **serialize_model_object(self.product_version), "product_info": { **serialize_model_object(self.product_version.product), - "content_type_string": str( - self.product_version.product.content_type + "content_type_string": "{} | {}".format( + self.product_version.product.content_type.app_label, + self.product_version.product.content_type.name, ), "content_object": serialize_model_object( self.product_version.product.content_object diff --git a/b2b_ecommerce/models_test.py b/b2b_ecommerce/models_test.py index b63048ed1..98d71f919 100644 --- a/b2b_ecommerce/models_test.py +++ b/b2b_ecommerce/models_test.py @@ -33,7 +33,7 @@ def test_b2b_order_audit(): **serialize_model_object(order.product_version), "product_info": { **serialize_model_object(order.product_version.product), - "content_type_string": str(order.product_version.product.content_type), + "content_type_string": f"{order.product_version.product.content_type.app_label} | {order.product_version.product.content_type.name}", "content_object": serialize_model_object( order.product_version.product.content_object ), diff --git a/cms/models.py b/cms/models.py index c01bafa66..f351f6ea4 100644 --- a/cms/models.py +++ b/cms/models.py @@ -1051,7 +1051,6 @@ class Meta: ], blank=True, help_text="The content of this tab on the program page", - use_json_field=True, ) content_panels = Page.content_panels + [ # noqa: RUF005 @@ -1726,7 +1725,6 @@ class UserTestimonialsPage(CourseProgramChildPage): [("testimonial", UserTestimonialBlock())], blank=False, help_text="Add testimonials to display in this section.", - use_json_field=True, ) content_panels = [ FieldPanel("heading"), @@ -1767,7 +1765,6 @@ class NewsAndEventsPage(DisableSitemapURLMixin, Page): [("news_and_events", NewsAndEventsBlock())], blank=False, help_text="Add news and events updates to display in this section.", - use_json_field=True, ) content_panels = [FieldPanel("heading"), FieldPanel("items")] api_fields = [ @@ -1825,7 +1822,6 @@ class LearningOutcomesPage(CourseProgramChildPage): [("outcome", TextBlock(icon="plus"))], blank=False, help_text="Detail about What you'll learn as learning outcome.", - use_json_field=True, ) content_panels = [ @@ -1851,7 +1847,6 @@ class LearningTechniquesPage(CourseProgramChildPage): [("techniques", LearningTechniqueBlock())], blank=False, help_text="Enter detail about how you'll learn.", - use_json_field=True, ) content_panels = [FieldPanel("title"), FieldPanel("technique_items")] @@ -2035,7 +2030,6 @@ class WhoShouldEnrollPage(CourseProgramChildPage): ], blank=False, help_text='Contents of the "Who Should Enroll" section.', - use_json_field=True, ) switch_layout = models.BooleanField( blank=True, @@ -2095,7 +2089,6 @@ class CoursesInProgramPage(CourseProgramChildPage): ], help_text="The courseware to display in this carousel", blank=True, - use_json_field=True, ) @property @@ -2155,7 +2148,6 @@ class FacultyMembersPage(CourseProgramChildPage): members = StreamField( [("member", FacultyBlock())], help_text="The faculty members to display on this page", - use_json_field=True, ) content_panels = [ FieldPanel("heading"), @@ -2180,7 +2172,6 @@ class AbstractImageCarousel(Page): [("image", ImageChooserBlock(help_text="Choose an image to upload."))], blank=False, help_text="Add images for this section.", - use_json_field=True, ) content_panels = [FieldPanel("title"), FieldPanel("images")] @@ -2240,7 +2231,6 @@ class ResourcePage(Page): [("content", ResourceBlock())], blank=False, help_text="Enter details of content.", - use_json_field=True, ) content_panels = Page.content_panels + [ # noqa: RUF005 @@ -2395,7 +2385,6 @@ class PartnerLogoPlacement(models.IntegerChoices): ), blank=True, help_text="You can choose upto 5 signatories.", - use_json_field=True, ) overrides = StreamField( @@ -2403,7 +2392,6 @@ class PartnerLogoPlacement(models.IntegerChoices): blank=True, help_text="Overrides for specific runs of this Course/Program", validators=[validate_unique_readable_ids], - use_json_field=True, ) display_mit_seal = models.BooleanField( @@ -2659,7 +2647,6 @@ class LearningJourneySection(EnterpriseChildPage): [("journey", TextBlock(icon="plus"))], blank=False, help_text="Enter the text for this learning journey item.", - use_json_field=True, ) call_to_action = models.CharField( max_length=30, @@ -2735,7 +2722,6 @@ class SuccessStoriesSection(EnterpriseChildPage): [("success_story", SuccessStoriesBlock())], blank=False, help_text="Manage the individual success stories. Each story is a separate block.", - use_json_field=True, ) content_panels = [ @@ -2794,7 +2780,6 @@ class EnterprisePage(WagtailCachedPageMixin, Page): headings = StreamField( [("heading", BannerHeadingBlock())], help_text="Add banner headings for this page.", - use_json_field=True, ) background_image = models.ForeignKey( Image, diff --git a/courses/utils_test.py b/courses/utils_test.py index ee5f260b5..50a1be684 100644 --- a/courses/utils_test.py +++ b/courses/utils_test.py @@ -2,8 +2,7 @@ Tests for signals """ -from datetime import timedelta, datetime -import pytz +from datetime import timedelta, datetime, timezone import re import factory @@ -34,8 +33,8 @@ pytestmark = pytest.mark.django_db -START_DT = datetime(2098, 1, 1, tzinfo=pytz.UTC) -END_DT = datetime(2099, 2, 1, tzinfo=pytz.UTC) +START_DT = datetime(2098, 1, 1, tzinfo=timezone.utc) +END_DT = datetime(2099, 2, 1, tzinfo=timezone.utc) def make_api_course(course_id, name): @@ -352,11 +351,13 @@ def test_sync_course_runs( if re.match(COURSE_KEY_PATTERN, data["courseware_id"]) ] mock_course_list.get_courses.assert_called_once_with( - course_keys=valid_course_keys, username=None + course_keys=valid_course_keys, + username=settings.OPENEDX_SERVICE_WORKER_USERNAME, ) elif not api_error: mock_course_list.get_courses.assert_called_once_with( - course_keys=[data["courseware_id"] for data in local_data], username=None + course_keys=[data["courseware_id"] for data in local_data], + username=settings.OPENEDX_SERVICE_WORKER_USERNAME, ) else: mock_course_list.get_courses.assert_called_once() diff --git a/courseware/migrations/0005_rename_openedxapiauth_user_access_token_expires_on_courseware__user_id_7b66fa_idx.py b/courseware/migrations/0005_rename_openedxapiauth_user_access_token_expires_on_courseware__user_id_7b66fa_idx.py new file mode 100644 index 000000000..cc49b0ab7 --- /dev/null +++ b/courseware/migrations/0005_rename_openedxapiauth_user_access_token_expires_on_courseware__user_id_7b66fa_idx.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.30 on 2026-04-10 10:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("courseware", "0004_add_courseware_related_names"), + ] + + operations = [ + migrations.RenameIndex( + model_name="openedxapiauth", + new_name="courseware__user_id_7b66fa_idx", + old_fields=("user", "access_token_expires_on"), + ), + ] diff --git a/courseware/models.py b/courseware/models.py index 7f39bae1a..2b5a501c9 100644 --- a/courseware/models.py +++ b/courseware/models.py @@ -47,4 +47,6 @@ def __str__(self): return f"OpenEdxApiAuth for {self.user}" class Meta: - index_together = ("user", "access_token_expires_on") + indexes = [ + models.Index(fields=["user", "access_token_expires_on"]), + ] diff --git a/ecommerce/api.py b/ecommerce/api.py index 593f6799a..3c9db1227 100644 --- a/ecommerce/api.py +++ b/ecommerce/api.py @@ -275,7 +275,8 @@ def _generate_cybersource_sa_payload(*, order, receipt_url, cancel_url, ip_addre product_version=product_version, tax_rate=order.tax_rate, ) - line_items[f"item_{i}_code"] = str(product_version.product.content_type) + ct = product_version.product.content_type + line_items[f"item_{i}_code"] = f"{ct.app_label} | {ct.name}" line_items[f"item_{i}_name"] = str(product_version.description)[:254] line_items[f"item_{i}_quantity"] = line.quantity line_items[f"item_{i}_sku"] = product_version.product.content_object.id @@ -296,7 +297,7 @@ def _generate_cybersource_sa_payload(*, order, receipt_url, cancel_url, ip_addre readable_id = get_readable_id(content_object) merchant_fields = { - "merchant_defined_data1": str(product.content_type), + "merchant_defined_data1": f"{product.content_type.app_label} | {product.content_type.name}", "merchant_defined_data2": readable_id, "merchant_defined_data3": "1", } diff --git a/ecommerce/models.py b/ecommerce/models.py index accbf0071..e4a2be96f 100644 --- a/ecommerce/models.py +++ b/ecommerce/models.py @@ -409,8 +409,9 @@ def to_dict(self): **serialize_model_object(line.product_version), "product_info": { **serialize_model_object(line.product_version.product), - "content_type_string": str( - line.product_version.product.content_type + "content_type_string": "{} | {}".format( + line.product_version.product.content_type.app_label, + line.product_version.product.content_type.name, ), "content_object": serialize_model_object( line.product_version.product.content_object diff --git a/ecommerce/models_test.py b/ecommerce/models_test.py index 7d16507b0..265b8c666 100644 --- a/ecommerce/models_test.py +++ b/ecommerce/models_test.py @@ -75,9 +75,7 @@ def test_order_audit(has_user, has_lines): **serialize_model_object(line.product_version), "product_info": { **serialize_model_object(line.product_version.product), - "content_type_string": str( - line.product_version.product.content_type - ), + "content_type_string": f"{line.product_version.product.content_type.app_label} | {line.product_version.product.content_type.name}", "content_object": serialize_model_object( line.product_version.product.content_object ), diff --git a/mail/api_test.py b/mail/api_test.py index 696fba0e3..13d0c50ad 100644 --- a/mail/api_test.py +++ b/mail/api_test.py @@ -246,8 +246,9 @@ def test_send_message(mailoutbox): send_messages(messages) + messages_to = [m.to for m in messages] for message in mailoutbox: - assert message in messages + assert message.to in messages_to def test_send_message_failure(mocker): diff --git a/mail/templatetags/timezone_converter_url.py b/mail/templatetags/timezone_converter_url.py index cb8f78b36..39e62d7c8 100644 --- a/mail/templatetags/timezone_converter_url.py +++ b/mail/templatetags/timezone_converter_url.py @@ -1,5 +1,7 @@ """custom template tag to convert a DateTimeField object to a URL for timezone conversion""" +from datetime import timezone as dt_timezone + from django import template from django.utils import timezone @@ -21,7 +23,7 @@ def timezone_converter_url(datetime_obj): return "" if timezone.is_naive(datetime_obj): - datetime_obj = timezone.make_aware(datetime_obj, timezone.utc) + datetime_obj = timezone.make_aware(datetime_obj, dt_timezone.utc) time_param = datetime_obj.strftime("%H%M") date_param = datetime_obj.strftime("%Y-%m-%d") diff --git a/maxmind/migrations/0001_add_geoname_and_netblock_tables.py b/maxmind/migrations/0001_add_geoname_and_netblock_tables.py index a5b4637ed..75280032a 100644 --- a/maxmind/migrations/0001_add_geoname_and_netblock_tables.py +++ b/maxmind/migrations/0001_add_geoname_and_netblock_tables.py @@ -110,7 +110,7 @@ class Migration(migrations.Migration): migrations.AddConstraint( model_name="netblock", constraint=models.CheckConstraint( - check=models.Q( + condition=models.Q( ("geoname_id__isnull", False), ("registered_country_geoname_id__isnull", False), ("represented_country_geoname_id__isnull", False), diff --git a/maxmind/models.py b/maxmind/models.py index 47cb3866b..a3a04d7c4 100644 --- a/maxmind/models.py +++ b/maxmind/models.py @@ -91,7 +91,7 @@ class NetBlock(models.Model): class Meta: constraints = [ models.CheckConstraint( - check=models.Q(geoname_id__isnull=False) + condition=models.Q(geoname_id__isnull=False) | models.Q(registered_country_geoname_id__isnull=False) | models.Q(represented_country_geoname_id__isnull=False), name="at_least_one_geoname_id", diff --git a/mitxpro/settings.py b/mitxpro/settings.py index 167dc4d89..8fc7037f8 100644 --- a/mitxpro/settings.py +++ b/mitxpro/settings.py @@ -26,7 +26,7 @@ from mitxpro.celery_utils import OffsettingSchedule from mitxpro.sentry import init_sentry -VERSION = "0.193.2" +VERSION = "0.194.0" env.reset() @@ -623,10 +623,14 @@ "AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, or " "AWS_STORAGE_BUCKET_NAME" ) +STORAGES = { + "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"}, + "staticfiles": {"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"}, +} if MITXPRO_USE_S3: if CLOUDFRONT_DIST: AWS_S3_CUSTOM_DOMAIN = f"{CLOUDFRONT_DIST}.cloudfront.net" - DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" + STORAGES["default"] = {"BACKEND": "storages.backends.s3boto3.S3Boto3Storage"} FEATURES = get_features() @@ -950,6 +954,9 @@ OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = "oauth2_provider.RefreshToken" # noqa: S105 OAUTH2_PROVIDER = { + # Disable PKCE requirement to maintain backward compatibility with existing OAuth clients + # (PKCE_REQUIRED changed default to True in django-oauth-toolkit 2.0.0) + "PKCE_REQUIRED": False, # this is the list of available scopes "SCOPES": { "read": "Read scope", diff --git a/mitxpro/settings_test.py b/mitxpro/settings_test.py index 2328cc617..09e6ff419 100644 --- a/mitxpro/settings_test.py +++ b/mitxpro/settings_test.py @@ -85,7 +85,10 @@ def test_s3_settings(settings_sandbox): {"MITXPRO_USE_S3": "False", "AWS_ACCESS_KEY_ID": ""} ) - assert settings_vars.get("DEFAULT_FILE_STORAGE") is None + assert ( + settings_vars["STORAGES"]["default"]["BACKEND"] + == "django.core.files.storage.FileSystemStorage" + ) with pytest.raises(ImproperlyConfigured): settings_sandbox.patch({"MITXPRO_USE_S3": "True"}) @@ -100,7 +103,7 @@ def test_s3_settings(settings_sandbox): } ) assert ( - settings_vars.get("DEFAULT_FILE_STORAGE") + settings_vars["STORAGES"]["default"]["BACKEND"] == "storages.backends.s3boto3.S3Boto3Storage" ) diff --git a/pyproject.toml b/pyproject.toml index ea7a0941f..05409ca71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,17 +14,17 @@ classifiers = [ dependencies = [ "Pillow==10.4.0", "PyNaCl==1.6.2", - "beautifulsoup4==4.8.2", + "beautifulsoup4==4.14.3", "boto3==1.42.88", "celery==5.6.3", "celery-redbeat==2.3.3", "dj-database-url==3.1.2", - "django==4.2.30", + "django==5.2.13", "django-anymail[mailgun]==14.0", - "django-filter>=23.4,<24", + "django-filter>=24", "django-hijack==3.7.7", "django-ipware==7.0.1", - "django-oauth-toolkit==1.7.1", + "django-oauth-toolkit==3.2.0", "django-redis>=6.0.0,<7", "django-robots==6.1", "django-silk>=5.0.3,<6", @@ -60,7 +60,7 @@ dependencies = [ "user-agents==2.2.0", "user-util==2.0.0", "uwsgi==2.0.31", - "wagtail==5.2.8", + "wagtail==6.4.2", "wagtail-metadata==5.0.0", "xmltodict>=1.0.0,<2", "zeep==4.3.2", diff --git a/pytest.ini b/pytest.ini index 0a38df194..648849841 100644 --- a/pytest.ini +++ b/pytest.ini @@ -7,7 +7,6 @@ filterwarnings = ignore:Failed to load HostKeys ignore:Coverage disabled via --no-cov switch! ignore:.*Not importing directory.*:ImportWarning - ignore:.*:django.utils.deprecation.RemovedInDjango51Warning env = CELERY_TASK_ALWAYS_EAGER=True DJANGO_SETTINGS_MODULE=mitxpro.settings diff --git a/static/js/components/UserMenu.js b/static/js/components/UserMenu.js index 558dd7308..3f05d51f5 100644 --- a/static/js/components/UserMenu.js +++ b/static/js/components/UserMenu.js @@ -4,6 +4,7 @@ import React from "react"; import MixedLink from "./MixedLink"; import { routes } from "../lib/urls"; +import { getCookie } from "../lib/api"; import type { User } from "../flow/authTypes"; @@ -70,10 +71,21 @@ const UserMenu = ({ currentUser, onMouseDown }: Props) => {
- -
- Sign Out - +
+ + +
); diff --git a/static/js/components/UserMenu_test.js b/static/js/components/UserMenu_test.js index f1b75b892..dfb22a7ec 100644 --- a/static/js/components/UserMenu_test.js +++ b/static/js/components/UserMenu_test.js @@ -33,9 +33,8 @@ describe("UserMenu component", () => { it("has a link to logout", () => { assert.equal( shallow() - .find("a") - .at(1) - .prop("href"), + .find("form") + .prop("action"), routes.logout, ); }); diff --git a/users/migrations/0017_rename_changeemailrequest_expires_on_confirmed_code_users_chang_expires_dbd4e5_idx.py b/users/migrations/0017_rename_changeemailrequest_expires_on_confirmed_code_users_chang_expires_dbd4e5_idx.py new file mode 100644 index 000000000..95004ef8b --- /dev/null +++ b/users/migrations/0017_rename_changeemailrequest_expires_on_confirmed_code_users_chang_expires_dbd4e5_idx.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.30 on 2026-04-10 10:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("users", "0016_remove_null_vat_id"), + ] + + operations = [ + migrations.RenameIndex( + model_name="changeemailrequest", + new_name="users_chang_expires_dbd4e5_idx", + old_fields=("expires_on", "confirmed", "code"), + ), + ] diff --git a/users/models.py b/users/models.py index d83c02b2d..abf398fc6 100644 --- a/users/models.py +++ b/users/models.py @@ -195,7 +195,9 @@ class ChangeEmailRequest(TimestampedModel): expires_on = models.DateTimeField(default=generate_change_email_expires) class Meta: - index_together = ("expires_on", "confirmed", "code") + indexes = [ + models.Index(fields=["expires_on", "confirmed", "code"]), + ] def validate_iso_3166_1_code(value): diff --git a/uv.lock b/uv.lock index 9a502c700..261645c11 100644 --- a/uv.lock +++ b/uv.lock @@ -116,14 +116,15 @@ wheels = [ [[package]] name = "beautifulsoup4" -version = "4.8.2" +version = "4.14.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "soupsieve" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/ba/0e121661f529e7f456e903bf5c4d255b8051d8ce2b5e629c5212efe4c3f1/beautifulsoup4-4.8.2.tar.gz", hash = "sha256:05fd825eb01c290877657a56df4c6e4c311b3965bda790c613a3d6fb01a5462a", size = 298650, upload-time = "2019-12-24T22:28:22.187Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/a1/c698cf319e9cfed6b17376281bd0efc6bfc8465698f54170ef60a485ab5d/beautifulsoup4-4.8.2-py3-none-any.whl", hash = "sha256:9fbb4d6e48ecd30bcacc5b63b94088192dcda178513b2ae3c394229f8911b887", size = 106874, upload-time = "2019-12-24T22:28:20.142Z" }, + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, ] [[package]] @@ -632,16 +633,16 @@ wheels = [ [[package]] name = "django" -version = "4.2.30" +version = "5.2.13" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asgiref" }, { name = "sqlparse" }, { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/11/b5/f1a53dc68da6429d6e0345bb848161e2381a2e9f02700148911e8582c2b3/django-4.2.30.tar.gz", hash = "sha256:4ebc7a434e3819db6cf4b399fb5b3f536310a30e8486f08b66886840be84b37c", size = 10468707, upload-time = "2026-04-07T14:05:45.57Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/c5/c69e338eb2959f641045802e5ea87ca4bf5ac90c5fd08953ca10742fad51/django-5.2.13.tar.gz", hash = "sha256:a31589db5188d074c63f0945c3888fad104627dfcc236fb2b97f71f89da33bc4", size = 10890368, upload-time = "2026-04-07T14:02:15.072Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/b7/a7c96f239cf91313a6589233fed55111c7063b26683b226802732c455dbc/django-4.2.30-py3-none-any.whl", hash = "sha256:4d07aaf1c62f9984842b67c2874ebbf7056a17be253860299b93ae1881faad65", size = 7997231, upload-time = "2026-04-07T14:05:38.241Z" }, + { url = "https://files.pythonhosted.org/packages/59/b1/51ab36b2eefcf8cdb9338c7188668a157e29e30306bfc98a379704c9e10d/django-5.2.13-py3-none-any.whl", hash = "sha256:5788fce61da23788a8ce6f02583765ab060d396720924789f97fa42119d37f7a", size = 8310982, upload-time = "2026-04-07T14:02:08.883Z" }, ] [[package]] @@ -674,14 +675,14 @@ wheels = [ [[package]] name = "django-filter" -version = "23.5" +version = "25.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/e874b30a66684e3e79b183b0197dd3d08f842a8674972e47a90c09a6ec9c/django-filter-23.5.tar.gz", hash = "sha256:67583aa43b91fe8c49f74a832d95f4d8442be628fd4c6d65e9f811f5153a4e5c", size = 141058, upload-time = "2023-12-05T08:15:31.888Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/40/c702a6fe8cccac9bf426b55724ebdf57d10a132bae80a17691d0cf0b9bac/django_filter-25.1.tar.gz", hash = "sha256:1ec9eef48fa8da1c0ac9b411744b16c3f4c31176c867886e4c48da369c407153", size = 143021, upload-time = "2025-02-14T16:30:53.238Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/ff/4ae79361e09c3803562368700bc56672cca37bab7c1a8a91e7a225ce8fa0/django_filter-23.5-py3-none-any.whl", hash = "sha256:99122a201d83860aef4fe77758b69dda913e874cc5e0eaa50a86b0b18d708400", size = 94402, upload-time = "2023-12-05T08:15:29.191Z" }, + { url = "https://files.pythonhosted.org/packages/07/a6/70dcd68537c434ba7cb9277d403c5c829caf04f35baf5eb9458be251e382/django_filter-25.1-py3-none-any.whl", hash = "sha256:4fa48677cf5857b9b1347fed23e355ea792464e0fe07244d1fdfb8a806215b80", size = 94114, upload-time = "2025-02-14T16:30:50.435Z" }, ] [[package]] @@ -722,7 +723,7 @@ wheels = [ [[package]] name = "django-oauth-toolkit" -version = "1.7.1" +version = "3.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, @@ -730,9 +731,9 @@ dependencies = [ { name = "oauthlib" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/84/11/7841f88b6e200e49533307425271c356399319b35738aedb3fcb487afc3b/django-oauth-toolkit-1.7.1.tar.gz", hash = "sha256:37b690fa53f340c7391bdbc0fdbb32fd9ef8a7c012e72ee8754c331a2d7b4adb", size = 46685, upload-time = "2022-03-19T22:35:09.076Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/95/efd83b35c34b86eb2249d2b54c5eaf383c48f3f19034aa6f3807e37471b6/django_oauth_toolkit-3.2.0.tar.gz", hash = "sha256:c36761ae6810083d95a652e9c820046cde0d45a2e2a5574bbe7202656ec20bb6", size = 114211, upload-time = "2026-01-08T22:03:13.311Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/1a/3a8332b8c507ae41491f6b049ff4521225eabca83032d2e67372d63d973b/django_oauth_toolkit-1.7.1-py3-none-any.whl", hash = "sha256:756e44421d0993f27705736b6c33a3d89018393859a31ac926296950f76e4433", size = 63199, upload-time = "2022-03-19T22:35:36.988Z" }, + { url = "https://files.pythonhosted.org/packages/ae/cc/f27a784c0ecd13335abd9ef85ebb80dbc04945f919da5f496f56e3562751/django_oauth_toolkit-3.2.0-py3-none-any.whl", hash = "sha256:bd2cd2719b010231a2f370f927dbcc740454fb1d0dd7e7f4138f36227363dc26", size = 87077, upload-time = "2026-01-08T22:03:12.123Z" }, ] [[package]] @@ -825,14 +826,28 @@ wheels = [ [[package]] name = "django-taggit" -version = "4.0.0" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/a6/f1beaf8f552fe90c153cc039316ebab942c23dfbc88588dde081fefca816/django_taggit-6.1.0.tar.gz", hash = "sha256:c4d1199e6df34125dd36db5eb0efe545b254dec3980ce5dd80e6bab3e78757c3", size = 38151, upload-time = "2024-09-29T08:07:39.477Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/34/4185c345530b91d05cb82e05d07148f481a5eb5dc2ac44e092b3daa6f206/django_taggit-6.1.0-py3-none-any.whl", hash = "sha256:ab776264bbc76cb3d7e49e1bf9054962457831bd21c3a42db9138b41956e4cf0", size = 75749, upload-time = "2024-09-29T08:07:14.612Z" }, +] + +[[package]] +name = "django-tasks" +version = "0.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, + { name = "django-stubs-ext" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/54/241541a011c1e146c2708f0cf148f9255b7c04abecdf28e0f589d54c1851/django-taggit-4.0.0.tar.gz", hash = "sha256:4d52de9d37245a9b9f98c0ec71fdccf1d2283e38e8866d40a7ae6a3b6787a161", size = 58638, upload-time = "2023-05-04T02:15:57.57Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/63/46f3c2deb2a9df48d46c2f7a067f1286adc674a2a16e99c0344e327a2d4b/django_tasks-0.6.1.tar.gz", hash = "sha256:4086e7eb9e965f79c4ac76f5c3690ec3bf41c461585237b71b4bde729ced9826", size = 26575, upload-time = "2024-12-27T22:03:36.877Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/64/bb3432a4c177e16b236f0a211a984f51f44a287bac6a194762f1731a8994/django_taggit-4.0.0-py3-none-any.whl", hash = "sha256:eb800dabef5f0a4e047ab0751f82cf805bc4a9e972037ef12bf519f52cd92480", size = 60337, upload-time = "2023-05-04T02:16:48.847Z" }, + { url = "https://files.pythonhosted.org/packages/bf/a3/f9c6634f67c5bcd309b17fe756ee4a321779806ab515e7ccc6333a439275/django_tasks-0.6.1-py3-none-any.whl", hash = "sha256:b3648e28bdcda809cb7831f3aff98aa46c327025447c462b8943cce9dfbb0281", size = 36330, upload-time = "2024-12-27T22:03:34.416Z" }, ] [[package]] @@ -1510,16 +1525,15 @@ wheels = [ ] [[package]] -name = "l18n" -version = "2021.3" +name = "laces" +version = "0.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pytz" }, - { name = "six" }, + { name = "django" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/9c/13d732e16e8fbdef7e68f4f339f3b86618430d4003c7ffe82e15bf925d7c/l18n-2021.3.tar.gz", hash = "sha256:1956e890d673d17135cc20913253c154f6bc1c00266c22b7d503cc1a5a42d848", size = 50712, upload-time = "2021-11-12T09:32:36.255Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/9a/9192d6a74e2c6db4f705dd98f56be488e47373172c13f4916aeabc4d68b8/laces-0.1.2.tar.gz", hash = "sha256:3218e09c1889ae5cf3fc7a82f5bb63ec0c7879889b6a9760bfc42323c694b84d", size = 29264, upload-time = "2025-01-14T04:37:34.805Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/e7/dfa82d0bb2b314950e457a755463b429090127ffc0ab9d8d14ef4563ad44/l18n-2021.3-py3-none-any.whl", hash = "sha256:78495d1df95b6f7dcc694d1ba8994df709c463a1cbac1bf016e1b9a5ce7280b9", size = 51534, upload-time = "2021-11-12T09:32:34.296Z" }, + { url = "https://files.pythonhosted.org/packages/60/fe/31f76f5cb2579bdda208aa257ce5482653f22ab1bad3e128fe2f803fa2f1/laces-0.1.2-py3-none-any.whl", hash = "sha256:980cdaf9a31e883a2b8198132e2388931a4eb8814f5bfa5d8bba13ff9f657b7c", size = 22462, upload-time = "2025-01-14T04:37:30.636Z" }, ] [[package]] @@ -1967,17 +1981,17 @@ dev = [ [package.metadata] requires-dist = [ - { name = "beautifulsoup4", specifier = "==4.8.2" }, + { name = "beautifulsoup4", specifier = "==4.14.3" }, { name = "boto3", specifier = "==1.42.88" }, { name = "celery", specifier = "==5.6.3" }, { name = "celery-redbeat", specifier = "==2.3.3" }, { name = "dj-database-url", specifier = "==3.1.2" }, - { name = "django", specifier = "==4.2.30" }, + { name = "django", specifier = "==5.2.13" }, { name = "django-anymail", extras = ["mailgun"], specifier = "==14.0" }, - { name = "django-filter", specifier = ">=23.4,<24" }, + { name = "django-filter", specifier = ">=24" }, { name = "django-hijack", specifier = "==3.7.7" }, { name = "django-ipware", specifier = "==7.0.1" }, - { name = "django-oauth-toolkit", specifier = "==1.7.1" }, + { name = "django-oauth-toolkit", specifier = "==3.2.0" }, { name = "django-redis", specifier = ">=6.0.0,<7" }, { name = "django-robots", specifier = "==6.1" }, { name = "django-silk", specifier = ">=5.0.3,<6" }, @@ -2015,7 +2029,7 @@ requires-dist = [ { name = "user-agents", specifier = "==2.2.0" }, { name = "user-util", specifier = "==2.0.0" }, { name = "uwsgi", specifier = "==2.0.31" }, - { name = "wagtail", specifier = "==5.2.8" }, + { name = "wagtail", specifier = "==6.4.2" }, { name = "wagtail-metadata", specifier = "==5.0.0" }, { name = "xmltodict", specifier = ">=1.0.0,<2" }, { name = "zeep", specifier = "==4.3.2" }, @@ -3366,7 +3380,7 @@ wheels = [ [[package]] name = "wagtail" -version = "5.2.8" +version = "6.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyascii" }, @@ -3376,20 +3390,20 @@ dependencies = [ { name = "django-modelcluster" }, { name = "django-permissionedforms" }, { name = "django-taggit" }, + { name = "django-tasks" }, { name = "django-treebeard" }, { name = "djangorestframework" }, { name = "draftjs-exporter" }, - { name = "html5lib" }, - { name = "l18n" }, + { name = "laces" }, { name = "openpyxl" }, { name = "pillow" }, { name = "requests" }, { name = "telepath" }, { name = "willow", extra = ["heif"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/13/a45e3a18a3e2b503fad7c7bc581d8e82c3cbaff101de8834feaa9005f87c/wagtail-5.2.8.tar.gz", hash = "sha256:92262336a6744de1b3015ae3c04135edd5e4d7722fb2e99e1b6e9191686bc3f9", size = 6390120, upload-time = "2025-02-03T15:52:16.777Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/00/f8ef0f9f52bef7fce16171de28ef025422295b15e6e19b3f4e4d8d589170/wagtail-6.4.2.tar.gz", hash = "sha256:fcf39430e5fb856674c0cfa3cda67591b44214cc5a99c9308019140eb26ddb42", size = 6637640, upload-time = "2025-06-12T14:58:28.159Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/53/69c569334486dd9bc84d941c08f4f563a58dbcca2db20d0f4b5ef176e0de/wagtail-5.2.8-py3-none-any.whl", hash = "sha256:dc2bfa1b7f434515182bc81c7a6e7a3b59c93f15f3ccd06c6389de2f26e4099d", size = 9009518, upload-time = "2025-02-03T15:52:10.247Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d0/c5f64128613d767f0765c76546f1ba7492201ce32a56c1548a736d856b49/wagtail-6.4.2-py3-none-any.whl", hash = "sha256:1ceff33a6edae02668d8cd08ea77fc7c1d9835986cb1b75a8fa1b353535ddd90", size = 9111602, upload-time = "2025-06-12T14:58:22.163Z" }, ] [[package]] @@ -3437,15 +3451,15 @@ wheels = [ [[package]] name = "willow" -version = "1.6.3" +version = "1.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "defusedxml" }, { name = "filetype" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/96/71ade7a7f58b597873ec34ccc1d9a61bd00def099f37544c0dd17a00da12/willow-1.6.3.tar.gz", hash = "sha256:143cefd30d3bb816cdff857c454da24991dda35a0315ea795101675e0b14262f", size = 112128, upload-time = "2023-11-26T22:47:02.658Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/bd/2a383be24c3e47423aa9b0aa5b4ca818ef193506b58800dd51e1b89d7bb3/willow-1.11.0.tar.gz", hash = "sha256:70292b2d0cd2d5bb4076f0b3d61308aeaa0b225f3970d00752f08a8fd386c3d1", size = 113827, upload-time = "2025-07-16T08:46:26.939Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/a9/4752fccf68f64500ce23f0618f1da7533deb3f74a8bc6ed6d4e473a4d2e9/willow-1.6.3-py3-none-any.whl", hash = "sha256:f4b17a16c6315864604dadb6cdf2987d0b685e295cca74c6da28b94167a3126e", size = 118095, upload-time = "2023-11-26T22:47:00.564Z" }, + { url = "https://files.pythonhosted.org/packages/c1/05/b3f1b443c31ad871c48e19ea2be189681c2df4ccf594b1dd83d6775c032b/willow-1.11.0-py3-none-any.whl", hash = "sha256:0a4388dbf18726eef8f27449659047689c39b7023045ca5a8a75410d3864ee6f", size = 119459, upload-time = "2025-07-16T08:46:25.596Z" }, ] [package.optional-dependencies]