add (correct) support for logged in users to sba and physics exams
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
<h2>Start exam: {{exam.name}}</h2>
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
{% if request.user.is_authenticated and valid_user %}
|
||||
<a href="{% url 'physics:exam_take_user' pk=exam.pk sk=0 %}">Start Exam</a>
|
||||
{% else %}
|
||||
Enter your CID and passcode in the below boxes.<br />
|
||||
|
||||
+182
-120
@@ -27,10 +27,6 @@ from django.contrib.auth.models import User
|
||||
APP_NAME = "physics"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def create_exam(db):
|
||||
exam: Exam = PhysicsExamViews.Exam.objects.create(
|
||||
name="Cid Exam", exam_mode=True, active=True
|
||||
@@ -43,7 +39,7 @@ def create_exam(db):
|
||||
|
||||
exam.valid_cid_users.add(*group1.ciduser_set.all())
|
||||
|
||||
#exam.valid_user_users.add(user1)
|
||||
# exam.valid_user_users.add(user1)
|
||||
exam.valid_user_users.add(*usergroup1.users.all())
|
||||
|
||||
assert exam in group1.physics_cid_user_groups.all()
|
||||
@@ -105,7 +101,11 @@ def test_exams(db, client):
|
||||
|
||||
create_category(db)
|
||||
|
||||
generated_questions = [create_question(exam), create_question(exam), create_question(exam)]
|
||||
generated_questions = [
|
||||
create_question(exam),
|
||||
create_question(exam),
|
||||
create_question(exam),
|
||||
]
|
||||
|
||||
active_exams = client.get(reverse(f"{APP_NAME}:active_exams"))
|
||||
assert active_exams.status_code == 200
|
||||
@@ -116,12 +116,12 @@ def test_exams(db, client):
|
||||
cid_user_2001 = CidUser.objects.get(cid=2001)
|
||||
|
||||
user1 = User.objects.get(username="user1")
|
||||
|
||||
user2 = User.objects.get(username="user2")
|
||||
|
||||
users_tested = 1
|
||||
|
||||
for cid_user in [cid_user_1001, cid_user_1000, user1]:
|
||||
|
||||
# for cid_user in [cid_user_1001, cid_user_1000, user1, user2]:
|
||||
for cid_user in [cid_user_1001, cid_user_1000, user1, user2]:
|
||||
if isinstance(cid_user, CidUser):
|
||||
testing_cid_user = True
|
||||
else:
|
||||
@@ -129,7 +129,6 @@ def test_exams(db, client):
|
||||
current_user = cid_user
|
||||
client.force_login(cid_user)
|
||||
|
||||
|
||||
exam.active = True
|
||||
exam.publish_results = False
|
||||
exam.save()
|
||||
@@ -137,10 +136,15 @@ def test_exams(db, client):
|
||||
reverse(f"physics:exam_start", kwargs={"pk": exam.pk})
|
||||
)
|
||||
|
||||
print(cid_user)
|
||||
|
||||
# Check user access to the start page
|
||||
assert start_page_res.status_code == HTTPStatus.OK
|
||||
start_page_soup = BeautifulSoup(start_page_res.content, "html.parser")
|
||||
if testing_cid_user:
|
||||
print(start_page_soup)
|
||||
if testing_cid_user or cid_user in [user2]:
|
||||
assert "Enter your CID and passcode" in str(start_page_soup)
|
||||
else:
|
||||
else:
|
||||
assert "Start Exam" in str(start_page_soup)
|
||||
|
||||
for n, question in enumerate(generated_questions):
|
||||
@@ -163,10 +167,15 @@ def test_exams(db, client):
|
||||
},
|
||||
)
|
||||
|
||||
cid_take_res = client.get(take_url)
|
||||
|
||||
# Check that users that have not been added cannot access the take pages
|
||||
if cid_user in [user2]:
|
||||
assert cid_take_res.status_code == HTTPStatus.NOT_FOUND
|
||||
continue
|
||||
else:
|
||||
assert cid_take_res.status_code == HTTPStatus.OK
|
||||
|
||||
cid_take_res = client.get(
|
||||
take_url
|
||||
)
|
||||
cid_take_soup = BeautifulSoup(cid_take_res.content, "html.parser")
|
||||
|
||||
# Check correct user is displayed
|
||||
@@ -174,10 +183,7 @@ def test_exams(db, client):
|
||||
display_user = str(cid_user.cid)
|
||||
else:
|
||||
display_user = current_user.username
|
||||
assert (
|
||||
display_user
|
||||
in cid_take_soup.find("span", {"id": "user-id"}).string
|
||||
)
|
||||
assert display_user in cid_take_soup.find("span", {"id": "user-id"}).string
|
||||
|
||||
# Check review mode alert not visible
|
||||
assert cid_take_soup.find("div", {"class": "review-mode-alert"}) is None
|
||||
@@ -216,7 +222,6 @@ def test_exams(db, client):
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
if testing_cid_user:
|
||||
take_overview_url = reverse(
|
||||
f"physics:exam_take_overview",
|
||||
@@ -234,27 +239,32 @@ def test_exams(db, client):
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
cid_take_overview_res = client.get(take_overview_url)
|
||||
|
||||
cid_take_overview_res = client.get(
|
||||
take_overview_url
|
||||
)
|
||||
cid_take_overview_soup = BeautifulSoup(
|
||||
cid_take_overview_res.content, "html.parser"
|
||||
)
|
||||
assert (
|
||||
"0 out of 3 questions answered"
|
||||
in cid_take_overview_soup.find(
|
||||
"div", attrs={"class": "overview-text"}
|
||||
).string
|
||||
)
|
||||
# Check that users that have not been added cannot access the take pages
|
||||
if cid_user in [user2]:
|
||||
assert cid_take_overview_res.status_code == HTTPStatus.NOT_FOUND
|
||||
else:
|
||||
assert cid_take_overview_res.status_code == HTTPStatus.OK
|
||||
|
||||
assert (
|
||||
len(
|
||||
cid_take_overview_soup.find_all("button", attrs={"class": "unanswered"})
|
||||
cid_take_overview_soup = BeautifulSoup(
|
||||
cid_take_overview_res.content, "html.parser"
|
||||
)
|
||||
assert (
|
||||
"0 out of 3 questions answered"
|
||||
in cid_take_overview_soup.find(
|
||||
"div", attrs={"class": "overview-text"}
|
||||
).string
|
||||
)
|
||||
|
||||
assert (
|
||||
len(
|
||||
cid_take_overview_soup.find_all(
|
||||
"button", attrs={"class": "unanswered"}
|
||||
)
|
||||
)
|
||||
== 3
|
||||
)
|
||||
== 3
|
||||
)
|
||||
|
||||
# Loop through questions again to test submissions
|
||||
# All physics questions have 5 parts (and require a csrf token)
|
||||
@@ -280,6 +290,13 @@ def test_exams(db, client):
|
||||
)
|
||||
|
||||
cid_take_res = client.get(question_url)
|
||||
|
||||
if cid_user in [user2]:
|
||||
assert cid_take_res.status_code == HTTPStatus.NOT_FOUND
|
||||
continue
|
||||
else:
|
||||
assert cid_take_res.status_code == HTTPStatus.OK
|
||||
|
||||
cid_take_soup = BeautifulSoup(cid_take_res.content, "html.parser")
|
||||
|
||||
# Extract csrftoken
|
||||
@@ -331,7 +348,6 @@ def test_exams(db, client):
|
||||
|
||||
assert all(cid_user_answer.get_answers())
|
||||
|
||||
|
||||
# Recheck the overview page (now that we have submitted answers)
|
||||
if testing_cid_user:
|
||||
take_overview_url = reverse(
|
||||
@@ -350,86 +366,119 @@ def test_exams(db, client):
|
||||
},
|
||||
)
|
||||
|
||||
cid_take_overview_res = client.get(
|
||||
take_overview_url
|
||||
)
|
||||
cid_take_overview_soup = BeautifulSoup(
|
||||
cid_take_overview_res.content, "html.parser"
|
||||
)
|
||||
assert (
|
||||
"3 out of 3 questions answered"
|
||||
in cid_take_overview_soup.find(
|
||||
"div", attrs={"class": "overview-text"}
|
||||
).string
|
||||
)
|
||||
cid_take_overview_res = client.get(take_overview_url)
|
||||
|
||||
assert (
|
||||
len(
|
||||
cid_take_overview_soup.find_all("button", attrs={"class": "unanswered"})
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
if testing_cid_user:
|
||||
# Get overview scores page (invalid passcode)
|
||||
AssertNotFound(client,
|
||||
reverse(f"cid_scores", kwargs={"cid": cid_user.cid, "passcode": "AAAA"})
|
||||
)
|
||||
|
||||
cid_scores_url = reverse(f"cid_scores", kwargs={"cid": cid_user.cid, "passcode": cid_user.passcode})
|
||||
if cid_user in [user2]:
|
||||
assert cid_take_overview_res.status_code == HTTPStatus.NOT_FOUND
|
||||
else:
|
||||
cid_scores_url = reverse(f"user_scores")
|
||||
assert cid_take_overview_res.status_code == HTTPStatus.OK
|
||||
|
||||
cid_take_overview_soup = BeautifulSoup(
|
||||
cid_take_overview_res.content, "html.parser"
|
||||
)
|
||||
assert (
|
||||
"3 out of 3 questions answered"
|
||||
in cid_take_overview_soup.find(
|
||||
"div", attrs={"class": "overview-text"}
|
||||
).string
|
||||
)
|
||||
|
||||
assert (
|
||||
len(
|
||||
cid_take_overview_soup.find_all(
|
||||
"button", attrs={"class": "unanswered"}
|
||||
)
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
if testing_cid_user:
|
||||
# Get overview scores page (invalid passcode)
|
||||
AssertNotFound(
|
||||
client,
|
||||
reverse(
|
||||
f"cid_scores", kwargs={"cid": cid_user.cid, "passcode": "AAAA"}
|
||||
),
|
||||
)
|
||||
|
||||
cid_scores_url = reverse(
|
||||
f"cid_scores",
|
||||
kwargs={"cid": cid_user.cid, "passcode": cid_user.passcode},
|
||||
)
|
||||
else:
|
||||
cid_scores_url = reverse(f"user_scores")
|
||||
|
||||
# Get overview scores page
|
||||
cid_scores_res = client.get(
|
||||
cid_scores_url
|
||||
)
|
||||
cid_scores_res = client.get(cid_scores_url)
|
||||
|
||||
assert cid_scores_res.status_code == HTTPStatus.OK
|
||||
|
||||
cid_scores_soup = BeautifulSoup(cid_scores_res.content, "html.parser")
|
||||
|
||||
search_exam = (
|
||||
cid_scores_soup.find("div", {"id": "exam-assigned"})
|
||||
.find("ul", {"class": exam.app_name.lower()})
|
||||
.find_all("li", attrs={"data-exam-id": exam.pk})
|
||||
)
|
||||
assert search_exam
|
||||
assert exam.name in str(search_exam)
|
||||
assert "Active" in str(search_exam)
|
||||
# assert "Active" in assigned_exams # check it is active
|
||||
if cid_user in [user2]:
|
||||
# page should load but we shouldn't be able to find the exam
|
||||
with pytest.raises(AttributeError):
|
||||
search_exam = (
|
||||
cid_scores_soup.find("div", {"id": "exam-assigned"})
|
||||
.find("ul", {"class": exam.app_name.lower()})
|
||||
.find_all("li", attrs={"data-exam-id": exam.pk})
|
||||
)
|
||||
else:
|
||||
search_exam = (
|
||||
cid_scores_soup.find("div", {"id": "exam-assigned"})
|
||||
.find("ul", {"class": exam.app_name.lower()})
|
||||
.find_all("li", attrs={"data-exam-id": exam.pk})
|
||||
)
|
||||
assert search_exam
|
||||
assert exam.name in str(search_exam)
|
||||
assert "Active" in str(search_exam)
|
||||
# assert "Active" in assigned_exams # check it is active
|
||||
|
||||
invalid_exam = cid_scores_soup.find_all(
|
||||
"li", attrs={"data-exam-id": exam.pk + 5}
|
||||
)
|
||||
assert not invalid_exam # Check we can't find an invalid exam
|
||||
|
||||
# Check that we have a results link
|
||||
results_exam = (
|
||||
cid_scores_soup.find("div", {"id": "exam-results"})
|
||||
.find("ul", {"class": exam.app_name})
|
||||
.find_all("li", attrs={"data-exam-id": exam.pk})
|
||||
)
|
||||
assert results_exam
|
||||
assert exam.name in str(results_exam)
|
||||
assert exam.name + " test" not in str(results_exam)
|
||||
assert "Results Published" not in str(
|
||||
results_exam
|
||||
) # It should not be published yet
|
||||
invalid_exam = cid_scores_soup.find_all(
|
||||
"li", attrs={"data-exam-id": exam.pk + 5}
|
||||
)
|
||||
assert not invalid_exam # Check we can't find an invalid exam
|
||||
|
||||
# Check that we have a results link
|
||||
results_exam = (
|
||||
cid_scores_soup.find("div", {"id": "exam-results"})
|
||||
.find("ul", {"class": exam.app_name})
|
||||
.find_all("li", attrs={"data-exam-id": exam.pk})
|
||||
)
|
||||
assert results_exam
|
||||
assert exam.name in str(results_exam)
|
||||
assert exam.name + " test" not in str(results_exam)
|
||||
assert "Results Published" not in str(
|
||||
results_exam
|
||||
) # It should not be published yet
|
||||
|
||||
if testing_cid_user:
|
||||
AssertNotFound(client,
|
||||
AssertNotFound(
|
||||
client,
|
||||
reverse(
|
||||
f"{exam.app_name}:exam_scores_cid_user",
|
||||
kwargs={"pk": exam.pk, "cid": cid_user.cid, "passcode": "AAAA"},
|
||||
)
|
||||
),
|
||||
)
|
||||
AssertNotFound(client,
|
||||
reverse(f"{exam.app_name}:exam_scores_cid_user", kwargs={"pk": exam.pk, "cid": cid_user_2001.cid, "passcode": cid_user_2001.passcode})
|
||||
AssertNotFound(
|
||||
client,
|
||||
reverse(
|
||||
f"{exam.app_name}:exam_scores_cid_user",
|
||||
kwargs={
|
||||
"pk": exam.pk,
|
||||
"cid": cid_user_2001.cid,
|
||||
"passcode": cid_user_2001.passcode,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
exam_scores_url = reverse(
|
||||
f"{exam.app_name}:exam_scores_cid_user",
|
||||
kwargs={"pk": exam.pk, "cid": cid_user.cid, "passcode": cid_user.passcode},
|
||||
kwargs={
|
||||
"pk": exam.pk,
|
||||
"cid": cid_user.cid,
|
||||
"passcode": cid_user.passcode,
|
||||
},
|
||||
)
|
||||
else:
|
||||
exam_scores_url = reverse(
|
||||
@@ -437,11 +486,15 @@ def test_exams(db, client):
|
||||
kwargs={"pk": exam.pk},
|
||||
)
|
||||
|
||||
|
||||
# Load the exam results page
|
||||
cid_exam_scores_res = client.get(
|
||||
exam_scores_url
|
||||
)
|
||||
cid_exam_scores_res = client.get(exam_scores_url)
|
||||
|
||||
if cid_user in [user2]:
|
||||
assert cid_exam_scores_res.status_code == HTTPStatus.NOT_FOUND
|
||||
continue
|
||||
else:
|
||||
assert cid_exam_scores_res.status_code == HTTPStatus.OK
|
||||
|
||||
cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")
|
||||
|
||||
# Check that we have an alert that exams are not published
|
||||
@@ -468,9 +521,7 @@ def test_exams(db, client):
|
||||
generated_questions.append(extra_question)
|
||||
|
||||
# Load the exam results page (again)
|
||||
cid_exam_scores_res = client.get(
|
||||
exam_scores_url
|
||||
)
|
||||
cid_exam_scores_res = client.get(exam_scores_url)
|
||||
cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")
|
||||
|
||||
answer_lis = cid_exam_scores_soup.find_all("li", {"class": "question-part"})
|
||||
@@ -493,9 +544,7 @@ def test_exams(db, client):
|
||||
exam.save()
|
||||
|
||||
# Load the exam results page (again)
|
||||
cid_exam_scores_res = client.get(
|
||||
exam_scores_url
|
||||
)
|
||||
cid_exam_scores_res = client.get(exam_scores_url)
|
||||
cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")
|
||||
|
||||
assert (
|
||||
@@ -541,14 +590,30 @@ def test_exams(db, client):
|
||||
|
||||
# answer the final question
|
||||
if testing_cid_user:
|
||||
cid_user_answer = UserAnswer(question=extra_question, a=True, b=False, c=True, d=False, e=True, cid=cid_user.cid, exam=exam)
|
||||
cid_user_answer = UserAnswer(
|
||||
question=extra_question,
|
||||
a=True,
|
||||
b=False,
|
||||
c=True,
|
||||
d=False,
|
||||
e=True,
|
||||
cid=cid_user.cid,
|
||||
exam=exam,
|
||||
)
|
||||
else:
|
||||
cid_user_answer = UserAnswer(question=extra_question, a=True, b=False, c=True, d=False, e=True, user=current_user, exam=exam)
|
||||
cid_user_answer = UserAnswer(
|
||||
question=extra_question,
|
||||
a=True,
|
||||
b=False,
|
||||
c=True,
|
||||
d=False,
|
||||
e=True,
|
||||
user=current_user,
|
||||
exam=exam,
|
||||
)
|
||||
cid_user_answer.save()
|
||||
|
||||
cid_exam_scores_res = client.get(
|
||||
exam_scores_url
|
||||
)
|
||||
cid_exam_scores_res = client.get(exam_scores_url)
|
||||
cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")
|
||||
|
||||
# As we have answered correctly the new score should be increased by 5
|
||||
@@ -559,7 +624,6 @@ def test_exams(db, client):
|
||||
== "14"
|
||||
)
|
||||
|
||||
|
||||
# Check what happens when exam is not active
|
||||
exam.active = False
|
||||
exam.save()
|
||||
@@ -589,11 +653,9 @@ def test_exams(db, client):
|
||||
"sk": 0,
|
||||
},
|
||||
)
|
||||
cid_take_res = client.get(
|
||||
cid_take_url
|
||||
)
|
||||
cid_take_res = client.get(cid_take_url)
|
||||
cid_take_soup = BeautifulSoup(cid_take_res.content, "html.parser")
|
||||
assert "Exam is currently inactive" in cid_take_soup.text
|
||||
|
||||
generated_questions.pop(-1).delete()
|
||||
users_tested = users_tested + 1
|
||||
users_tested = users_tested + 1
|
||||
|
||||
+1
-5
@@ -9,13 +9,8 @@ urlpatterns = []
|
||||
|
||||
urlpatterns.extend(
|
||||
[
|
||||
# path("question/", views.QuestionView.as_view(), name="question_view"),
|
||||
path("question/", views.QuestionView.as_view(), name="question_view"),
|
||||
path("question/<int:pk>/", views.question_detail, name="question_detail"),
|
||||
# path("question/create/", views.QuestionCreate.as_view(), name="anatomy_question_create"),
|
||||
# path("question/<int:pk>/update", views.QuestionUpdate.as_view(), name="anatomy_question_update"),
|
||||
# path("exam/<int:pk>/<int:sk>/mark", views.mark, name="mark"),
|
||||
# path("exam/<int:pk>/mark", views.mark_overview, name="mark_overview"),
|
||||
path("exam/<int:pk>/take_old", views.exam_take_old, name="exam_take_old"),
|
||||
path(
|
||||
"exam/<int:pk>/<int:sk>/<str:cid>/<str:passcode>/take",
|
||||
@@ -64,6 +59,7 @@ urlpatterns.extend(
|
||||
views.UserAnswerDelete.as_view(),
|
||||
name="user_answer_delete",
|
||||
),
|
||||
# TODO: consider merging with generic...
|
||||
path(
|
||||
"exam/<int:pk>/scores/<int:cid>/<str:passcode>/",
|
||||
views.exam_scores_cid_user,
|
||||
|
||||
+12
-13
@@ -99,13 +99,15 @@ def active_exams(request):
|
||||
active_exams.append(exam)
|
||||
return render(request, "physics/available_exam_list.html", {"exams": active_exams})
|
||||
|
||||
|
||||
def exam_scores_cid_user(request, pk, cid=None, passcode=None):
|
||||
exam = get_object_or_404(Exam, pk=pk)
|
||||
|
||||
if not exam.exam_mode:
|
||||
raise Http404("Packet not in exam mode")
|
||||
|
||||
if cid is not None and not exam.check_cid_user(cid, passcode, request):
|
||||
print(cid, passcode, request.user)
|
||||
if not exam.check_cid_user(cid, passcode, request):
|
||||
raise Http404("Error accessing exam")
|
||||
|
||||
questions = exam.exam_questions.all()
|
||||
@@ -168,7 +170,6 @@ def exam_scores_cid_user(request, pk, cid=None, passcode=None):
|
||||
|
||||
|
||||
def exam_take_old(request, pk):
|
||||
|
||||
exam = get_object_or_404(Exam, pk=pk)
|
||||
|
||||
if not exam.active:
|
||||
@@ -197,6 +198,9 @@ def exam_start(request, pk):
|
||||
"physics/exam_start.html",
|
||||
{
|
||||
"exam": exam,
|
||||
# This may be overly comlicated but if the logged in user
|
||||
# does not have access we give the option to take it as a cid user
|
||||
"valid_user": exam.check_logged_in_user(request),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -204,12 +208,11 @@ def exam_start(request, pk):
|
||||
def exam_take_overview(request, pk, cid=None, passcode=None):
|
||||
exam = get_object_or_404(Exam, pk=pk)
|
||||
|
||||
if cid is not None and not exam.check_cid_user(cid, passcode):
|
||||
raise Http404("Error accessing exam")
|
||||
|
||||
if not exam.active:
|
||||
return exam_inactive(request, context={"exam": exam})
|
||||
|
||||
if not exam.check_cid_user(cid, passcode, request):
|
||||
raise Http404("Error accessing exam")
|
||||
|
||||
questions = exam.exam_questions.all()
|
||||
|
||||
@@ -253,14 +256,13 @@ def exam_take_overview(request, pk, cid=None, passcode=None):
|
||||
)
|
||||
|
||||
|
||||
def exam_take(request, pk, sk, cid=None, passcode=None):
|
||||
|
||||
def exam_take(request, pk: int, sk: int, cid: str = None, passcode: str = None):
|
||||
exam = get_object_or_404(Exam, pk=pk)
|
||||
|
||||
if not exam.active:
|
||||
return exam_inactive(request, context={"exam": exam})
|
||||
|
||||
if cid is not None and not exam.check_cid_user(cid, passcode):
|
||||
if not exam.check_cid_user(cid, passcode, request):
|
||||
raise Http404("Error accessing exam")
|
||||
|
||||
if cid is not None:
|
||||
@@ -329,7 +331,6 @@ def exam_take(request, pk, sk, cid=None, passcode=None):
|
||||
else:
|
||||
form = UserAnswerForm(instance=answer)
|
||||
|
||||
|
||||
saved_answer = False
|
||||
if answer is not None:
|
||||
saved_answer = [answer.a, answer.b, answer.c, answer.d, answer.e]
|
||||
@@ -346,6 +347,7 @@ def exam_take(request, pk, sk, cid=None, passcode=None):
|
||||
"previous": previous,
|
||||
"exam_length": exam_length,
|
||||
"pos": pos,
|
||||
"saved_answer": saved_answer,
|
||||
"passcode": passcode,
|
||||
"cid_user_exam": cid_user_exam,
|
||||
},
|
||||
@@ -413,6 +415,7 @@ GenericExamViews = ExamViews(Exam, Question, None, UserAnswer, "physics", "physi
|
||||
|
||||
GenericViews = GenericViewBase("physics", Question, UserAnswer, Exam)
|
||||
|
||||
|
||||
class ExamCreate(ExamCreateBase):
|
||||
model = Exam
|
||||
form_class = ExamForm
|
||||
@@ -427,7 +430,6 @@ class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
|
||||
model = Exam
|
||||
|
||||
|
||||
|
||||
class ExamClone(AuthorOrCheckerRequiredMixin, ExamCloneMixin, ExamCreate):
|
||||
"""Clone exam view"""
|
||||
|
||||
@@ -439,8 +441,6 @@ class ExamAuthorUpdate(
|
||||
form_class = ExamAuthorForm
|
||||
|
||||
|
||||
|
||||
|
||||
class QuestionView(
|
||||
LoginRequiredMixin, SingleTableMixin, FilterView, AuthorOrCheckerRequiredMixin
|
||||
):
|
||||
@@ -456,7 +456,6 @@ class QuestionView(
|
||||
return context
|
||||
|
||||
|
||||
|
||||
class UserAnswerView(AuthorOrCheckerRequiredMixin, LoginRequiredMixin, DetailView):
|
||||
model = UserAnswer
|
||||
|
||||
|
||||
Reference in New Issue
Block a user