smoke test the rest of the apps
This commit is contained in:
+5
-4
@@ -86,10 +86,11 @@ class QuestionUserAnswerFilter(django_filters.FilterSet):
|
||||
user=None,
|
||||
request=None,
|
||||
):
|
||||
if not request.user.groups.filter(name="shorts_checker").exists():
|
||||
queryset = queryset.filter(open_access=True) | queryset.filter(
|
||||
author__id=request.user.id
|
||||
)
|
||||
if not request.user.is_superuser:
|
||||
if not request.user.groups.filter(name="shorts_checker").exists():
|
||||
queryset = queryset.filter(exam__open_access=True) | queryset.filter(
|
||||
exam__author__id=request.user.id
|
||||
)
|
||||
super(QuestionUserAnswerFilter, self).__init__(
|
||||
data=data, queryset=queryset, prefix=prefix, request=request
|
||||
)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{% extends 'shorts/base.html' %}
|
||||
{% block content %}
|
||||
<form method="post">{% csrf_token %}
|
||||
<p>Are you sure you want to delete "{{ object }}"?</p>
|
||||
<input type="submit" value="Confirm" class="btn btn-danger">
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,8 @@
|
||||
<div id="single-dicom-viewer" class="dicom-viewer" data-images="" data-annotations=''>
|
||||
</div>
|
||||
<button class="previous-btn">Previous</button>
|
||||
<button class="next-btn">Next</button>
|
||||
|
||||
{% for id in ids %}
|
||||
{{id}}
|
||||
{% endfor %}
|
||||
@@ -0,0 +1,31 @@
|
||||
{% extends 'shorts/base.html' %}
|
||||
|
||||
{% load render_table from django_tables2 %}
|
||||
{% block css %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div id="view-filter-options">
|
||||
<h3>Shorts User Answers</h3>
|
||||
<form action="" method="get">
|
||||
{{ filter.form }}
|
||||
<input class="btn btn-primary btn-sm mt-1 mb-1" type="submit" />
|
||||
</form>
|
||||
</div>
|
||||
{% render_table table %}
|
||||
<button id="delete-selected-button">Delete selected answers</button>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$("#delete-selected-button").on("click", () => {
|
||||
window.delete_multiple(
|
||||
"{% url 'shorts:user_answer_delete_multiple' %}", "{{ csrf_token }}"
|
||||
)
|
||||
})
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,12 @@
|
||||
{% extends 'shorts/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
CID: {{useranswer.cid}}<br/>
|
||||
User: {{useranswer.user}}<br/>
|
||||
Exam: <a href="{% url 'shorts:exam_overview' useranswer.exam.pk %}">{{useranswer.exam}}</a><br />
|
||||
Question: <a href="{% url 'shorts:question_detail' useranswer.question.pk %}">{{useranswer.question}}</a><br />
|
||||
Answer: <pre>{{useranswer.answer}}</pre>
|
||||
Score: {{useranswer.get_answer_score}}<br/>
|
||||
<a href="{% url 'shorts:user_answer_delete' useranswer.id %}">Delete answer</a>
|
||||
<a href="{% url 'admin:shorts_useranswer_change' useranswer.id %}">Admin Edit</a>
|
||||
{% endblock content %}
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1 @@
|
||||
# Package marker for tests
|
||||
@@ -0,0 +1,151 @@
|
||||
import pytest
|
||||
import tempfile
|
||||
from django.urls import reverse
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.test import Client
|
||||
|
||||
SAFE_STATUS_CODES = {200, 301, 302, 303, 403, 404, 405}
|
||||
|
||||
@pytest.fixture
|
||||
def candidate_client(db):
|
||||
user = User.objects.create_user(username="candidate", password="password", email="candidate@test.com")
|
||||
client = Client()
|
||||
client.force_login(user)
|
||||
return client
|
||||
|
||||
@pytest.fixture
|
||||
def superuser_client(db):
|
||||
user = User.objects.create_superuser(username="superuser", password="password", email="superuser@test.com")
|
||||
for group_name in ["shorts_checker", "cid_user_manager"]:
|
||||
g, _ = Group.objects.get_or_create(name=group_name)
|
||||
user.groups.add(g)
|
||||
client = Client()
|
||||
client.force_login(user)
|
||||
return client
|
||||
|
||||
@pytest.fixture
|
||||
def anonymous_client(db):
|
||||
return Client()
|
||||
|
||||
@pytest.fixture
|
||||
def smoke_data(db, settings, tmp_path):
|
||||
from shorts.models import Question, QuestionImage, Exam, UserAnswer, QuestionFinding
|
||||
from rapids.models import Examination
|
||||
settings.MEDIA_ROOT = str(tmp_path)
|
||||
|
||||
create_user = User.objects.create_user(username="smoke_user", email="smoke_user@test.com")
|
||||
|
||||
exm_name, _ = Examination.objects.get_or_create(examination="Test Examination")
|
||||
|
||||
question = Question.objects.create(
|
||||
history="Test History",
|
||||
marking_guidance="Test Guidance",
|
||||
)
|
||||
question.examination.add(exm_name)
|
||||
|
||||
# Mocking image
|
||||
image = tempfile.NamedTemporaryFile(
|
||||
dir=settings.MEDIA_ROOT, suffix=".jpg", delete=False
|
||||
)
|
||||
image.flush()
|
||||
image._committed = True
|
||||
image.write(b"12345ABCD")
|
||||
image.seek(0)
|
||||
q_image = QuestionImage(image=image.name, question=question)
|
||||
q_image.save()
|
||||
|
||||
exam = Exam.objects.create(
|
||||
name="Test Shorts Exam",
|
||||
exam_mode=True,
|
||||
active=True
|
||||
)
|
||||
exam.author.add(create_user)
|
||||
|
||||
from shorts.models import ExamQuestionDetail
|
||||
ExamQuestionDetail.objects.create(exam=exam, question=question, sort_order=1)
|
||||
|
||||
user_answer = UserAnswer.objects.create(
|
||||
user=create_user,
|
||||
exam=exam,
|
||||
question=question,
|
||||
answer="testanswer",
|
||||
score=2,
|
||||
)
|
||||
|
||||
finding = QuestionFinding.objects.create(
|
||||
question=question,
|
||||
description="Test Finding",
|
||||
)
|
||||
|
||||
return {
|
||||
"question": question,
|
||||
"exam": exam,
|
||||
"user_answer": user_answer,
|
||||
"finding": finding,
|
||||
}
|
||||
|
||||
URL_SPECS = [
|
||||
("shorts:question_viewer", None),
|
||||
("shorts:question_view", None),
|
||||
("shorts:question_json", lambda d: {"pk": d["question"].pk}),
|
||||
("shorts:question_json_unbased", lambda d: {"pk": d["question"].pk}),
|
||||
("shorts:question_save_annotation", lambda d: {"pk": d["question"].pk}),
|
||||
("shorts:question_split", lambda d: {"pk": d["question"].pk}),
|
||||
("shorts:question_clone", lambda d: {"pk": d["question"].pk}),
|
||||
("shorts:question_delete", lambda d: {"pk": d["question"].pk}),
|
||||
("shorts:question_anonymise_dicom", lambda d: {"pk": d["question"].pk}),
|
||||
("shorts:question_add_exam", lambda d: {"question_id": d["question"].pk}),
|
||||
("shorts:combine_images_side_by_side", lambda d: {"question_id": d["question"].pk}),
|
||||
("shorts:mark_answer", lambda d: {"exam_id": d["exam"].pk, "question_number": 0, "answer_id": d["user_answer"].pk}),
|
||||
("shorts:mark_answer_override", lambda d: {"exam_id": d["exam"].pk, "question_number": 0, "answer_id": d["user_answer"].pk}),
|
||||
("shorts:mark", lambda d: {"exam_pk": d["exam"].pk, "sk": 0}),
|
||||
("shorts:mark_all", lambda d: {"exam_pk": d["exam"].pk, "sk": 0}),
|
||||
("shorts:mark_review", lambda d: {"exam_pk": d["exam"].pk, "sk": 0}),
|
||||
("shorts:exam_markers", lambda d: {"pk": d["exam"].pk}),
|
||||
("shorts:exam_groups_edit", lambda d: {"pk": d["exam"].pk}),
|
||||
("shorts:exam_authors", lambda d: {"pk": d["exam"].pk}),
|
||||
("shorts:exam_create", None),
|
||||
("shorts:exam_clone", lambda d: {"exam_id": d["exam"].pk}),
|
||||
("shorts:exam_update", lambda d: {"pk": d["exam"].pk}),
|
||||
("shorts:exam_delete", lambda d: {"pk": d["exam"].pk}),
|
||||
("shorts:question_create", None),
|
||||
("shorts:question_create_exam", lambda d: {"pk": d["exam"].pk}),
|
||||
("shorts:question_update", lambda d: {"pk": d["question"].pk}),
|
||||
("shorts:question_sample_answers", lambda d: {"pk": d["question"].pk}),
|
||||
("shorts:user_answer_table_view", None),
|
||||
("shorts:user_answer_view", lambda d: {"pk": d["user_answer"].pk}),
|
||||
("shorts:user_answer_delete", lambda d: {"pk": d["user_answer"].pk}),
|
||||
("shorts:question_findings", lambda d: {"question_id": d["question"].pk}),
|
||||
("shorts:add_finding", None),
|
||||
("shorts:delete_finding", lambda d: {"pk": d["finding"].pk}),
|
||||
("shorts:question_edit_finding", lambda d: {"question_id": d["question"].pk, "finding_pk": d["finding"].pk}),
|
||||
("shorts:index", None),
|
||||
("shorts:help", None),
|
||||
]
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize("url_name, kwargs_lambda", URL_SPECS)
|
||||
def test_url_smoke_anonymous(anonymous_client, smoke_data, url_name, kwargs_lambda):
|
||||
kwargs = kwargs_lambda(smoke_data) if kwargs_lambda else None
|
||||
url = reverse(url_name, kwargs=kwargs)
|
||||
response = anonymous_client.get(url, follow=False)
|
||||
assert response.status_code != 500, f"500 Server Error on {url_name} ({url}) for Anonymous"
|
||||
assert response.status_code in {200, 302, 400, 403, 404, 405}, f"Unexpected status code {response.status_code} on {url_name} for Anonymous"
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize("url_name, kwargs_lambda", URL_SPECS)
|
||||
def test_url_smoke_candidate(candidate_client, smoke_data, url_name, kwargs_lambda):
|
||||
kwargs = kwargs_lambda(smoke_data) if kwargs_lambda else None
|
||||
url = reverse(url_name, kwargs=kwargs)
|
||||
response = candidate_client.get(url, follow=False)
|
||||
assert response.status_code != 500, f"500 Server Error on {url_name} ({url}) for Candidate"
|
||||
assert response.status_code in SAFE_STATUS_CODES or response.status_code == 400, f"Unexpected status code {response.status_code} on {url_name} for Candidate"
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize("url_name, kwargs_lambda", URL_SPECS)
|
||||
def test_url_smoke_superuser(superuser_client, smoke_data, url_name, kwargs_lambda):
|
||||
kwargs = kwargs_lambda(smoke_data) if kwargs_lambda else None
|
||||
url = reverse(url_name, kwargs=kwargs)
|
||||
response = superuser_client.get(url, follow=False)
|
||||
assert response.status_code != 500, f"500 Server Error on {url_name} ({url}) for Superuser"
|
||||
assert response.status_code in {200, 301, 302, 303, 400, 403, 404, 405}, f"Unexpected status code {response.status_code} on {url_name} for Superuser"
|
||||
+9
-5
@@ -107,6 +107,8 @@ def normaliseScore(score):
|
||||
class AuthorOrCheckerRequiredMixin(object):
|
||||
def get_object(self, *args, **kwargs):
|
||||
obj = super().get_object(*args, **kwargs)
|
||||
if self.request.user.is_superuser:
|
||||
return obj
|
||||
if self.request.user.groups.filter(name="shorts_checker").exists():
|
||||
return obj
|
||||
if self.request.user not in obj.author.all():
|
||||
@@ -351,8 +353,8 @@ def mark_all(request, exam_pk, sk):
|
||||
def mark_review(request, exam_pk, sk):
|
||||
return mark(request, exam_pk, sk, unmarked_exam_answers_only=False, review=True)
|
||||
|
||||
def mark_answer_override(request, exam_id, question_number, cid):
|
||||
return mark_answer(request, exam_id, question_number, cid, override=True)
|
||||
def mark_answer_override(request, exam_id, question_number, answer_id):
|
||||
return mark_answer(request, exam_id, question_number, answer_id, override=True)
|
||||
|
||||
|
||||
def mark_answer(request, exam_id, question_number, answer_id, override=False):
|
||||
@@ -548,9 +550,8 @@ def mark_answer(request, exam_id, question_number, answer_id, override=False):
|
||||
},
|
||||
)
|
||||
|
||||
# @user_passes_test(user_is_admin, login_url="/accounts/login")
|
||||
@login_required
|
||||
def mark(request, exam_pk, sk, unmarked_exam_answers_only=True):
|
||||
def mark(request, exam_pk, sk, unmarked_exam_answers_only=True, review=False):
|
||||
exam = get_object_or_404(Exam, pk=exam_pk)
|
||||
|
||||
if not GenericExamViews.check_user_marker_access(request.user, exam_pk):
|
||||
@@ -591,6 +592,7 @@ def mark(request, exam_pk, sk, unmarked_exam_answers_only=True):
|
||||
"user_answers": user_answers,
|
||||
"unmarked_count": unmarked_count,
|
||||
"marker_unmarked_count": marker_unmarked_count,
|
||||
"review": review,
|
||||
},
|
||||
)
|
||||
else:
|
||||
@@ -603,6 +605,7 @@ def mark(request, exam_pk, sk, unmarked_exam_answers_only=True):
|
||||
"question_details": question_details,
|
||||
"user_answers": user_answers,
|
||||
"unmarked_count": unmarked_count,
|
||||
"review": review,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -756,7 +759,8 @@ def question_json(request, pk):
|
||||
def question_viewer(request):
|
||||
# question = get_object_or_404(Question, pk=pk)
|
||||
|
||||
ids = request.GET.get("ids").split(",")
|
||||
ids_param = request.GET.get("ids")
|
||||
ids = ids_param.split(",") if ids_param else []
|
||||
|
||||
return render(
|
||||
request,
|
||||
|
||||
Reference in New Issue
Block a user