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.PhysicsExamViews.index, name="index"),
#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/create/", views.QuestionCreate.as_view(), name="anatomy_question_create"),
#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/json/<int:pk>", views.exam_json, name="exam_json"),
#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 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)
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:
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()
+1 -1
View File
@@ -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",
),
]
+13 -11
View File
@@ -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)