tidy up some longs related stuff
This commit is contained in:
+2
-2
@@ -89,9 +89,9 @@ class AuthorOrCheckerRequiredMixin(object):
|
|||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def get_item(dictionary, key):
|
def get_item(dictionary, key, default=None):
|
||||||
if dictionary is None:
|
if dictionary is None:
|
||||||
return None
|
return default
|
||||||
return dictionary.get(key)
|
return dictionary.get(key)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -870,7 +870,6 @@ class ExamBase(ExamOrCollectionGenericBase):
|
|||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
print("TESTING")
|
|
||||||
if self.start_date and self.start_date is not None:
|
if self.start_date and self.start_date is not None:
|
||||||
print(f"Start date: {self.start_date:%d %B %Y}")
|
print(f"Start date: {self.start_date:%d %B %Y}")
|
||||||
return f"{self.name} ({self.start_date:%d %B %Y})"
|
return f"{self.name} ({self.start_date:%d %B %Y})"
|
||||||
|
|||||||
+28
-7
@@ -1860,8 +1860,6 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
return self.exam_question_json_unbased(request, pk, sk, cid, passcode)
|
return self.exam_question_json_unbased(request, pk, sk, cid, passcode)
|
||||||
|
|
||||||
def exam_question_json_unbased(self, request, pk, sk, cid=None, passcode=None):
|
def exam_question_json_unbased(self, request, pk, sk, cid=None, passcode=None):
|
||||||
print("TESTING")
|
|
||||||
print("**************************")
|
|
||||||
question = get_object_or_404(self.Question, pk=sk)
|
question = get_object_or_404(self.Question, pk=sk)
|
||||||
exam = get_object_or_404(self.Exam, pk=pk)
|
exam = get_object_or_404(self.Exam, pk=pk)
|
||||||
|
|
||||||
@@ -1951,7 +1949,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
).first()
|
).first()
|
||||||
# user_answer = cid_user_answers_q_map[q]
|
# user_answer = cid_user_answers_q_map[q]
|
||||||
|
|
||||||
print("QUSETION", q)
|
feedback = None
|
||||||
if not user_answer or user_answer is None:
|
if not user_answer or user_answer is None:
|
||||||
# skip if no answer
|
# skip if no answer
|
||||||
score, text = q.get_unanswered_mark_and_text()
|
score, text = q.get_unanswered_mark_and_text()
|
||||||
@@ -1962,8 +1960,10 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
else:
|
else:
|
||||||
ans = user_answer.get_answer()
|
ans = user_answer.get_answer()
|
||||||
answer_score = user_answer.get_answer_score()
|
answer_score = user_answer.get_answer_score()
|
||||||
print("ans", ans)
|
|
||||||
print("ans score", answer_score)
|
if self.app_name == "longs":
|
||||||
|
feedback = user_answer.candidate_feedback
|
||||||
|
|
||||||
|
|
||||||
match self.app_name:
|
match self.app_name:
|
||||||
case "sbas":
|
case "sbas":
|
||||||
@@ -1998,6 +1998,10 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
answer_score = 0
|
answer_score = 0
|
||||||
answers.append(ans)
|
answers.append(ans)
|
||||||
answers_marks.append(answer_score)
|
answers_marks.append(answer_score)
|
||||||
|
|
||||||
|
if self.app_name == "longs":
|
||||||
|
answers_and_marks.append((ans, answer_score, correct_answer, feedback))
|
||||||
|
else:
|
||||||
answers_and_marks.append((ans, answer_score, correct_answer))
|
answers_and_marks.append((ans, answer_score, correct_answer))
|
||||||
|
|
||||||
print(answers_marks)
|
print(answers_marks)
|
||||||
@@ -2072,7 +2076,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
|
|
||||||
# user_answers_and_marks = defaultdict(list)
|
# user_answers_and_marks = defaultdict(list)
|
||||||
user_answers_marks = defaultdict(list)
|
user_answers_marks = defaultdict(list)
|
||||||
user_answers = defaultdict(list)
|
#user_answers = defaultdict(list)
|
||||||
# user_names = {}
|
# user_names = {}
|
||||||
# cid_passcodes = {}
|
# cid_passcodes = {}
|
||||||
|
|
||||||
@@ -2146,7 +2150,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
if answer_score == "unmarked":
|
if answer_score == "unmarked":
|
||||||
index = exam.get_question_index(q)
|
index = exam.get_question_index(q)
|
||||||
unmarked.add(index)
|
unmarked.add(index)
|
||||||
user_answers[cid].append(ans)
|
#user_answers[cid].append(ans)
|
||||||
user_answers_marks[cid].append(answer_score)
|
user_answers_marks[cid].append(answer_score)
|
||||||
|
|
||||||
if self.app_name == "rapids":
|
if self.app_name == "rapids":
|
||||||
@@ -2159,10 +2163,27 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
zipped_ans_scores = zip(ans, answer_score)
|
zipped_ans_scores = zip(ans, answer_score)
|
||||||
by_question[q][cid] = zipped_ans_scores
|
by_question[q][cid] = zipped_ans_scores
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
user_scores = {}
|
user_scores = {}
|
||||||
user_scores_normalised = {}
|
user_scores_normalised = {}
|
||||||
user_answer_count = {}
|
user_answer_count = {}
|
||||||
for user in user_answers_marks:
|
for user in user_answers_marks:
|
||||||
|
|
||||||
|
# For longs we have a default score of 3 (not 0) if unanswered
|
||||||
|
# so we need to check for those
|
||||||
|
if self.app_name == "longs":
|
||||||
|
for question in questions:
|
||||||
|
if question not in by_question:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if user not in by_question[question]:
|
||||||
|
#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"):
|
if self.app_name in ("rapids", "anatomy", "sbas", "longs"):
|
||||||
user_scores[user] = sum(
|
user_scores[user] = sum(
|
||||||
[i for i in user_answers_marks[user] if i != "unmarked"]
|
[i for i in user_answers_marks[user] if i != "unmarked"]
|
||||||
|
|||||||
+5
-2
@@ -51,13 +51,16 @@ class LongAnswerForm(ModelForm):
|
|||||||
class MarkLongQuestionSingleForm(ModelForm):
|
class MarkLongQuestionSingleForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserAnswer
|
model = UserAnswer
|
||||||
fields = ["score"]
|
fields = ["score", "candidate_feedback"]
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
"candidate_feedback": Textarea(attrs={"rows": 3}),
|
||||||
|
}
|
||||||
|
|
||||||
class MarkLongQuestionDoubleForm(ModelForm):
|
class MarkLongQuestionDoubleForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AnswerMarks
|
model = AnswerMarks
|
||||||
fields = ["score", "mark_reason", "candidate_feedback"]
|
fields = ["score", "mark_reason"]
|
||||||
|
|
||||||
|
|
||||||
class ExaminationForm(ModelForm):
|
class ExaminationForm(ModelForm):
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-07-08 11:49
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('longs', '0024_alter_exam_end_date_alter_exam_exam_results_emailed_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='answermarks',
|
||||||
|
name='candidate_feedback',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='useranswer',
|
||||||
|
name='candidate_feedback',
|
||||||
|
field=models.TextField(blank=True, help_text='Feedback for the candidate', null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
+9
-7
@@ -328,12 +328,13 @@ class Long(QuestionBase):
|
|||||||
|
|
||||||
return url
|
return url
|
||||||
|
|
||||||
def get_unanswered_mark_and_text(self) -> tuple[int, tuple[str, str, str, str, str]]:
|
def get_unanswered_mark_and_text(self) -> tuple[int, str]:
|
||||||
"""
|
"""
|
||||||
Long cases are receive a mark between 4 and 8
|
Long cases are receive a mark between 4 and 8
|
||||||
Therefore unmarked = 4
|
This has changed to a 3 if unanswered
|
||||||
|
Therefore unmarked = 3
|
||||||
"""
|
"""
|
||||||
return (4, ("Not answered",) * 5)
|
return (3, ("Not answered",) * 5)
|
||||||
|
|
||||||
# def GetNonFeedbackQuestionImages(self):
|
# def GetNonFeedbackQuestionImages(self):
|
||||||
# return self.get_images()
|
# return self.get_images()
|
||||||
@@ -612,6 +613,7 @@ class UserAnswer(UserAnswerBase):
|
|||||||
|
|
||||||
class ScoreOptions(models.TextChoices):
|
class ScoreOptions(models.TextChoices):
|
||||||
UNMARKED = "", _("Unmarked")
|
UNMARKED = "", _("Unmarked")
|
||||||
|
#UNANSWERED = "3", _("Unanswered")
|
||||||
FOUR = "4", _("4")
|
FOUR = "4", _("4")
|
||||||
FOUR_HALF = "4.5", _("4.5")
|
FOUR_HALF = "4.5", _("4.5")
|
||||||
FIVE = "5", _("5")
|
FIVE = "5", _("5")
|
||||||
@@ -630,6 +632,10 @@ class UserAnswer(UserAnswerBase):
|
|||||||
help_text="The final score for the candidate",
|
help_text="The final score for the candidate",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
candidate_feedback = models.TextField(
|
||||||
|
null=True, blank=True, help_text="Feedback for the candidate"
|
||||||
|
)
|
||||||
|
|
||||||
# mark = models.ManyToManyField(
|
# mark = models.ManyToManyField(
|
||||||
# "AnswerMarks", related_name="user_answer", blank=True
|
# "AnswerMarks", related_name="user_answer", blank=True
|
||||||
# )
|
# )
|
||||||
@@ -752,10 +758,6 @@ class AnswerMarks(models.Model):
|
|||||||
help_text="Reason for the given mark - not visible to candidates",
|
help_text="Reason for the given mark - not visible to candidates",
|
||||||
)
|
)
|
||||||
|
|
||||||
candidate_feedback = models.TextField(
|
|
||||||
null=True, blank=True, help_text="Feedback for the candidate"
|
|
||||||
)
|
|
||||||
|
|
||||||
marker = models.ForeignKey(
|
marker = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL, related_name="marker", on_delete=models.CASCADE
|
settings.AUTH_USER_MODEL, related_name="marker", on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
{% extends 'generic/exam_scores_base.html' %}
|
{% extends 'generic/exam_scores_base.html' %}
|
||||||
|
|
||||||
{% block table_answers %}
|
{% block table_answers %}
|
||||||
|
aoeu
|
||||||
|
eu
|
||||||
{% for question in questions %}
|
{% for question in questions %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'anatomy:mark' exam_pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter}}</a>
|
<td><a href="{% url 'anatomy:mark' exam_pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter}}</a>
|
||||||
|
|||||||
@@ -16,10 +16,14 @@
|
|||||||
<div>
|
<div>
|
||||||
{% comment %} <h4>Answers</h4> {% endcomment %}
|
{% comment %} <h4>Answers</h4> {% endcomment %}
|
||||||
<ul class="score-answer-list">
|
<ul class="score-answer-list">
|
||||||
{% for ans, score, correct_answer in answers_and_marks %}
|
{% for ans, score, correct_answer, feedback in answers_and_marks %}
|
||||||
<li class="user-answer-li" data-question-number="{{forloop.counter}}"><b>Question {{forloop.counter}}</b> <span class="view-question-link-longs"
|
<li class="user-answer-li
|
||||||
|
{% if ans.0 == 'Not answered' %}
|
||||||
|
not-answered
|
||||||
|
{% endif %}
|
||||||
|
" data-question-number="{{forloop.counter}}"><b>Question {{forloop.counter}}</b> <span class="view-question-link-longs"
|
||||||
data-qn={{forloop.counter0}}>View</span>
|
data-qn={{forloop.counter0}}>View</span>
|
||||||
<ul>
|
<ul class="">
|
||||||
<li class="Observations" data-qidn="1">Observations</br>
|
<li class="Observations" data-qidn="1">Observations</br>
|
||||||
<pre>{{ans.0}}</pre>
|
<pre>{{ans.0}}</pre>
|
||||||
</li>
|
</li>
|
||||||
@@ -35,8 +39,14 @@
|
|||||||
<li class="Management" data-qidn="5">Management</br>
|
<li class="Management" data-qidn="5">Management</br>
|
||||||
<pre>{{ans.4}}</pre>
|
<pre>{{ans.4}}</pre>
|
||||||
</li>
|
</li>
|
||||||
{% if exam.publish_results %}
|
{% if view_all_results or exam.publish_results %}
|
||||||
<span class="answer-score">{{score}}</span>
|
Question score: <span class="answer-score">{{score}}</span>
|
||||||
|
|
||||||
|
|
||||||
|
{% if feedback %}
|
||||||
|
<p>Question feedback: {{feedback}}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
@@ -47,6 +57,13 @@
|
|||||||
{% include 'user_scores_footer.html' %}
|
{% include 'user_scores_footer.html' %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block css %}
|
||||||
|
<style>
|
||||||
|
.not-answered {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock css %}
|
||||||
{% block js %}
|
{% block js %}
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends 'longs/exams.html' %}
|
{% extends 'longs/exams.html' %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<a href="{% url 'longs:exam_question_detail' exam.id question_details.current %}" title="View the Question">View</a>
|
<a href="{% url 'longs:exam_question_detail' exam.id question_details.current %}" title="View the Question">View</a>
|
||||||
@@ -111,7 +112,7 @@
|
|||||||
<div class="marking">
|
<div class="marking">
|
||||||
<form method="POST" class="post-form">{% csrf_token %}
|
<form method="POST" class="post-form">{% csrf_token %}
|
||||||
<input type="hidden" name="form_id" value="mark_form">
|
<input type="hidden" name="form_id" value="mark_form">
|
||||||
{{ form.as_p }}
|
{{ form|crispy }}
|
||||||
<button type="submit" name="save" class="save btn btn-default" title="Save answer">Save</button>
|
<button type="submit" name="save" class="save btn btn-default" title="Save answer">Save</button>
|
||||||
{% if next_unmarked_id %}
|
{% if next_unmarked_id %}
|
||||||
<button type="submit" name="next" class="save btn btn-default"
|
<button type="submit" name="next" class="save btn btn-default"
|
||||||
|
|||||||
@@ -601,7 +601,7 @@ def test_exams(db, client):
|
|||||||
cid_exam_scores_soup.find("div", {"class": "score-overview"})
|
cid_exam_scores_soup.find("div", {"class": "score-overview"})
|
||||||
.find("span", {"id": "total-score"})
|
.find("span", {"id": "total-score"})
|
||||||
.string
|
.string
|
||||||
== "4 (3 unmarked)"
|
== "3 (3 unmarked)"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mark the questions
|
# Mark the questions
|
||||||
@@ -636,7 +636,7 @@ def test_exams(db, client):
|
|||||||
cid_exam_scores_soup.find("div", {"class": "score-overview"})
|
cid_exam_scores_soup.find("div", {"class": "score-overview"})
|
||||||
.find("span", {"id": "total-score"})
|
.find("span", {"id": "total-score"})
|
||||||
.string
|
.string
|
||||||
== "28.0"
|
== "27.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
generated_questions.pop(-1).delete()
|
generated_questions.pop(-1).delete()
|
||||||
|
|||||||
+39
-10
@@ -75,7 +75,15 @@ from django.forms.models import model_to_dict
|
|||||||
from longs.forms import LongCreationDefaultForm
|
from longs.forms import LongCreationDefaultForm
|
||||||
from longs.models import LongCreationDefault
|
from longs.models import LongCreationDefault
|
||||||
|
|
||||||
from generic.views import AuthorRequiredMixin, ExamCloneMixin, ExamCreateBase, ExamDeleteBase, ExamUpdateBase, ExamViews, SeriesImagesZipViewBase
|
from generic.views import (
|
||||||
|
AuthorRequiredMixin,
|
||||||
|
ExamCloneMixin,
|
||||||
|
ExamCreateBase,
|
||||||
|
ExamDeleteBase,
|
||||||
|
ExamUpdateBase,
|
||||||
|
ExamViews,
|
||||||
|
SeriesImagesZipViewBase,
|
||||||
|
)
|
||||||
from reversion.views import RevisionMixin
|
from reversion.views import RevisionMixin
|
||||||
import reversion
|
import reversion
|
||||||
|
|
||||||
@@ -271,7 +279,10 @@ def long_clone(request, pk):
|
|||||||
new_item.pk = None # autogen a new pk (item_id)
|
new_item.pk = None # autogen a new pk (item_id)
|
||||||
# new_item.name = "Copy of " + new_item.name #need to change uniques
|
# new_item.name = "Copy of " + new_item.name #need to change uniques
|
||||||
|
|
||||||
if request.user not in new_item.get_author_objects() and not request.user.is_superuser:
|
if (
|
||||||
|
request.user not in new_item.get_author_objects()
|
||||||
|
and not request.user.is_superuser
|
||||||
|
):
|
||||||
raise PermissionDenied() # or Http404
|
raise PermissionDenied() # or Http404
|
||||||
|
|
||||||
form = LongForm(request.POST or None, instance=new_item)
|
form = LongForm(request.POST or None, instance=new_item)
|
||||||
@@ -413,7 +424,11 @@ class LongCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(LongCreateBase, self).get_context_data(**kwargs)
|
context = super(LongCreateBase, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
queryset = LongSeries.objects.select_related("modality", "examination", "plane").prefetch_related("long").filter(pk=62)#.values()
|
queryset = (
|
||||||
|
LongSeries.objects.select_related("modality", "examination", "plane")
|
||||||
|
.prefetch_related("long")
|
||||||
|
.filter(pk=62)
|
||||||
|
) # .values()
|
||||||
print("TEST")
|
print("TEST")
|
||||||
print(queryset)
|
print(queryset)
|
||||||
# queryset = LongSeries.objects.all()
|
# queryset = LongSeries.objects.all()
|
||||||
@@ -624,7 +639,6 @@ class LongSeriesView(LoginRequiredMixin, SingleTableMixin, FilterView):
|
|||||||
filterset_class = LongSeriesFilter
|
filterset_class = LongSeriesFilter
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@user_is_long_marker
|
@user_is_long_marker
|
||||||
@reversion.create_revision()
|
@reversion.create_revision()
|
||||||
def mark_answer_override(request, exam_id, question_number, cid):
|
def mark_answer_override(request, exam_id, question_number, cid):
|
||||||
@@ -704,6 +718,7 @@ def mark_answer(request, exam_id, question_number, answer_id, override=False):
|
|||||||
|
|
||||||
# Extract score from form and save it to the object
|
# Extract score from form and save it to the object
|
||||||
answer.score = form.cleaned_data["score"]
|
answer.score = form.cleaned_data["score"]
|
||||||
|
answer.candidate_feedback = form.cleaned_data["candidate_feedback"]
|
||||||
answer.save()
|
answer.save()
|
||||||
|
|
||||||
if "next" in request.POST:
|
if "next" in request.POST:
|
||||||
@@ -724,7 +739,12 @@ def mark_answer(request, exam_id, question_number, answer_id, override=False):
|
|||||||
# return redirect("longs:mark", pk=exam_id, sk=n - 1)
|
# return redirect("longs:mark", pk=exam_id, sk=n - 1)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
form = MarkLongQuestionSingleForm(initial={"score": answer.score})
|
form = MarkLongQuestionSingleForm(
|
||||||
|
initial={
|
||||||
|
"score": answer.score,
|
||||||
|
"candidate_feedback": answer.candidate_feedback,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
@@ -794,7 +814,10 @@ def mark_answer(request, exam_id, question_number, answer_id, override=False):
|
|||||||
marks = set(answer.mark.values_list("score", flat=True))
|
marks = set(answer.mark.values_list("score", flat=True))
|
||||||
if len(marks) > 1 or override:
|
if len(marks) > 1 or override:
|
||||||
discrepancy_form = MarkLongQuestionSingleForm(
|
discrepancy_form = MarkLongQuestionSingleForm(
|
||||||
initial={"score": answer.score}
|
initial={
|
||||||
|
"score": answer.score,
|
||||||
|
"candidate_feedback": answer.candidate_feedback,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
@@ -838,9 +861,7 @@ def mark(request, exam_id, sk):
|
|||||||
|
|
||||||
user_answers = question.cid_user_answers.filter(exam__id=exam_id)
|
user_answers = question.cid_user_answers.filter(exam__id=exam_id)
|
||||||
|
|
||||||
unmarked_count = user_answers.filter(
|
unmarked_count = user_answers.filter(score=UserAnswer.ScoreOptions.UNMARKED).count()
|
||||||
score=UserAnswer.ScoreOptions.UNMARKED
|
|
||||||
).count()
|
|
||||||
|
|
||||||
if exam.double_mark:
|
if exam.double_mark:
|
||||||
marker_unmarked_count = question.get_unmarked_user_answer_count(
|
marker_unmarked_count = question.get_unmarked_user_answer_count(
|
||||||
@@ -1125,6 +1146,7 @@ def long_series_order_upload_filename(request, pk):
|
|||||||
|
|
||||||
GenericExamViews = ExamViews(Exam, Long, None, UserAnswer, "longs", "long")
|
GenericExamViews = ExamViews(Exam, Long, None, UserAnswer, "longs", "long")
|
||||||
|
|
||||||
|
|
||||||
class ExamCreate(ExamCreateBase):
|
class ExamCreate(ExamCreateBase):
|
||||||
model = Exam
|
model = Exam
|
||||||
form_class = ExamForm
|
form_class = ExamForm
|
||||||
@@ -1138,11 +1160,18 @@ class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
|||||||
class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
|
class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
|
||||||
model = Exam
|
model = Exam
|
||||||
|
|
||||||
|
|
||||||
class ExamClone(ExamCloneMixin, ExamCreate):
|
class ExamClone(ExamCloneMixin, ExamCreate):
|
||||||
"""Clone exam view"""
|
"""Clone exam view"""
|
||||||
|
|
||||||
|
|
||||||
class ExamAuthorUpdate(RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
|
class ExamAuthorUpdate(
|
||||||
|
RevisionMixin,
|
||||||
|
CheckCanEditMixin,
|
||||||
|
LoginRequiredMixin,
|
||||||
|
AuthorRequiredMixin,
|
||||||
|
UpdateView,
|
||||||
|
):
|
||||||
model = Exam
|
model = Exam
|
||||||
form_class = ExamAuthorForm
|
form_class = ExamAuthorForm
|
||||||
template_name = "author_form.html"
|
template_name = "author_form.html"
|
||||||
|
|||||||
Reference in New Issue
Block a user