diff --git a/anatomy/views.py b/anatomy/views.py index 8aa151b3..c6d257e8 100644 --- a/anatomy/views.py +++ b/anatomy/views.py @@ -50,7 +50,7 @@ from .models import ( # IncorrectAnswers, ) from generic.models import CidUser, Examination -from generic.views import AuthorRequiredMixin, ExamCloneMixin, ExamViews, GenericViewBase +from generic.views import AuthorRequiredMixin, ExamCloneMixin, ExamCreateBase, ExamDeleteBase, ExamUpdateBase, ExamViews, GenericViewBase from reversion.views import RevisionMixin from .decorators import user_is_author_or_anatomy_checker @@ -963,30 +963,19 @@ class QuestionDelete(AuthorOrCheckerRequiredMixin, DeleteView): success_url = reverse_lazy("anatomy:question_list") -class ExamCreate(RevisionMixin, LoginRequiredMixin, CreateView): +class ExamCreate(ExamCreateBase): model = Exam form_class = ExamForm - def form_valid(self, form): - self.object = form.save(commit=False) - self.object.save() - form.instance.author.add(self.request.user.id) - return super().form_valid(form) - - -class ExamUpdate( - RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView -): +class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin): model = Exam form_class = ExamForm - def form_valid(self, form): - self.object = form.save(commit=False) - self.object.save() - form.instance.author.add(self.request.user.id) - return super().form_valid(form) +class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase): + model = Exam + class ExamViewSet(RevisionMixin, viewsets.ModelViewSet): @@ -1009,10 +998,6 @@ class ExamAuthorUpdate(RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, U model = Exam form_class = ExamAuthorForm -class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView): - model = Exam - template_name = "exam_confirm_delete.html" - success_url = reverse_lazy("anatomy:index") GenericViews = GenericViewBase("anatomy", AnatomyQuestion, CidUserAnswer, Exam) diff --git a/generic/views.py b/generic/views.py index 0c9d60ee..d4ae4f07 100644 --- a/generic/views.py +++ b/generic/views.py @@ -32,7 +32,7 @@ import urllib from django.views import View from django.views.generic.edit import CreateView, UpdateView, DeleteView from django_filters.views import FilterView -from django_filters import FilterSet +from django_filters import FilterSet, OrderingFilter from django_tables2.views import SingleTableMixin from reversion.views import RevisionMixin from atlas.models import CaseCollection @@ -294,6 +294,8 @@ class ExamViews(View, LoginRequiredMixin): # exam_group = group_map[self.app_name] class ExamFilter(FilterSet): + sort_order = OrderingFilter(fields=(("name", "name"), ("exam_mode", "exam_mode"))) + class Meta: model = exam fields = { @@ -329,6 +331,8 @@ class ExamViews(View, LoginRequiredMixin): # filter param is either missing or empty, use initial as default if not data.get("archive"): data["archive"] = False + if not data.get("sort_order"): + data["sort_order"] = "name" super().__init__(data, *args, **kwargs) @@ -523,15 +527,18 @@ class ExamViews(View, LoginRequiredMixin): @method_decorator(login_required) def exam_list(self, request, all=False): - if all: - exams = self.Exam.objects.all().order_by("name") + + if not self.check_user_access(request.user): + #raise PermissionDenied + exam_list = self.Exam.objects.filter(author__id=request.user.id).order_by("name") else: - exams = self.Exam.objects.filter(exam_mode=True, archive=False).order_by( + exam_list = self.Exam.objects.all().order_by("name") + + if not all: + exams = exam_list.filter(exam_mode=True, archive=False).order_by( "name" ) - if not self.check_user_access(request.user): - raise PermissionDenied marking = False @@ -1021,7 +1028,7 @@ class ExamViews(View, LoginRequiredMixin): view_feedback = False if ( - #request.user.groups.filter(name=self.checker_group).exists() or + # request.user.groups.filter(name=self.checker_group).exists() or request.user.groups.filter(name="feedback_checker").exists() or request.user in question.author.all() ): @@ -2095,6 +2102,14 @@ class ExamCreateBase(RevisionMixin, LoginRequiredMixin, CreateView): form.instance.author.add(self.request.user.id) return super().form_valid(form) + def get_context_data(self, **kwargs): + # Call the base implementation first to get a context + context = super().get_context_data(**kwargs) + print(dir(context["view"])) + print(context["view"].template_name_suffix) + print(context["view"].model.app_name) + return context + class ExamUpdateBase(RevisionMixin, LoginRequiredMixin, UpdateView): template_name = "exam_update_form.html" @@ -2106,3 +2121,11 @@ class ExamUpdateBase(RevisionMixin, LoginRequiredMixin, UpdateView): form.instance.author.add(self.request.user.id) return super().form_valid(form) + + +class ExamDeleteBase(RevisionMixin, DeleteView): + template_name = "exam_confirm_delete.html" + + def get_success_url(self) -> str: + # return super().get_success_url()(self): + return reverse_lazy(f"{self.model.app_name}:index") diff --git a/longs/views.py b/longs/views.py index 6b3035be..e9ecf03a 100755 --- a/longs/views.py +++ b/longs/views.py @@ -75,7 +75,7 @@ from django.forms.models import model_to_dict from longs.forms import LongCreationDefaultForm from longs.models import LongCreationDefault -from generic.views import AuthorRequiredMixin, ExamCloneMixin, ExamViews +from generic.views import AuthorRequiredMixin, ExamCloneMixin, ExamCreateBase, ExamDeleteBase, ExamUpdateBase, ExamViews from reversion.views import RevisionMixin import reversion @@ -1112,47 +1112,28 @@ def long_series_order_upload_filename(request, pk): GenericExamViews = ExamViews(Exam, Long, None, CidUserAnswer, "longs", "long") - -class ExamCreate(RevisionMixin, LoginRequiredMixin, CreateView): +class ExamCreate(ExamCreateBase): model = Exam form_class = ExamForm - def form_valid(self, form): - self.object = form.save(commit=False) - self.object.save() - form.instance.author.add(self.request.user.id) - return super().form_valid(form) +class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin): + model = Exam + form_class = ExamForm +class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase): + model = Exam + class ExamClone(ExamCloneMixin, ExamCreate): """Clone exam view""" -class ExamUpdate( - RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView -): - model = Exam - form_class = ExamForm - - def form_valid(self, form): - self.object = form.save(commit=False) - self.object.save() - - form.instance.author.add(self.request.user.id) - return super().form_valid(form) - class ExamAuthorUpdate(RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView): model = Exam form_class = ExamAuthorForm -class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView): - model = Exam - template_name = "exam_confirm_delete.html" - success_url = reverse_lazy("longs:index") - - class ExamViewSet(RevisionMixin, viewsets.ModelViewSet): queryset = Exam.objects.all().order_by("name") serializer_class = ExamSerializer diff --git a/physics/views.py b/physics/views.py index feaa7373..406cbd0a 100644 --- a/physics/views.py +++ b/physics/views.py @@ -45,6 +45,9 @@ import plotly.express as px from generic.views import ( AuthorRequiredMixin, ExamCloneMixin, + ExamCreateBase, + ExamDeleteBase, + ExamUpdateBase, ExamViews, GenericViewBase, ) @@ -377,17 +380,19 @@ GenericExamViews = ExamViews(Exam, Question, None, CidUserAnswer, "physics", "ph GenericViews = GenericViewBase("physics", Question, CidUserAnswer, Exam) - -class ExamCreate(RevisionMixin, LoginRequiredMixin, CreateView): +class ExamCreate(ExamCreateBase): model = Exam form_class = ExamForm - def form_valid(self, form): - self.object = form.save(commit=False) - self.object.save() - form.instance.author.add(self.request.user.id) - return super().form_valid(form) +class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin): + model = Exam + form_class = ExamForm + + +class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase): + model = Exam + class ExamClone(AuthorOrCheckerRequiredMixin, ExamCloneMixin, ExamCreate): @@ -401,24 +406,6 @@ class ExamAuthorUpdate( form_class = ExamAuthorForm -class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView): - model = Exam - template_name = "exam_confirm_delete.html" - success_url = reverse_lazy("physics:index") - - -class ExamUpdate( - RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView -): - model = Exam - form_class = ExamForm - - def form_valid(self, form): - self.object = form.save(commit=False) - self.object.save() - - form.instance.author.add(self.request.user.id) - return super().form_valid(form) class QuestionView( diff --git a/rad/filters.py b/rad/filters.py index 352f7530..4d6990b5 100644 --- a/rad/filters.py +++ b/rad/filters.py @@ -1,12 +1,16 @@ +from cProfile import label import django_filters +from generic.models import UserGrades + from django.contrib.auth.models import User class UserListFilter(django_filters.FilterSet): - userprofile__grade = django_filters.CharFilter(label="Grade") + userprofile__grade = django_filters.ModelChoiceFilter(label="Grade", queryset=UserGrades.objects.all()) + #test2 = django_filters.CharFilter() class Meta: model= User diff --git a/rapids/templates/rapids/question_display_block.html b/rapids/templates/rapids/question_display_block.html index 06954db2..c9e01a84 100755 --- a/rapids/templates/rapids/question_display_block.html +++ b/rapids/templates/rapids/question_display_block.html @@ -36,6 +36,7 @@ +
Open Access: {{ question.open_access }}
@@ -156,8 +157,15 @@ }) }) + $("#cancel-add-to-exam").click(function (evt) { + $("#add-to-exam").toggle(); + $("#cancel-add-to-exam").toggle(); + $("#exam-options").empty(); + }) $("#add-to-exam").click(function (evt) { + $("#add-to-exam").toggle(); + $("#cancel-add-to-exam").toggle(); var jqxhr = $.get(evt.target.dataset.exam_list_url, function (data) { console.log(data); @@ -380,11 +388,18 @@ \ No newline at end of file diff --git a/rapids/tests/test_urls.py b/rapids/tests/test_urls.py index 0fe027d8..a03f2483 100644 --- a/rapids/tests/test_urls.py +++ b/rapids/tests/test_urls.py @@ -33,6 +33,18 @@ def create_superuser(db, django_user_model): "admin", "ross@xkjq.uk", "adminpassword" ) +@pytest.fixture(params=EXAM_VIEWS) +def set_up_exams(request, django_user_model, create_superuser): + basic_user = django_user_model.objects.create_user( + "user1", "user@user.net", "pass2" + ) + + e1 = request.param.Exam.objects.create(name="test exam", exam_mode=True) + e = request.param.Exam.objects.create(name="test exam 2", exam_mode=True) + e.author.add(basic_user) + return request.param + + # @pytest.fixture # def create_exam(db): @@ -40,6 +52,18 @@ def create_superuser(db, django_user_model): # Very basic tests, make sure we can hit key urls without error +def test_index2(client, set_up_exams): + client.login(username="admin", password="adminpassword") + response = client.get(reverse(f"{set_up_exams.app_name}:index")) + assert response.status_code == 200 + + assert ( + BeautifulSoup(response.content, "html.parser") + .find("h4", {"class": "exam-number-title"}) + .getText() + == "2 exams found." + ) + @pytest.mark.parametrize("exam_views", EXAM_VIEWS) def test_index(client, create_superuser, exam_views, django_user_model): @@ -63,6 +87,15 @@ def test_index(client, create_superuser, exam_views, django_user_model): == "2 exams found." ) + + response = client.get(reverse(f"{exam_views.app_name}:exam_list")) + assert response.status_code == 200 + assert ( + len(BeautifulSoup(response.content, "html.parser") + .select("a.exam-name")) + == 2 + ) + if exam_views.app_name in JSON_APPS: response = client.get(reverse(f"{exam_views.app_name}:active_exams")) @@ -86,7 +119,6 @@ def test_index(client, create_superuser, exam_views, django_user_model): HTTP_X_REQUESTED_WITH="XMLHttpRequest", ) - print(f"{e1.valid_user_users.all()=}") assert json.loads(add_user_response.content)["status"] == "success" @@ -103,6 +135,15 @@ def test_index(client, create_superuser, exam_views, django_user_model): == "1 exams found." ) + response = client.get(reverse(f"{exam_views.app_name}:exam_list")) + + assert ( + len(BeautifulSoup(response.content, "html.parser") + .select("a.exam-name")) + == 1 + ) + + if exam_views.app_name in JSON_APPS: response = client.get(reverse(f"{exam_views.app_name}:active_exams")) diff --git a/rapids/views.py b/rapids/views.py index 5f3cec54..fe1f411d 100755 --- a/rapids/views.py +++ b/rapids/views.py @@ -46,6 +46,7 @@ from generic.views import ( AuthorRequiredMixin, ExamCloneMixin, ExamCreateBase, + ExamDeleteBase, ExamUpdateBase, ExamViews, GenericViewBase, @@ -787,6 +788,9 @@ class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin): form_class = ExamForm +class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase): + model = Exam + class ExamAuthorUpdate( RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView ): @@ -794,11 +798,6 @@ class ExamAuthorUpdate( form_class = ExamAuthorForm -class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView): - model = Exam - template_name = "exam_confirm_delete.html" - success_url = reverse_lazy("rapids:index") - class UserAnswerView(LoginRequiredMixin, DetailView): model = CidUserAnswer @@ -840,9 +839,9 @@ class ExamViewSet(RevisionMixin, viewsets.ModelViewSet): """ user = self.request.user if user.groups.filter(name="rapid_checker").exists(): - return Exam.objects.all() + return Exam.objects.filter(archive=False).order_by('name') - return Exam.objects.filter(author__id=user.id) + return Exam.objects.filter(author__id=user.id, archive=False).order_by('name') class QuestionAnswerViewSet(viewsets.ModelViewSet): diff --git a/sbas/views.py b/sbas/views.py index 1d353bbd..ea2f22ea 100644 --- a/sbas/views.py +++ b/sbas/views.py @@ -40,7 +40,7 @@ import json import statistics import plotly.express as px -from generic.views import AuthorRequiredMixin, ExamCloneMixin, ExamViews, GenericViewBase +from generic.views import AuthorRequiredMixin, ExamCloneMixin, ExamCreateBase, ExamDeleteBase, ExamUpdateBase, ExamViews, GenericViewBase from rest_framework import viewsets, permissions from rest_framework.pagination import PageNumberPagination @@ -342,31 +342,17 @@ class QuestionDelete(AuthorOrCheckerRequiredMixin, DeleteView): success_url = reverse_lazy("sbas:question_list") -class ExamCreate(RevisionMixin, LoginRequiredMixin, CreateView): +class ExamCreate(ExamCreateBase): model = Exam form_class = ExamForm - def form_valid(self, form): - self.object = form.save(commit=False) - self.object.save() - form.instance.author.add(self.request.user.id) - return super().form_valid(form) - - -class ExamUpdate( - RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView -): +class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin): model = Exam form_class = ExamForm - def form_valid(self, form): - self.object = form.save(commit=False) - self.object.save() - - form.instance.author.add(self.request.user.id) - return super().form_valid(form) - +class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase): + model = Exam # class ExamViewSet(RevisionMixin, viewsets.ModelViewSet): # # queryset = Exam.objects.all().order_by('name') @@ -388,30 +374,6 @@ class ExamAuthorUpdate(RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, U form_class = ExamAuthorForm -class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView): - model = Exam - template_name = "exam_confirm_delete.html" - success_url = reverse_lazy("sbas:index") - class ExamClone(ExamCloneMixin, ExamCreate): - """Clone exam view""" - -class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView): - model = Exam - template_name = "exam_confirm_delete.html" - success_url = reverse_lazy("sbas:index") - - -class ExamUpdate( - RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView -): - model = Exam - form_class = ExamForm - - def form_valid(self, form): - self.object = form.save(commit=False) - self.object.save() - - form.instance.author.add(self.request.user.id) - return super().form_valid(form) \ No newline at end of file + """Clone exam view""" \ No newline at end of file diff --git a/templates/exam_create_form.html b/templates/exam_create_form.html index 887f9ed1..533f1e65 100644 --- a/templates/exam_create_form.html +++ b/templates/exam_create_form.html @@ -1,12 +1,10 @@ -{% extends "base.html" %} -{% comment %} {% extends exam.app_name|add:"/base.html" %} {% endcomment %} +{% extends view.model.app_name|add:"/base.html" %} {% block js %} {{ form.media }} {% endblock %} {% block content %} -{{form.instance}}