Implement self-review functionality with enhanced UI and dynamic scoring options
This commit is contained in:
@@ -1062,6 +1062,14 @@ class AddCollectionToCaseForm(Form):
|
|||||||
|
|
||||||
|
|
||||||
class SelfReviewForm(ModelForm):
|
class SelfReviewForm(ModelForm):
|
||||||
|
SCORE_CHOICES = [
|
||||||
|
(1, "1 - Poor"),
|
||||||
|
(2, "2 - Limited"),
|
||||||
|
(3, "3 - Adequate"),
|
||||||
|
(4, "4 - Good"),
|
||||||
|
(5, "5 - Excellent"),
|
||||||
|
]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SelfReview
|
model = SelfReview
|
||||||
fields = ["user_exam", "case", "comments", "findings", "interpretation"]
|
fields = ["user_exam", "case", "comments", "findings", "interpretation"]
|
||||||
@@ -1076,6 +1084,44 @@ class SelfReviewForm(ModelForm):
|
|||||||
ModelForm.__init__(self, *args, **kwargs)
|
ModelForm.__init__(self, *args, **kwargs)
|
||||||
super(SelfReviewForm, self).__init__(*args, **kwargs)
|
super(SelfReviewForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Render scoring fields as explicit 1-5 selector choices for cleaner UX.
|
||||||
|
self.fields["findings"] = forms.TypedChoiceField(
|
||||||
|
choices=self.SCORE_CHOICES,
|
||||||
|
coerce=int,
|
||||||
|
empty_value=None,
|
||||||
|
required=False,
|
||||||
|
widget=RadioSelect,
|
||||||
|
label="Findings",
|
||||||
|
help_text="How well did you identify key findings?",
|
||||||
|
)
|
||||||
|
self.fields["interpretation"] = forms.TypedChoiceField(
|
||||||
|
choices=self.SCORE_CHOICES,
|
||||||
|
coerce=int,
|
||||||
|
empty_value=None,
|
||||||
|
required=False,
|
||||||
|
widget=RadioSelect,
|
||||||
|
label="Interpretation",
|
||||||
|
help_text="How strong was your overall interpretation?",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.fields["comments"].widget.attrs.update(
|
||||||
|
{
|
||||||
|
"rows": 5,
|
||||||
|
"placeholder": "What went well? What would you do differently next time?",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.helper = FormHelper()
|
||||||
|
self.helper.form_method = "post"
|
||||||
|
self.helper.form_tag = False
|
||||||
|
self.helper.layout = Layout(
|
||||||
|
Field("user_exam"),
|
||||||
|
Field("case"),
|
||||||
|
Field("comments"),
|
||||||
|
Field("findings"),
|
||||||
|
Field("interpretation"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UncategorisedDicomForm(ModelForm):
|
class UncategorisedDicomForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -214,9 +214,23 @@
|
|||||||
|
|
||||||
{% if collection.self_review %}
|
{% if collection.self_review %}
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<div class="self-review-actions mb-3">
|
||||||
<a href="{% url 'atlas:add_self_review' cid_user_exam.id case.id %}"><button type="button">Add self review</button></a>
|
<div class="btn-group btn-group-sm" role="group" aria-label="Self review actions">
|
||||||
</p>
|
<a class="btn btn-primary" href="{% url 'atlas:add_self_review' cid_user_exam.id case.id %}">
|
||||||
|
<i class="bi bi-card-checklist"></i> Add Self Review
|
||||||
|
</a>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline-info"
|
||||||
|
onclick="openSelfReviewPopup('{% url 'atlas:add_self_review' cid_user_exam.id case.id %}')">
|
||||||
|
Popup
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
onclick="openSelfReviewSidebar('{% url 'atlas:add_self_review' cid_user_exam.id case.id %}')">
|
||||||
|
Sidebar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% if self_review %}
|
{% if self_review %}
|
||||||
<h4>Self Feedback</h4>
|
<h4>Self Feedback</h4>
|
||||||
|
|
||||||
@@ -263,9 +277,23 @@
|
|||||||
{% if question_completed %}
|
{% if question_completed %}
|
||||||
<div>
|
<div>
|
||||||
{% if collection.self_review %}
|
{% if collection.self_review %}
|
||||||
<p>
|
<div class="self-review-actions mb-3">
|
||||||
<a target="_blank" title="Add self review, this will open in a new page / tab" href="{% url 'atlas:add_self_review' cid_user_exam.id case.id %}"><button type="button">Add self review</button></a>
|
<div class="btn-group btn-group-sm" role="group" aria-label="Self review actions">
|
||||||
</p>
|
<a class="btn btn-primary" href="{% url 'atlas:add_self_review' cid_user_exam.id case.id %}">
|
||||||
|
<i class="bi bi-card-checklist"></i> Add Self Review
|
||||||
|
</a>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline-info"
|
||||||
|
onclick="openSelfReviewPopup('{% url 'atlas:add_self_review' cid_user_exam.id case.id %}')">
|
||||||
|
Popup
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
onclick="openSelfReviewSidebar('{% url 'atlas:add_self_review' cid_user_exam.id case.id %}')">
|
||||||
|
Sidebar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% if self_review %}
|
{% if self_review %}
|
||||||
<h4>Self Feedback</h4>
|
<h4>Self Feedback</h4>
|
||||||
|
|
||||||
@@ -306,6 +334,17 @@
|
|||||||
<button type="submit" id="goto-button" value="test" name="goto" class="hide">goto</button>
|
<button type="submit" id="goto-button" value="test" name="goto" class="hide">goto</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<aside id="selfReviewSidebar" class="self-review-sidebar" aria-live="polite" aria-label="Self review sidebar">
|
||||||
|
<div class="self-review-sidebar-header">
|
||||||
|
<h5 class="mb-0" id="selfReviewSidebarLabel">Self Review</h5>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="closeSelfReviewSidebar()">Close</button>
|
||||||
|
</div>
|
||||||
|
<div id="selfReviewSidebarBody" class="self-review-sidebar-body">
|
||||||
|
<div class="p-3 text-muted small">Choose Sidebar to load the self-review form here.</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
label {
|
label {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
@@ -376,6 +415,44 @@
|
|||||||
background-color: darkblue;
|
background-color: darkblue;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.self-review-actions .btn {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.self-review-sidebar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: min(760px, 94vw);
|
||||||
|
height: 100vh;
|
||||||
|
background: var(--bs-body-bg);
|
||||||
|
border-left: 1px solid var(--bs-border-color);
|
||||||
|
box-shadow: -8px 0 18px rgba(0, 0, 0, 0.35);
|
||||||
|
z-index: 1050;
|
||||||
|
transform: translateX(100%);
|
||||||
|
transition: transform 0.2s ease-in-out;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.self-review-sidebar.open {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.self-review-sidebar-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-bottom: 1px solid var(--bs-border-color);
|
||||||
|
background: var(--bs-secondary-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.self-review-sidebar-body {
|
||||||
|
overflow: auto;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -590,6 +667,89 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.openSelfReviewPopup = function (url) {
|
||||||
|
var features = 'width=980,height=840,resizable=yes,scrollbars=yes';
|
||||||
|
window.open(url, 'selfReviewPopup', features);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.selfReviewCurrentEmbedUrl = null;
|
||||||
|
|
||||||
|
window.bindSelfReviewEmbedForm = function () {
|
||||||
|
var root = document.getElementById('selfReviewSidebarBody');
|
||||||
|
if (!root) return;
|
||||||
|
|
||||||
|
var form = root.querySelector('form.self-review-embed-form');
|
||||||
|
if (!form) return;
|
||||||
|
|
||||||
|
if (form.dataset.bound === '1') return;
|
||||||
|
form.dataset.bound = '1';
|
||||||
|
|
||||||
|
form.addEventListener('submit', async function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
var formData = new FormData(form);
|
||||||
|
var csrftokenInput = form.querySelector('input[name="csrfmiddlewaretoken"]');
|
||||||
|
var csrftoken = csrftokenInput ? csrftokenInput.value : '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
var resp = await fetch(window.selfReviewCurrentEmbedUrl || window.location.href, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
'X-CSRFToken': csrftoken,
|
||||||
|
},
|
||||||
|
credentials: 'same-origin',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resp.redirected) {
|
||||||
|
root.innerHTML = '<div class="p-3"><div class="alert alert-success mb-0">Self review saved. You can continue reviewing this case.</div></div>';
|
||||||
|
setTimeout(function () { window.closeSelfReviewSidebar(); }, 700);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var html = await resp.text();
|
||||||
|
root.innerHTML = html;
|
||||||
|
window.bindSelfReviewEmbedForm();
|
||||||
|
} catch (e) {
|
||||||
|
var msg = root.querySelector('.self-review-embed-message');
|
||||||
|
if (msg) {
|
||||||
|
msg.innerHTML = '<div class="alert alert-danger py-2">Unable to save self review. Please try again.</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.closeSelfReviewSidebar = function () {
|
||||||
|
var sidebar = document.getElementById('selfReviewSidebar');
|
||||||
|
if (sidebar) {
|
||||||
|
sidebar.classList.remove('open');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.openSelfReviewSidebar = async function (url) {
|
||||||
|
var sidebar = document.getElementById('selfReviewSidebar');
|
||||||
|
var body = document.getElementById('selfReviewSidebarBody');
|
||||||
|
if (!sidebar || !body) {
|
||||||
|
window.location.href = url;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var embedUrl = url + (url.indexOf('?') === -1 ? '?embed=1' : '&embed=1');
|
||||||
|
window.selfReviewCurrentEmbedUrl = embedUrl;
|
||||||
|
body.innerHTML = '<div class="p-3 text-muted small">Loading self-review form...</div>';
|
||||||
|
sidebar.classList.add('open');
|
||||||
|
|
||||||
|
try {
|
||||||
|
var resp = await fetch(embedUrl, { credentials: 'same-origin' });
|
||||||
|
var html = await resp.text();
|
||||||
|
body.innerHTML = html;
|
||||||
|
window.bindSelfReviewEmbedForm();
|
||||||
|
} catch (e) {
|
||||||
|
body.innerHTML = '<div class="p-3"><div class="alert alert-danger py-2 mb-0">Could not load self-review sidebar content. Use Popup as fallback.</div></div>';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock js %}
|
{% endblock js %}
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
<div class="p-3">
|
||||||
|
<div class="card border-secondary shadow-sm mb-2">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h2 class="h6 mb-0">Self Review</h2>
|
||||||
|
<span class="badge bg-primary-subtle text-primary-emphasis">{{ user_exam.exam }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="text-muted small mb-2">
|
||||||
|
Record a quick reflection while reviewing this case.
|
||||||
|
</p>
|
||||||
|
<div class="small text-muted mb-3">
|
||||||
|
Case: <strong>{{ case }}</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" class="self-review-embed-form" action="">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
{{ form.user_exam }}
|
||||||
|
{{ form.case }}
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
{{ form.comments|as_crispy_field }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold d-block">{{ form.findings.label }}</label>
|
||||||
|
<div class="btn-group" role="group" aria-label="Findings self rating">
|
||||||
|
{% for value, label in form.fields.findings.choices %}
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
class="btn-check"
|
||||||
|
name="{{ form.findings.html_name }}"
|
||||||
|
id="id_embed_findings_{{ forloop.counter0 }}"
|
||||||
|
value="{{ value }}"
|
||||||
|
{% if form.findings.value|stringformat:'s' == value|stringformat:'s' %}checked{% endif %}
|
||||||
|
>
|
||||||
|
<label class="btn btn-outline-info" for="id_embed_findings_{{ forloop.counter0 }}">{{ label }}</label>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% if form.findings.help_text %}
|
||||||
|
<div class="form-text">{{ form.findings.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if form.findings.errors %}
|
||||||
|
<div class="text-danger small mt-1">{{ form.findings.errors|striptags }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label fw-semibold d-block">{{ form.interpretation.label }}</label>
|
||||||
|
<div class="btn-group" role="group" aria-label="Interpretation self rating">
|
||||||
|
{% for value, label in form.fields.interpretation.choices %}
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
class="btn-check"
|
||||||
|
name="{{ form.interpretation.html_name }}"
|
||||||
|
id="id_embed_interpretation_{{ forloop.counter0 }}"
|
||||||
|
value="{{ value }}"
|
||||||
|
{% if form.interpretation.value|stringformat:'s' == value|stringformat:'s' %}checked{% endif %}
|
||||||
|
>
|
||||||
|
<label class="btn btn-outline-warning" for="id_embed_interpretation_{{ forloop.counter0 }}">{{ label }}</label>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% if form.interpretation.help_text %}
|
||||||
|
<div class="form-text">{{ form.interpretation.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if form.interpretation.errors %}
|
||||||
|
<div class="text-danger small mt-1">{{ form.interpretation.errors|striptags }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2 flex-wrap">
|
||||||
|
<button type="submit" class="btn btn-primary btn-sm">Save Self Review</button>
|
||||||
|
{% if review %}
|
||||||
|
<a href="{% url 'atlas:self_review_delete' user_exam.id case.id review.id %}" class="btn btn-outline-danger btn-sm">Delete</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="self-review-embed-message mt-2"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,38 +1,88 @@
|
|||||||
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Add Self Review</h2>
|
<div class="container px-0" style="max-width: 920px;">
|
||||||
<div class="alert alert-info" role="alert">
|
<div class="card border-secondary shadow-sm mb-3">
|
||||||
Adding self feedback for {{user_exam.exam}}/{{case}}
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h2 class="h5 mb-0">Self Review</h2>
|
||||||
|
<span class="badge bg-primary-subtle text-primary-emphasis">{{ user_exam.exam }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="text-muted mb-3">
|
||||||
|
Reflect on this case and score your own performance. These scores are personal and intended to support learning.
|
||||||
|
</p>
|
||||||
|
<div class="small text-muted mb-3">
|
||||||
|
Case: <strong>{{ case }}</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" class="needs-validation" novalidate>
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
{{ form.user_exam }}
|
||||||
|
{{ form.case }}
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
{{ form.comments|as_crispy_field }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold d-block">{{ form.findings.label }}</label>
|
||||||
|
<div class="btn-group" role="group" aria-label="Findings self rating">
|
||||||
|
{% for value, label in form.fields.findings.choices %}
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
class="btn-check"
|
||||||
|
name="{{ form.findings.html_name }}"
|
||||||
|
id="id_findings_{{ forloop.counter0 }}"
|
||||||
|
value="{{ value }}"
|
||||||
|
{% if form.findings.value|stringformat:'s' == value|stringformat:'s' %}checked{% endif %}
|
||||||
|
>
|
||||||
|
<label class="btn btn-outline-info" for="id_findings_{{ forloop.counter0 }}">{{ label }}</label>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% if form.findings.help_text %}
|
||||||
|
<div class="form-text">{{ form.findings.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if form.findings.errors %}
|
||||||
|
<div class="text-danger small mt-1">{{ form.findings.errors|striptags }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label fw-semibold d-block">{{ form.interpretation.label }}</label>
|
||||||
|
<div class="btn-group" role="group" aria-label="Interpretation self rating">
|
||||||
|
{% for value, label in form.fields.interpretation.choices %}
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
class="btn-check"
|
||||||
|
name="{{ form.interpretation.html_name }}"
|
||||||
|
id="id_interpretation_{{ forloop.counter0 }}"
|
||||||
|
value="{{ value }}"
|
||||||
|
{% if form.interpretation.value|stringformat:'s' == value|stringformat:'s' %}checked{% endif %}
|
||||||
|
>
|
||||||
|
<label class="btn btn-outline-warning" for="id_interpretation_{{ forloop.counter0 }}">{{ label }}</label>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% if form.interpretation.help_text %}
|
||||||
|
<div class="form-text">{{ form.interpretation.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if form.interpretation.errors %}
|
||||||
|
<div class="text-danger small mt-1">{{ form.interpretation.errors|striptags }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2 flex-wrap">
|
||||||
|
<button type="submit" class="btn btn-primary">Save Self Review</button>
|
||||||
|
<a href="{{ case.get_absolute_url }}" class="btn btn-outline-secondary">Open Case</a>
|
||||||
|
{% if review %}
|
||||||
|
<a href="{% url 'atlas:self_review_delete' user_exam.id case.id review.id %}" class="btn btn-outline-danger">Delete Review</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
This form allows you to record your own feedback for the question / case. This can take the form of a note or a ranking (1-5) of your findings and interpretation.
|
|
||||||
<form class="highlight" action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form.non_field_errors }}
|
|
||||||
|
|
||||||
{{ form }}
|
|
||||||
{% 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>
|
|
||||||
{% if review %}<br/><a href="{% url 'atlas:self_review_delete' user_exam.id case.id review.id %}"><button>Delete review</button></a>{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -5613,6 +5613,11 @@ class CollectionClone(CollectionCloneMixin, CaseCollectionCreate):
|
|||||||
|
|
||||||
|
|
||||||
class SelfReviewDelete(DeleteView):
|
class SelfReviewDelete(DeleteView):
|
||||||
|
"""Delete a user's self-review entry for a specific case attempt.
|
||||||
|
|
||||||
|
The view resolves ``user_exam``, ``case`` and ``review`` from URL params and,
|
||||||
|
on success, returns the learner to the case take page.
|
||||||
|
"""
|
||||||
model = SelfReview
|
model = SelfReview
|
||||||
template_name = "confirm_delete.html"
|
template_name = "confirm_delete.html"
|
||||||
# success_url = reverse_lazy("atlas:finding_view")
|
# success_url = reverse_lazy("atlas:finding_view")
|
||||||
@@ -5640,9 +5645,18 @@ class SelfReviewDelete(DeleteView):
|
|||||||
|
|
||||||
|
|
||||||
class SelfReviewUpdate(UpdateView):
|
class SelfReviewUpdate(UpdateView):
|
||||||
|
"""Edit an existing self-review entry for a case attempt.
|
||||||
|
|
||||||
|
Uses ``SelfReviewForm`` and redirects back to the case take page after save.
|
||||||
|
"""
|
||||||
model = SelfReview
|
model = SelfReview
|
||||||
form_class = SelfReviewForm
|
form_class = SelfReviewForm
|
||||||
|
|
||||||
|
def get_template_names(self):
|
||||||
|
if self.request.GET.get("embed") == "1":
|
||||||
|
return ["atlas/partials/_selfreview_form_embed.html"]
|
||||||
|
return ["atlas/selfreview_form.html"]
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
user_exam_id = self.kwargs["user_exam_id"]
|
user_exam_id = self.kwargs["user_exam_id"]
|
||||||
case_id = self.kwargs["case_id"]
|
case_id = self.kwargs["case_id"]
|
||||||
@@ -5659,6 +5673,7 @@ class SelfReviewUpdate(UpdateView):
|
|||||||
context["user_exam"] = self.user_exam
|
context["user_exam"] = self.user_exam
|
||||||
context["case"] = self.case
|
context["case"] = self.case
|
||||||
context["review"] = self.review
|
context["review"] = self.review
|
||||||
|
context["embed"] = self.request.GET.get("embed") == "1"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_success_url(self) -> str:
|
def get_success_url(self) -> str:
|
||||||
@@ -5677,9 +5692,19 @@ class SelfReviewUpdate(UpdateView):
|
|||||||
|
|
||||||
|
|
||||||
class AddSelfReview(CreateView):
|
class AddSelfReview(CreateView):
|
||||||
|
"""Create a new self-review entry linked to a user exam and case.
|
||||||
|
|
||||||
|
The form captures free-text comments and 1-5 self-assessment scores for
|
||||||
|
findings and interpretation, then returns the learner to the case take page.
|
||||||
|
"""
|
||||||
model = SelfReview
|
model = SelfReview
|
||||||
form_class = SelfReviewForm
|
form_class = SelfReviewForm
|
||||||
|
|
||||||
|
def get_template_names(self):
|
||||||
|
if self.request.GET.get("embed") == "1":
|
||||||
|
return ["atlas/partials/_selfreview_form_embed.html"]
|
||||||
|
return ["atlas/selfreview_form.html"]
|
||||||
|
|
||||||
# fields = ("author", "note_type", "note")
|
# fields = ("author", "note_type", "note")
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
@@ -5702,6 +5727,7 @@ class AddSelfReview(CreateView):
|
|||||||
context = super(AddSelfReview, self).get_context_data(**kwargs)
|
context = super(AddSelfReview, self).get_context_data(**kwargs)
|
||||||
context["user_exam"] = self.user_exam
|
context["user_exam"] = self.user_exam
|
||||||
context["case"] = self.case
|
context["case"] = self.case
|
||||||
|
context["embed"] = self.request.GET.get("embed") == "1"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_success_url(self) -> str:
|
def get_success_url(self) -> str:
|
||||||
|
|||||||
Reference in New Issue
Block a user