This commit is contained in:
Ross
2021-09-30 20:24:50 +01:00
parent 5e6a48480f
commit fd99f51d97
8 changed files with 235 additions and 18 deletions
+63
View File
@@ -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
+34
View File
@@ -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"]
+84
View File
@@ -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('<img src="/media/{}" />', thumbnailer["exam-list"])
# except InvalidImageFormatError:
# return format_html('<span title="{}">Invalid image url<span>', 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("<ol type='A'><li>{{ record.a_answer|safe}}</li><li>{{ record.b_answer|safe}}</li><li>{{ record.c_answer|safe}}</li><li>{{ record.d_answer|safe}}</li><li>{{ record.e_answer|safe}}</li></ol>")
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",
)
+15
View File
@@ -7,6 +7,7 @@ urlpatterns = [
# path('', views.question_list, name='question_list'), # path('', views.question_list, name='question_list'),
path("", views.PhysicsExamViews.index, name="index"), 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.QuestionView.as_view(), name="question_view"),
path("question/<int:pk>/", views.question_detail, name="question_detail"), path("question/<int:pk>/", views.question_detail, name="question_detail"),
#path("question/create/", views.QuestionCreate.as_view(), name="anatomy_question_create"), #path("question/create/", views.QuestionCreate.as_view(), name="anatomy_question_create"),
#path("question/<int:pk>/update", views.QuestionUpdate.as_view(), name="anatomy_question_update"), #path("question/<int:pk>/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/available", views.active_exams, name="active_exams"),
#path("exam/json/<int:pk>", views.exam_json, name="exam_json"), #path("exam/json/<int:pk>", views.exam_json, name="exam_json"),
#path("exam/json/<int:pk>/recreate", views.exam_json_recreate, name="exam_json_recreate"), #path("exam/json/<int:pk>/recreate", views.exam_json_recreate, name="exam_json_recreate"),
path("user_answers/", views.UserAnswerTableView.as_view(), name="user_answer_table_view"),
path(
"user_answers/<int:pk>", views.UserAnswerView.as_view(), name="user_answer_view"
),
path(
"user_answers/<int:pk>/delete",
views.UserAnswerDelete.as_view(),
name="user_answer_delete",
),
path(
"user_answers/delete",
views.GenericViews.user_answer_delete_multiple,
name="user_answer_delete_multiple",
),
] ]
+24 -5
View File
@@ -20,6 +20,7 @@ from django.urls import reverse_lazy, reverse
from django.http import Http404, JsonResponse from django.http import Http404, JsonResponse
from django.http import HttpResponseRedirect, HttpResponse from django.http import HttpResponseRedirect, HttpResponse
from reversion.views import RevisionMixin
from .models import Question, Category, Exam, CidUserAnswer from .models import Question, Category, Exam, CidUserAnswer
@@ -31,13 +32,15 @@ import json
import statistics import statistics
import plotly.express as px 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 import viewsets, permissions
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from .forms import ExamForm
class AuthorOrCheckerRequiredMixin(object): class AuthorOrCheckerRequiredMixin(object):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs) obj = super().get_object(*args, **kwargs)
@@ -253,9 +256,9 @@ def loadJsonAnswer(answer):
return False, JsonResponse({"success": False, "error": "invalid"}) return False, JsonResponse({"success": False, "error": "invalid"})
# The model should catch invalid data but this should be less intensive # The model should catch invalid data but this should be less intensive
max_int = 10000000 #max_int = 10000000
if answer["cid"] > max_int or answer["eid"] > max_int: #if answer["cid"] > max_int or answer["eid"] > max_int:
return False, JsonResponse({"success": False}) # return False, JsonResponse({"success": False})
exam = get_object_or_404(Exam, pk=answer["eid"]) exam = get_object_or_404(Exam, pk=answer["eid"])
@@ -303,4 +306,20 @@ def loadJsonAnswer(answer):
return True, None return True, None
PhysicsExamViews = ExamViews(Exam, Question, "physics", "physics", loadJsonAnswer) 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"""
+1 -1
View File
@@ -10,7 +10,7 @@ def get_exams(request):
if request is None: if request is None:
return Exam.objects.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) queryset = Exam.objects.filter(author__id=request.user.id)
else: else:
queryset = Exam.objects.all() queryset = Exam.objects.all()
+1 -1
View File
@@ -39,7 +39,7 @@ urlpatterns = [
), ),
path( path(
"user_answers/delete", "user_answers/delete",
views.user_answer_delete_multiple, views.GenericViews.user_answer_delete_multiple,
name="user_answer_delete_multiple", name="user_answer_delete_multiple",
), ),
] ]
+13 -11
View File
@@ -37,7 +37,7 @@ import json
import statistics import statistics
import plotly.express as px 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 import viewsets, permissions
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
@@ -387,15 +387,17 @@ class UserAnswerDelete(SuperuserRequiredMixin, DeleteView):
template_name = "user_answer_delete.html" template_name = "user_answer_delete.html"
success_url = reverse_lazy("sbas:user_answer_table_view") success_url = reverse_lazy("sbas:user_answer_table_view")
@user_passes_test(lambda u: u.is_superuser) #@user_passes_test(lambda u: u.is_superuser)
def user_answer_delete_multiple(request): #def user_answer_delete_multiple(request):
if request.is_ajax(): # if request.is_ajax():
print(request.POST["answer_ids"]) # print(request.POST["answer_ids"])
answer_ids = json.loads(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)