Skip to content

Commit 7abc70e

Browse files
authored
Merge pull request #278 from open-craft/agrendalath/bb-10290-ulmo
chore: add support for Python 3.11 + 3.12 and Django 4.2 + 5.2
2 parents 2dfb6e2 + b73907f commit 7abc70e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+803
-843
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@ jobs:
2525
strategy:
2626
fail-fast: false
2727
matrix:
28-
os: [ubuntu-20.04]
29-
python-version: [3.8]
30-
toxenv: [py38-django32, quality, docs]
28+
os: [ubuntu-latest]
29+
python-version: [3.11, 3.12]
30+
toxenv: [django42, django52, quality, docs]
3131

3232
steps:
3333
- name: checkout repo
34-
uses: actions/checkout@v3
34+
uses: actions/checkout@v5
3535

3636
- name: setup python
37-
uses: actions/setup-python@v4
37+
uses: actions/setup-python@v6
3838
with:
3939
python-version: ${{ matrix.python-version }}
4040

@@ -47,7 +47,7 @@ jobs:
4747
run: tox
4848

4949
- name: Run coverage
50-
if: matrix.python-version == '3.8' && matrix.toxenv == 'py38-django32'
50+
if: matrix.python-version == '3.11' && matrix.toxenv == 'django42'
5151
uses: codecov/codecov-action@v3
5252
with:
5353
token: ${{ secrets.CODECOV_TOKEN }}

.github/workflows/pypi-publish.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ on:
66

77
jobs:
88
push:
9-
runs-on: ubuntu-20.04
9+
runs-on: ubuntu-latest
1010

1111
steps:
1212
- name: Checkout
13-
uses: actions/checkout@v3
13+
uses: actions/checkout@v5
1414

1515
- name: setup python
16-
uses: actions/setup-python@v4
16+
uses: actions/setup-python@v6
1717
with:
18-
python-version: 3.8
18+
python-version: 3.11
1919

2020
- name: Install Dependencies
2121
run: pip install -r requirements/pip.txt

CHANGELOG.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ Change Log
1414
Unreleased
1515
~~~~~~~~~~
1616

17+
[4.3.0] - 2026-01-28
18+
~~~~~~~~~~~~~~~~~~~~
19+
20+
* Drop support for Python 2, Python 3.8, and Django 3.2.
21+
* Add support for Python 3.11 + 3.12 and Django 4.2 + 5.2.
22+
* Fix the ``stats`` API endpoint.
23+
* Fix the ``ENROLLMENT_TRACK_UPDATED`` signal.
24+
* Remove unused compat imports.
25+
1726
[4.2.0] - 2024-06-21
1827
~~~~~~~~~~~~~~~~~~~~
1928

completion_aggregator/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55

66
from __future__ import absolute_import, unicode_literals
77

8-
__version__ = '4.2.0'
8+
__version__ = '4.3.0'

completion_aggregator/api/v0/urls.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@
44

55
from __future__ import absolute_import, division, print_function, unicode_literals
66

7-
from django.urls import re_path
7+
from django.urls import path
88

99
from . import views
1010

1111
app_name = 'completion_aggregator'
1212

1313
urlpatterns = [
14-
re_path(
15-
r'^course/(?P<course_key>.+)/blocks/(?P<block_key>.+)/$',
14+
path(
15+
'course/<path:course_key>/blocks/<path:block_key>/',
1616
views.CompletionBlockUpdateView.as_view(),
1717
name='blockcompletion-update'
1818
),
19-
re_path(r'^course/$', views.CompletionListView.as_view(), name='aggregator-list'),
20-
re_path(r'^course/(?P<course_key>.+)/$', views.CompletionDetailView.as_view(), name='aggregator-detail'),
19+
path('course/', views.CompletionListView.as_view(), name='aggregator-list'),
20+
path('course/<path:course_key>/', views.CompletionDetailView.as_view(), name='aggregator-detail'),
2121
]

completion_aggregator/api/v1/urls.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44

55
from __future__ import absolute_import, division, print_function, unicode_literals
66

7-
from django.urls import re_path
7+
from django.urls import path
88

99
from . import views
1010

1111
app_name = 'completion_aggregator'
1212

1313
urlpatterns = [
14-
re_path(r'^course/$', views.CompletionListView.as_view()),
15-
re_path(r'^course/(?P<course_key>.+)/$', views.CompletionDetailView.as_view()),
16-
re_path(r'^stats/(?P<course_key>.+)/$', views.CourseLevelCompletionStatsView.as_view()),
14+
path('course/', views.CompletionListView.as_view()),
15+
path('course/<path:course_key>/', views.CompletionDetailView.as_view()),
16+
path('stats/<path:course_key>/', views.CourseLevelCompletionStatsView.as_view()),
1717
]

completion_aggregator/api/v1/views.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -563,11 +563,16 @@ def get(self, request, course_key):
563563
course_key=course_key,
564564
aggregation_name='course',
565565
user_id__in=[enrollment.user_id for enrollment in enrollments])
566-
completion_stats = aggregator_qs.aggregate(
567-
possible=Avg('possible'),
568-
earned=Sum('earned') / len(enrollments),
569-
percent=Sum('earned') / (Avg('possible') * len(enrollments)))
570-
completion_stats['course_key'] = course_key
566+
num_enrollments = len(enrollments)
567+
aggregates = aggregator_qs.aggregate(avg_possible=Avg("possible"), total_earned=Sum("earned"))
568+
avg_possible = aggregates["avg_possible"] or 0
569+
total_earned = aggregates["total_earned"] or 0
570+
completion_stats = {
571+
"course_key": course_key,
572+
"possible": avg_possible,
573+
"earned": total_earned / num_enrollments if num_enrollments else 0,
574+
"percent": total_earned / (avg_possible * num_enrollments) if avg_possible and num_enrollments else 0,
575+
}
571576

