|
|
|
@@ -48,7 +48,12 @@ from atlas.models import CaseCollection, CaseDetail
|
|
|
|
|
from generic.decorators import user_is_cid_user_manager
|
|
|
|
|
from generic.filters import CidUserFilter, ExaminationFilter, SupervisorFilter
|
|
|
|
|
|
|
|
|
|
from generic.tables import CidUserExamTable, CidUserTable, ExaminationTable, SupervisorTable
|
|
|
|
|
from generic.tables import (
|
|
|
|
|
CidUserExamTable,
|
|
|
|
|
CidUserTable,
|
|
|
|
|
ExaminationTable,
|
|
|
|
|
SupervisorTable,
|
|
|
|
|
)
|
|
|
|
|
from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin
|
|
|
|
|
|
|
|
|
|
import zipfile
|
|
|
|
@@ -79,6 +84,7 @@ from .models import (
|
|
|
|
|
ExamCollection,
|
|
|
|
|
ExamUserStatus,
|
|
|
|
|
Examination,
|
|
|
|
|
QuestionBase,
|
|
|
|
|
QuestionNote,
|
|
|
|
|
Supervisor,
|
|
|
|
|
UserGrades,
|
|
|
|
@@ -87,15 +93,31 @@ from .models import (
|
|
|
|
|
get_next_cid,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
from rapids.models import Rapid as RapidQuestion, ExamQuestionDetail as RapidsExamQuestionDetail
|
|
|
|
|
from rapids.models import (
|
|
|
|
|
Rapid as RapidQuestion,
|
|
|
|
|
ExamQuestionDetail as RapidsExamQuestionDetail,
|
|
|
|
|
)
|
|
|
|
|
from rapids.models import Exam as RapidsExam
|
|
|
|
|
from longs.models import Long as LongQuestion, LongSeries, ExamQuestionDetail as LongsExamQuestionDetail
|
|
|
|
|
from longs.models import (
|
|
|
|
|
Long as LongQuestion,
|
|
|
|
|
LongSeries,
|
|
|
|
|
ExamQuestionDetail as LongsExamQuestionDetail,
|
|
|
|
|
)
|
|
|
|
|
from longs.models import Exam as LongsExam
|
|
|
|
|
from anatomy.models import AnatomyQuestion as AnatomyQuestion, ExamQuestionDetail as AnatomyExamQuestionDetail
|
|
|
|
|
from anatomy.models import (
|
|
|
|
|
AnatomyQuestion as AnatomyQuestion,
|
|
|
|
|
ExamQuestionDetail as AnatomyExamQuestionDetail,
|
|
|
|
|
)
|
|
|
|
|
from anatomy.models import Exam as AnatomyExam
|
|
|
|
|
from sbas.models import Question as SbasQuestion, ExamQuestionDetail as SbasExamQuestionDetail
|
|
|
|
|
from sbas.models import (
|
|
|
|
|
Question as SbasQuestion,
|
|
|
|
|
ExamQuestionDetail as SbasExamQuestionDetail,
|
|
|
|
|
)
|
|
|
|
|
from sbas.models import Exam as SbasExam
|
|
|
|
|
from physics.models import Question as PhysicsQuestion, ExamQuestionDetail as PhysicsExamQuestionDetail
|
|
|
|
|
from physics.models import (
|
|
|
|
|
Question as PhysicsQuestion,
|
|
|
|
|
ExamQuestionDetail as PhysicsExamQuestionDetail,
|
|
|
|
|
)
|
|
|
|
|
from physics.models import Exam as PhysicsExam
|
|
|
|
|
|
|
|
|
|
from django.db.models import Case, When
|
|
|
|
@@ -112,13 +134,13 @@ import plotly.express as px
|
|
|
|
|
from django.db.models import Prefetch
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RedirectMixin():
|
|
|
|
|
class RedirectMixin:
|
|
|
|
|
def get_success_url(self) -> str:
|
|
|
|
|
if "redirect" in self.request.GET:
|
|
|
|
|
return self.request.GET["redirect"]
|
|
|
|
|
return super().get_success_url()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AuthorRequiredMixin(object):
|
|
|
|
|
def get_object(self, *args, **kwargs):
|
|
|
|
|
obj = super().get_object(*args, **kwargs)
|
|
|
|
@@ -141,6 +163,7 @@ class CidManagerRequiredMixin(UserPassesTestMixin):
|
|
|
|
|
# return obj
|
|
|
|
|
# raise PermissionDenied() # or Http404
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_exam_model_from_app_name(app_name: str) -> ExamBase:
|
|
|
|
|
EXAM_MAP = {
|
|
|
|
|
"physics": PhysicsExam,
|
|
|
|
@@ -471,7 +494,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
if (exam.open_access and exam.active) or user in exam.get_author_objects():
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
if marker and user in exam.markers.all():
|
|
|
|
|
if marker and user in exam.markers.all():
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
if exam.authors_only:
|
|
|
|
@@ -535,7 +558,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
if exam_id is not None:
|
|
|
|
|
exam = get_object_or_404(self.Exam, pk=exam_id)
|
|
|
|
|
# Remove open_access check for the momement
|
|
|
|
|
#if exam.open_access or user in exam.get_author_objects():
|
|
|
|
|
# if exam.open_access or user in exam.get_author_objects():
|
|
|
|
|
# return True
|
|
|
|
|
if user in exam.get_author_objects():
|
|
|
|
|
return True
|
|
|
|
@@ -687,13 +710,21 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
def exam_list(self, request, all=False):
|
|
|
|
|
if not self.check_user_access(request.user):
|
|
|
|
|
# raise PermissionDenied
|
|
|
|
|
exam_list = self.Exam.objects.filter(author__id=request.user.id, exam_mode=True).order_by(
|
|
|
|
|
"name"
|
|
|
|
|
)
|
|
|
|
|
exam_list = self.Exam.objects.filter(
|
|
|
|
|
author__id=request.user.id, exam_mode=True
|
|
|
|
|
).order_by("name")
|
|
|
|
|
|
|
|
|
|
exam_list = exam_list | self.Exam.objects.filter(markers__id=request.user.id, exam_mode=True).order_by("name")
|
|
|
|
|
exam_list = exam_list | self.Exam.objects.filter(
|
|
|
|
|
markers__id=request.user.id, exam_mode=True
|
|
|
|
|
).order_by("name")
|
|
|
|
|
else:
|
|
|
|
|
exam_list = self.Exam.objects.prefetch_related("valid_user_users", "valid_cid_users").filter(exam_mode=True).order_by("name")
|
|
|
|
|
exam_list = (
|
|
|
|
|
self.Exam.objects.prefetch_related(
|
|
|
|
|
"valid_user_users", "valid_cid_users"
|
|
|
|
|
)
|
|
|
|
|
.filter(exam_mode=True)
|
|
|
|
|
.order_by("name")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not all:
|
|
|
|
|
exam_list = exam_list.filter(archive=False).order_by("name")
|
|
|
|
@@ -743,7 +774,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
|
|
|
|
|
status = {}
|
|
|
|
|
for user in users:
|
|
|
|
|
#user = User.objects.get(pk=u)
|
|
|
|
|
# user = User.objects.get(pk=u)
|
|
|
|
|
user_exam = exam.get_or_create_cid_user_exam(user_user=user)
|
|
|
|
|
if user_exam.results_emailed_status:
|
|
|
|
|
status[user] = json.loads(user_exam.results_emailed_status)
|
|
|
|
@@ -796,7 +827,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
|
|
|
|
|
email_results = {}
|
|
|
|
|
for user in users:
|
|
|
|
|
#user = User.objects.get(pk=u)
|
|
|
|
|
# user = User.objects.get(pk=u)
|
|
|
|
|
user_exam = exam.get_or_create_cid_user_exam(user_user=user)
|
|
|
|
|
|
|
|
|
|
if unsent_only and user_exam.results_emailed_status:
|
|
|
|
@@ -904,7 +935,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
queryset=self.Answer.objects.filter(
|
|
|
|
|
proposed=False, status=self.Answer.MarkOptions.CORRECT
|
|
|
|
|
),
|
|
|
|
|
to_attr="prefetched_primary_answer"
|
|
|
|
|
to_attr="prefetched_primary_answer",
|
|
|
|
|
# queryset=self.Answer.objects.filter(),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
@@ -913,21 +944,20 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
elif self.app_name == "longs":
|
|
|
|
|
questions = (
|
|
|
|
|
exam.exam_questions.prefetch_related(
|
|
|
|
|
#Prefetch(
|
|
|
|
|
# Prefetch(
|
|
|
|
|
# "series",
|
|
|
|
|
# queryset=LongSeries.objects.select_related(
|
|
|
|
|
# "modality", "examination", "plane", "contrast"
|
|
|
|
|
# ).prefetch_related(Prefetch("images")),
|
|
|
|
|
#),
|
|
|
|
|
# ),
|
|
|
|
|
"author",
|
|
|
|
|
#"examquestiondetail",
|
|
|
|
|
#"longsseries",
|
|
|
|
|
# "examquestiondetail",
|
|
|
|
|
# "longsseries",
|
|
|
|
|
"series",
|
|
|
|
|
#"seriesdetail",
|
|
|
|
|
# "seriesdetail",
|
|
|
|
|
"series__images",
|
|
|
|
|
"series__plane",
|
|
|
|
|
"series__examination"
|
|
|
|
|
|
|
|
|
|
"series__examination",
|
|
|
|
|
# to_attr="prefetched_primary_answer"
|
|
|
|
|
# queryset=self.Answer.objects.filter(),
|
|
|
|
|
# "series",
|
|
|
|
@@ -948,7 +978,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
queryset=self.Answer.objects.filter(
|
|
|
|
|
proposed=False, status=self.Answer.MarkOptions.CORRECT
|
|
|
|
|
),
|
|
|
|
|
to_attr="prefetched_primary_answer"
|
|
|
|
|
to_attr="prefetched_primary_answer",
|
|
|
|
|
# queryset=self.Answer.objects.filter(),
|
|
|
|
|
),
|
|
|
|
|
"images",
|
|
|
|
@@ -966,25 +996,29 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
# )
|
|
|
|
|
elif self.app_name == "physics":
|
|
|
|
|
questions = (
|
|
|
|
|
exam.exam_questions.select_related("category").all()
|
|
|
|
|
exam.exam_questions.select_related("category")
|
|
|
|
|
.all()
|
|
|
|
|
.order_by("examquestiondetail__sort_order")
|
|
|
|
|
# .prefetch_related("images", "abnormality", "region", "examination")
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
questions = exam.exam_questions.select_related().all().order_by("examquestiondetail__sort_order")
|
|
|
|
|
questions = (
|
|
|
|
|
exam.exam_questions.select_related()
|
|
|
|
|
.all()
|
|
|
|
|
.order_by("examquestiondetail__sort_order")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
question_number = len(questions)
|
|
|
|
|
|
|
|
|
|
cid_candidates = exam.valid_cid_users.count()
|
|
|
|
|
users_candidates = exam.valid_user_users.count()
|
|
|
|
|
|
|
|
|
|
#question_orders = [q.examquestiondetail.sort_order for q in questions]
|
|
|
|
|
# question_orders = [q.examquestiondetail.sort_order for q in questions]
|
|
|
|
|
|
|
|
|
|
# Exam order can be missing when, this can be checked with
|
|
|
|
|
# exam.exam_questions.select_related().all().order_by("examquestiondetail__sort_order").values_list("examquestiondetail__sort_order")
|
|
|
|
|
# TODO decide if / how this should be flagged to a user (updating teh exam order will fix)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return render(
|
|
|
|
|
request,
|
|
|
|
|
"{}/exam_overview.html".format(self.app_name),
|
|
|
|
@@ -1049,10 +1083,12 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
if not request.user.is_superuser:
|
|
|
|
|
raise PermissionDenied
|
|
|
|
|
|
|
|
|
|
cid_users = exam.valid_cid_users.all()#.order_by("cid")#.prefetch_related("*")
|
|
|
|
|
cid_users = (
|
|
|
|
|
exam.valid_cid_users.all()
|
|
|
|
|
) # .order_by("cid")#.prefetch_related("*")
|
|
|
|
|
cid_user_count = len(cid_users)
|
|
|
|
|
|
|
|
|
|
user_users = exam.valid_user_users.all()#.order_by("username")
|
|
|
|
|
user_users = exam.valid_user_users.all() # .order_by("username")
|
|
|
|
|
user_user_count = len(user_users)
|
|
|
|
|
|
|
|
|
|
user_exam_data = exam.cid_users.all().prefetch_related("cid_user", "user_user")
|
|
|
|
@@ -1072,7 +1108,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
|
|
|
|
|
return render(request, "exam_cids.html", context)
|
|
|
|
|
|
|
|
|
|
#def exam_groups_edit(self, request, exam_id):
|
|
|
|
|
# def exam_groups_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():
|
|
|
|
@@ -1080,8 +1116,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
# if request.user not in exam.author.all():
|
|
|
|
|
# raise PermissionDenied
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
# context = {
|
|
|
|
|
# "exam": exam,
|
|
|
|
@@ -1208,7 +1243,9 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
if cid is not None:
|
|
|
|
|
statuses = exam.exam_user_status.filter(cid_user_exam__cid_user__cid=cid)
|
|
|
|
|
elif user_id is not None:
|
|
|
|
|
statuses = exam.exam_user_status.filter(cid_user_exam__user_user__id=user_id)
|
|
|
|
|
statuses = exam.exam_user_status.filter(
|
|
|
|
|
cid_user_exam__user_user__id=user_id
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
statuses = exam.exam_user_status.all()
|
|
|
|
|
|
|
|
|
@@ -1332,7 +1369,8 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
)
|
|
|
|
|
case "longs":
|
|
|
|
|
question_detail = LongsExamQuestionDetail.objects.get(
|
|
|
|
|
question=question, exam=exam)
|
|
|
|
|
question=question, exam=exam
|
|
|
|
|
)
|
|
|
|
|
case "physics":
|
|
|
|
|
question_detail = PhysicsExamQuestionDetail.objects.get(
|
|
|
|
|
question=question, exam=exam
|
|
|
|
@@ -1341,7 +1379,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
question_detail = SbasExamQuestionDetail.objects.get(
|
|
|
|
|
question=question, exam=exam
|
|
|
|
|
)
|
|
|
|
|
case _:
|
|
|
|
|
case _:
|
|
|
|
|
data = {"status": "error"}
|
|
|
|
|
return JsonResponse(data, status=200)
|
|
|
|
|
question_detail.sort_order = n
|
|
|
|
@@ -1408,7 +1446,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
queryset=self.Answer.objects.filter(
|
|
|
|
|
proposed=False, status=self.Answer.MarkOptions.CORRECT
|
|
|
|
|
),
|
|
|
|
|
to_attr="prefetched_primary_answer"
|
|
|
|
|
to_attr="prefetched_primary_answer",
|
|
|
|
|
# queryset=self.Answer.objects.filter(),
|
|
|
|
|
),
|
|
|
|
|
# Prefetch(
|
|
|
|
@@ -1490,15 +1528,17 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@method_decorator(login_required)
|
|
|
|
|
def exam_question_user_answer(self, request, pk: int, sk: int, c_or_u: str, user_or_cid: str):
|
|
|
|
|
def exam_question_user_answer(
|
|
|
|
|
self, request, pk: int, sk: int, c_or_u: str, user_or_cid: str
|
|
|
|
|
):
|
|
|
|
|
exam: ExamBase = get_object_or_404(self.Exam, pk=pk)
|
|
|
|
|
|
|
|
|
|
if c_or_u == "u":
|
|
|
|
|
user = get_object_or_404(User,pk=user_or_cid)
|
|
|
|
|
user = get_object_or_404(User, pk=user_or_cid)
|
|
|
|
|
answer = exam.get_question_user_user_answer(sk, user)
|
|
|
|
|
elif c_or_u == "c":
|
|
|
|
|
#cid_user = CidUser.objects.filter(cid=user_or_cid)
|
|
|
|
|
answer =exam.get_question_cid_user_answer(sk, user_or_cid)
|
|
|
|
|
# cid_user = CidUser.objects.filter(cid=user_or_cid)
|
|
|
|
|
answer = exam.get_question_cid_user_answer(sk, user_or_cid)
|
|
|
|
|
else:
|
|
|
|
|
raise Http404
|
|
|
|
|
|
|
|
|
@@ -1510,7 +1550,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
exam: ExamBase = get_object_or_404(self.Exam, pk=pk)
|
|
|
|
|
|
|
|
|
|
cid_user = CidUser.objects.filter(cid=cid)
|
|
|
|
|
answer =exam.get_question_cid_user_answer(sk, cid_user)
|
|
|
|
|
answer = exam.get_question_cid_user_answer(sk, cid_user)
|
|
|
|
|
|
|
|
|
|
print(answer)
|
|
|
|
|
|
|
|
|
@@ -1522,12 +1562,12 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
if not self.check_user_access(request.user, pk, marker=True):
|
|
|
|
|
raise PermissionDenied
|
|
|
|
|
|
|
|
|
|
#question = exam.get_questions()[sk]
|
|
|
|
|
# question = exam.get_questions()[sk]
|
|
|
|
|
question = exam.get_question_by_index(sk)
|
|
|
|
|
|
|
|
|
|
exam_length = len(exam.exam_questions.all())
|
|
|
|
|
|
|
|
|
|
#pos = exam.get_question_index(question) + 1
|
|
|
|
|
# pos = exam.get_question_index(question) + 1
|
|
|
|
|
pos = sk + 1
|
|
|
|
|
|
|
|
|
|
previous = -1
|
|
|
|
@@ -1801,7 +1841,9 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
cid_user=c, start_time=t
|
|
|
|
|
)
|
|
|
|
|
exam.exam_user_status.create(
|
|
|
|
|
cid_user_exam=cid_user_exam, status="submitted", extra="manual submission"
|
|
|
|
|
cid_user_exam=cid_user_exam,
|
|
|
|
|
status="submitted",
|
|
|
|
|
extra="manual submission",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
cid_user_exam.end_time = timezone.now()
|
|
|
|
@@ -1831,7 +1873,9 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
user_id = None
|
|
|
|
|
else:
|
|
|
|
|
user_id = request.user.pk
|
|
|
|
|
if exam.exam_mode and not exam.check_cid_user(cid, passcode, request.user, user_id):
|
|
|
|
|
if exam.exam_mode and not exam.check_cid_user(
|
|
|
|
|
cid, passcode, request.user, user_id
|
|
|
|
|
):
|
|
|
|
|
raise Http404("No available exam")
|
|
|
|
|
|
|
|
|
|
# exam_json_cache = cache.get("{}_exam_json_{}".format(self.app_name, pk))
|
|
|
|
@@ -2010,7 +2054,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
|
|
|
|
|
if not exam.check_cid_user(cid, passcode, request.user):
|
|
|
|
|
if not self.check_user_access(request.user, pk):
|
|
|
|
|
#raise PermissionDenied
|
|
|
|
|
# raise PermissionDenied
|
|
|
|
|
raise Http404("Error accessing exam")
|
|
|
|
|
|
|
|
|
|
if user is not None:
|
|
|
|
@@ -2060,7 +2104,6 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
if self.app_name == "longs":
|
|
|
|
|
feedback = user_answer.candidate_feedback
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
match self.app_name:
|
|
|
|
|
case "sbas":
|
|
|
|
|
correct_answer = q.get_correct_answer()
|
|
|
|
@@ -2084,7 +2127,9 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
answers_marks.append(answer_score)
|
|
|
|
|
|
|
|
|
|
print(q.get_questions(), ans, answer_score, correct_answer)
|
|
|
|
|
merged_ans = zip(q.get_questions(), ans, answer_score, correct_answer)
|
|
|
|
|
merged_ans = zip(
|
|
|
|
|
q.get_questions(), ans, answer_score, correct_answer
|
|
|
|
|
)
|
|
|
|
|
answers_and_marks.append((q, merged_ans))
|
|
|
|
|
case _:
|
|
|
|
|
correct_answer = q.get_primary_answer()
|
|
|
|
@@ -2096,7 +2141,9 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
answers_marks.append(answer_score)
|
|
|
|
|
|
|
|
|
|
if self.app_name == "longs":
|
|
|
|
|
answers_and_marks.append((ans, answer_score, correct_answer, feedback))
|
|
|
|
|
answers_and_marks.append(
|
|
|
|
|
(ans, answer_score, correct_answer, feedback)
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
answers_and_marks.append((ans, answer_score, correct_answer))
|
|
|
|
|
|
|
|
|
@@ -2110,7 +2157,9 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
answered = [i for i in answers_marks if type(i) == int]
|
|
|
|
|
total_score = sum(answered)
|
|
|
|
|
unmarked_number = len(answers_marks) - len(answered)
|
|
|
|
|
total_score = "{} ({} unmarked)".format(total_score, unmarked_number)
|
|
|
|
|
total_score = "{} ({} unmarked)".format(
|
|
|
|
|
total_score, unmarked_number
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
total_score = sum(answers_marks)
|
|
|
|
|
|
|
|
|
@@ -2172,7 +2221,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
|
|
|
|
|
# user_answers_and_marks = defaultdict(list)
|
|
|
|
|
user_answers_marks = defaultdict(list)
|
|
|
|
|
#user_answers = defaultdict(list)
|
|
|
|
|
# user_answers = defaultdict(list)
|
|
|
|
|
# user_names = {}
|
|
|
|
|
# cid_passcodes = {}
|
|
|
|
|
|
|
|
|
@@ -2246,7 +2295,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
if answer_score == "unmarked":
|
|
|
|
|
index = exam.get_question_index(q)
|
|
|
|
|
unmarked.add(index)
|
|
|
|
|
#user_answers[cid].append(ans)
|
|
|
|
|
# user_answers[cid].append(ans)
|
|
|
|
|
user_answers_marks[cid].append(answer_score)
|
|
|
|
|
|
|
|
|
|
if self.app_name == "rapids":
|
|
|
|
@@ -2259,9 +2308,6 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
zipped_ans_scores = zip(ans, answer_score)
|
|
|
|
|
by_question[q][cid] = zipped_ans_scores
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
user_scores = {}
|
|
|
|
|
user_scores_normalised = {}
|
|
|
|
|
user_answer_count = {}
|
|
|
|
@@ -2274,11 +2320,10 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
# If either question or user do not exist we give a default
|
|
|
|
|
# not answered == score of 3
|
|
|
|
|
if user not in by_question[question]:
|
|
|
|
|
#print("NOT in")
|
|
|
|
|
# print("NOT in")
|
|
|
|
|
by_question[question][user] = ("Not answered", 3.0)
|
|
|
|
|
user_answers_marks[user].append(3.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if self.app_name in ("rapids", "anatomy", "sbas", "longs"):
|
|
|
|
|
user_scores[user] = sum(
|
|
|
|
|
[i for i in user_answers_marks[user] if i != "unmarked"]
|
|
|
|
@@ -2313,7 +2358,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
mode = 0
|
|
|
|
|
fig_html = ""
|
|
|
|
|
else:
|
|
|
|
|
#Exclude very low scores from statistics
|
|
|
|
|
# Exclude very low scores from statistics
|
|
|
|
|
lower_score_bound = max_score / 5
|
|
|
|
|
bounded_scores = [i for i in user_scores_list if i > lower_score_bound]
|
|
|
|
|
if bounded_scores:
|
|
|
|
@@ -2400,9 +2445,9 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.app_name == "rapids":
|
|
|
|
|
template_variables[
|
|
|
|
|
"user_answers_callstates"
|
|
|
|
|
] = user_answers_callstates_counted
|
|
|
|
|
template_variables["user_answers_callstates"] = (
|
|
|
|
|
user_answers_callstates_counted
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return render(
|
|
|
|
|
request,
|
|
|
|
@@ -2419,9 +2464,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|
|
|
|
if not exam.exam_mode:
|
|
|
|
|
raise Http404("Packet not in exam mode")
|
|
|
|
|
|
|
|
|
|
template_variables = {
|
|
|
|
|
"exam": exam
|
|
|
|
|
}
|
|
|
|
|
template_variables = {"exam": exam}
|
|
|
|
|
|
|
|
|
|
return render(
|
|
|
|
|
request,
|
|
|
|
@@ -2476,7 +2519,7 @@ class GenericViewBase:
|
|
|
|
|
|
|
|
|
|
@method_decorator(login_required)
|
|
|
|
|
def question_detail(self, request, pk):
|
|
|
|
|
question = get_object_or_404(self.question_object, pk=pk)
|
|
|
|
|
question: QuestionBase = get_object_or_404(self.question_object, pk=pk)
|
|
|
|
|
|
|
|
|
|
if not question.open_access:
|
|
|
|
|
if (
|
|
|
|
@@ -2499,7 +2542,12 @@ class GenericViewBase:
|
|
|
|
|
return render(
|
|
|
|
|
request,
|
|
|
|
|
f"{self.app_name}/question_detail.html",
|
|
|
|
|
{"question": question, "view_feedback": view_feedback, "remote_url": settings.REMOTE_URL},
|
|
|
|
|
{
|
|
|
|
|
"question": question,
|
|
|
|
|
"view_feedback": view_feedback,
|
|
|
|
|
"remote_url": settings.REMOTE_URL,
|
|
|
|
|
"can_edit": question.can_edit(request.user),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# TODO: improve permissions on these
|
|
|
|
@@ -2514,15 +2562,22 @@ class GenericViewBase:
|
|
|
|
|
question = get_object_or_404(self.question_object, pk=pk)
|
|
|
|
|
|
|
|
|
|
if answer_compare is None:
|
|
|
|
|
answers = self.cid_user_answer_object.objects.filter(question__id=question.pk).prefetch_related("question", "exam", "user")
|
|
|
|
|
answers = self.cid_user_answer_object.objects.filter(
|
|
|
|
|
question__id=question.pk
|
|
|
|
|
).prefetch_related("question", "exam", "user")
|
|
|
|
|
else:
|
|
|
|
|
answers = self.cid_user_answer_object.objects.filter(question__id=question.pk, answer_compare=answer_compare).prefetch_related("question", "exam", "user")
|
|
|
|
|
|
|
|
|
|
answers = self.cid_user_answer_object.objects.filter(
|
|
|
|
|
question__id=question.pk, answer_compare=answer_compare
|
|
|
|
|
).prefetch_related("question", "exam", "user")
|
|
|
|
|
|
|
|
|
|
return render(
|
|
|
|
|
request,
|
|
|
|
|
f"{self.app_name}/question_user_answers.html",
|
|
|
|
|
{"question": question, "answers": answers, "answer_compare": answer_compare},
|
|
|
|
|
{
|
|
|
|
|
"question": question,
|
|
|
|
|
"answers": answers,
|
|
|
|
|
"answer_compare": answer_compare,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@method_decorator(login_required)
|
|
|
|
@@ -2548,11 +2603,10 @@ class GenericViewBase:
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExamCloneMixin():
|
|
|
|
|
class ExamCloneMixin:
|
|
|
|
|
def get_template_names(self) -> list[str]:
|
|
|
|
|
return "exam_clone_form.html"
|
|
|
|
|
#return super().get_template_names()
|
|
|
|
|
|
|
|
|
|
# return super().get_template_names()
|
|
|
|
|
|
|
|
|
|
def get_initial(self):
|
|
|
|
|
old_object = get_object_or_404(self.model, pk=self.kwargs["exam_id"])
|
|
|
|
@@ -2617,6 +2671,8 @@ class ExaminationAutocomplete(autocomplete.Select2QuerySetView):
|
|
|
|
|
return Examination.objects.none()
|
|
|
|
|
|
|
|
|
|
return qs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SupervisorAutocomplete(autocomplete.Select2QuerySetView):
|
|
|
|
|
def get_queryset(self):
|
|
|
|
|
# TODO: we should probably filter this to only
|
|
|
|
@@ -2746,6 +2802,7 @@ def cid_group_view_all(request):
|
|
|
|
|
{"groups": groups, "view_all": True},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@user_is_cid_user_manager
|
|
|
|
|
def user_group_add_candidates_to_exams(request, group_id):
|
|
|
|
|
if request.htmx:
|
|
|
|
@@ -2764,13 +2821,12 @@ def user_group_add_candidates_to_exams(request, group_id):
|
|
|
|
|
else:
|
|
|
|
|
exam.valid_user_users.add(*group.users.all())
|
|
|
|
|
return HttpResponse(f"Candidates added")
|
|
|
|
|
#exam.save()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# exam.save()
|
|
|
|
|
|
|
|
|
|
raise PermissionDenied() # or Http404
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@user_is_cid_user_manager
|
|
|
|
|
def user_group_view_detail(request, group_id):
|
|
|
|
|
group = get_object_or_404(UserUserGroup, pk=group_id)
|
|
|
|
@@ -2817,6 +2873,7 @@ def get_user_selection_from_request(request):
|
|
|
|
|
user_models = User.objects.filter(id__in=selected_users)
|
|
|
|
|
return user_models
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@user_is_cid_user_manager
|
|
|
|
|
def user_not_trainee(request, user_id):
|
|
|
|
|
user = get_object_or_404(User, pk=user_id)
|
|
|
|
@@ -2824,7 +2881,10 @@ def user_not_trainee(request, user_id):
|
|
|
|
|
user.userprofile.peninsula_trainee = False
|
|
|
|
|
user.userprofile.save()
|
|
|
|
|
|
|
|
|
|
return HttpResponse(f"{user.username} is no longer a trainee", content_type="text/plain")
|
|
|
|
|
return HttpResponse(
|
|
|
|
|
f"{user.username} is no longer a trainee", content_type="text/plain"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@user_is_cid_user_manager
|
|
|
|
|
def users_bulk_edit(request):
|
|
|
|
@@ -2863,8 +2923,7 @@ def users_bulk_edit(request):
|
|
|
|
|
# TODO: work out how to actually use hyperscript
|
|
|
|
|
html = (
|
|
|
|
|
html
|
|
|
|
|
+
|
|
|
|
|
f"""<button class='change-grade-button' id='add-grade--remove'
|
|
|
|
|
+ f"""<button class='change-grade-button' id='add-grade--remove'
|
|
|
|
|
hx-post="{reverse('generic:users_bulk_edit')}"
|
|
|
|
|
hx-include="[name='selection']"
|
|
|
|
|
hx-confirm="This will remove grades of all selected users, are you sure you wish to continue?"
|
|
|
|
@@ -3065,6 +3124,7 @@ def candidate_email_results_resend(request, cid, resend=True):
|
|
|
|
|
def create_cid_email(request):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@user_is_cid_user_manager
|
|
|
|
|
def cid_details(request, cid: int):
|
|
|
|
|
print(cid)
|
|
|
|
@@ -3077,6 +3137,7 @@ def cid_details(request, cid: int):
|
|
|
|
|
|
|
|
|
|
raise PermissionDenied() # or Http404
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@user_is_cid_user_manager
|
|
|
|
|
def manage_cid_users(request):
|
|
|
|
|
if request.method == "POST":
|
|
|
|
@@ -3308,11 +3369,13 @@ class UserUserGroupUpdate(RevisionMixin, CidManagerRequiredMixin, UpdateView):
|
|
|
|
|
context["group_type"] = "user"
|
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UserUserGroupDelete(RevisionMixin, CidManagerRequiredMixin, DeleteView):
|
|
|
|
|
model = UserUserGroup
|
|
|
|
|
template_name = "confirm_delete.html"
|
|
|
|
|
success_url = reverse_lazy("generic:user_group_view")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UserGroupExamUpdate(CidManagerRequiredMixin, UpdateView):
|
|
|
|
|
model = UserUserGroup
|
|
|
|
|
form_class = UserGroupExamForm
|
|
|
|
@@ -3322,6 +3385,7 @@ class UserGroupExamUpdate(CidManagerRequiredMixin, UpdateView):
|
|
|
|
|
context["group_type"] = "user"
|
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CidGroupExamUpdate(CidManagerRequiredMixin, UpdateView):
|
|
|
|
|
model = CidUserGroup
|
|
|
|
|
form_class = CidGroupExamForm
|
|
|
|
@@ -3331,6 +3395,7 @@ class CidGroupExamUpdate(CidManagerRequiredMixin, UpdateView):
|
|
|
|
|
context["group_type"] = "cid"
|
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExamCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
|
|
|
|
|
template_name = "exam_create_form.html"
|
|
|
|
|
|
|
|
|
@@ -3391,26 +3456,30 @@ def exam_inactive(request, context):
|
|
|
|
|
#
|
|
|
|
|
# success_url = reverse_lazy("accounts_list")
|
|
|
|
|
|
|
|
|
|
@user_is_cid_user_manager
|
|
|
|
|
def trainees(request, grade: None|str=None):
|
|
|
|
|
|
|
|
|
|
trainees = UserProfile.objects.filter(peninsula_trainee=True, user__is_active=True).order_by("grade__name", Lower("user__last_name")).prefetch_related("supervisor", "grade", "user")
|
|
|
|
|
@user_is_cid_user_manager
|
|
|
|
|
def trainees(request, grade: None | str = None):
|
|
|
|
|
|
|
|
|
|
trainees = (
|
|
|
|
|
UserProfile.objects.filter(peninsula_trainee=True, user__is_active=True)
|
|
|
|
|
.order_by("grade__name", Lower("user__last_name"))
|
|
|
|
|
.prefetch_related("supervisor", "grade", "user")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if grade is not None:
|
|
|
|
|
trainees = trainees.filter(grade__name=grade)
|
|
|
|
|
|
|
|
|
|
context = {
|
|
|
|
|
"trainees" : trainees,
|
|
|
|
|
"grade": grade
|
|
|
|
|
}
|
|
|
|
|
context = {"trainees": trainees, "grade": grade}
|
|
|
|
|
|
|
|
|
|
return render(request, "generic/trainees.html", context)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_trainee(request, context=None):
|
|
|
|
|
return create_user(request, context, trainee=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@user_is_cid_user_manager
|
|
|
|
|
def create_user(request, context=None, trainee: bool=False):
|
|
|
|
|
def create_user(request, context=None, trainee: bool = False):
|
|
|
|
|
if trainee:
|
|
|
|
|
form_template = "generic/trainee_creation_form.html"
|
|
|
|
|
else:
|
|
|
|
@@ -3500,9 +3569,10 @@ class SupervisorCreate(CidManagerRequiredMixin, CreateView):
|
|
|
|
|
# success_url = reverse_lazy("generic:supervisor_detail")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#class SupervisorList(CidManagerRequiredMixin, ListView):
|
|
|
|
|
# class SupervisorList(CidManagerRequiredMixin, ListView):
|
|
|
|
|
# model = Supervisor
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SupervisorList(CidManagerRequiredMixin, SingleTableMixin, FilterView):
|
|
|
|
|
model = Supervisor
|
|
|
|
|
table_class = SupervisorTable
|
|
|
|
@@ -3510,20 +3580,25 @@ class SupervisorList(CidManagerRequiredMixin, SingleTableMixin, FilterView):
|
|
|
|
|
|
|
|
|
|
filterset_class = SupervisorFilter
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExamCollectionList(ListView):
|
|
|
|
|
model = ExamCollection
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExamCollectionDetail(DetailView, AuthorRequiredMixin):
|
|
|
|
|
model = ExamCollection
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExamCollectionEdit(UpdateView, AuthorRequiredMixin):
|
|
|
|
|
model = ExamCollection
|
|
|
|
|
form_class = ExamCollectionForm
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExamCollectionCreate(CreateView, AuthorRequiredMixin):
|
|
|
|
|
model = ExamCollection
|
|
|
|
|
form_class = ExamCollectionForm
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExamCollectionClone(CreateView, AuthorRequiredMixin):
|
|
|
|
|
model = ExamCollection
|
|
|
|
|
template_name = "generic/examcollection_clone_form.html"
|
|
|
|
@@ -3533,23 +3608,22 @@ class ExamCollectionClone(CreateView, AuthorRequiredMixin):
|
|
|
|
|
old_object = get_object_or_404(self.model, pk=self.kwargs["pk"])
|
|
|
|
|
self.initial_object = old_object
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
initial_data = model_to_dict(old_object, exclude=["id", "date"])
|
|
|
|
|
|
|
|
|
|
## We manually transfer the forign keys / m2m relationships
|
|
|
|
|
#questions = old_object.exam_questions.all().values_list("id", flat=True)
|
|
|
|
|
# questions = old_object.exam_questions.all().values_list("id", flat=True)
|
|
|
|
|
authors = old_object.author.all().values_list("id", flat=True)
|
|
|
|
|
|
|
|
|
|
## clear the associated groups
|
|
|
|
|
#initial_data["valid_user_users"] = []
|
|
|
|
|
#initial_data["valid_cid_users"] = []
|
|
|
|
|
#initial_data["cid_user_groups"] = []
|
|
|
|
|
#initial_data["user_user_groups"] = []
|
|
|
|
|
# initial_data["valid_user_users"] = []
|
|
|
|
|
# initial_data["valid_cid_users"] = []
|
|
|
|
|
# initial_data["cid_user_groups"] = []
|
|
|
|
|
# initial_data["user_user_groups"] = []
|
|
|
|
|
|
|
|
|
|
#initial_data["active"] = False
|
|
|
|
|
#initial_data["publish_results"] = False
|
|
|
|
|
# initial_data["active"] = False
|
|
|
|
|
# initial_data["publish_results"] = False
|
|
|
|
|
|
|
|
|
|
#self.exam_questions = list(questions)
|
|
|
|
|
# self.exam_questions = list(questions)
|
|
|
|
|
self.author = list(authors)
|
|
|
|
|
|
|
|
|
|
return initial_data
|
|
|
|
@@ -3557,7 +3631,7 @@ class ExamCollectionClone(CreateView, AuthorRequiredMixin):
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
|
object = form.save()
|
|
|
|
|
# Reapply these otherwise they get lost?
|
|
|
|
|
#object.exam_questions.set(self.exam_questions)
|
|
|
|
|
# object.exam_questions.set(self.exam_questions)
|
|
|
|
|
|
|
|
|
|
GROUP_TYPES = (
|
|
|
|
|
("Rapid Exams", "rapids_exams", RapidsExam),
|
|
|
|
@@ -3571,11 +3645,13 @@ class ExamCollectionClone(CreateView, AuthorRequiredMixin):
|
|
|
|
|
object.save()
|
|
|
|
|
return HttpResponseRedirect(object.get_absolute_url())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExamCollectionDelete(DeleteView, AuthorRequiredMixin):
|
|
|
|
|
model = ExamCollection
|
|
|
|
|
template_name = "confirm_delete.html"
|
|
|
|
|
success_url = reverse_lazy("generic:examcollection_list")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExaminationView(SuperuserRequiredMixin, SingleTableMixin, FilterView):
|
|
|
|
|
model = Examination
|
|
|
|
|
table_class = ExaminationTable
|
|
|
|
@@ -3597,7 +3673,8 @@ class ExaminationUpdate(UpdateView, SuperuserRequiredMixin):
|
|
|
|
|
|
|
|
|
|
class SeriesImagesZipViewBase(SuperuserRequiredMixin, View):
|
|
|
|
|
"""Download all images from an image series"""
|
|
|
|
|
http_method_names = ['get']
|
|
|
|
|
|
|
|
|
|
http_method_names = ["get"]
|
|
|
|
|
series_object = None
|
|
|
|
|
|
|
|
|
|
def get_files(self):
|
|
|
|
@@ -3606,12 +3683,14 @@ class SeriesImagesZipViewBase(SuperuserRequiredMixin, View):
|
|
|
|
|
return [i.image.file for i in series.get_images()]
|
|
|
|
|
|
|
|
|
|
def get_archive_name(self, request):
|
|
|
|
|
#series = Series.objects.get(pk=self.kwargs["pk"])
|
|
|
|
|
# series = Series.objects.get(pk=self.kwargs["pk"])
|
|
|
|
|
return f"Series {self.kwargs['pk']}.zip"
|
|
|
|
|
|
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
|
temp_file = ContentFile(b"", name=self.get_archive_name(request))
|
|
|
|
|
with zipfile.ZipFile(temp_file, mode='w', compression=zipfile.ZIP_DEFLATED) as zip_file:
|
|
|
|
|
with zipfile.ZipFile(
|
|
|
|
|
temp_file, mode="w", compression=zipfile.ZIP_DEFLATED
|
|
|
|
|
) as zip_file:
|
|
|
|
|
files = self.get_files()
|
|
|
|
|
for file_ in files:
|
|
|
|
|
path = os.path.split(file_.name)[-1]
|
|
|
|
@@ -3620,24 +3699,35 @@ class SeriesImagesZipViewBase(SuperuserRequiredMixin, View):
|
|
|
|
|
file_size = temp_file.tell()
|
|
|
|
|
temp_file.seek(0)
|
|
|
|
|
|
|
|
|
|
response = HttpResponse(temp_file, content_type='application/zip')
|
|
|
|
|
response['Content-Disposition'] = 'attachment; filename=%s' % self.get_archive_name(request)
|
|
|
|
|
response['Content-Length'] = file_size
|
|
|
|
|
response = HttpResponse(temp_file, content_type="application/zip")
|
|
|
|
|
response["Content-Disposition"] = (
|
|
|
|
|
"attachment; filename=%s" % self.get_archive_name(request)
|
|
|
|
|
)
|
|
|
|
|
response["Content-Length"] = file_size
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExamGroupsUpdateBase(
|
|
|
|
|
RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView
|
|
|
|
|
RevisionMixin,
|
|
|
|
|
CheckCanEditMixin,
|
|
|
|
|
LoginRequiredMixin,
|
|
|
|
|
AuthorRequiredMixin,
|
|
|
|
|
UpdateView,
|
|
|
|
|
):
|
|
|
|
|
template_name = "generic/exam_groups_edit.html"
|
|
|
|
|
|
|
|
|
|
def get_success_url(self) -> str:
|
|
|
|
|
return reverse_lazy(f"{self.object.get_app_name()}:exam_cids", kwargs={"exam_id": self.object.pk})
|
|
|
|
|
return reverse_lazy(
|
|
|
|
|
f"{self.object.get_app_name()}:exam_cids",
|
|
|
|
|
kwargs={"exam_id": self.object.pk},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def get_form_kwargs(self):
|
|
|
|
|
kwargs = super(ExamGroupsUpdateBase, self).get_form_kwargs()
|
|
|
|
|
kwargs.update({"user": self.request.user})
|
|
|
|
|
return kwargs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UpdateQuestionMixin(RedirectMixin, RevisionMixin, UpdateView):
|
|
|
|
|
|
|
|
|
|
def get_form_kwargs(self):
|
|
|
|
@@ -3661,4 +3751,3 @@ class UpdateQuestionMixin(RedirectMixin, RevisionMixin, UpdateView):
|
|
|
|
|
if self.request.user in obj.get_author_objects():
|
|
|
|
|
return context
|
|
|
|
|
raise PermissionDenied() # or Http404
|
|
|
|
|
|
|
|
|
|