This commit is contained in:
Ross
2022-05-24 12:30:11 +01:00
parent bbcbc02e0d
commit 41a09ea30d
29 changed files with 506 additions and 247 deletions
+176 -97
View File
@@ -8,6 +8,7 @@ from django.forms.models import model_to_dict
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required, user_passes_test
from django.urls import reverse_lazy
from django.urls.base import reverse
from django.utils import timezone
from django.utils.html import format_html
@@ -29,8 +30,9 @@ import json
import urllib
from django.views import View
from django.views.generic.edit import CreateView, UpdateView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django_filters.views import FilterView
from django_filters import FilterSet
from django_tables2.views import SingleTableMixin
from reversion.views import RevisionMixin
from atlas.models import CaseCollection
@@ -71,6 +73,7 @@ import plotly.express as px
# from rad.views import get_question_and_content_type
from django.db.models import Prefetch
class AuthorRequiredMixin(object):
def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs)
@@ -78,6 +81,7 @@ class AuthorRequiredMixin(object):
raise PermissionDenied() # or Http404
return obj
class CidManagerRequiredMixin(UserPassesTestMixin):
def test_func(self):
return self.request.user.groups.filter(name="cid_user_manager").exists()
@@ -289,7 +293,47 @@ class ExamViews(View, LoginRequiredMixin):
# 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):
class ExamFilter(FilterSet):
class Meta:
model = exam
fields = {
"name": ["contains"],
"exam_mode": ["exact"],
"active": ["exact"],
"archive": ["exact"],
"open_access": ["exact"],
#'release_date': ['isnull'],
}
# filter_overrides = {
# models.CharField: {
# 'filter_class': django_filters.CharFilter,
# 'extra': lambda f: {
# 'lookup_expr': 'icontains',
# },
# },
# models.BooleanField: {
# 'filter_class': django_filters.BooleanFilter,
# 'extra': lambda f: {
# 'widget': forms.CheckboxInput,
# },
# },
# }
def __init__(self, data=None, *args, **kwargs):
# if filterset is bound, use initial values as defaults
if data is not None:
# get a mutable copy of the QueryDict
data = data.copy()
#for name, f in self.base_filters.items():
# filter param is either missing or empty, use initial as default
if not data.get("archive"):
data["archive"] = False
super().__init__(data, *args, **kwargs)
self.ExamFilter = ExamFilter
def check_user_access(self, user: User, exam_id: int = None):
"""Check if a user should be able to access a view
Args:
@@ -547,9 +591,7 @@ class ExamViews(View, LoginRequiredMixin):
)
elif self.app_name == "rapids":
questions = (
exam.exam_questions.select_related(
)
exam.exam_questions.select_related()
.all()
.prefetch_related(
Prefetch(
@@ -559,7 +601,11 @@ class ExamViews(View, LoginRequiredMixin):
),
to_attr="prefetched_primary_answer"
# queryset=self.Answer.objects.filter(),
), "images", "abnormality", "region", "examination"
),
"images",
"abnormality",
"region",
"examination",
)
)
# questions = (
@@ -620,7 +666,7 @@ class ExamViews(View, LoginRequiredMixin):
def exam_cids(self, request, exam_id):
exam = get_object_or_404(self.Exam, pk=exam_id)
#if not request.user.groups.filter(name="cid_user_manager").exists():
# if not request.user.groups.filter(name="cid_user_manager").exists():
# raise PermissionDenied
if request.user not in exam.author.all():
@@ -635,29 +681,24 @@ class ExamViews(View, LoginRequiredMixin):
user_user_count = user_users.count()
context = {
"exam": exam,
"cid_users": cid_users,
"cid_user_count": cid_user_count,
"user_users": user_users,
"user_user_count": user_user_count,
"app_name": self.app_name,
}
"exam": exam,
"cid_users": cid_users,
"cid_user_count": cid_user_count,
"user_users": user_users,
"user_user_count": user_user_count,
"app_name": self.app_name,
}
if self.app_name == "atlas":
context["collection"] = exam
return render(
request,
"exam_cids.html",
context
)
return render(request, "exam_cids.html", context)
def exam_users_edit(self, request, exam_id):
exam = get_object_or_404(self.Exam, pk=exam_id)
if not request.user.groups.filter(name="cid_user_manager").exists():
#raise PermissionDenied
# raise PermissionDenied
if request.user not in exam.author.all():
if not self.check_user_access(request.user, exam_id):
raise PermissionDenied
@@ -666,33 +707,32 @@ class ExamViews(View, LoginRequiredMixin):
exam_groups = exam.user_user_groups.all()
available_user_users = list(User.objects.filter(user_groups__in=exam_groups).difference(current_user_users))
available_user_users = list(
User.objects.filter(user_groups__in=exam_groups).difference(
current_user_users
)
)
print(available_user_users)
#available_user_users = User.objects.all()
# available_user_users = User.objects.all()
context = {
"exam": exam,
"groups": exam_groups,
"current_user_users": current_user_users,
"available_user_users": available_user_users,
"app_name": self.app_name,
}
"exam": exam,
"groups": exam_groups,
"current_user_users": current_user_users,
"available_user_users": available_user_users,
"app_name": self.app_name,
}
if self.app_name == "atlas":
context["collection"] = exam
return render(
request,
"exam_users_edit.html",
context
)
return render(request, "exam_users_edit.html", context)
def exam_cids_edit(self, request, exam_id):
exam = get_object_or_404(self.Exam, pk=exam_id)
if not request.user.groups.filter(name="cid_user_manager").exists():
#raise PermissionDenied
# raise PermissionDenied
if request.user not in exam.author.all():
if not self.check_user_access(request.user, exam_id):
raise PermissionDenied
@@ -701,24 +741,22 @@ class ExamViews(View, LoginRequiredMixin):
exam_groups = exam.cid_user_groups.all()
available_cid_users = CidUser.objects.filter(group__in=exam_groups).difference(current_cid_users)
available_cid_users = CidUser.objects.filter(group__in=exam_groups).difference(
current_cid_users
)
context = {
"exam": exam,
"groups": exam_groups,
"current_cid_users": current_cid_users,
"available_cid_users": available_cid_users,
"app_name": self.app_name,
}
"exam": exam,
"groups": exam_groups,
"current_cid_users": current_cid_users,
"available_cid_users": available_cid_users,
"app_name": self.app_name,
}
if self.app_name == "atlas":
context["collection"] = exam
return render(
request,
"exam_cids_edit.html",
context
)
return render(request, "exam_cids_edit.html", context)
# @method_decorator(login_required)
def exam_notes(self, request, pk):
@@ -751,7 +789,7 @@ class ExamViews(View, LoginRequiredMixin):
if "edit_cid_user" in request.POST:
user_id = request.POST.get("edit_cid_user")
add = request.POST.get("add") == "true"
cid_user = CidUser.objects.get(pk=user_id)
@@ -765,27 +803,25 @@ class ExamViews(View, LoginRequiredMixin):
else:
app_exam_map[self.app_name].remove(pk)
data = {"status": "success", "added": add}
return JsonResponse(data, status=200)
if "edit_user_user" in request.POST:
user_id = request.POST.get("edit_user_user")
add = request.POST.get("add") == "true"
user_user = User.objects.get(pk=user_id)
app_exam_map = {}
app_exam_map["rapids"] = user_user.user_rapid_exams
#app_exam_map["atlas"] = user_user.casecollection_exams
# app_exam_map["atlas"] = user_user.casecollection_exams
if add:
app_exam_map[self.app_name].add(pk)
else:
app_exam_map[self.app_name].remove(pk)
data = {"status": "success", "added": add}
return JsonResponse(data, status=200)
@@ -927,25 +963,30 @@ class ExamViews(View, LoginRequiredMixin):
@method_decorator(login_required)
def index(self, request):
exams = self.Exam.objects.filter(author__id=request.user.id)
# self.check_user_access(request.user, exam.pk)
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()
):
exams = self.Exam.objects.all()
if (
self.app_name == "longs"
and request.user.groups.filter(name="long_checker").exists()
(
self.app_name == "rapids"
and request.user.groups.filter(name="rapid_checker").exists()
)
or (
self.app_name == "anatomy"
and request.user.groups.filter(name="anatomy_checker").exists()
)
or (
self.app_name == "longs"
and request.user.groups.filter(name="long_checker").exists()
)
):
exams = self.Exam.objects.all()
else:
exams = self.Exam.objects.filter(author__id=request.user.id)
return render(request, "{}/index.html".format(self.app_name), {"exams": exams})
filter = self.ExamFilter(request.GET, queryset=exams)
return render(request, "generic/exam_index.html", {"filter": filter, "app_name": self.app_name})
@method_decorator(login_required)
def exam_question_detail(self, request, pk, sk):
@@ -996,7 +1037,9 @@ class ExamViews(View, LoginRequiredMixin):
for exam in exams:
if exam.active or self.check_user_access(request.user, exam.pk):
print(exam.name, cid, passcode)
if exam.exam_mode and not exam.check_cid_user(cid, passcode, user_id=request.user.pk):
if exam.exam_mode and not exam.check_cid_user(
cid, passcode, user_id=request.user.pk
):
print(exam.name, "fail")
continue
@@ -1061,7 +1104,10 @@ class ExamViews(View, LoginRequiredMixin):
else:
return JsonResponse(
{"success": False, "error": "cid or eid or answers not defined"}
{
"success": False,
"error": "cid or eid or answers not defined",
}
)
if not uid:
@@ -1070,7 +1116,10 @@ class ExamViews(View, LoginRequiredMixin):
not isinstance(eid, int) or (not isinstance(answer["ans"], str))
):
return JsonResponse(
{"success": False, "error": "cid or eid or answers not defined"}
{
"success": False,
"error": "cid or eid or answers not defined",
}
)
exam = get_object_or_404(self.Exam, pk=eid)
@@ -1100,9 +1149,15 @@ class ExamViews(View, LoginRequiredMixin):
if not existing_answers:
if uid:
ans = self.CidUserAnswer(answer=posted_answer, normal=normal, user=User.objects.get(id=uid))
ans = self.CidUserAnswer(
answer=posted_answer,
normal=normal,
user=User.objects.get(id=uid),
)
else:
ans = self.CidUserAnswer(answer=posted_answer, normal=normal, cid=cid)
ans = self.CidUserAnswer(
answer=posted_answer, normal=normal, cid=cid
)
ans.question_id = answer["qid"]
ans.exam_id = eid
@@ -1118,11 +1173,12 @@ class ExamViews(View, LoginRequiredMixin):
case "anatomy":
if not existing_answers:
if uid:
ans = self.CidUserAnswer(answer=posted_answer, uid=User.objects.get(id=uid))
ans = self.CidUserAnswer(
answer=posted_answer, uid=User.objects.get(id=uid)
)
else:
ans = self.CidUserAnswer(answer=posted_answer, cid=cid)
ans.question_id = answer["qid"]
ans.exam_id = eid
ans.score = self.Answer.MarkOptions.UNMARKED
@@ -1168,14 +1224,12 @@ class ExamViews(View, LoginRequiredMixin):
ans.full_clean()
ans.save()
# if ret is not True:
# return ret
n = n + 1
# print(UserAnswer.objects.filter(exam__id=q["eid"]))
# print(UserAnswer.objects.filter(exam__id=q["eid"]))
# print(request.urlencode())
exam_type, exam_id = request.POST.get("eid").split("/")
@@ -1185,10 +1239,14 @@ class ExamViews(View, LoginRequiredMixin):
)
if request.POST.get("cid").startswith("u-"):
cid_user_exam = exam.get_or_create_cid_user_exam(user_user=request.user, start_time=t)
cid_user_exam = exam.get_or_create_cid_user_exam(
user_user=request.user, start_time=t
)
else:
c = CidUser.objects.filter(cid=request.POST.get("cid")).first()
cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c, start_time=t)
cid_user_exam = exam.get_or_create_cid_user_exam(
cid_user=c, start_time=t
)
cid_user_exam.end_time = timezone.now()
cid_user_exam.save()
@@ -1303,10 +1361,10 @@ class ExamViews(View, LoginRequiredMixin):
if not exam.exam_mode:
raise Http404("Packet not in exam mode")
#user_answers_and_marks = defaultdict(list)
# user_answers_and_marks = defaultdict(list)
user_answers_marks = defaultdict(list)
user_answers = defaultdict(list)
#user_names = {}
# user_names = {}
# cid_passcodes = {}
by_question = defaultdict(dict)
@@ -1333,7 +1391,7 @@ class ExamViews(View, LoginRequiredMixin):
cids.add(cid)
s = cid_user_answer
#user_names[cid] = cid
# user_names[cid] = cid
q = cid_user_answer.question
@@ -1352,7 +1410,7 @@ class ExamViews(View, LoginRequiredMixin):
unmarked.add(index)
user_answers[cid].append(ans)
user_answers_marks[cid].append(answer_score)
#user_answers_and_marks[cid].append((ans, answer_score))
# user_answers_and_marks[cid].append((ans, answer_score))
if self.app_name in ("rapids", "anatomy", "sbas"):
by_question[q][cid] = (ans, answer_score)
@@ -1440,13 +1498,13 @@ class ExamViews(View, LoginRequiredMixin):
"unmarked": unmarked,
"questions": questions,
"by_question": by_question,
#"user_answers": dict(user_answers),
#"user_answers_marks": dict(user_answers_marks),
# "user_answers": dict(user_answers),
# "user_answers_marks": dict(user_answers_marks),
"user_scores": user_scores,
"user_scores_normalised": user_scores_normalised,
#"user_scores_list": user_scores_list,
#"user_names": user_names,
#"user_answers_and_marks": user_answers_and_marks,
# "user_scores_list": user_scores_list,
# "user_names": user_names,
# "user_answers_and_marks": user_answers_and_marks,
"max_score": max_score,
"mean": mean,
"median": median,
@@ -1621,7 +1679,10 @@ class CidUserExamView(CidManagerRequiredMixin, SingleTableMixin, FilterView):
sba_exams = [(i.name, i.pk) for i in SbasExam.objects.filter(**filters)]
longs_exams = [(i.name, i.pk) for i in LongExam.objects.filter(**filters)]
anatomy_exams = [(i.name, i.pk) for i in AnatomyExam.objects.filter(**filters)]
casecollection_exams = [(i.name, i.pk) for i in CaseCollection.objects.filter(archive=False, collection_type__gt = 0)]
casecollection_exams = [
(i.name, i.pk)
for i in CaseCollection.objects.filter(archive=False, collection_type__gt=0)
]
cid_user_groups = [
(i.name, i.pk) for i in CidUserGroup.objects.filter(archive=False)
@@ -1656,7 +1717,10 @@ class CidUserView(CidManagerRequiredMixin, SingleTableMixin, FilterView):
sba_exams = [(i.name, i.pk) for i in SbasExam.objects.filter(**filters)]
longs_exams = [(i.name, i.pk) for i in LongExam.objects.filter(**filters)]
anatomy_exams = [(i.name, i.pk) for i in AnatomyExam.objects.filter(**filters)]
casecollection_exams = [(i.name, i.pk) for i in CaseCollection.objects.filter(archive=False, collection_type__gt = 0)]
casecollection_exams = [
(i.name, i.pk)
for i in CaseCollection.objects.filter(archive=False, collection_type__gt=0)
]
context["physics_exams"] = physics_exams
context["rapid_exams"] = rapid_exams
@@ -1683,6 +1747,7 @@ def cid_group_view(request):
{"groups": groups},
)
@user_is_cid_user_manager
def cid_group_view_all(request):
groups = CidUserGroup.objects.filter()
@@ -1693,6 +1758,7 @@ def cid_group_view_all(request):
{"groups": groups, "view_all": True},
)
@user_is_cid_user_manager
def user_group_view(request):
groups = UserUserGroup.objects.filter(archive=False)
@@ -1703,6 +1769,7 @@ def user_group_view(request):
{"groups": groups},
)
@user_is_cid_user_manager
def user_group_view_all(request):
groups = UserUserGroup.objects.filter()
@@ -1776,12 +1843,11 @@ def group_email_results(request, pk, resend=False):
def candidate_email_details(request, cid, resend=False):
user = CidUser.objects.get(cid=cid)
email_sent, comment = user.email_details(
resend=False
)
email_sent, comment = user.email_details(resend=False)
return JsonResponse({"sent": email_sent, "comment": comment})
@user_is_cid_user_manager
def candidate_email_results(request, cid, resend=False):
user = CidUser.objects.get(cid=cid)
@@ -1965,6 +2031,12 @@ class CidUserUpdate(RevisionMixin, CidManagerRequiredMixin, UpdateView):
form_class = CidUserForm
class CidUserGroupDelete(RevisionMixin, CidManagerRequiredMixin, DeleteView):
model = CidUserGroup
template_name = "confirm_delete.html"
success_url = reverse_lazy("generic:cid_group_view")
class CidUserGroupCreate(RevisionMixin, CidManagerRequiredMixin, CreateView):
model = CidUserGroup
form_class = CidUserGroupForm
@@ -1974,6 +2046,7 @@ class CidUserGroupUpdate(RevisionMixin, CidManagerRequiredMixin, UpdateView):
model = CidUserGroup
form_class = CidUserGroupForm
class UserUserGroupCreate(RevisionMixin, CidManagerRequiredMixin, CreateView):
model = UserUserGroup
form_class = UserUserGroupForm
@@ -1981,4 +2054,10 @@ class UserUserGroupCreate(RevisionMixin, CidManagerRequiredMixin, CreateView):
class UserUserGroupUpdate(RevisionMixin, CidManagerRequiredMixin, UpdateView):
model = UserUserGroup
form_class = UserUserGroupForm
form_class = UserUserGroupForm
class UserUserGroupDelete(RevisionMixin, CidManagerRequiredMixin, DeleteView):
model = UserUserGroup
template_name = "confirm_delete.html"
success_url = reverse_lazy("generic:user_group_view")