continue working on case collections

This commit is contained in:
Ross
2023-07-31 10:25:23 +01:00
parent dd300afd63
commit f268f040e2
15 changed files with 343 additions and 49 deletions
+17
View File
@@ -3,6 +3,7 @@ from django.contrib.admin import widgets
from django.forms import (
BaseInlineFormSet,
Form,
HiddenInput,
ModelForm,
ModelMultipleChoiceField,
ModelChoiceField,
@@ -22,6 +23,7 @@ from atlas.models import (
CidReportAnswer,
Differential,
Finding,
SelfReview,
Series,
SeriesImage,
SeriesFinding,
@@ -571,3 +573,18 @@ class AddCollectionToCaseForm(Form):
raise ValidationError("Invalid collection")
return collection_set
class SelfReviewForm(ModelForm):
class Meta:
model = SelfReview
fields = ["user_exam", "case", "comments", "findings", "interpretation"]
widgets = {"user_exam": HiddenInput(), "case": HiddenInput()}
def __init__(self, *args, **kwargs):
user_exam_id = kwargs.pop("user_exam_id")
case_id = kwargs.pop("case_id")
ModelForm.__init__(self, *args, **kwargs)
super(SelfReviewForm, self).__init__(*args, **kwargs)
+29
View File
@@ -0,0 +1,29 @@
# Generated by Django 4.1.4 on 2023-07-24 11:42
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('generic', '0008_delete_selfreview'),
('atlas', '0006_casecollection_candidates_only_and_more'),
]
operations = [
migrations.CreateModel(
name='SelfReview',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('comments', models.TextField(blank=True, null=True)),
('findings', models.IntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(5), django.core.validators.MinValueValidator(1)])),
('interpretation', models.IntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(5), django.core.validators.MinValueValidator(1)])),
('review_date', models.DateTimeField(auto_now_add=True)),
('review_update_date', models.DateTimeField(auto_now=True)),
('case', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='atlas.case')),
('user_exam', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='generic.ciduserexam')),
],
),
]
+45
View File
@@ -578,3 +578,48 @@ class CidReportAnswer(BaseReportAnswer):
class UserReportAnswer(BaseReportAnswer):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
class SelfReview(models.Model):
"""Holds data regarding a users self review / reflection"""
user_exam = models.ForeignKey(CidUserExam, on_delete=models.CASCADE)
case = models.ForeignKey(Case, on_delete=models.CASCADE)
comments = models.TextField(null=True, blank=True)
findings = models.IntegerField(
null=True, blank=True, validators=[MaxValueValidator(5), MinValueValidator(1)]
)
interpretation = models.IntegerField(
null=True, blank=True, validators=[MaxValueValidator(5), MinValueValidator(1)]
)
review_date = models.DateTimeField(auto_now_add=True)
review_update_date = models.DateTimeField(auto_now=True)
def get_absolute_url(self):
return reverse(
"atlas:collection_take_overview_user", kwargs={"pk": self.user_exam.exam.pk}
)
def get_display_block(self):
html = """<div class='self-feedback-block'>
Comments: <span class="comment">{comments}</span><br/>
Findings: <span class="findings">{findings}</span><br/>
Interpretation: <span class="interpretation">{interpretation}</span><br/>
<span class="date">Date: {date}</span><br/>
</div>
"""
return format_html(
html,
comments=self.comments,
findings=self.findings,
interpretation=self.interpretation,
date=self.review_date,
)
@@ -7,11 +7,11 @@
: {{case.title}}
{% endif %}
{% if collection.publish_results or cid_user_exam.completed %}
{% if completed %}
<span class="stamp-white">REVIEW</span>
{% endif %}
</h2>
@@ -78,16 +78,30 @@
<form method="POST" class="post-form">{% csrf_token %}
{% if collection.collection_type == "REP" %}
<div class="form-contents">
<fieldset {% if collection.publish_results or cid_user_exam.completed %}disabled="disabled"{% endif %}>
<fieldset {% if completed %}disabled="disabled"{% endif %}>
{{form}}
</fieldset>
</div>
<p>
{% if collection.publish_results %}
{% if completed %}
<h4>Answer score: {{answer.score}}</h4>
Answer feedback: {{answer.feedback|safe}}
<br/>
{% if self_review %}
<h4>Self Feedback</h4>
{% for review in self_review %}
{{review.get_display_block}}
{% endfor %}
{% endif %}
{% endif %}
</p>
{% endif %}
{% if previous %}
<button type="submit" name="previous" class="save btn btn-default">Previous</button>
@@ -103,6 +117,10 @@
<br />
<button type="submit" name="finish" class="save btn btn-default">Overview</button>
<button type="submit" id="goto-button" value="test" name="goto" class="hide">goto</button>
{% if collection.publish_results or cid_user_exam.completed %}
<a href="{% url 'atlas:add_self_review' cid_user_exam.id case.id %}">Add self review</a>
{% endif %}
</form>
<style>
label {
@@ -8,7 +8,7 @@
<div class="floating-header">
<a href="{% url 'atlas:exam_update' collection.id %}" title="Edit the Collection">Edit</a>
\ <a href="{% url 'atlas:exam_deleted' collection.id %}" title="Delete the Collection">Delete</a>
{% comment %} \ <a href="{% url 'atlas:collection_clone' collection.id %}" title="Clone the Collection">Clone</a> {% endcomment %}
\ <a href="{% url 'atlas:exam_clone' collection.id %}" title="Clone the Collection">Clone</a>
{% if request.user.is_superuser %}
\ <a href="{% url 'admin:atlas_casecollection_change' collection.id %}" title="Edit the Collection using the admin interface">Admin Edit</a>
{% endif %}
@@ -3,10 +3,26 @@
{% block content %}
<h2>Collection: {{exam.name}}</h2>
<ul>
{% comment %} <ul>
{% for case in collection.cases.all %}
<li><a href="{% url 'atlas:collection_case_view_review' pk=collection.id case_number=forloop.counter0 %}">Case {{forloop.counter}}</a></li>
<li><a href="{% url 'atlas:collection_case_view_take_user' pk=collection.id case_number=forloop.counter0 %}">Case {{forloop.counter}}</a></li>
{% endfor %}
</ul>
</ul> {% endcomment %}
User: {{request.user}}<br/>
{% if cid_exam %}
Started: {{cid_exam.start_time}} <br/>
{% if cid_exam.completed %}
Ended: {{cid_exam.end_time}} <br/>
{% endif %}
{% endif %}
<a href="{% url 'atlas:collection_case_view_take_user' pk=collection.pk case_number=0 %}">
{% if cid_exam.completed %}
<button>Review</button>
{% else %}
<button>Start</button>
{% endif %}
</a>
{% endblock %}
@@ -16,7 +16,7 @@
<div class="sba-finish-list">
<ul>
{% for question, answer in question_answer_tuples %}
<li> <span {% if not answer %}class="unanswered"{% endif %}>
<li> <span {% if not collection.review_only and not answer %}class="unanswered"{% endif %}>
<a href="
{% if cid is not None %}
{% url 'atlas:collection_case_view_take' pk=collection.id case_number=forloop.counter0 cid=cid passcode=passcode %}
@@ -27,10 +27,12 @@
Case: {{forloop.counter}}
</a>
<br/>
{% if not answer %}
No answer
{% else %}
{{answer.answer}}
{% if not collection.review_only %}
{% if not answer %}
No answer
{% else %}
{{answer.answer}}
{% endif %}
{% endif %}
</span>
</li>
@@ -4,23 +4,23 @@
<h2>Start: {{collection.name}}{% if cid_exam.completed %} <span class="stamp-white">REVIEW</span>{% endif %}</h2>
{% if request.user.is_authenticated and valid_user %}
User: {{request.user}}<br/>
{% if cid_exam %}
Started: {{cid_exam.start_time}} <br/>
User: {{request.user}}<br/>
{% if cid_exam.completed %}
Ended: {{cid_exam.end_time}} <br/>
{% endif %}
{% endif %}
{% if cid_exam %}
Started: {{cid_exam.start_time}} <br/>
{% if cid_exam.completed %}
Ended: {{cid_exam.end_time}} <br/>
{% endif %}
{% endif %}
<a href="{% url 'atlas:collection_case_view_take_user' pk=collection.pk case_number=0 %}">
{% if cid_exam.completed %}
<button>Review</button>
{% else %}
<button>Start</button>
{% endif %}
{% if cid_exam.completed %}
<button>Review</button>
{% else %}
<button>Start</button>
{% endif %}
</a>
{% else %}
Enter your CID and passcode in the below boxes.<br />
@@ -0,0 +1,36 @@
{% extends "base.html" %}
{% block content %}
<h2>Add Self Review</h2>
<div class="alert alert-info" role="alert">
Adding self feedback for {{user_exam.exam}}/{{case}}
</div>
<form action="" method="post">
{% csrf_token %}
{{ form.non_field_errors }}
{{ form.as_p }}
{% comment %} <div class="fieldWrapper">
{{ form.content_type.errors }}
{{ form.content_type }}
</div>
<div class="fieldWrapper">
{{ form.object_id.errors }}
{{ form.object_id }}
</div>
<div class="fieldWrapper">
{{ form.note_type.errors }}
<label for="{{ form.note_type.id_for_label }}">Note type</label>
{{ form.note_type }}
</div>
<div class="fieldWrapper">
{{ form.note.errors }}
<label for="{{ form.note.id_for_label }}">Additional information</label><br/>
{{ form.note }}
</div> {% endcomment %}
<input type="submit" value="Submit">
</form>
{% endblock %}
+6
View File
@@ -14,6 +14,11 @@ urlpatterns = [
RedirectView.as_view(pattern_name="atlas:case_create", permanent=False),
name="create",
),
path(
"add_self_review/<int:user_exam_id>/<int:case_id>",
views.AddSelfReview.as_view(),
name="add_self_review",
),
path("author/<int:pk>/", views.author_detail, name="author_detail"),
path("author/", views.author_list, name="author_list"),
path("case/", views.CaseView.as_view(), name="case_view"),
@@ -23,6 +28,7 @@ urlpatterns = [
views.CaseCollectionCreate.as_view(),
name="exam_create",
),
path("collection/<int:exam_id>/clone", views.CollectionClone.as_view(), name="exam_clone"),
path(
"collection/<int:pk>/delete",
views.CaseCollectionDelete.as_view(),
+90 -21
View File
@@ -27,7 +27,7 @@ from django.urls import reverse_lazy, reverse
from django.http import Http404, JsonResponse
from django.http import HttpResponseRedirect, HttpResponse
from generic.models import CidUser
from generic.models import CidUser, CidUserExam
from .forms import (
AddCollectionToCaseForm,
@@ -38,6 +38,7 @@ from .forms import (
CidReportAnswerMarkForm,
ConditionForm,
FindingForm,
SelfReviewForm,
SeriesForm,
SeriesImageFormSet,
SeriesFormSet,
@@ -54,6 +55,7 @@ from .models import (
Condition,
PathologicalProcess,
Presentation,
SelfReview,
Series,
Examination,
Finding,
@@ -1114,7 +1116,7 @@ def collection_take_start(request, pk):
valid_user = (collection.check_logged_in_user(request),)
cid_exam = None
if valid_user:
if collection.collection_type == "REV" or valid_user:
cid_exam = collection.get_cid_user_exams(user_user=request.user).first()
template_variables = {
@@ -1372,21 +1374,23 @@ def collection_case_view_take(
collection = get_object_or_404(CaseCollection, pk=pk)
form = None
answer: CidReportAnswer | UserReportAnswer
answer: None | CidReportAnswer | UserReportAnswer = None
collection.check_user_can_take(cid, passcode, request)
cases = collection.cases.all().order_by("casedetail__sort_order").prefetch_related()
case = cases[case_number]
if not collection.review_only():
cid_user_exam = collection.get_or_create_cid_user_exam(
cid=cid, user_user=request.user
)
cid_user_exam = collection.get_or_create_cid_user_exam(
cid=cid, user_user=request.user
)
if cid_user_exam.start_time is None:
cid_user_exam.start_time = timezone.now()
cid_user_exam.save()
if cid_user_exam.start_time is None:
cid_user_exam.start_time = timezone.now()
cid_user_exam.save()
if not collection.review_only():
if collection.collection_type == "REP":
case_detail = CaseDetail.objects.get(case=case, collection=collection)
@@ -1420,18 +1424,14 @@ def collection_case_view_take(
cid_user_exam.end_time = timezone.now()
cid_user_exam.save()
if cid is not None:
kwargs = {"pk": pk, "cid": cid, "passcode": passcode}
redirect_url = "atlas:collection_case_view_take"
overview_url = "atlas:collection_take_overview"
else:
kwargs = {"pk": pk}
redirect_url = "atlas:collection_case_view_take_user"
overview_url = "atlas:collection_take_overview_user"
if cid is not None:
kwargs = {"pk": pk, "cid": cid, "passcode": passcode}
redirect_url = "atlas:collection_case_view_take"
overview_url = "atlas:collection_take_overview"
else:
kwargs = {"pk": pk}
redirect_url = "atlas:collection_case_view_take"
redirect_url = "atlas:collection_case_view_take_user"
overview_url = "atlas:collection_take_overview_user"
if "next" in request.POST:
return redirect(redirect_url, case_number=case_number + 1, **kwargs)
@@ -1457,18 +1457,23 @@ def collection_case_view_take(
# Set what details a user is / is not able to view when taking the case
# this can be configured via the collection edit menu
if collection.publish_results:
if collection.publish_results or cid_user_exam.completed:
completed = True
show_title = collection.show_title_post
show_history = collection.show_history_post
show_description = collection.show_description_post
show_discussion = collection.show_discussion_post
show_report = collection.show_report_post
self_review = cid_user_exam.selfreview_set.filter(case=case)
else:
completed = False
show_title = collection.show_title_pre
show_history = collection.show_history_pre
show_description = collection.show_description_pre
show_discussion = collection.show_discussion_pre
show_report = collection.show_report_pre
self_review = []
return render(
request,
@@ -1490,6 +1495,8 @@ def collection_case_view_take(
"show_discussion": show_discussion,
"show_report": show_report,
"cid_user_exam": cid_user_exam,
"completed": completed,
"self_review": self_review,
},
)
@@ -1735,3 +1742,65 @@ def collection_scores_cid(request, pk):
GenericExamViews = ExamViews(
CaseCollection, Case, None, CidReportAnswer, "atlas", "casecollection"
)
class CollectionCloneMixin:
def get_initial(self):
old_object = get_object_or_404(self.model, pk=self.kwargs["exam_id"])
initial_data = model_to_dict(old_object, exclude=["id"])
# We manually transfer the forign keys / m2m relationships
questions = old_object.cases.all().values_list("id", flat=True)
authors = old_object.author.all().values_list("id", flat=True)
self.cases = list(questions)
self.author = list(authors)
return initial_data
def form_valid(self, form):
object = form.save()
# Reapply these otherwise they get lost?
object.cases.set(self.cases)
object.author.set(self.author)
object.save()
return HttpResponseRedirect(object.get_absolute_url())
class CollectionClone(CollectionCloneMixin, CaseCollectionCreate):
"""Clone exam view"""
class AddSelfReview(CreateView):
model = SelfReview
form_class = SelfReviewForm
# fields = ("author", "note_type", "note")
def get_form_kwargs(self):
kwargs = super(AddSelfReview, self).get_form_kwargs()
# update the kwargs for the form init method with yours
kwargs.update(self.kwargs) # self.kwargs contains all url conf params
#kwargs.update({"user": self.request.user})
return kwargs
def get_initial(self):
user_exam_id = self.kwargs["user_exam_id"]
case_id = self.kwargs["case_id"]
self.user_exam = get_object_or_404(CidUserExam, pk=user_exam_id)
self.case = get_object_or_404(Case, pk=case_id)
return {"user_exam": user_exam_id, "case": case_id}
def get_context_data(self, **kwargs):
# This can also be accessed via view.**** in the template
context = super(AddSelfReview, self).get_context_data(**kwargs)
context["user_exam"] = self.user_exam
context["case"] = self.case
return context
# def form_valid(self, form):
# model = form.save(commit=False)
#
# model.save()
#
# response = super().form_valid(form)
#
# return response
+29
View File
@@ -0,0 +1,29 @@
# Generated by Django 4.1.4 on 2023-07-24 11:15
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('atlas', '0006_casecollection_candidates_only_and_more'),
('generic', '0006_alter_ciduserexam_completed'),
]
operations = [
migrations.CreateModel(
name='SelfReview',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('comments', models.TextField(blank=True, null=True)),
('findings', models.IntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(5), django.core.validators.MinValueValidator(1)])),
('interpretation', models.IntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(5), django.core.validators.MinValueValidator(1)])),
('review_date', models.DateTimeField(auto_now_add=True)),
('review_update_date', models.DateTimeField(auto_now=True)),
('case', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='atlas.case')),
('user_exam', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='generic.ciduserexam')),
],
),
]
@@ -0,0 +1,16 @@
# Generated by Django 4.1.4 on 2023-07-24 11:42
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('generic', '0007_selfreview'),
]
operations = [
migrations.DeleteModel(
name='SelfReview',
),
]
+2
View File
@@ -6,6 +6,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import models
from django.http import Http404, HttpRequest
from django.utils import timezone
from django.core.validators import MaxValueValidator, MinValueValidator
from smtplib import SMTPException
from django.urls import reverse
@@ -1315,3 +1316,4 @@ def create_user_profile(sender, instance, created, **kwargs):
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.userprofile.save()
+9
View File
@@ -999,4 +999,13 @@ h1, h2, h3, h4, h5, h6 {
opacity:0.9;
text-shadow: 0 0 2px white;
box-shadow: 0 0 2px white;
}
.self-feedback-block {
border: gray 1px dashed;
}
.self-feedback-block.data {
opacity: 60%;
font-size: small;
}