smoke test the rest of the apps

This commit is contained in:
Ross
2026-06-10 22:05:12 +01:00
parent e670adf2d0
commit 6bb9441748
55 changed files with 1454 additions and 109 deletions
+20 -15
View File
@@ -971,26 +971,21 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
def clone_model(self):
M2M_fields = (
"exam_questions",
"author",
"valid_cid_users",
"valid_user_users",
"cid_user_groups",
"user_user_groups",
)
m2m_fields = [f.name for f in self._meta.many_to_many]
FK_fields = ("examcollection",)
model_dict = model_to_dict(self)
m2m_to_update = {}
for field in M2M_fields:
m2m_to_update[field] = model_dict[field]
del model_dict[field]
for field in m2m_fields:
if field in model_dict:
m2m_to_update[field] = model_dict[field]
del model_dict[field]
fk_to_update = {}
for field in FK_fields:
fk_to_update[field] = model_dict[field]
del model_dict[field]
if field in model_dict:
fk_to_update[field] = model_dict[field]
del model_dict[field]
cloned_model = self.__class__(**model_dict)
cloned_model.pk = None
@@ -2381,7 +2376,17 @@ class FindingBase(models.Model):
return json.dumps(self.annotation_json)
def get_pretty_annotation_json(self):
return get_pretty_json(json.loads(self.annotation_json))
if not self.annotation_json:
return ""
try:
return get_pretty_json(json.loads(self.annotation_json))
except Exception:
return ""
def get_pretty_viewport_json(self):
return get_pretty_json(json.loads(self.viewport_json))
if not self.viewport_json:
return ""
try:
return get_pretty_json(json.loads(self.viewport_json))
except Exception:
return ""
+14
View File
@@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block content %}
<div class="container my-5">
<div class="card bg-dark text-white border-secondary">
<div class="card-body">
<h1 class="card-title mb-4">{{ app_name|title }} Help</h1>
<p class="card-text">Welcome to the radiology education platform. There is no custom help document for the <strong>{{ app_name }}</strong> application yet.</p>
<hr class="border-secondary my-4">
<a href="javascript:history.back()" class="btn btn-secondary">Go Back</a>
</div>
</div>
</div>
{% endblock %}
+117
View File
@@ -0,0 +1,117 @@
import pytest
from django.urls import reverse
from django.contrib.auth.models import Group, User
from django.test import Client
from generic.models import Examination, Supervisor, CidUserGroup, UserUserGroup, CidUser
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 cid_manager_client(db):
user = User.objects.create_user(username="manager", password="password", email="manager@test.com")
g, _ = Group.objects.get_or_create(name="cid_user_manager")
user.groups.add(g)
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")
g, _ = Group.objects.get_or_create(name="cid_user_manager")
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, create_user, create_examination):
supervisor = Supervisor.objects.create(
email="sup@test.com",
name="Test Supervisor",
user=create_user
)
cid_group = CidUserGroup.objects.create(name="Cid Group")
user_group = UserUserGroup.objects.create(name="User Group")
cid_user = CidUser.objects.create(cid=99999, name="Test Cid User", email="cid@test.com", passcode="smokepasscode")
return {
"examination": create_examination,
"supervisor": supervisor,
"cid_group": cid_group,
"user_group": user_group,
"cid_user": cid_user,
}
URL_SPECS = [
("generic:examination_view", None),
("generic:create_examination", None),
("generic:examination_detail", lambda d: {"pk": d["examination"].pk}),
("generic:examination_update", lambda d: {"pk": d["examination"].pk}),
("generic:examination_delete", lambda d: {"pk": d["examination"].pk}),
("generic:supervisor", None),
("generic:supervisor_create", None),
("generic:supervisor_detail", lambda d: {"pk": d["supervisor"].pk}),
("generic:supervisor_edit", lambda d: {"pk": d["supervisor"].pk}),
("generic:supervisor_delete", lambda d: {"pk": d["supervisor"].pk}),
("generic:supervisor_overview", lambda d: {"pk": d["supervisor"].pk}),
("generic:manage_cids", None),
("generic:manage_cid_exams", None),
("generic:cid_group_view", None),
("generic:cid_group_create", None),
("generic:cid_group_detail", lambda d: {"group_id": d["cid_group"].pk}),
("generic:cid_group_update", lambda d: {"pk": d["cid_group"].pk}),
("generic:cid_group_delete", lambda d: {"pk": d["cid_group"].pk}),
("generic:user_group_view", None),
("generic:user_group_create", None),
("generic:user_group_detail", lambda d: {"group_id": d["user_group"].pk}),
("generic:user_group_update", lambda d: {"pk": d["user_group"].pk}),
("generic:user_group_delete", lambda d: {"pk": d["user_group"].pk}),
]
@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, 403, 404}, 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, 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_manager(cid_manager_client, smoke_data, url_name, kwargs_lambda):
kwargs = kwargs_lambda(smoke_data) if kwargs_lambda else None
url = reverse(url_name, kwargs=kwargs)
response = cid_manager_client.get(url, follow=False)
assert response.status_code != 500, f"500 Server Error on {url_name} ({url}) for Manager"
assert response.status_code in SAFE_STATUS_CODES, f"Unexpected status code {response.status_code} on {url_name} for Manager"
@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}, f"Unexpected status code {response.status_code} on {url_name} for Superuser"
+11 -3
View File
@@ -4085,7 +4085,13 @@ class GenericViewBase:
return False
def help(self, request):
return render(request, f"{self.app_name}/help.html", {"app_name": self.app_name})
from django.template.loader import TemplateDoesNotExist, get_template
try:
get_template(f"{self.app_name}/help.html")
template_name = f"{self.app_name}/help.html"
except TemplateDoesNotExist:
template_name = "generic/help.html"
return render(request, template_name, {"app_name": self.app_name})
def question_review(self, request, pk):
"""
@@ -6456,6 +6462,7 @@ class UpdateQuestionMixin(RedirectMixin, RevisionMixin, UpdateView):
raise PermissionDenied() # or Http404
@login_required
def supervisor_overview(request, pk):
supervisor = get_object_or_404(Supervisor, pk=pk)
@@ -6463,7 +6470,7 @@ def supervisor_overview(request, pk):
try:
if not request.user.supervisor == supervisor:
raise PermissionDenied()
except Supervisor.RelatedObjectDoesNotExist:
except User.supervisor.RelatedObjectDoesNotExist:
raise PermissionDenied()
trainees = User.objects.filter(userprofile__supervisor=supervisor)
@@ -6475,6 +6482,7 @@ def supervisor_overview(request, pk):
)
@login_required
def supervisor_trainee(request, pk, trainee_id):
supervisor = get_object_or_404(Supervisor, pk=pk)
@@ -6482,7 +6490,7 @@ def supervisor_trainee(request, pk, trainee_id):
try:
if not request.user.supervisor == supervisor:
raise PermissionDenied()
except Supervisor.RelatedObjectDoesNotExist:
except User.supervisor.RelatedObjectDoesNotExist:
raise PermissionDenied()
# We do this to check that the supervisor can (should) see the trainee results
+14 -1
View File
@@ -21,7 +21,20 @@ class UserSearchSelectMultipleWidget:
def __init__(self, name, value):
self.name = name
self.value = value or []
if value is None:
self.value = []
elif hasattr(value, "values_list"):
self.value = list(value.values_list("pk", flat=True))
elif isinstance(value, (list, tuple, set)):
self.value = [v.pk if hasattr(v, "pk") else v for v in value]
elif hasattr(value, "pk"):
self.value = [value.pk]
elif isinstance(value, (int, str)):
self.value = [value]
elif hasattr(value, "__iter__") and not isinstance(value, (str, bytes)):
self.value = [v.pk if hasattr(v, "pk") else v for v in value]
else:
self.value = [value]
def render(self):
field_id = f"id_{self.name}"