diff --git a/analytics_data_api/urls.py b/analytics_data_api/urls.py index e1c9e0bc..34355a4c 100644 --- a/analytics_data_api/urls.py +++ b/analytics_data_api/urls.py @@ -1,11 +1,11 @@ -from django.conf.urls import include, url +from django.urls import include, re_path from rest_framework.urlpatterns import format_suffix_patterns app_name = 'analytics_data_api' urlpatterns = [ - url(r'^v0/', include('analytics_data_api.v0.urls')), - url(r'^v1/', include('analytics_data_api.v1.urls')), + re_path(r'^v0/', include('analytics_data_api.v0.urls')), + re_path(r'^v1/', include('analytics_data_api.v1.urls')), ] urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/analytics_data_api/v0/__init__.py b/analytics_data_api/v0/__init__.py index ce955d0d..e69de29b 100644 --- a/analytics_data_api/v0/__init__.py +++ b/analytics_data_api/v0/__init__.py @@ -1 +0,0 @@ -default_app_config = 'analytics_data_api.v0.apps.ApiAppConfig' diff --git a/analytics_data_api/v0/models.py b/analytics_data_api/v0/models.py index ec2ec69d..f7d65010 100644 --- a/analytics_data_api/v0/models.py +++ b/analytics_data_api/v0/models.py @@ -135,7 +135,7 @@ class Meta(BaseCourseModel.Meta): module_id = models.CharField(db_index=True, max_length=255) part_id = models.CharField(db_index=True, max_length=255) - correct = models.NullBooleanField() + correct = models.BooleanField(default=None, null=True) value_id = models.CharField(db_index=True, max_length=255, null=True) answer_value = models.TextField(null=True, db_column='answer_value_text') variant = models.IntegerField(null=True) diff --git a/analytics_data_api/v0/urls/__init__.py b/analytics_data_api/v0/urls/__init__.py index 91e7dec8..89811edc 100644 --- a/analytics_data_api/v0/urls/__init__.py +++ b/analytics_data_api/v0/urls/__init__.py @@ -1,5 +1,4 @@ -from django.conf.urls import include, url -from django.urls import reverse_lazy +from django.urls import include, path, reverse_lazy from django.views.generic import RedirectView app_name = 'analytics_data_api.v0' @@ -7,15 +6,15 @@ COURSE_ID_PATTERN = r'(?P[^/+]+[/+][^/+]+[/+][^/]+)' urlpatterns = [ - url(r'^courses/', include('analytics_data_api.v0.urls.courses')), - url(r'^problems/', include('analytics_data_api.v0.urls.problems')), - url(r'^videos/', include('analytics_data_api.v0.urls.videos')), - url('^', include('analytics_data_api.v0.urls.learners')), - url('^', include('analytics_data_api.v0.urls.course_summaries')), - url('^', include('analytics_data_api.v0.urls.programs')), + path('courses/', include('analytics_data_api.v0.urls.courses')), + path('problems/', include('analytics_data_api.v0.urls.problems')), + path('videos/', include('analytics_data_api.v0.urls.videos')), + path('', include('analytics_data_api.v0.urls.learners')), + path('', include('analytics_data_api.v0.urls.course_summaries')), + path('', include('analytics_data_api.v0.urls.programs')), # pylint: disable=no-value-for-parameter - url(r'^authenticated/$', RedirectView.as_view(url=reverse_lazy('authenticated')), name='authenticated'), - url(r'^health/$', RedirectView.as_view(url=reverse_lazy('health')), name='health'), - url(r'^status/$', RedirectView.as_view(url=reverse_lazy('status')), name='status'), + path('authenticated/', RedirectView.as_view(url=reverse_lazy('authenticated')), name='authenticated'), + path('health/', RedirectView.as_view(url=reverse_lazy('health')), name='health'), + path('status/', RedirectView.as_view(url=reverse_lazy('status')), name='status'), ] diff --git a/analytics_data_api/v0/urls/course_summaries.py b/analytics_data_api/v0/urls/course_summaries.py index 27039f2f..fa741af2 100644 --- a/analytics_data_api/v0/urls/course_summaries.py +++ b/analytics_data_api/v0/urls/course_summaries.py @@ -1,9 +1,9 @@ -from django.conf.urls import url +from django.urls import re_path from analytics_data_api.v0.views import course_summaries as views app_name = 'course_summaries' urlpatterns = [ - url(r'^course_summaries/$', views.CourseSummariesView.as_view(), name='course_summaries'), + re_path(r'^course_summaries/$', views.CourseSummariesView.as_view(), name='course_summaries'), ] diff --git a/analytics_data_api/v0/urls/courses.py b/analytics_data_api/v0/urls/courses.py index b4c31dae..e2d4e4a4 100644 --- a/analytics_data_api/v0/urls/courses.py +++ b/analytics_data_api/v0/urls/courses.py @@ -1,4 +1,4 @@ -from django.conf.urls import url +from django.urls import re_path from analytics_data_api.v0.urls import COURSE_ID_PATTERN from analytics_data_api.v0.views import courses as views @@ -25,4 +25,4 @@ for path, view, name in COURSE_URLS: regex = fr'^{COURSE_ID_PATTERN}/{path}/$' - urlpatterns.append(url(regex, view.as_view(), name=name)) + urlpatterns.append(re_path(regex, view.as_view(), name=name)) diff --git a/analytics_data_api/v0/urls/learners.py b/analytics_data_api/v0/urls/learners.py index 425074b0..18d802bc 100644 --- a/analytics_data_api/v0/urls/learners.py +++ b/analytics_data_api/v0/urls/learners.py @@ -1,4 +1,4 @@ -from django.conf.urls import url +from django.urls import re_path from analytics_data_api.constants.learner import UUID_REGEX_PATTERN from analytics_data_api.v0.views import learners as views @@ -8,6 +8,6 @@ USERNAME_PATTERN = r'(?P[\w.+-]+)' urlpatterns = [ - url(fr'^enterprise/(?P{UUID_REGEX_PATTERN})/engagements/$', - views.EnterpriseLearnerEngagementView.as_view(), name='engagements'), + re_path(fr'^enterprise/(?P{UUID_REGEX_PATTERN})/engagements/$', + views.EnterpriseLearnerEngagementView.as_view(), name='engagements'), ] diff --git a/analytics_data_api/v0/urls/problems.py b/analytics_data_api/v0/urls/problems.py index e9fa2b59..b11ebf47 100644 --- a/analytics_data_api/v0/urls/problems.py +++ b/analytics_data_api/v0/urls/problems.py @@ -1,6 +1,6 @@ import re -from django.conf.urls import url +from django.urls import re_path from analytics_data_api.v0.views import problems as views @@ -12,9 +12,10 @@ ] urlpatterns = [ - url(r'^(?P.+)/sequential_open_distribution/$', - views.SequentialOpenDistributionView.as_view(), name='sequential_open_distribution'), + re_path(r'^(?P.+)/sequential_open_distribution/$', + views.SequentialOpenDistributionView.as_view(), + name='sequential_open_distribution'), ] for path, view, name in PROBLEM_URLS: - urlpatterns.append(url(r'^(?P.+)/' + re.escape(path) + r'/$', view.as_view(), name=name)) + urlpatterns.append(re_path(r'^(?P.+)/' + re.escape(path) + r'/$', view.as_view(), name=name)) diff --git a/analytics_data_api/v0/urls/programs.py b/analytics_data_api/v0/urls/programs.py index 051df031..76686cad 100644 --- a/analytics_data_api/v0/urls/programs.py +++ b/analytics_data_api/v0/urls/programs.py @@ -1,9 +1,9 @@ -from django.conf.urls import url +from django.urls import path from analytics_data_api.v0.views import programs as views app_name = 'programs' urlpatterns = [ - url(r'^programs/$', views.ProgramsView.as_view(), name='programs'), + path('programs/', views.ProgramsView.as_view(), name='programs'), ] diff --git a/analytics_data_api/v0/urls/videos.py b/analytics_data_api/v0/urls/videos.py index eb19c1ee..ea6e633d 100644 --- a/analytics_data_api/v0/urls/videos.py +++ b/analytics_data_api/v0/urls/videos.py @@ -1,6 +1,6 @@ import re -from django.conf.urls import url +from django.urls import re_path from analytics_data_api.v0.views import videos as views @@ -13,4 +13,4 @@ urlpatterns = [] for path, view, name in VIDEO_URLS: - urlpatterns.append(url(r'^(?P.+)/' + re.escape(path) + r'/$', view.as_view(), name=name)) + urlpatterns.append(re_path(r'^(?P.+)/' + re.escape(path) + r'/$', view.as_view(), name=name)) diff --git a/analytics_data_api/v0/views/courses.py b/analytics_data_api/v0/views/courses.py index 4c8bba42..65b1b6cb 100644 --- a/analytics_data_api/v0/views/courses.py +++ b/analytics_data_api/v0/views/courses.py @@ -7,7 +7,7 @@ from django.db import connections, router from django.db.models import Max from django.http import Http404 -from django.utils.timezone import make_aware, utc +from django.utils.timezone import make_aware from opaque_keys.edx.keys import CourseKey from rest_framework import generics from rest_framework.response import Response @@ -32,7 +32,7 @@ def get(self, request, *args, **kwargs): self.course_id = self.kwargs.get('course_id') start_date = request.query_params.get('start_date') end_date = request.query_params.get('end_date') - timezone = utc + timezone = datetime.timezone.utc self.start_date = self.parse_date(start_date, timezone) self.end_date = self.parse_date(end_date, timezone) diff --git a/analytics_data_api/v1/urls.py b/analytics_data_api/v1/urls.py index 39677cb2..17cc4991 100644 --- a/analytics_data_api/v1/urls.py +++ b/analytics_data_api/v1/urls.py @@ -1,5 +1,4 @@ -from django.conf.urls import include, url -from django.urls import reverse_lazy +from django.urls import include, re_path, reverse_lazy from django.views.generic import RedirectView from analytics_data_api.v0.urls import COURSE_ID_PATTERN @@ -26,16 +25,16 @@ for path, view, name in COURSE_URLS: regex = fr'^courses/{COURSE_ID_PATTERN}/{path}/$' - course_urlpatterns.append(url(regex, view.as_view(), name=name)) + course_urlpatterns.append(re_path(regex, view.as_view(), name=name)) urlpatterns = course_urlpatterns + [ - url(r'^problems/', include('analytics_data_api.v0.urls.problems')), - url(r'^videos/', include('analytics_data_api.v0.urls.videos')), - url('^', include('analytics_data_api.v0.urls.course_summaries')), - url('^', include('analytics_data_api.v0.urls.programs')), + re_path(r'^problems/', include('analytics_data_api.v0.urls.problems')), + re_path(r'^videos/', include('analytics_data_api.v0.urls.videos')), + re_path('^', include('analytics_data_api.v0.urls.course_summaries')), + re_path('^', include('analytics_data_api.v0.urls.programs')), # pylint: disable=no-value-for-parameter - url(r'^authenticated/$', RedirectView.as_view(url=reverse_lazy('authenticated')), name='authenticated'), - url(r'^health/$', RedirectView.as_view(url=reverse_lazy('health')), name='health'), - url(r'^status/$', RedirectView.as_view(url=reverse_lazy('status')), name='status'), + re_path(r'^authenticated/$', RedirectView.as_view(url=reverse_lazy('authenticated')), name='authenticated'), + re_path(r'^health/$', RedirectView.as_view(url=reverse_lazy('health')), name='health'), + re_path(r'^status/$', RedirectView.as_view(url=reverse_lazy('status')), name='status'), ] diff --git a/analyticsdataserver/urls.py b/analyticsdataserver/urls.py index 30df184a..df0d3f45 100644 --- a/analyticsdataserver/urls.py +++ b/analyticsdataserver/urls.py @@ -1,5 +1,5 @@ -from django.conf.urls import include, url from django.contrib import admin +from django.urls import include, re_path from django.views.generic import RedirectView from edx_api_doc_tools import make_api_info, make_docs_ui_view from rest_framework.authtoken.views import obtain_auth_token @@ -10,16 +10,16 @@ admin.site.site_title = admin.site.site_header urlpatterns = [ - url(r'^api-auth/', include('rest_framework.urls', 'rest_framework')), - url(r'^api-token-auth/', obtain_auth_token), + re_path(r'^api-auth/', include('rest_framework.urls', 'rest_framework')), + re_path(r'^api-token-auth/', obtain_auth_token), - url(r'^api/', include('analytics_data_api.urls')), - url(r'^status/$', views.StatusView.as_view(), name='status'), - url(r'^authenticated/$', views.AuthenticationTestView.as_view(), name='authenticated'), - url(r'^health/$', views.HealthView.as_view(), name='health'), + re_path(r'^api/', include('analytics_data_api.urls')), + re_path(r'^status/$', views.StatusView.as_view(), name='status'), + re_path(r'^authenticated/$', views.AuthenticationTestView.as_view(), name='authenticated'), + re_path(r'^health/$', views.HealthView.as_view(), name='health'), ] -urlpatterns.append(url(r'', include('enterprise_data.urls'))) +urlpatterns.append(re_path(r'', include('enterprise_data.urls'))) api_ui_view = make_docs_ui_view( api_info=make_api_info( @@ -31,8 +31,8 @@ ) urlpatterns += [ - url(r'^docs/$', api_ui_view, name='api-docs'), - url(r'^$', RedirectView.as_view(url='/docs')), # pylint: disable=no-value-for-parameter + re_path(r'^docs/$', api_ui_view, name='api-docs'), + re_path(r'^$', RedirectView.as_view(url='/docs')), # pylint: disable=no-value-for-parameter ] handler500 = 'analyticsdataserver.views.handle_internal_server_error' # pylint: disable=invalid-name diff --git a/docs/api/source/conf.py b/docs/api/source/conf.py index 358711c8..79a5ea85 100644 --- a/docs/api/source/conf.py +++ b/docs/api/source/conf.py @@ -1,10 +1,9 @@ import os import sys -import django +import django from path import Path - # Add any paths that contain templates here, relative to this directory. # templates_path.append('source/_templates') diff --git a/tox.ini b/tox.ini index d45f6e84..c28250f5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,32 +1,30 @@ [tox] skipsdist = True -envlist = py38-django{32} +envlist = py38-django{32, 42} [testenv] -passenv = - ELASTICSEARCH_LEARNERS_HOST - COVERAGE_DIR -setenv = - tests: DJANGO_SETTINGS_MODULE = analyticsdataserver.settings.test - NODE_BIN = ./node_modules/.bin - PATH = $PATH:$NODE_BIN -deps = - django32: Django>=3.2,<3.3 - -r requirements/test.txt -commands = - {posargs:pytest} +passenv = + ELASTICSEARCH_LEARNERS_HOST + COVERAGE_DIR +setenv = + tests: DJANGO_SETTINGS_MODULE = analyticsdataserver.settings.test + NODE_BIN = ./node_modules/.bin + PATH = $PATH:$NODE_BIN +deps = + django32: Django>=3.2,<3.3 + django42: Django>=4.2,<4.3 + -r requirements/test.txt +commands = + {posargs:pytest} [testenv:docs] -deps = +deps = -r{toxinidir}/requirements/doc.txt -allowlist_externals = +allowlist_externals = make env -setenv = -# -W will treat warnings as errors. +setenv = SPHINXOPTS = -W -commands = -# -e allows for overriding setting from the environment. -# -C changes the directory to `docs` before running the command. +commands = make -e -C docs/api clean make -e -C docs/api html