Compare commits

..
15 Commits
Author SHA1 Message Date
Ross cf9b514597 a few minor changes 2024-09-01 18:54:27 +01:00
Ross 98d45fbdfc allow rodering questions 2024-08-28 18:59:35 +01:00
Ross 0054d832cd fix a few sba related things 2024-08-28 18:45:08 +01:00
Ross f5dcb7bd69 . 2024-08-28 18:08:07 +01:00
Ross 850d9637c3 update rcr 2024-08-28 18:06:18 +01:00
Ross ca071168c5 . 2024-08-19 17:11:13 +01:00
Ross 9154fa699c . 2024-08-19 17:10:29 +01:00
Ross ce5623e251 . 2024-08-19 17:09:49 +01:00
Ross 4205d4c6d6 . 2024-08-19 17:05:28 +01:00
Ross 5de5367978 . 2024-08-19 16:58:33 +01:00
Ross 441c691832 fix vieving feedback 2024-08-19 16:46:01 +01:00
Ross 062d7e7827 . 2024-08-19 16:26:30 +01:00
Ross 672e3adec1 . 2024-08-19 16:22:58 +01:00
Ross b5dc3b3a61 . 2024-08-19 16:15:09 +01:00
Ross 58eeb4f442 fix a few missing things 2024-08-19 13:16:47 +01:00
25 changed files with 237 additions and 83 deletions
+1
View File
@@ -53,6 +53,7 @@ urlpatterns = [
"exam/<int:exam_pk>/<int:sk>/mark/review", views.mark_review, name="mark_review" "exam/<int:exam_pk>/<int:sk>/mark/review", views.mark_review, name="mark_review"
), ),
path("exam/<int:pk>/markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"), path("exam/<int:pk>/markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"),
path("exam/<int:pk>/groups", views.ExamGroupsUpdate.as_view(), name="exam_groups_edit"),
path("exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"), path("exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"),
path("exam/create", views.ExamCreate.as_view(), name="exam_create"), path("exam/create", views.ExamCreate.as_view(), name="exam_create"),
path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"), path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"),
+4 -1
View File
@@ -57,7 +57,7 @@ from autocomplete import widgets as htmx_widgets
import logging import logging
from generic.forms import ExamAuthorFormMixin from generic.forms import ExamAuthorFormMixin, ExamGroupsFormMixin
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
@@ -749,6 +749,9 @@ class SeriesAuthorForm(ExamAuthorFormMixin):
class Meta(ExamAuthorFormMixin.Meta): class Meta(ExamAuthorFormMixin.Meta):
model = Series model = Series
class ExamGroupsForm(ExamGroupsFormMixin):
class Meta(ExamGroupsFormMixin.Meta):
model = CaseCollection
class AnswerJSONForm(Form): class AnswerJSONForm(Form):
+8
View File
@@ -1188,6 +1188,14 @@ class SelfReview(models.Model):
review_date = models.DateTimeField(auto_now_add=True) review_date = models.DateTimeField(auto_now_add=True)
review_update_date = models.DateTimeField(auto_now=True) review_update_date = models.DateTimeField(auto_now=True)
def __str__(self) -> str:
date = self.review_date
if self.review_update_date is not None:
date = self.review_update_date
return f"Self review: {date:%Y-%m-%d} / {self.user_exam.exam} - {self.case}"
def get_absolute_url(self): def get_absolute_url(self):
return self.user_exam.exam.get_case_take_url(self.case) return self.user_exam.exam.get_case_take_url(self.case)
@@ -168,10 +168,10 @@
</div> </div>
<div>
{% if collection.self_review %} {% if collection.self_review %}
<div>
<p> <p>
<a href="{% url 'atlas:add_self_review' cid_user_exam.id case.id %}"><button>Add self review</button></a> <a href="{% url 'atlas:add_self_review' cid_user_exam.id case.id %}"><button type="button">Add self review</button></a>
</p> </p>
{% if self_review %} {% if self_review %}
<h4>Self Feedback</h4> <h4>Self Feedback</h4>
@@ -181,12 +181,12 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</div>
{% else %} {% else %}
<h4>Answer score: {{answer.score}}</h4> <h4>Answer score: {{answer.score}}</h4>
Answer feedback: {{answer.feedback|safe}} Answer feedback: {{answer.feedback|safe}}
<br/> <br/>
{% endif %} {% endif %}
</div>
<details><summary class="opacity-50">View questions</summary> <details><summary class="opacity-50">View questions</summary>
{{form.json.errors}} {{form.json.errors}}
+5 -1
View File
@@ -7,7 +7,11 @@
{% block content %} {% block content %}
<div class="container-fluid"> <div class="container-fluid">
Create <a href='{{request.path}}create'>new</a><br/> Create a new <a href='{{request.path}}create'>collection</a> / <a href="{% url 'atlas:user_collections' %}">Collections to view / take</a>
<h2>My Collections</h2>
<details> <details>
<summary> <summary>
<h4>Filter</h4> <h4>Filter</h4>
+1
View File
@@ -111,6 +111,7 @@ urlpatterns = [
views.GenericExamViews.exam_users_edit, views.GenericExamViews.exam_users_edit,
name="exam_users_edit", name="exam_users_edit",
), ),
path("exam/<int:pk>/groups", views.ExamGroupsUpdate.as_view(), name="exam_groups_edit"),
path( path(
"collection/<int:exam_id>/case/<int:case_id>/details", "collection/<int:exam_id>/case/<int:case_id>/details",
views.collection_case_details, views.collection_case_details,
+18 -2
View File
@@ -47,6 +47,7 @@ from .forms import (
CidReportAnswerMarkForm, CidReportAnswerMarkForm,
ConditionAutocompleteForm, ConditionAutocompleteForm,
ConditionForm, ConditionForm,
ExamGroupsForm,
FindingForm, FindingForm,
JsonAnswerForm, JsonAnswerForm,
PriorCaseForm, PriorCaseForm,
@@ -144,6 +145,7 @@ from django.forms.models import model_to_dict
from generic.views import ( from generic.views import (
AuthorRequiredMixin, AuthorRequiredMixin,
ExamCloneMixin, ExamCloneMixin,
ExamGroupsUpdateBase,
ExamViews, ExamViews,
SeriesImagesZipViewBase, SeriesImagesZipViewBase,
) )
@@ -2713,6 +2715,15 @@ class CaseCollectionAuthorUpdate(RevisionMixin, AuthorRequiredMixin, UpdateView)
context["collection"] = context["object"] context["collection"] = context["object"]
return context return context
class ExamGroupsUpdate(ExamGroupsUpdateBase):
model = CaseCollection
form_class = ExamGroupsForm
def get_context_data(self, **kwargs):
context = super(ExamGroupsUpdate, self).get_context_data(**kwargs)
context["exam"] = context["object"]
return context
class CaseAuthorUpdate(RevisionMixin, AuthorRequiredMixin, UpdateView): class CaseAuthorUpdate(RevisionMixin, AuthorRequiredMixin, UpdateView):
model = Case model = Case
@@ -2816,8 +2827,12 @@ def collection_reset_answers(request, exam_id: int):
case_details = collection.casedetail_set.all().prefetch_related() case_details = collection.casedetail_set.all().prefetch_related()
# Delete all answers # Delete all answers
cid_answers = case_details.cidreportanswer_set.all() for case in case_details:
user_answers = case_details.userreportanswer_set.all() cid_answers = case.cidreportanswer_set.all()
cid_answers.delete()
user_answers = case.userreportanswer_set.all()
user_answers.delete()
# User statuses # User statuses
@@ -2825,6 +2840,7 @@ def collection_reset_answers(request, exam_id: int):
# CidUserExams # CidUserExams
collection.cid_users.all().delete() collection.cid_users.all().delete()
return HttpResponse("Success")
else: else:
+7 -1
View File
@@ -789,7 +789,7 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
raise NotImplementedError raise NotImplementedError
def get_cid_user_exams( def get_cid_user_exams(
self, cid_user: Optional["CidUser"] = None, user_user: User = None self, cid_user: Optional["CidUser"] = None, user_user: User | None = None
) -> "CidUserExam": ) -> "CidUserExam":
content_type = ContentType.objects.get_for_model(self) content_type = ContentType.objects.get_for_model(self)
if cid_user is None and user_user is None: if cid_user is None and user_user is None:
@@ -992,6 +992,12 @@ class ExamBase(ExamOrCollectionGenericBase):
else: else:
return False return False
def get_user_users_with_scores(self):
user_ids = [i[2:] for i in self.user_scores if i.startswith("u/")]
return User.objects.filter(pk__in=user_ids)
def generate_user_report(self, user): def generate_user_report(self, user):
exam_text = [f"Candidate {user.first_name} [{user.email}]"] exam_text = [f"Candidate {user.first_name} [{user.email}]"]
@@ -107,6 +107,14 @@ Author(s): {% for author in exam.author.all %}
{{ author }}{% if not forloop.last %}, {% endif %} {{ author }}{% if not forloop.last %}, {% endif %}
{% endfor %}<br/> {% endfor %}<br/>
<details>
<summary>Extra</summary>
<button title="This will clear all user answers and attempts"
hx-post="{% url exam.get_app_name|add:':order_questions' exam.pk %}"
hx-swap="outerHTML"
>Order questions</button>
</details>
{% block css %} {% block css %}
<style> <style>
.answer-management[open] { .answer-management[open] {
@@ -1,16 +1,35 @@
<ul> <ul>
<h3>Email to send</h3>
<table>
<tr>
<th>Username</th>
<th>Supervisor</th>
</tr>
{% for user in users %}
<tr>
<td>{{user.username}}</td>
<td>{{user.userprofile.supervisor}}</td>
</tr>
{% endfor %}
</table>
<br/>
{% for user, status in status.items %} {% for user, status in status.items %}
<li class='{% if status.0 %}text-success{% else %}text-warning{% endif %}'> <li class='{% if status.0 %}text-success{% else %}text-warning{% endif %}'>
{{user.username}} {% if status.0 %}success{% else %}failed [{{status.1}}]{% endif %} ({{status.2}}) {{user.username}} {% if status.0 %}success{% else %}failed [{{status.1}}]{% endif %} ({{status.2}})
<button hx-get="{% url exam.app_name|add:':exam_user_report_email' exam_id=exam.pk user_id=user.pk %}" <button hx-get="{% url exam.app_name|add:':exam_user_report_email' exam_id=exam.pk user_id=user.pk %}"
hx-prompt="Enter any additional emails" hx-prompt="Enter any additional emails"
hx-confirm="Are you sure you wish to delete your account?" hx-confirm="Are you sure you wish to resend all emails?"
hx-trigger="click, click from:#resend-all" hx-trigger="click, click from:#resend-all"
>Resend</button> >Resend</button>
</li> </li>
{% empty %} {% empty %}
No emails sent. <p>No emails sent.</p>
{% endfor %} {% endfor %}
</ul> </ul>
<button id="resend-all">Resend all</button> <button id="resend-all">Resend all</button>
@@ -131,7 +131,10 @@
{% block breakdown %}{% endblock breakdown %} {% block breakdown %}{% endblock breakdown %}
<details> <details
hx-get="{% url exam.app_name|add:':exam_report_email_status' exam_id=exam.pk %}"
hx-trigger="revealed"
hx-target="#user-details">
<summary>Email results</summary> <summary>Email results</summary>
User results emailed: {{exam.exam_results_emailed|default:"Never"}}<br/> User results emailed: {{exam.exam_results_emailed|default:"Never"}}<br/>
<button id="email-results-button" <button id="email-results-button"
@@ -149,7 +152,8 @@
>Email unsent user results</button> >Email unsent user results</button>
<button id="email-results-check-button" title="Check the status of emailed results" <button id="email-results-check-button" title="Check the status of emailed results"
hx-get="{% url exam.app_name|add:':exam_report_email_status' exam_id=exam.pk %}" hx-get="{% url exam.app_name|add:':exam_report_email_status' exam_id=exam.pk %}"
hx-target="#user-details">Check email status</button> hx-trigger="click"
hx-target="#user-details">Refresh email status</button>
<p>Note: currently only works with registered users</p> <p>Note: currently only works with registered users</p>
<div id="user-details"></div> <div id="user-details"></div>
</details> </details>
@@ -31,7 +31,7 @@
</ol> </ol>
{% comment %} Hide the toggle button if there are no users (to add or remove) {% endcomment %} {% comment %} Hide the toggle button if there are no users (to add or remove) {% endcomment %}
{% if current_cid_users or available_cid_users %} {% if current_user_users or available_user_users %}
<p><button class="toggle-all-btn">Toggle all</button></p> <p><button class="toggle-all-btn">Toggle all</button></p>
{% endif %} {% endif %}
+4 -3
View File
@@ -239,11 +239,11 @@ def generic_exam_urls(generic_exam_view: GenericExamViews):
generic_exam_view.exam_question_detail, generic_exam_view.exam_question_detail,
name="exam_question_detail", name="exam_question_detail",
), ),
#path( # path(
# "exam/<int:pk>/question/<int:sk>/answer/<str:user_or_cid>", # "exam/<int:pk>/question/<int:sk>/answer/<str:user_or_cid>",
# generic_exam_view.exam_question_user_answer, # generic_exam_view.exam_question_user_answer,
# name="exam_question_user_answer", # name="exam_question_user_answer",
#), # ),
path( path(
"exam/<int:pk>/question/<int:sk>/answer/<str:c_or_u>/<str:user_or_cid>", "exam/<int:pk>/question/<int:sk>/answer/<str:c_or_u>/<str:user_or_cid>",
generic_exam_view.exam_question_user_answer, generic_exam_view.exam_question_user_answer,
@@ -342,8 +342,9 @@ def generic_exam_urls(generic_exam_view: GenericExamViews):
), ),
path("exam/", generic_exam_view.exam_list, name="exam_list"), path("exam/", generic_exam_view.exam_list, name="exam_list"),
path("exam/all", generic_exam_view.exam_list_all, name="exam_list_all"), path("exam/all", generic_exam_view.exam_list_all, name="exam_list_all"),
path("exam/<int:exam_id>/order_questions", generic_exam_view.order_questions, name="order_questions"),
path("exam/<int:exam_id>/cids", generic_exam_view.exam_cids, name="exam_cids"), path("exam/<int:exam_id>/cids", generic_exam_view.exam_cids, name="exam_cids"),
#path("exam/<int:exam_id>/groups", generic_exam_view.exam_groups_edit, name="exam_groups_edit"), # path("exam/<int:exam_id>/groups", generic_exam_view.exam_groups_edit, name="exam_groups_edit"),
path( path(
"exam/<int:exam_id>/reset_answers", "exam/<int:exam_id>/reset_answers",
generic_exam_view.exam_reset_answers, generic_exam_view.exam_reset_answers,
+23 -9
View File
@@ -374,7 +374,7 @@ class ExamViews(View, LoginRequiredMixin):
question_type, question_type,
normalise_score=None, normalise_score=None,
): ):
self.Exam = exam self.Exam: ExamBase = exam
self.Question = question self.Question = question
self.Answer = answer self.Answer = answer
self.UserAnswer = cid_user_answer self.UserAnswer = cid_user_answer
@@ -582,7 +582,7 @@ class ExamViews(View, LoginRequiredMixin):
@method_decorator(login_required) @method_decorator(login_required)
def exam_toggle_results_published(self, request, pk): def exam_toggle_results_published(self, request, pk):
if request.method == "POST": if request.method == "POST":
if not self.check_user_edit_access(request.user): if not self.check_user_edit_access(request.user, exam_id=pk):
data = {"status": "error, insufficient permission"} data = {"status": "error, insufficient permission"}
return JsonResponse(data, status=400) return JsonResponse(data, status=400)
@@ -606,7 +606,7 @@ class ExamViews(View, LoginRequiredMixin):
@method_decorator(login_required) @method_decorator(login_required)
def exam_toggle_active(self, request, pk): def exam_toggle_active(self, request, pk):
if request.method == "POST": if request.method == "POST":
if not self.check_user_edit_access(request.user): if not self.check_user_edit_access(request.user, exam_id=pk):
data = {"status": "error, insufficient permission"} data = {"status": "error, insufficient permission"}
return JsonResponse(data, status=400) return JsonResponse(data, status=400)
@@ -654,6 +654,16 @@ class ExamViews(View, LoginRequiredMixin):
def exam_list_all(self, request): def exam_list_all(self, request):
return self.exam_list(request, all=True) return self.exam_list(request, all=True)
def order_questions(self, request, exam_id):
if not self.check_user_edit_access(request.user):
raise PermissionDenied
exam = get_object_or_404(self.Exam, pk=exam_id)
exam.order_questions()
return HttpResponse("Done")
@method_decorator(login_required) @method_decorator(login_required)
def exam_list(self, request, all=False): def exam_list(self, request, all=False):
if not self.check_user_access(request.user): if not self.check_user_access(request.user):
@@ -710,11 +720,11 @@ class ExamViews(View, LoginRequiredMixin):
if not self.check_user_access(request.user, exam_id): if not self.check_user_access(request.user, exam_id):
raise PermissionDenied raise PermissionDenied
users = [i[2:] for i in exam.user_scores if i.startswith("u/")] users = exam.get_user_users_with_scores()
status = {} status = {}
for u in users: 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) user_exam = exam.get_or_create_cid_user_exam(user_user=user)
if user_exam.results_emailed_status: if user_exam.results_emailed_status:
status[user] = json.loads(user_exam.results_emailed_status) status[user] = json.loads(user_exam.results_emailed_status)
@@ -726,6 +736,7 @@ class ExamViews(View, LoginRequiredMixin):
"exam": exam, "exam": exam,
"status": status, "status": status,
"app_name": self.app_name, "app_name": self.app_name,
"users": users,
}, },
) )
return JsonResponse(status) return JsonResponse(status)
@@ -760,13 +771,13 @@ class ExamViews(View, LoginRequiredMixin):
# We only need to send emails to those who have scores # We only need to send emails to those who have scores
# (who should be in exam.user_scores) # (who should be in exam.user_scores)
if users is None: if users is None:
users = [i[2:] for i in exam.user_scores if i.startswith("u/")] users = exam.get_user_users_with_scores()
time = timezone.now() time = timezone.now()
email_results = {} email_results = {}
for u in users: 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) user_exam = exam.get_or_create_cid_user_exam(user_user=user)
if unsent_only and user_exam.results_emailed_status: if unsent_only and user_exam.results_emailed_status:
@@ -1978,6 +1989,8 @@ class ExamViews(View, LoginRequiredMixin):
raise Http404("Packet not in exam mode") raise Http404("Packet not in exam mode")
if not exam.check_cid_user(cid, passcode, request.user): if not exam.check_cid_user(cid, passcode, request.user):
if not self.check_user_access(request.user, pk):
#raise PermissionDenied
raise Http404("Error accessing exam") raise Http404("Error accessing exam")
if user is not None: if user is not None:
@@ -2564,6 +2577,7 @@ class ExamCloneMixin():
object.exam_questions.set(self.exam_questions) object.exam_questions.set(self.exam_questions)
object.author.set(self.author) object.author.set(self.author)
object.save() object.save()
object.order_questions()
return HttpResponseRedirect(object.get_absolute_url()) return HttpResponseRedirect(object.get_absolute_url())
@@ -62,6 +62,12 @@
.not-answered { .not-answered {
opacity: 0.5; opacity: 0.5;
} }
.image-link {
cursor: pointer;
border: 1px solid purple;
padding-left: 4px;
padding-right: 4px;
}
</style> </style>
{% endblock css %} {% endblock css %}
{% block js %} {% block js %}
+6 -6
View File
@@ -62,11 +62,11 @@ urlpatterns = [
# path("all_questions/", views.all_questions, name="all_questions"), # path("all_questions/", views.all_questions, name="all_questions"),
path("question/<int:pk>/scrap", views.long_scrap, name="long_scrap"), path("question/<int:pk>/scrap", views.long_scrap, name="long_scrap"),
path("question/<int:pk>/delete", views.LongDelete.as_view(), name="long_delete"), path("question/<int:pk>/delete", views.LongDelete.as_view(), name="long_delete"),
path( # path(
"exam/<int:pk>/review", # "exam/<int:pk>/review",
views.question_review, # views.question_review,
name="question_review", # name="question_review",
), # ),
path( path(
"exam/<int:exam_id>/<int:question_number>/<int:answer_id>/mark", "exam/<int:exam_id>/<int:question_number>/<int:answer_id>/mark",
views.mark_answer, views.mark_answer,
@@ -157,5 +157,5 @@ urlpatterns = [
] ]
#urlpatterns.extend(generic_view_urls(views.GenericViews)) urlpatterns.extend(generic_view_urls(views.GenericViews))
urlpatterns.extend(generic_exam_urls(views.GenericExamViews)) urlpatterns.extend(generic_exam_urls(views.GenericExamViews))
+2
View File
@@ -85,6 +85,7 @@ from generic.views import (
ExamGroupsUpdateBase, ExamGroupsUpdateBase,
ExamUpdateBase, ExamUpdateBase,
ExamViews, ExamViews,
GenericViewBase,
RedirectMixin, RedirectMixin,
SeriesImagesZipViewBase, SeriesImagesZipViewBase,
UpdateQuestionMixin, UpdateQuestionMixin,
@@ -954,6 +955,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")
GenericViews = GenericViewBase("longs", Long, UserAnswer, Exam)
class ExamCreate(ExamCreateBase): class ExamCreate(ExamCreateBase):
@@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2024-08-28 17:22
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('physics', '0013_alter_exam_markers'),
]
operations = [
migrations.RenameField(
model_name='examquestiondetail',
old_name='rapid',
new_name='question',
),
]
+1 -1
View File
@@ -141,7 +141,7 @@ class ExamQuestionDetail(models.Model):
sort_order = models.IntegerField(default=1000) sort_order = models.IntegerField(default=1000)
exam = models.ForeignKey("Exam", on_delete=models.CASCADE) exam = models.ForeignKey("Exam", on_delete=models.CASCADE)
rapid = models.ForeignKey(Question, on_delete=models.CASCADE) question = models.ForeignKey(Question, on_delete=models.CASCADE)
class Meta: class Meta:
ordering = ("sort_order",) ordering = ("sort_order",)
+1
View File
@@ -34,6 +34,7 @@ urlpatterns.extend(
name="exam_take_overview_user", name="exam_take_overview_user",
), ),
path("exam/<int:pk>/markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"), path("exam/<int:pk>/markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"),
path("exam/<int:pk>/groups", views.ExamGroupsUpdate.as_view(), name="exam_groups_edit"),
path( path(
"exam/<int:pk>/authors", "exam/<int:pk>/authors",
views.ExamAuthorUpdate.as_view(), views.ExamAuthorUpdate.as_view(),
+1
View File
@@ -51,6 +51,7 @@ urlpatterns = [
"exam/<int:exam_pk>/<int:sk>/mark/review", views.mark_review, name="mark_review" "exam/<int:exam_pk>/<int:sk>/mark/review", views.mark_review, name="mark_review"
), ),
path("exam/<int:pk>/markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"), path("exam/<int:pk>/markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"),
path("exam/<int:pk>/groups", views.ExamGroupsUpdate.as_view(), name="exam_groups_edit"),
path("exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"), path("exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"),
path("exam/create", views.ExamCreate.as_view(), name="exam_create"), path("exam/create", views.ExamCreate.as_view(), name="exam_create"),
path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"), path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"),
+24 -2
View File
@@ -2,7 +2,7 @@
{% block content %} {% block content %}
<table> <table>
<thead> <thead>
<tr> <tr>
<th>Specialty</th> <th>Specialty</th>
@@ -13,6 +13,9 @@
<th>Outcome (reason)</th> <th>Outcome (reason)</th>
<th>Outcome (free text)</th> <th>Outcome (free text)</th>
<th>Comments</th> <th>Comments</th>
<th>Radiology Category</th>
<th>Oncology Category</th>
<th>Radiology Condition</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -27,10 +30,29 @@
<td>{{item.outcome_reason}}</td> <td>{{item.outcome_reason}}</td>
<td>{{item.outcome_free_text}}</td> <td>{{item.outcome_free_text}}</td>
<td>{{item.comments}}</td> <td>{{item.comments}}</td>
<td>{% for cat in item.radiology_categories.all %}
{% if forloop.last %}
{{cat}}
{% else %}
{{cat}} |
{% endif %}
{% endfor %}
</td>
<td>{{item.oncology_category}}</td>
<td>{% for cat in item.radiology_condition.all %}
{% if forloop.last %}
{{cat}}
{% else %}
{{cat}} |
{% endif %}
{% endfor %}
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% endblock %} {% endblock %}
@@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2024-08-28 17:22
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('sbas', '0013_alter_exam_markers'),
]
operations = [
migrations.RenameField(
model_name='examquestiondetail',
old_name='rapid',
new_name='question',
),
]
+1 -1
View File
@@ -159,7 +159,7 @@ class ExamQuestionDetail(models.Model):
sort_order = models.IntegerField(default=1000) sort_order = models.IntegerField(default=1000)
exam = models.ForeignKey("Exam", on_delete=models.CASCADE) exam = models.ForeignKey("Exam", on_delete=models.CASCADE)
rapid = models.ForeignKey(Question, on_delete=models.CASCADE) question = models.ForeignKey(Question, on_delete=models.CASCADE)
class Meta: class Meta:
ordering = ("sort_order",) ordering = ("sort_order",)
+1
View File
@@ -50,6 +50,7 @@ urlpatterns = [
), ),
path("exam/<int:pk>/markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"), path("exam/<int:pk>/markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"),
path("exam/<int:pk>/groups", views.ExamGroupsUpdate.as_view(), name="exam_groups_edit"),
path( path(
"exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors" "exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"
), ),