diff --git a/README.rst b/README.rst index 2c95c61..ab3adfe 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,7 @@ Requirements ------------ * Python 3.8+ * RabbitMQ 3.8+ -* Django 3.2 +* Django 4.2 * Celery 5.2 * Memcached 1.5 * and other Python packages, see ``requirements.txt`` @@ -83,4 +83,3 @@ teemu.t.lehtinen@aalto.fi, 9.2.2015 .. _A+: https://github.com/apluslms/a-plus .. _Django LTI login: https://github.com/Aalto-LeTech/django-lti-login .. _greedy string tiling: https://github.com/Aalto-LeTech/greedy-string-tiling - diff --git a/accounts/models.py b/accounts/models.py index 9a62a69..d43a695 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -1,6 +1,6 @@ from django.contrib.auth.models import AbstractUser from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from aplus_client.client import AplusTokenClient from aplus_client.django.models import ApiNamespace @@ -32,4 +32,3 @@ def add_api_token(self, token, site): def __str__(self): return self.email - diff --git a/data/urls.py b/data/urls.py index cb6690d..fa31cff 100644 --- a/data/urls.py +++ b/data/urls.py @@ -1,8 +1,8 @@ -from django.conf.urls import url +from django.urls import re_path from data.views import hook_submission urlpatterns = [ - url(r'^(?P\w+)/hook-submission$', hook_submission, name='hook_submission'), + re_path(r'^(?P\w+)/hook-submission$', hook_submission, name='hook_submission'), ] diff --git a/ltilogin/__init__.py b/ltilogin/__init__.py index a16b3ed..e69de29 100644 --- a/ltilogin/__init__.py +++ b/ltilogin/__init__.py @@ -1 +0,0 @@ -default_app_config = 'ltilogin.apps.RadarConfig' diff --git a/ltilogin/receivers.py b/ltilogin/receivers.py index 02d0669..616d572 100644 --- a/ltilogin/receivers.py +++ b/ltilogin/receivers.py @@ -79,4 +79,3 @@ def add_course_permissions(sender, **kwargs): user_logged_in.connect(add_course_permissions) - diff --git a/radar/settings.py b/radar/settings.py index e5022ad..5ce5d84 100644 --- a/radar/settings.py +++ b/radar/settings.py @@ -51,9 +51,9 @@ 'data', 'accounts', 'review', - 'aplus_client', + 'aplus_client.django.apps.AplusClientConfig', 'django_lti_login', - 'ltilogin', + 'ltilogin.apps.RadarConfig', 'provider', 'aplus_auth', ) diff --git a/radar/urls.py b/radar/urls.py index ddce971..7f55387 100644 --- a/radar/urls.py +++ b/radar/urls.py @@ -1,6 +1,7 @@ -from django.conf.urls import include, url +from django.conf.urls import include from django.contrib import admin from django.contrib.auth.views import LoginView, LogoutView +from django.urls import re_path import data.urls import review.urls @@ -9,17 +10,17 @@ urlpatterns = [ - url(r'^accounts/login/$', LoginView.as_view(template_name='login.html'), name='login'), - url(r'^accounts/logout/$', LogoutView.as_view(template_name='login.html'), name='logout'), - url(r'^auth/', include('django_lti_login.urls')), - url(r'^admin/', admin.site.urls), + re_path(r'^accounts/login/$', LoginView.as_view(template_name='login.html'), name='login'), + re_path(r'^accounts/logout/$', LogoutView.as_view(template_name='login.html'), name='logout'), + re_path(r'^auth/', include('django_lti_login.urls')), + re_path(r'^admin/', admin.site.urls), - url(r'^', include(data.urls)), - url(r'^', include(review.urls)), + re_path(r'^', include(data.urls)), + re_path(r'^', include(review.urls)), ] if settings.DEBUG: import debug_toolbar urlpatterns = [ - url(r'^__debug__/', include(debug_toolbar.urls)), - ] + urlpatterns \ No newline at end of file + re_path(r'^__debug__/', include(debug_toolbar.urls)), + ] + urlpatterns diff --git a/requirements.txt b/requirements.txt index 7066c7b..2755757 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,10 @@ -Django ~= 3.2.18 +Django ~= 4.2.3 celery >= 5.2.7, < 6 django-bootstrap-form >= 3.4, < 4 oauthlib >= 3.2.2, < 4 requests >= 2.28.2, < 3 -git+https://github.com/apluslms/django-lti-login.git@3.2.0#egg=django-lti-login==3.2.0 -git+https://github.com/apluslms/django-essentials.git@1.5.0#egg=django-essentials==1.5.0 -git+https://github.com/apluslms/a-plus-client.git@v1.2.0#egg=a_plus_client +git+https://github.com/apluslms/django-lti-login.git@3.3.0#egg=django-lti-login==3.3.0 +git+https://github.com/apluslms/django-essentials.git@1.6.0#egg=django-essentials==1.6.0 +git+https://github.com/apluslms/a-plus-client.git@v1.3.0#egg=a_plus_client git+https://github.com/apluslms/greedy-string-tiling.git@v0.13.0 aplus-auth ~= 0.2.3 diff --git a/review/urls.py b/review/urls.py index 8527eab..8ab5a26 100644 --- a/review/urls.py +++ b/review/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import url +from django.urls import re_path from review.views import ( build_graph, @@ -21,20 +21,20 @@ urlpatterns = [ - url(r'^$', index, name='index'), - url(r'^(?P\w+)/$', course, name='course'), - url(r'^(?P\w+)/histogram/$', course_histograms, name='course_histograms'), - url(r'^(?P\w+)/marked/$', marked_submissions, name='marked_submissions'), - url(r'^(?P\w+)/configure/$', configure_course, name='configure_course'), - url(r'^(?P\w+)/graph/$', graph_ui, name='graph_ui'), - url(r'^(?P\w+)/students/$', students_view, name='students_view'), - url(r'^(?P\w+)/students/(?P\w+)/$', student_view, name='student_view'), - url(r'^(?P\w+)/(?P\w+)-(?P\w+)/$', pair_view, name='pair_view'), - url(r'^(?P\w+)/(?P\w+)-(?P\w+)/summary/$', pair_view_summary, name='pair_view_summary'), - url(r'^(?P\w+)/flagged_pairs/$', flagged_pairs, name='flagged_pairs'), - url(r'^(?P\w+)/graph/build$', build_graph, name='build_graph'), - url(r'^(?P\w+)/graph/invalidate$', invalidate_graph_cache, name='invalidate_graph_cache'), - url(r'^(?P\w+)/(?P\w+)/$', exercise, name='exercise'), - url(r'^(?P\w+)/(?P\w+)/settings/$', exercise_settings, name='exercise_settings'), - url(r'^(?P\w+)/(?P\w+)/compare/(?P\w+)-(?P\w+)/(?P\w+)$', comparison, name='comparison'), + re_path(r'^$', index, name='index'), + re_path(r'^(?P\w+)/$', course, name='course'), + re_path(r'^(?P\w+)/histogram/$', course_histograms, name='course_histograms'), + re_path(r'^(?P\w+)/marked/$', marked_submissions, name='marked_submissions'), + re_path(r'^(?P\w+)/configure/$', configure_course, name='configure_course'), + re_path(r'^(?P\w+)/graph/$', graph_ui, name='graph_ui'), + re_path(r'^(?P\w+)/students/$', students_view, name='students_view'), + re_path(r'^(?P\w+)/students/(?P\w+)/$', student_view, name='student_view'), + re_path(r'^(?P\w+)/(?P\w+)-(?P\w+)/$', pair_view, name='pair_view'), + re_path(r'^(?P\w+)/(?P\w+)-(?P\w+)/summary/$', pair_view_summary, name='pair_view_summary'), + re_path(r'^(?P\w+)/flagged_pairs/$', flagged_pairs, name='flagged_pairs'), + re_path(r'^(?P\w+)/graph/build$', build_graph, name='build_graph'), + re_path(r'^(?P\w+)/graph/invalidate$', invalidate_graph_cache, name='invalidate_graph_cache'), + re_path(r'^(?P\w+)/(?P\w+)/$', exercise, name='exercise'), + re_path(r'^(?P\w+)/(?P\w+)/settings/$', exercise_settings, name='exercise_settings'), + re_path(r'^(?P\w+)/(?P\w+)/compare/(?P\w+)-(?P\w+)/(?P\w+)$', comparison, name='comparison'), ] diff --git a/review/views.py b/review/views.py index ce519b2..ff0efb6 100644 --- a/review/views.py +++ b/review/views.py @@ -17,6 +17,7 @@ from radar.config import provider_config, configured_function from review.decorators import access_resource from review.forms import ExerciseForm, ExerciseTemplateForm, DeleteExerciseFrom +from util.misc import is_ajax logger = logging.getLogger("radar.review") @@ -75,7 +76,7 @@ def comparison(request, course_key=None, exercise_key=None, ak=None, bk=None, ck submission_a__student__key=ak, submission_b__student__key=bk) if request.method == "POST": result = "review" in request.POST and comparison.update_review(request.POST["review"]) - if request.is_ajax(): + if is_ajax(request): return JsonResponse({ "success": result }) reverse_flag = False @@ -208,7 +209,7 @@ def configure_course(request, course_key=None, course=None): full_reload(exercise, p_config) return redirect(reverse("configure_course", kwargs={"course_key": course.key}) + "?success=true") - if not request.is_ajax(): + if not is_ajax(request): return HttpResponseBadRequest("Unknown POST request") pending_api_read = json.loads(request.body.decode("utf-8")) @@ -257,7 +258,7 @@ def graph_ui(request, course, course_key): @access_resource def build_graph(request, course, course_key): - if request.method != "POST" or not request.is_ajax(): + if request.method != "POST" or not is_ajax(request): return HttpResponseBadRequest() task_state = json.loads(request.body.decode("utf-8")) @@ -339,7 +340,7 @@ def exercise_settings(request, course_key=None, exercise_key=None, course=None, return redirect("course", course_key=course.key) else: context["change_failure"]["delete_exercise"] = form.cleaned_data["name"] - + template_source = configured_function(p_config, 'get_exercise_template')(exercise, p_config) if exercise.template_tokens and not template_source: context["template_source_error"] = True @@ -383,7 +384,7 @@ def students_view(request, course=None, course_key=None): "course": course, "submissions": submissions, } - + return render(request, "review/students_view.html", context) @access_resource @@ -392,7 +393,7 @@ def student_view(request, course=None, course_key=None, student=None, student_ke comparisons = (Comparison.objects .filter(submission_a__exercise__course=course) .filter(similarity__gt=0.75) - .select_related("submission_a", "submission_b","submission_a__exercise", + .select_related("submission_a", "submission_b","submission_a__exercise", "submission_b__exercise", "submission_a__student", "submission_b__student") .filter(Q(submission_a__student__key=student_key) | Q(submission_b__student__key=student_key)) .exclude(submission_b__isnull=True)) @@ -421,10 +422,10 @@ def pair_view(request, course=None, course_key=None, a=None, a_key=None, b=None, comparisons = (Comparison.objects .filter(submission_a__exercise__course=course) .filter(similarity__gt=0) - .select_related("submission_a", "submission_b","submission_a__exercise", + .select_related("submission_a", "submission_b","submission_a__exercise", "submission_b__exercise", "submission_a__student", "submission_b__student") .filter(Q(submission_a__student__key__in=authors) & Q(submission_b__student__key__in=authors))) - + context = { "hierarchy": ( (settings.APP_NAME, reverse("index")), @@ -452,7 +453,7 @@ def pair_view_summary(request, course=None, course_key=None, a=None, a_key=None, comparisons = (Comparison.objects .filter(submission_a__exercise__course=course) .filter(similarity__gt=0) - .select_related("submission_a", "submission_b","submission_a__exercise", + .select_related("submission_a", "submission_b","submission_a__exercise", "submission_b__exercise", "submission_a__student", "submission_b__student") .filter(Q(submission_a__student__key__in=authors) & Q(submission_b__student__key__in=authors)) .filter(review=settings.REVIEW_CHOICES[4][0])) @@ -531,4 +532,4 @@ def flagged_pairs(request, course=None, course_key=None): def grouped(l, n): # Yield successive n-sized chunks from l. for i in range(0, len(l), n): - yield l[i:i+n] \ No newline at end of file + yield l[i:i+n] diff --git a/util/misc.py b/util/misc.py new file mode 100644 index 0000000..9a1c64d --- /dev/null +++ b/util/misc.py @@ -0,0 +1,6 @@ +def is_ajax(request): + """ + Detect AJAX requests. + Request object method is_ajax() was removed in Django 4.0, this can be used instead. + """ + return request.headers.get('x-requested-with') == 'XMLHttpRequest'