From fd99f51d97d154aa25a27e2a400aa3868d29809c Mon Sep 17 00:00:00 2001 From: Ross Date: Thu, 30 Sep 2021 20:24:50 +0100 Subject: [PATCH] . --- physics/filters.py | 63 ++++++++++++++++++++++++++++++++++ physics/forms.py | 34 +++++++++++++++++++ physics/tables.py | 84 ++++++++++++++++++++++++++++++++++++++++++++++ physics/urls.py | 15 +++++++++ physics/views.py | 29 +++++++++++++--- sbas/filters.py | 2 +- sbas/urls.py | 2 +- sbas/views.py | 24 +++++++------ 8 files changed, 235 insertions(+), 18 deletions(-) create mode 100644 physics/filters.py create mode 100755 physics/forms.py create mode 100644 physics/tables.py diff --git a/physics/filters.py b/physics/filters.py new file mode 100644 index 00000000..e3d82b25 --- /dev/null +++ b/physics/filters.py @@ -0,0 +1,63 @@ +import django_filters + +from .models import Category, Question, CidUserAnswer, Exam + +#from generic.filters import UserAnswerFilter +from django.contrib.auth.models import User + + +def get_exams(request): + if request is None: + return Exam.objects.none() + + if not request.user.groups.filter(name="physics_checker").exists(): + queryset = Exam.objects.filter(author__id=request.user.id) + else: + queryset = Exam.objects.all() + + return queryset + +def get_authors(request): + if request is None: + return User.objects.none() + + if not request.user.groups.filter(name="physics_checker").exists(): + queryset = User.objects.filter(id=request.user.id) + else: + queryset = User.objects.all() + + return queryset + + +class QuestionFilter(django_filters.FilterSet): + exams = django_filters.ModelMultipleChoiceFilter(queryset=get_exams, null_label="No exam") + author = django_filters.ModelMultipleChoiceFilter(queryset=get_authors, null_label="No author") + category = django_filters.ModelChoiceFilter(queryset=Category.objects.all(),null_label="Uncategorised") + class Meta: + model = Question + fields = ("exams", "created_date") + + def __init__(self, data=None, queryset=None, prefix=None, strict=None, user=None, request=None): + if not request.user.groups.filter(name="physics_checker").exists(): + queryset = queryset.filter(open_access=True) | queryset.filter(author__id=request.user.id) + super(QuestionFilter, self).__init__(data=data, queryset=queryset, prefix=prefix, request=request) + pass + +class UserAnswerFilter(django_filters.FilterSet): + class Meta: + model= CidUserAnswer + fields = ( + "cid", + #"question", + "answer", + #"answer_compare", + "exam", + "created", + "updated", + ) + + def __init__(self, data=None, queryset=None, prefix=None, strict=None, user=None, request=None): + if not request.user.groups.filter(name="physics_checker").exists(): + queryset = queryset.filter(open_access=True) | queryset.filter(author__id=request.user.id) + super(UserAnswerFilter, self).__init__(data=data, queryset=queryset, prefix=prefix, request=request) + pass \ No newline at end of file diff --git a/physics/forms.py b/physics/forms.py new file mode 100755 index 00000000..f8baab48 --- /dev/null +++ b/physics/forms.py @@ -0,0 +1,34 @@ +from django.forms import ( + Form, + ModelForm, + ModelMultipleChoiceField, + ModelChoiceField, + ChoiceField, + CharField, +) +from django.forms import inlineformset_factory + +from .models import ( + Question, + CidUserAnswer, + Exam, +) + +from django.contrib.admin.widgets import FilteredSelectMultiple +from django.forms.widgets import RadioSelect, TextInput, Textarea + + +class CidUserAnswerForm(ModelForm): + class Meta: + model = CidUserAnswer + fields = ["answer"] + + def __init__(self, *args, **kwargs): + super(CidUserAnswerForm, self).__init__(*args, **kwargs) + self.fields['answer'].required = False + +# This should be made generic? +class ExamForm(ModelForm): + class Meta: + model = Exam + fields = ["name", "time_limit", "exam_mode", "active"] \ No newline at end of file diff --git a/physics/tables.py b/physics/tables.py new file mode 100644 index 00000000..08bb5aad --- /dev/null +++ b/physics/tables.py @@ -0,0 +1,84 @@ +from django.utils.safestring import mark_safe +import django_tables2 as tables +from django_tables2.utils import A + +from .models import Question, CidUserAnswer + +from django.utils.html import format_html + +#from easy_thumbnails.files import get_thumbnailer + +#from easy_thumbnails.exceptions import InvalidImageFormatError + +#from generic.tables import UserAnswerTable + + +#class ImageColumn(tables.Column): +# def render(self, value): +# try: +# thumbnailer = get_thumbnailer(value) +# return format_html('', thumbnailer["exam-list"]) +# except InvalidImageFormatError: +# return format_html('Invalid image url', value) + + +class QuestionTable(tables.Table): + #edit = tables.LinkColumn( + # "physics:physics_question_update", text="Edit", args=[A("pk")], orderable=False + #) + view = tables.LinkColumn( + "physics:question_detail", text="View", args=[A("pk")], orderable=False + ) + #delete = tables.LinkColumn( + # "physics:question_delete", text="Delete", args=[A("pk")], orderable=False + #) + exams = tables.ManyToManyColumn(verbose_name="Exams") + + selection = tables.CheckBoxColumn(accessor="pk", orderable=False) + + stem = tables.Column() + answers = tables.TemplateColumn("
  1. {{ record.a_answer|safe}}
  2. {{ record.b_answer|safe}}
  3. {{ record.c_answer|safe}}
  4. {{ record.d_answer|safe}}
  5. {{ record.e_answer|safe}}
