This commit is contained in:
Ross
2021-12-11 23:22:35 +00:00
parent c85ce0962c
commit 2e7051c27f
+194 -110
View File
@@ -54,7 +54,8 @@ import os
import string
import random
#from rad.views import get_question_and_content_type
# from rad.views import get_question_and_content_type
def get_question_and_content_type(question_type):
if question_type == "rapid":
@@ -74,9 +75,10 @@ def get_question_and_content_type(question_type):
content_type = ContentType.objects.get(app_label="physics", model="question")
else:
raise PermissionError()
return question, content_type
# Create your views here.
@login_required
def create_examination(request):
@@ -91,6 +93,7 @@ def create_examination(request):
request, "rapids/create_simple.html", {"form": form, "name": "Examination"}
)
@csrf_exempt
def get_examination_id(request):
if request.is_ajax():
@@ -102,6 +105,7 @@ def get_examination_id(request):
return HttpResponse(json.dumps(data), content_type="application/json")
return HttpResponse("/")
# Generic view to dispatch to indivuals app views
@login_required
def generic_exam_json_edit(request):
@@ -109,28 +113,27 @@ def generic_exam_json_edit(request):
question_type = request.POST.get("type")
if question_type == "rapid":
Exam = RapidExam
Exam = RapidExam
Question = RapidQuestion
elif question_type == "long":
Exam = LongExam
Exam = LongExam
Question = LongQuestion
elif question_type == "anatomy":
Exam = AnatomyExam
Exam = AnatomyExam
Question = AnatomyQuestion
elif question_type == "physics":
Exam = PhysicsExam
Exam = PhysicsExam
Question = PhysicsQuestion
elif question_type == "sbas":
Exam = SbasExam
Exam = SbasExam
Question = SbasQuestion
else:
data = {"status": "error"}
return JsonResponse(data, status=400)
exam = get_object_or_404(Exam, pk=request.POST.get("exam_id"))
#if "exam_toggle_active" in request.POST:
# if "exam_toggle_active" in request.POST:
# active = json.loads(request.POST.get("active"))
# data = {"status": "success"}
@@ -138,7 +141,7 @@ def generic_exam_json_edit(request):
if "add_exam_questions" in request.POST:
question_ids = json.loads(request.POST.get("add_exam_questions"))
#question_objects = Question.objects.filter(pk__in=question_ids)
# question_objects = Question.objects.filter(pk__in=question_ids)
add_count = 0
for i in question_ids:
@@ -146,15 +149,17 @@ def generic_exam_json_edit(request):
if not exam.exam_questions.filter(pk=i).exists():
add_count += 1
exam.exam_questions.add(i)
exam.save()
data = {"status": "success", "questions added" : add_count}
data = {"status": "success", "questions added": add_count}
return JsonResponse(data, status=200)
if "set_exam_order" in request.POST:
new_order = json.loads(request.POST.get("set_exam_order"))
preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(new_order)])
preserved = Case(
*[When(pk=pk, then=pos) for pos, pk in enumerate(new_order)]
)
objects = Question.objects.filter(pk__in=new_order).order_by(preserved)
if len(objects) != exam.exam_questions.count():
@@ -167,13 +172,22 @@ def generic_exam_json_edit(request):
return JsonResponse(data, status=200)
# Only checkers can do the following
if question_type == "rapid" and not request.user.groups.filter(name='rapid_checker').exists():
if (
question_type == "rapid"
and not request.user.groups.filter(name="rapid_checker").exists()
):
data = {"status": "error, permission denied"}
return JsonResponse(data, status=400)
if question_type == "anatomy" and not request.user.groups.filter(name='anatomy_checker').exists():
if (
question_type == "anatomy"
and not request.user.groups.filter(name="anatomy_checker").exists()
):
data = {"status": "error, permission denied"}
return JsonResponse(data, status=400)
if question_type == "long" and not request.user.groups.filter(name='long_checker').exists():
if (
question_type == "long"
and not request.user.groups.filter(name="long_checker").exists()
):
data = {"status": "error, permission denied"}
return JsonResponse(data, status=400)
@@ -192,8 +206,6 @@ def generic_exam_json_edit(request):
data = {"status": "success", "questions": questions_changed, "state": state}
return JsonResponse(data, status=200)
data = {"status": "error, unknown request"}
return JsonResponse(data, status=400)
else:
@@ -201,7 +213,6 @@ def generic_exam_json_edit(request):
return JsonResponse(data, status=400)
class ExamViews(View, LoginRequiredMixin):
def __init__(self, exam, question, app, question_type, loadJsonAnswer):
self.Exam = exam
@@ -211,8 +222,8 @@ class ExamViews(View, LoginRequiredMixin):
self.loadJsonAnswer = loadJsonAnswer
# THis may be better than check_user_access below
#group_map = {"rapids" : "rapid_checker", "anatomy" : "anatomy_checker", "longs":"long_checker"}
#exam_group = group_map[self.app_name]
# group_map = {"rapids" : "rapid_checker", "anatomy" : "anatomy_checker", "longs":"long_checker"}
# exam_group = group_map[self.app_name]
def check_user_access(self, user, exam_id=None):
"""Check if a user should be able to access a view
@@ -222,21 +233,38 @@ class ExamViews(View, LoginRequiredMixin):
Returns:
[boolean]: True if the user has access
"""
"""
if exam_id is not None:
exam = get_object_or_404(self.Exam, pk=exam_id)
if exam.open_access or user in exam.get_author_objects():
return True
if self.app_name == "rapids" and not user.groups.filter(name='rapid_checker').exists():
if (
self.app_name == "rapids"
and not user.groups.filter(name="rapid_checker").exists()
):
return False
if self.app_name == "anatomy" and not user.groups.filter(name='anatomy_checker').exists():
if (
self.app_name == "anatomy"
and not user.groups.filter(name="anatomy_checker").exists()
):
return False
if self.app_name == "longs" and not user.groups.filter(name__in=('long_checker', 'long_marker')).exists():
if (
self.app_name == "longs"
and not user.groups.filter(
name__in=("long_checker", "long_marker")
).exists()
):
return False
if self.app_name == "physics" and not user.groups.filter(name='physics_checker').exists():
if (
self.app_name == "physics"
and not user.groups.filter(name="physics_checker").exists()
):
return False
if self.app_name == "sbas" and not user.groups.filter(name='sba_checker').exists():
if (
self.app_name == "sbas"
and not user.groups.filter(name="sba_checker").exists()
):
return False
return True
@@ -248,27 +276,40 @@ class ExamViews(View, LoginRequiredMixin):
Returns:
[boolean]: True if the user has access
"""
"""
# If a user is an exam author they should have acccess
if exam_id is not None:
exam = get_object_or_404(self.Exam, pk=exam_id)
if exam.open_access or user in exam.get_author_objects():
return True
if self.app_name == "rapids" and not user.groups.filter(name='rapid_checker').exists():
if (
self.app_name == "rapids"
and not user.groups.filter(name="rapid_checker").exists()
):
return False
if self.app_name == "anatomy" and not user.groups.filter(name='anatomy_checker').exists():
if (
self.app_name == "anatomy"
and not user.groups.filter(name="anatomy_checker").exists()
):
return False
if self.app_name == "longs" and not user.groups.filter(name__in=('long_checker')).exists():
if (
self.app_name == "longs"
and not user.groups.filter(name__in=("long_checker")).exists()
):
return False
if self.app_name == "physics" and not user.groups.filter(name='physics_checker').exists():
if (
self.app_name == "physics"
and not user.groups.filter(name="physics_checker").exists()
):
return False
if self.app_name == "sbas" and not user.groups.filter(name='sba_checker').exists():
if (
self.app_name == "sbas"
and not user.groups.filter(name="sba_checker").exists()
):
return False
return True
@method_decorator(login_required)
def exam_toggle_results_published(self, request, pk):
if request.is_ajax() and request.method == "POST":
@@ -285,7 +326,6 @@ class ExamViews(View, LoginRequiredMixin):
data = {"status": "error"}
return JsonResponse(data, status=400)
@method_decorator(login_required)
def exam_toggle_active(self, request, pk):
if request.is_ajax() and request.method == "POST":
@@ -329,26 +369,31 @@ class ExamViews(View, LoginRequiredMixin):
if all:
exams = self.Exam.objects.all().order_by("name")
else:
exams = self.Exam.objects.filter(exam_mode=True, archive=False).order_by("name")
exams = self.Exam.objects.filter(exam_mode=True, archive=False).order_by(
"name"
)
if not self.check_user_access(request.user):
raise PermissionDenied
return render(request, "{}/exam_list.html".format(self.app_name), {"exams": exams})
return render(
request, "{}/exam_list.html".format(self.app_name), {"exams": exams}
)
@method_decorator(login_required)
def exam_overview(self, request, pk):
#print(Exam.objects.all())
#exams = Exam.objects.all()
#print("test", Exam.objects.all().get(id=pk))
# print(Exam.objects.all())
# exams = Exam.objects.all()
# print("test", Exam.objects.all().get(id=pk))
exam = get_object_or_404(self.Exam, pk=pk)
if request.user not in exam.author.all():
if not self.check_user_access(request.user, pk):
raise PermissionDenied
can_edit = self.check_user_edit_access(request.user, pk) | request.user.is_superuser
can_edit = (
self.check_user_edit_access(request.user, pk) | request.user.is_superuser
)
notes = []
if can_edit:
@@ -361,10 +406,16 @@ class ExamViews(View, LoginRequiredMixin):
return render(
request,
"{}/exam_overview.html".format(self.app_name),
{"exam": exam, "questions": questions, "question_number": question_number, "can_edit": can_edit, "notes": notes},
{
"exam": exam,
"questions": questions,
"question_number": question_number,
"can_edit": can_edit,
"notes": notes,
},
)
#@method_decorator(login_required)
# @method_decorator(login_required)
def exam_notes(self, request, pk):
exam = get_object_or_404(self.Exam, pk=pk)
@@ -376,11 +427,12 @@ class ExamViews(View, LoginRequiredMixin):
question_pks = exam.exam_questions.values_list("pk", flat=True)
# Get active notes for type
notes = QuestionNote.objects.filter(content_type=content_type, object_id__in=question_pks, complete=False)
# Get active notes for type
notes = QuestionNote.objects.filter(
content_type=content_type, object_id__in=question_pks, complete=False
)
return notes
@method_decorator(login_required)
def exam_json_edit(self, request, pk):
@@ -404,11 +456,15 @@ class ExamViews(View, LoginRequiredMixin):
if "set_exam_order" in request.POST:
new_order = json.loads(request.POST.get("set_exam_order"))
preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(new_order)])
objects = self.Question.objects.filter(pk__in=new_order).order_by(preserved)
preserved = Case(
*[When(pk=pk, then=pos) for pos, pk in enumerate(new_order)]
)
objects = self.Question.objects.filter(pk__in=new_order).order_by(
preserved
)
# We now allow deleting exam questions
#if len(objects) != exam.exam_questions.count():
# if len(objects) != exam.exam_questions.count():
# data = {"status": "error, count does not match"}
# return JsonResponse(data, status=400)
@@ -417,7 +473,6 @@ class ExamViews(View, LoginRequiredMixin):
data = {"status": "success"}
return JsonResponse(data, status=200)
if not self.check_user_access(request.user, pk):
raise PermissionDenied
@@ -433,7 +488,11 @@ class ExamViews(View, LoginRequiredMixin):
q.save()
questions_changed.append(q.pk)
data = {"status": "success", "questions": questions_changed, "state": state}
data = {
"status": "success",
"questions": questions_changed,
"state": state,
}
return JsonResponse(data, status=200)
data = {"status": "error, unknown request"}
@@ -453,40 +512,63 @@ class ExamViews(View, LoginRequiredMixin):
if not self.check_user_access(request.user, pk):
raise PermissionDenied
questions = exam.exam_questions.all()
# Handle exams that require double marking
try:
if exam.double_mark:
question_unmarked_map = []
for q in questions:
question_unmarked_map.append((q, int(q.get_unmarked_user_answer_count(exam_pk=exam.pk)), int(q.get_unmarked_user_answer_count(exam_pk=exam.pk, marker=request.user)) ))
question_unmarked_map.append(
(
q,
int(q.get_unmarked_user_answer_count(exam_pk=exam.pk)),
int(
q.get_unmarked_user_answer_count(
exam_pk=exam.pk, marker=request.user
)
),
)
)
return render(
request, "{}/mark_overview.html".format(self.app_name), {"exam": exam, "question_unmarked_map": question_unmarked_map}
request,
"{}/mark_overview.html".format(self.app_name),
{"exam": exam, "question_unmarked_map": question_unmarked_map},
)
except AttributeError:
pass
question_unmarked_map = []
for q in questions:
question_unmarked_map.append((q, int(q.get_unmarked_user_answer_count(exam_pk=exam.pk))))
question_unmarked_map.append(
(q, int(q.get_unmarked_user_answer_count(exam_pk=exam.pk)))
)
return render(
request, "{}/mark_overview.html".format(self.app_name), {"exam": exam, "question_unmarked_map": question_unmarked_map}
request,
"{}/mark_overview.html".format(self.app_name),
{"exam": exam, "question_unmarked_map": question_unmarked_map},
)
@method_decorator(login_required)
def index(self, request):
exams = self.Exam.objects.filter(author__id=request.user.id)
if self.app_name == "rapids" and request.user.groups.filter(name='rapid_checker').exists():
if (
self.app_name == "rapids"
and request.user.groups.filter(name="rapid_checker").exists()
):
exams = self.Exam.objects.all()
if self.app_name == "anatomy" and request.user.groups.filter(name='anatomy_checker').exists():
if (
self.app_name == "anatomy"
and request.user.groups.filter(name="anatomy_checker").exists()
):
exams = self.Exam.objects.all()
if self.app_name == "longs" and request.user.groups.filter(name='long_checker').exists():
if (
self.app_name == "longs"
and request.user.groups.filter(name="long_checker").exists()
):
exams = self.Exam.objects.all()
return render(request, "{}/index.html".format(self.app_name), {"exams": exams})
@@ -499,7 +581,6 @@ class ExamViews(View, LoginRequiredMixin):
if not self.check_user_access(request.user, pk):
raise PermissionDenied
question = exam.exam_questions.all()[sk]
exam_length = len(exam.exam_questions.all())
@@ -544,14 +625,14 @@ class ExamViews(View, LoginRequiredMixin):
url = url + "/unbased"
obj = {
"name": exam.get_exam_name(),
"url": url,
"type": self.question_type,
"eid": "{}/{}".format(self.question_type, exam.pk),
"json_creation_time": creation_time,
"exam_json_id": exam.exam_json_id,
"exam_active": exam.active
}
"name": exam.get_exam_name(),
"url": url,
"type": self.question_type,
"eid": "{}/{}".format(self.question_type, exam.pk),
"json_creation_time": creation_time,
"exam_json_id": exam.exam_json_id,
"exam_active": exam.active,
}
if self.question_type == "long":
h = {}
@@ -561,8 +642,8 @@ class ExamViews(View, LoginRequiredMixin):
question.get_question_json()
h[question.pk] = question.question_json_id
obj["multi_question_json"] = h
active_exams["exams"].append( obj )
active_exams["exams"].append(obj)
if json == False:
return active_exams["exams"]
@@ -573,7 +654,7 @@ class ExamViews(View, LoginRequiredMixin):
if request.is_ajax and request.method == "POST":
n = 0
for answer in json.loads(request.POST.get("answers")):
try:
ret_status, ret = self.loadJsonAnswer(answer)
@@ -581,8 +662,7 @@ class ExamViews(View, LoginRequiredMixin):
# We catch the error here so users get an error (rather than 500)
return JsonResponse({"success": False, "error": "Invalid data"})
#if ret is not True:
# if ret is not True:
# return ret
if ret_status:
@@ -606,7 +686,7 @@ class ExamViews(View, LoginRequiredMixin):
raise Http404("No available exam")
exam_json = exam.get_exam_json(based=False)
#exam_json["exam_active"] = False
# exam_json["exam_active"] = False
return JsonResponse(exam_json)
def exam_json(self, request, pk):
@@ -616,19 +696,18 @@ class ExamViews(View, LoginRequiredMixin):
if not exam.active and not self.check_user_access(request.user, pk):
raise Http404("No available exam")
# exam_json_cache = cache.get("{}_exam_json_{}".format(self.app_name, pk))
#exam_json_cache = cache.get("{}_exam_json_{}".format(self.app_name, pk))
path= "{0}{1}/exam/{2}.json".format(settings.MEDIA_ROOT, self.app_name, pk)
url= "{0}{1}/exam/{2}.json".format(settings.MEDIA_URL, self.app_name, pk)
path = "{0}{1}/exam/{2}.json".format(settings.MEDIA_ROOT, self.app_name, pk)
url = "{0}{1}/exam/{2}.json".format(settings.MEDIA_URL, self.app_name, pk)
if os.path.isfile(path) and not exam.recreate_json:
#exam_json_cache["cached"] = True
# exam_json_cache["cached"] = True
# Make sure we pass on an argument if we are forcing a reload
if request.GET.get('_'):
query_string = urllib.parse.urlencode({'_': request.GET.get('_')})
url = '{}?{}'.format(url, query_string)
return redirect(url)
if request.GET.get("_"):
query_string = urllib.parse.urlencode({"_": request.GET.get("_")})
url = "{}?{}".format(url, query_string)
return redirect(url)
return redirect(url)
return JsonResponse(exam_json_cache)
@@ -649,11 +728,11 @@ class ExamViews(View, LoginRequiredMixin):
# We also try to clear the cache of any associated questions (longs)
# see exam_question_json
#n = exam.exam_questions.count()
#question_keys = ["{}_exam_json_{}_{}".format(self.app_name, pk, i) for i in range(1, n)]
#cache.delete_many(question_keys)
# n = exam.exam_questions.count()
# question_keys = ["{}_exam_json_{}_{}".format(self.app_name, pk, i) for i in range(1, n)]
# cache.delete_many(question_keys)
#cache.set("{}_exam_json_{}".format(self.app_name, pk), exam_json, 3600)
# cache.set("{}_exam_json_{}".format(self.app_name, pk), exam_json, 3600)
return JsonResponse(exam_json)
@@ -664,12 +743,11 @@ class ExamViews(View, LoginRequiredMixin):
if not exam.active and not self.check_user_access(request.user, pk):
raise Http404("No available exam")
if request.GET.get('_'):
if request.GET.get("_"):
base_url = redirect("{}:question_json".format(self.app_name), pk=sk)
query_string = urllib.parse.urlencode({'_': request.GET.get('_')})
url = '{}?{}'.format(base_url, query_string)
return redirect(url)
query_string = urllib.parse.urlencode({"_": request.GET.get("_")})
url = "{}?{}".format(base_url, query_string)
return redirect(url)
return redirect("{}:question_json".format(self.app_name), pk=sk)
@@ -695,7 +773,7 @@ class ExamViews(View, LoginRequiredMixin):
return JsonResponse(question_json)
class GenericViewBase:
def __init__(self, app_name, QuestionObject, CidUserAnswerObject, ExamObject):
self.question_object = QuestionObject
@@ -704,11 +782,11 @@ class GenericViewBase:
self.app_name = app_name
g = {
"rapids" : "rapid_checker",
"anatomy" : "anatomy_checker",
"longs" : "longs_checker",
"sbas" : "sbas_checker",
"physics" : "physics_checker",
"rapids": "rapid_checker",
"anatomy": "anatomy_checker",
"longs": "longs_checker",
"sbas": "sbas_checker",
"physics": "physics_checker",
}
self.checker_group = g[app_name]
@@ -793,18 +871,21 @@ class GenericViewBase:
questions = self.question_object.objects.filter(author=pk)
return render(
request, f"{self.app_name}/category_detail.html", {"category": author, "questions": questions}
request,
f"{self.app_name}/category_detail.html",
{"category": author, "questions": questions},
)
@method_decorator(login_required)
def author_list(self, request):
authors = User.objects.all()
return render(request, f"{self.app_name}/author_list.html", {"authors": authors})
return render(
request, f"{self.app_name}/author_list.html", {"authors": authors}
)
class ExamCloneMixin():
class ExamCloneMixin:
def get_initial(self):
old_object = get_object_or_404(self.model, pk=self.kwargs["exam_id"])
initial_data = model_to_dict(old_object, exclude=["id"])
@@ -849,19 +930,22 @@ class CidUserView(LoginRequiredMixin, SingleTableMixin, FilterView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
#user = self.request.user
# user = self.request.user
physics_exams = [(i.name, i.pk) for i in PhysicsExam.objects.filter(archived=False)]
physics_exams = [
(i.name, i.pk) for i in PhysicsExam.objects.filter(archive=False)
]
context["physics_exams"] = physics_exams
return context
@login_required
def create_cid_users(request):
if request.is_ajax() and request.method == "POST":
number = int(request.POST.get("number"))
new_cid = max(CidUser.objects.all().order_by("-cid")[0].cid+1, 50000)
new_cid = max(CidUser.objects.all().order_by("-cid")[0].cid + 1, 50000)
for n in range(number):
passcode = "".join(random.choices(string.ascii_uppercase, k=4))
@@ -870,4 +954,4 @@ def create_cid_users(request):
new_cid = new_cid + 1
return JsonResponse({"status": "success"})
return JsonResponse({"status": "fail"})
return JsonResponse({"status": "fail"})