Skip to content
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
strategy:
matrix:
python-version: ["3.12"]
django-version: ["django42"]
django-version: ["django42", "django52"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
Expand Down
2 changes: 1 addition & 1 deletion credentials/apps/api/v2/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def wrapper(*args, **kwargs):
data = request.body
logger.info(
f"{request.method} request received to endpoint [{request.get_full_path()}] from user "
f"[{request.user.username}] originating from [{request.META.get('HTTP_HOST', 'Unknown')}] "
f"[{request.user.username}] originating from [{request.headers.get('host', 'Unknown')}] "
f"with data: [{data}]"
)
except Exception as exc:
Expand Down
6 changes: 2 additions & 4 deletions credentials/apps/api/v2/tests/test_serializers.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from collections import namedtuple
from datetime import datetime
from logging import WARNING
from uuid import uuid4

import ddt
import pytz
from django.test import TestCase
from django.urls import reverse
from django.utils.timezone import datetime, timezone
from rest_framework.exceptions import ValidationError
from rest_framework.settings import api_settings
from rest_framework.test import APIRequestFactory
Expand Down Expand Up @@ -203,8 +202,7 @@ def test_to_representation(self):
def test_to_internal_value(self):
Request = namedtuple("Request", ["site"])
serializer = UserGradeSerializer(context={"request": Request(site=self.site)})
updated_at_dt = datetime.now()
updated_at_utc = updated_at_dt.replace(tzinfo=pytz.UTC)
updated_at_utc = datetime.now(timezone.utc)
Copy link

Copilot AI Jun 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same import concern here: datetime from django.utils.timezone may not map to the built-in datetime. Prefer from datetime import datetime or use timezone.now() to generate a UTC timestamp.

Suggested change
updated_at_utc = datetime.now(timezone.utc)
updated_at_utc = timezone.now()

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good suggestion, using timezone.now will give you zone aware times in UTC and we don't have to use the passthrough import of datetime which may go away in the future.


data = {
"username": "alice",
Expand Down
6 changes: 2 additions & 4 deletions credentials/apps/api/v2/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import datetime
import json
from decimal import Decimal
from unittest import mock

import ddt
import pytz
from django.contrib.auth.models import Permission
from django.core.exceptions import ObjectDoesNotExist
from django.test import TestCase
from django.urls import reverse
from django.utils.timezone import datetime, timezone
from rest_framework.renderers import JSONRenderer
from rest_framework.test import APIRequestFactory, APITestCase
from testfixtures import LogCapture
Expand Down Expand Up @@ -597,8 +596,7 @@ def test_upgrade_with_lms_last_updated_at_data(self):
self.add_user_permission(self.user, "add_usergrade")

# simulate updating the existing record with the new field in the data
dt = datetime.datetime.now()
last_updated_at = dt.replace(tzinfo=pytz.UTC)
last_updated_at = datetime.now(timezone.utc)
Copy link

Copilot AI Jun 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Importing datetime from django.utils.timezone may not provide the standard datetime class. Consider from datetime import datetime and either use timezone.now() or from django.utils.timezone import utc for clarity.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, this is a good suggestion and we should switch to just calling timezone.now()

data = self.serialize_user_grade(grade)
data["lms_last_updated_at"] = last_updated_at
response = self.client.post(self.list_path, data=JSONRenderer().render(data), content_type=JSON_CONTENT_TYPE)
Expand Down
9 changes: 3 additions & 6 deletions credentials/apps/badges/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ def delete_queryset(self, request, queryset):
return
super().delete_queryset(request, queryset)

@admin.display(description=_("icon"))
def image(self, obj):
"""
Badge template preview image.
Expand All @@ -326,8 +327,6 @@ def image(self, obj):
return format_html('<img src="{}" width="50" height="auto" />', obj.icon)
return None

image.short_description = _("icon")

def save_model(self, request, obj, form, change):
pass

Expand Down Expand Up @@ -391,6 +390,7 @@ class BadgeRequirementAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
return False

@admin.display(description=_("badge template"))
def template_link(self, instance):
"""
Interactive link to parent (badge template).
Expand All @@ -401,8 +401,6 @@ def template_link(self, instance):
url = reverse(reverse_name, args=reverse_args)
return format_html('<a href="{}">{}</a>', url, instance.template)

template_link.short_description = _("badge template")

def response_change(self, request, obj):
if "_save" in request.POST:
reverse_name = ADMIN_CHANGE_VIEW_REVERSE_NAMES.get(obj.template.origin, "admin:index")
Expand Down Expand Up @@ -453,6 +451,7 @@ class BadgePenaltyAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
return False

@admin.display(description=_("badge template"))
def template_link(self, instance):
"""
Interactive link to parent (badge template).
Expand All @@ -462,8 +461,6 @@ def template_link(self, instance):
url = reverse(reverse_name, args=reverse_args)
return format_html('<a href="{}">{}</a>', url, instance.template)

template_link.short_description = _("badge template")

def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == "requirements":
object_id = request.resolver_match.kwargs.get("object_id")
Expand Down
9 changes: 5 additions & 4 deletions credentials/apps/catalog/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
Factories for tests of Credentials.
"""

import datetime
from uuid import uuid4

import factory
from django.utils.timezone import datetime, timezone
from factory.fuzzy import FuzzyDateTime, FuzzyInteger, FuzzyText
from pytz import UTC
from slugify import slugify

from credentials.apps.catalog.data import PathwayStatus
Expand Down Expand Up @@ -35,6 +34,7 @@ class Meta:
class CourseFactory(factory.django.DjangoModelFactory):
class Meta:
model = Course
skip_postgeneration_save = True
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for this change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reverted. it was by the package.


site = factory.SubFactory(SiteFactory)
uuid = factory.LazyFunction(uuid4)
Expand All @@ -55,13 +55,14 @@ class Meta:
uuid = factory.LazyFunction(uuid4)
key = FuzzyText(prefix="course-run-id/", suffix="/fake")
title_override = None
start_date = FuzzyDateTime(datetime.datetime(2014, 1, 1, tzinfo=UTC))
end_date = FuzzyDateTime(datetime.datetime(2014, 1, 1, tzinfo=UTC)).end_dt
start_date = FuzzyDateTime(datetime(2014, 1, 1, tzinfo=timezone.utc))
end_date = FuzzyDateTime(datetime(2014, 1, 1, tzinfo=timezone.utc)).end_dt


class ProgramFactory(factory.django.DjangoModelFactory):
class Meta:
model = Program
skip_postgeneration_save = True

site = factory.SubFactory(SiteFactory)
uuid = factory.LazyFunction(uuid4)
Expand Down
4 changes: 0 additions & 4 deletions credentials/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,13 +230,9 @@
TIME_ZONE = "UTC"
TIME_ZONE_CLASS = timezone.utc

# https://docs.djangoproject.com/en/4.2/releases/4.0/#zoneinfo-default-timezone-implementation
USE_DEPRECATED_PYTZ = True

USE_I18N = True

USE_L10N = True

USE_TZ = True

LOCALE_PATHS = (root("conf", "locale"),)
Expand Down
2 changes: 1 addition & 1 deletion credentials/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@

if is_badges_enabled():
urlpatterns += [
re_path(r"^badges/", include(("credentials.apps.badges.urls", "badges"), namespace="badges")),
path("badges/", include(("credentials.apps.badges.urls", "badges"), namespace="badges")),
]

# edx-drf-extensions csrf app
Expand Down
3 changes: 2 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py{3.12}-django{42}
envlist = py{3.12}-django{42,52}
skipsdist = true

[pytest]
Expand All @@ -9,6 +9,7 @@ testpaths = credentials/apps
[testenv]
deps =
django42: -r requirements/django.txt
django52: -r requirements/django.txt
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't actually testing Django 5.2, the django.txt file only pins to the current version of django.

Suggested change
django52: -r requirements/django.txt
django52: django>=5.2

-r {toxinidir}/requirements/test.txt
allowlist_externals:
make
Expand Down