") + + class Meta: + model = Question + template_name = "django_tables2/bootstrap4.html" + fields = ( + #"stem", + #"a_answer", + #"b_answer", + #"c_answer", + #"d_answer", + #"e_answer", + "best_answer", + "created_date", + "category", + "author", + ) + sequence = ("view", "stem", "answers", "exams") + + def render_stem(self, value): + return mark_safe(value) + + +class UserAnswerTable(tables.Table): + select = tables.CheckBoxColumn(accessor=("pk")) + #delete = tables.LinkColumn( + # "physics:user_answer_delete", text="Delete", args=[A("pk")], orderable=False + #) + view = tables.LinkColumn('physics:user_answer_view', + text='View', + args=[A('pk')], + orderable=False) + class Meta: + model = CidUserAnswer + template_name = "django_tables2/bootstrap4.html" + fields = ( + "cid", + "question", + "answer", + #"answer_compare", + "exam", + "created", + "updated", + ) + diff --git a/physics/urls.py b/physics/urls.py index 55108c4a..93228f27 100644 --- a/physics/urls.py +++ b/physics/urls.py @@ -7,6 +7,7 @@ urlpatterns = [ # path('', views.question_list, name='question_list'), path("", views.PhysicsExamViews.index, name="index"), #path("question/", views.QuestionView.as_view(), name="question_view"), + path("question/", views.QuestionView.as_view(), name="question_view"), path("question//", views.question_detail, name="question_detail"), #path("question/create/", views.QuestionCreate.as_view(), name="anatomy_question_create"), #path("question//update", views.QuestionUpdate.as_view(), name="anatomy_question_update"), @@ -25,4 +26,18 @@ urlpatterns = [ path("exam/available", views.active_exams, name="active_exams"), #path("exam/json/", views.exam_json, name="exam_json"), #path("exam/json//recreate", views.exam_json_recreate, name="exam_json_recreate"), + path("user_answers/", views.UserAnswerTableView.as_view(), name="user_answer_table_view"), + path( + "user_answers/", views.UserAnswerView.as_view(), name="user_answer_view" + ), + path( + "user_answers//delete", + views.UserAnswerDelete.as_view(), + name="user_answer_delete", + ), + path( + "user_answers/delete", + views.GenericViews.user_answer_delete_multiple, + name="user_answer_delete_multiple", + ), ] diff --git a/physics/views.py b/physics/views.py index 8289e027..10e93723 100644 --- a/physics/views.py +++ b/physics/views.py @@ -20,6 +20,7 @@ from django.urls import reverse_lazy, reverse from django.http import Http404, JsonResponse from django.http import HttpResponseRedirect, HttpResponse +from reversion.views import RevisionMixin from .models import Question, Category, Exam, CidUserAnswer @@ -31,13 +32,15 @@ import json import statistics import plotly.express as px -from generic.views import ExamViews +from generic.views import ExamCloneMixin, ExamViews, GenericViewBase from rest_framework import viewsets, permissions from rest_framework.pagination import PageNumberPagination from django.core.exceptions import PermissionDenied +from .forms import ExamForm + class AuthorOrCheckerRequiredMixin(object): def get_object(self, *args, **kwargs): obj = super().get_object(*args, **kwargs) @@ -253,9 +256,9 @@ def loadJsonAnswer(answer): return False, JsonResponse({"success": False, "error": "invalid"}) # The model should catch invalid data but this should be less intensive - max_int = 10000000 - if answer["cid"] > max_int or answer["eid"] > max_int: - return False, JsonResponse({"success": False}) + #max_int = 10000000 + #if answer["cid"] > max_int or answer["eid"] > max_int: + # return False, JsonResponse({"success": False}) exam = get_object_or_404(Exam, pk=answer["eid"]) @@ -303,4 +306,20 @@ def loadJsonAnswer(answer): return True, None -PhysicsExamViews = ExamViews(Exam, Question, "physics", "physics", loadJsonAnswer) \ No newline at end of file +PhysicsExamViews = ExamViews(Exam, Question, "physics", "physics", loadJsonAnswer) + +GenericViews = GenericViewBase("physics", Question, CidUserAnswer, Exam) + +class ExamCreate(RevisionMixin, LoginRequiredMixin, CreateView): + 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 ExamClone(ExamCloneMixin, ExamCreate): + """Clone exam view""" \ No newline at end of file diff --git a/sbas/filters.py b/sbas/filters.py index 60f259bd..bdd8be19 100644 --- a/sbas/filters.py +++ b/sbas/filters.py @@ -10,7 +10,7 @@ def get_exams(request): if request is None: return Exam.objects.none() - if not request.user.groups.filter(name="anatomy_checker").exists(): + if not request.user.groups.filter(name="sba_checker").exists(): queryset = Exam.objects.filter(author__id=request.user.id) else: queryset = Exam.objects.all() diff --git a/sbas/urls.py b/sbas/urls.py index 5b246a04..36e29648 100644 --- a/sbas/urls.py +++ b/sbas/urls.py @@ -39,7 +39,7 @@ urlpatterns = [ ), path( "user_answers/delete", - views.user_answer_delete_multiple, + views.GenericViews.user_answer_delete_multiple, name="user_answer_delete_multiple", ), ] diff --git a/sbas/views.py b/sbas/views.py index d30bb30f..a04b2827 100644 --- a/sbas/views.py +++ b/sbas/views.py @@ -37,7 +37,7 @@ import json import statistics import plotly.express as px -from generic.views import ExamViews +from generic.views import ExamViews, GenericViewBase from rest_framework import viewsets, permissions from rest_framework.pagination import PageNumberPagination @@ -387,15 +387,17 @@ class UserAnswerDelete(SuperuserRequiredMixin, DeleteView): template_name = "user_answer_delete.html" success_url = reverse_lazy("sbas:user_answer_table_view") -@user_passes_test(lambda u: u.is_superuser) -def user_answer_delete_multiple(request): - if request.is_ajax(): - print(request.POST["answer_ids"]) - answer_ids = json.loads(request.POST["answer_ids"]) +#@user_passes_test(lambda u: u.is_superuser) +#def user_answer_delete_multiple(request): +# if request.is_ajax(): +# print(request.POST["answer_ids"]) +# answer_ids = json.loads(request.POST["answer_ids"]) +# +# # We could probably delete them all at once.... +# for id in answer_ids: +# CidUserAnswer.objects.get(pk=id).delete() +# return JsonResponse({"status": "success"}) +# return JsonResponse({"status": "error"}) - # We could probably delete them all at once.... - for id in answer_ids: - CidUserAnswer.objects.get(pk=id).delete() - return JsonResponse({"status": "success"}) - return JsonResponse({"status": "error"}) +GenericViews = GenericViewBase("sbas", Question, CidUserAnswer, Exam) \ No newline at end of file