572577
serializer = self.get_serializer_class()(
573578
instance=completion_stats,

completion_aggregator/batch.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
import logging
1212
import time
1313

14-
import six
15-
1614
from django.conf import settings
1715
from django.core.cache import cache
1816

@@ -79,7 +77,7 @@ def perform_aggregation(batch_size=10000, delay=0.0, limit=None, routing_key=Non
7977
stale_blocks = collections.defaultdict(set)
8078
forced_updates = set()
8179
enqueued = 0
82-
for idx in six.moves.range(max_id, min([min_id + batch_size, max_id]) - 1, -1 * batch_size):
80+
for idx in range(max_id, min([min_id + batch_size, max_id]) - 1, -1 * batch_size):
8381
if enqueued >= limit:
8482
break
8583
evaluated = stale_queryset.filter(id__gt=idx - batch_size, id__lte=idx)
@@ -108,11 +106,11 @@ def perform_aggregation(batch_size=10000, delay=0.0, limit=None, routing_key=Non
108106
elif len(stale_blocks[enrollment]) > MAX_KEYS_PER_TASK:
109107
blocks = []
110108
else:
111-
blocks = [six.text_type(block_key) for block_key in stale_blocks[enrollment]]
109+
blocks = [str(block_key) for block_key in stale_blocks[enrollment]]
112110
aggregation_tasks.update_aggregators.apply_async(
113111
kwargs={
114112
'username': enrollment.username,
115-
'course_key': six.text_type(enrollment.course_key),
113+
'course_key': str(enrollment.course_key),
116114
'block_keys': blocks,
117115
'force': enrollment in forced_updates,
118116
},

completion_aggregator/compat.py

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -147,38 +147,3 @@ def get_mobile_only_courses(enrollments):
147147
course_overview_list = CourseOverview.objects.filter(id__in=course_keys, mobile_available=True)
148148
filtered_course_overview = [overview.id for overview in course_overview_list]
149149
return enrollments.filter(course_id__in=filtered_course_overview)
150-
151-
152-
def get_course(course_key):
153-
"""
154-
Get course for given key.
155-
"""
156-
from courseware.courses import _get_course # pylint: disable=import-error
157-
return _get_course(course_key)
158-
159-
160-
def get_cohorts_for_course(course_key):
161-
"""
162-
Get cohorts for given course key.
163-
"""
164-
from openedx.core.djangoapps.course_groups import cohorts # pylint: disable=import-error
165-
if cohorts.is_course_cohorted(course_key):
166-
return cohorts.get_course_cohort_id(course_key)
167-
return None
168-
169-
170-
def course_access_role_model():
171-
"""
172-
Return the student.models.CourseAccessRole model.
173-
"""
174-
# pragma: no-cover
175-
from common.djangoapps.student.models import CourseAccessRole # pylint: disable=import-error
176-
return CourseAccessRole
177-
178-
179-
def cohort_membership_model():
180-
"""
181-
Return the course_groups.models.CohortMembership model.
182-
"""
183-
from course_groups.models import CohortMembership # pylint: disable=import-error
184-
return CohortMembership

completion_aggregator/core.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from datetime import datetime
1313

1414
import pytz
15-
import six
1615
from xblock.completable import XBlockCompletionMode
1716
from xblock.core import XBlock
1817
from xblock.plugin import PluginMissingError
@@ -63,7 +62,7 @@ def set(self, value):
6362
6463
Sets the group to `str(self.course_key)`.
6564
"""
66-
group = six.text_type(self.course_key)
65+
group = str(self.course_key)
6766
CacheGroup().set(group, self.cache_key, value, timeout=UPDATER_CACHE_TIMEOUT)
6867

6968
def touch(self):

0 commit comments

Comments
 (0)