feat: Implement per-case query and messaging functionality with UI updates and tests
This commit is contained in:
+1
-1
@@ -120,7 +120,7 @@ class CaseSelect(Select):
|
||||
class MoveSelectedSeriesForm(Form):
|
||||
destination_case = ModelChoiceField(
|
||||
queryset=Case.objects.none(),
|
||||
widget=CaseSelect(attrs={"class": "form-select form-select-sm"}),
|
||||
widget=HiddenInput(),
|
||||
label="Move selected series to case",
|
||||
required=True,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2026-04-27 21:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('atlas', '0097_casereviewmessage'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='casecollection',
|
||||
name='case_query_messaging_enabled',
|
||||
field=models.BooleanField(default=True, help_text='Enable per-case query and messaging for this collection.'),
|
||||
),
|
||||
]
|
||||
@@ -1356,6 +1356,10 @@ class CaseCollection(ExamOrCollectionGenericBase):
|
||||
cid_user_exam = GenericRelation("generic.CidUserExam", related_query_name="atlas_exams")
|
||||
|
||||
feedback_once_collection_complete = models.BooleanField(default=True, help_text="If true feedback is only given once the collection is complete. If false feedback is given after each case.")
|
||||
case_query_messaging_enabled = models.BooleanField(
|
||||
default=True,
|
||||
help_text="Enable per-case query and messaging for this collection.",
|
||||
)
|
||||
|
||||
question_time_limit = models.PositiveIntegerField(
|
||||
blank=True,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
|
||||
{% load case_widgets %}
|
||||
|
||||
{% partialdef case-series %}
|
||||
{% for series in case.get_ordered_series %}
|
||||
<span class="series-block" id="series-block-{{ series.pk }}">
|
||||
@@ -471,13 +473,18 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="series-move-panel border rounded p-2">
|
||||
<div class="small fw-semibold mb-2">Move selected series to another case</div>
|
||||
<details class="series-move-panel border rounded p-2">
|
||||
<summary class="small fw-semibold">Move selected series to another case</summary>
|
||||
{% if move_series_form %}
|
||||
<form id="move-selected-series-form" class="d-flex flex-column gap-2">
|
||||
<form id="move-selected-series-form" class="d-flex flex-column gap-2 mt-2">
|
||||
{{ move_series_form.destination_case }}
|
||||
<div class="small text-muted">Click any case row below to select the destination case.</div>
|
||||
<div id="selected-move-case" class="small text-muted">No destination case selected.</div>
|
||||
{% case_search_widget collection=None input_id='move-series-case-search-input' target_id='move-series-case-search-results' %}
|
||||
<button type="button"
|
||||
id="move-selected-series-submit"
|
||||
class="btn btn-sm btn-outline-primary text-start"
|
||||
disabled
|
||||
hx-post="{% url 'atlas:move_selected_series_to_case' case.pk %}"
|
||||
hx-include="[name='series-ids']:checked, #move-selected-series-form [name='destination_case']"
|
||||
hx-target="#series-action-results"
|
||||
@@ -487,9 +494,9 @@
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="text-muted small">Case move options are unavailable in this view.</div>
|
||||
<div class="text-muted small mt-2">Case move options are unavailable in this view.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="alert alert-info mt-2" id="series-action-alert" style="display:none;">
|
||||
<span id="series-action-results" ></span>
|
||||
@@ -1024,6 +1031,39 @@
|
||||
detailsElement.addEventListener("toggle", toggleCheckboxes);
|
||||
}
|
||||
|
||||
const moveSeriesForm = document.getElementById("move-selected-series-form");
|
||||
if (moveSeriesForm) {
|
||||
const hiddenInput = moveSeriesForm.querySelector("[name='destination_case']");
|
||||
const moveSubmitButton = document.getElementById("move-selected-series-submit");
|
||||
|
||||
function syncMoveSeriesSubmitState() {
|
||||
if (!moveSubmitButton || !hiddenInput) {
|
||||
return;
|
||||
}
|
||||
moveSubmitButton.disabled = !hiddenInput.value;
|
||||
}
|
||||
|
||||
moveSeriesForm.addEventListener("case:selected", function(event) {
|
||||
const selectionLabel = document.getElementById("selected-move-case");
|
||||
if (!hiddenInput || !event.detail) {
|
||||
return;
|
||||
}
|
||||
|
||||
const casePk = event.detail.casePk;
|
||||
const caseTitle = event.detail.caseTitle || ("Case #" + casePk);
|
||||
hiddenInput.value = casePk;
|
||||
|
||||
if (selectionLabel) {
|
||||
selectionLabel.innerHTML = "Selected destination: <strong>" + caseTitle + " (" + casePk + ")</strong>";
|
||||
selectionLabel.classList.remove("text-muted");
|
||||
}
|
||||
|
||||
syncMoveSeriesSubmitState();
|
||||
});
|
||||
|
||||
syncMoveSeriesSubmitState();
|
||||
}
|
||||
|
||||
const modal = new bootstrap.Modal(document.getElementById("findingModal"));
|
||||
const modalBody = document.getElementById("findingModalBody");
|
||||
|
||||
|
||||
@@ -289,7 +289,7 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% if question_completed %}
|
||||
{% if question_completed and collection.case_query_messaging_enabled %}
|
||||
<details class="mt-3" {% if case_review_stats.has_outstanding_any %}open{% endif %}>
|
||||
<summary class="small fw-semibold d-flex flex-wrap align-items-center gap-2">
|
||||
<span>Case conversation</span>
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
|
||||
<div class="card border-secondary w-100 mt-3">
|
||||
<div class="card-body py-2 px-3 text-start text-md-end">
|
||||
{% if collection.case_query_messaging_enabled %}
|
||||
<div class="small text-muted">Outstanding messages</div>
|
||||
<div class="fw-semibold {% if total_outstanding_messages %}text-danger{% endif %}">
|
||||
{{ total_outstanding_messages }} pending acknowledgement
|
||||
@@ -128,6 +129,13 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="small text-muted">Case query/messaging</div>
|
||||
<div class="fw-semibold text-muted">Disabled for this collection</div>
|
||||
<div class="mt-2 d-flex flex-wrap gap-2 justify-content-md-end">
|
||||
<a class="btn btn-sm btn-outline-secondary" href="{% url 'atlas:collection_history' collection.pk %}">Open History</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a class="btn btn-sm btn-outline-secondary" href="{% url 'atlas:collection_detail' collection.pk %}">Collection Detail</a>
|
||||
{% if collection.case_query_messaging_enabled %}
|
||||
<a class="btn btn-sm btn-outline-secondary" href="{% url 'atlas:user_messages_inbox' %}">Global Messages Inbox</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,8 +42,13 @@
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div class="card border-secondary h-100 {% if total_outstanding_messages %}border-danger-subtle{% endif %}">
|
||||
<div class="card-body py-3">
|
||||
{% if collection.case_query_messaging_enabled %}
|
||||
<div class="small text-muted">Outstanding Messages</div>
|
||||
<div class="h4 mb-0 {% if total_outstanding_messages %}text-danger{% endif %}">{{ total_outstanding_messages }}</div>
|
||||
{% else %}
|
||||
<div class="small text-muted">Case Messaging</div>
|
||||
<div class="h6 mb-0 text-muted">Disabled</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,6 +78,7 @@
|
||||
<div class="small text-muted mt-1">Started: {{ userexam.start_time|default:"-" }}</div>
|
||||
<div class="small text-muted">Ended: {{ userexam.end_time|default:"-" }}</div>
|
||||
<div class="mt-2 d-flex flex-wrap gap-2">
|
||||
{% if collection.case_query_messaging_enabled %}
|
||||
{% if userexam.outstanding_feedback_count %}
|
||||
<span class="badge bg-danger">{{ userexam.outstanding_feedback_count }} outstanding message{{ userexam.outstanding_feedback_count|pluralize }}</span>
|
||||
{% endif %}
|
||||
@@ -79,11 +87,14 @@
|
||||
{% else %}
|
||||
<span class="badge bg-dark-subtle text-body-secondary">No conversation yet</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a class="btn btn-sm btn-primary" href="{% url 'atlas:collection_history_user' collection.pk userexam.user_user.pk %}">Open Attempt</a>
|
||||
{% if collection.case_query_messaging_enabled %}
|
||||
<a class="btn btn-sm btn-outline-secondary" href="{% url 'atlas:collection_user_messages' collection.pk userexam.user_user.pk %}">Messages</a>
|
||||
{% endif %}
|
||||
<button
|
||||
hx-post="{% url 'atlas:collection_reset_answers_user' collection.pk userexam.user_user.pk %}"
|
||||
hx-target="#user-history-{{ userexam.user_user.pk }}"
|
||||
@@ -124,6 +135,7 @@
|
||||
<div class="small text-muted mt-1">Started: {{ cidexam.start_time|default:"-" }}</div>
|
||||
<div class="small text-muted">Ended: {{ cidexam.end_time|default:"-" }}</div>
|
||||
<div class="mt-2 d-flex flex-wrap gap-2">
|
||||
{% if collection.case_query_messaging_enabled %}
|
||||
{% if cidexam.outstanding_feedback_count %}
|
||||
<span class="badge bg-danger">{{ cidexam.outstanding_feedback_count }} outstanding message{{ cidexam.outstanding_feedback_count|pluralize }}</span>
|
||||
{% endif %}
|
||||
@@ -132,11 +144,14 @@
|
||||
{% else %}
|
||||
<span class="badge bg-dark-subtle text-body-secondary">No conversation yet</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a class="btn btn-sm btn-primary" href="{% url 'atlas:collection_history_ciduser' collection.pk cidexam.cid_user.cid %}">Open Attempt</a>
|
||||
{% if collection.case_query_messaging_enabled %}
|
||||
<a class="btn btn-sm btn-outline-secondary" href="{% url 'atlas:collection_cid_messages' collection.pk cidexam.cid_user.cid %}">Messages</a>
|
||||
{% endif %}
|
||||
<button
|
||||
hx-post="{% url 'atlas:collection_reset_answers_ciduser' collection.pk cidexam.cid_user.cid %}"
|
||||
hx-target="#cid-history-{{ cidexam.cid_user.cid }}"
|
||||
|
||||
@@ -131,6 +131,7 @@
|
||||
</details>
|
||||
{% endif %}
|
||||
|
||||
{% if collection.case_query_messaging_enabled %}
|
||||
<details class="mt-3" {% if row.case_review_stats.has_outstanding_any %}open{% endif %}>
|
||||
<summary class="small fw-semibold d-flex flex-wrap align-items-center gap-2">
|
||||
<span>Case conversation</span>
|
||||
@@ -151,6 +152,7 @@
|
||||
<div class="small text-muted">Loading case conversation...</div>
|
||||
</div>
|
||||
</details>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{% if collection.in_review_mode or cid_user_exam.completed %}
|
||||
<span class="badge bg-light text-dark ms-2">Review</span>
|
||||
{% endif %}
|
||||
{% if total_outstanding_feedback %}
|
||||
{% if collection.case_query_messaging_enabled and total_outstanding_feedback %}
|
||||
<span class="badge bg-danger ms-2">{{ total_outstanding_feedback }} outstanding feedback</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
@@ -35,7 +35,7 @@
|
||||
{% if cid_user_exam.completed %}
|
||||
<div class="mt-1 small">End time: {{ cid_user_exam.end_time|default:"-" }}</div>
|
||||
{% endif %}
|
||||
{% if collection.in_review_mode or cid_user_exam.completed %}
|
||||
{% if collection.case_query_messaging_enabled and (collection.in_review_mode or cid_user_exam.completed) %}
|
||||
<div class="mt-2">
|
||||
<a class="btn btn-sm {% if total_outstanding_feedback %}btn-danger{% else %}btn-outline-secondary{% endif %}"
|
||||
href="{% if cid is not None %}{% url 'atlas:collection_feedback_overview' pk=collection.id cid=cid passcode=passcode %}{% else %}{% url 'atlas:collection_feedback_overview_user' pk=collection.id %}{% endif %}">
|
||||
@@ -68,11 +68,13 @@
|
||||
{% if self_review %}
|
||||
<span class="badge bg-info-subtle text-info-emphasis ms-1">Self review added</span>
|
||||
{% endif %}
|
||||
{% if collection.case_query_messaging_enabled %}
|
||||
{% if review_stats.has_outstanding_feedback %}
|
||||
<span class="badge bg-danger ms-1">{{ review_stats.outstanding_feedback_count }} feedback item{{ review_stats.outstanding_feedback_count|pluralize }} to review</span>
|
||||
{% elif review_stats.has_messages %}
|
||||
<span class="badge bg-secondary ms-1">{{ review_stats.message_count }} conversation message{{ review_stats.message_count|pluralize }}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if collection.show_title_post %}
|
||||
<div class="small text-muted">
|
||||
@@ -90,7 +92,7 @@
|
||||
href="{% url 'atlas:add_self_review' cid_user_exam.id question.case.id %}">
|
||||
{% if self_review %}Add New Self Review{% else %}Add Self Review{% endif %}
|
||||
</a>
|
||||
{% if review_stats.has_messages %}
|
||||
{% if collection.case_query_messaging_enabled and review_stats.has_messages %}
|
||||
<a class="btn btn-sm {% if review_stats.has_outstanding_feedback %}btn-danger{% else %}btn-outline-dark{% endif %}"
|
||||
href="{% if cid is not None %}{% url 'atlas:collection_feedback_overview' pk=collection.id cid=cid passcode=passcode %}{% else %}{% url 'atlas:collection_feedback_overview_user' pk=collection.id %}{% endif %}#case-feedback-{{ question.case.id }}">
|
||||
View Conversation
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import pytest
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.urls import reverse
|
||||
|
||||
from atlas.models import CaseCollection, CaseDetail, CaseReviewMessage
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_collection_case_query_messaging_enabled_default_true():
|
||||
collection = CaseCollection.objects.create(name="Default messaging collection")
|
||||
|
||||
assert collection.case_query_messaging_enabled is True
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_collection_case_review_thread_returns_404_when_messaging_disabled(create_case, client):
|
||||
user_model = get_user_model()
|
||||
author = user_model.objects.create_user(username="author-thread", password="testpass")
|
||||
|
||||
collection = CaseCollection.objects.create(
|
||||
name="Thread disabled",
|
||||
case_query_messaging_enabled=False,
|
||||
)
|
||||
collection.author.add(author)
|
||||
|
||||
case = create_case
|
||||
CaseDetail.objects.create(collection=collection, case=case, sort_order=0)
|
||||
attempt = collection.get_or_create_cid_user_exam(user_user=author)
|
||||
|
||||
client.force_login(author)
|
||||
url = reverse(
|
||||
"atlas:collection_case_review_thread",
|
||||
kwargs={
|
||||
"exam_id": collection.pk,
|
||||
"cid_user_exam_id": attempt.pk,
|
||||
"case_id": case.pk,
|
||||
},
|
||||
)
|
||||
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_collection_user_messages_returns_404_when_messaging_disabled(create_case, client):
|
||||
user_model = get_user_model()
|
||||
author = user_model.objects.create_user(username="author-user-messages", password="testpass")
|
||||
learner = user_model.objects.create_user(username="learner-user-messages", password="testpass")
|
||||
|
||||
collection = CaseCollection.objects.create(
|
||||
name="User messages disabled",
|
||||
case_query_messaging_enabled=False,
|
||||
)
|
||||
collection.author.add(author)
|
||||
|
||||
case = create_case
|
||||
CaseDetail.objects.create(collection=collection, case=case, sort_order=0)
|
||||
collection.get_or_create_cid_user_exam(user_user=learner)
|
||||
|
||||
client.force_login(author)
|
||||
url = reverse(
|
||||
"atlas:collection_user_messages",
|
||||
kwargs={"exam_id": collection.pk, "user_pk": learner.pk},
|
||||
)
|
||||
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_user_messages_inbox_ignores_disabled_collection_messages(create_case, client):
|
||||
user_model = get_user_model()
|
||||
moderator = user_model.objects.create_user(username="moderator", password="testpass")
|
||||
learner = user_model.objects.create_user(username="learner", password="testpass")
|
||||
|
||||
enabled_collection = CaseCollection.objects.create(
|
||||
name="Enabled messaging collection",
|
||||
case_query_messaging_enabled=True,
|
||||
)
|
||||
disabled_collection = CaseCollection.objects.create(
|
||||
name="Disabled messaging collection",
|
||||
case_query_messaging_enabled=False,
|
||||
)
|
||||
enabled_collection.author.add(moderator)
|
||||
disabled_collection.author.add(moderator)
|
||||
|
||||
case = create_case
|
||||
CaseDetail.objects.create(collection=enabled_collection, case=case, sort_order=0)
|
||||
CaseDetail.objects.create(collection=disabled_collection, case=case, sort_order=0)
|
||||
|
||||
enabled_attempt = enabled_collection.get_or_create_cid_user_exam(user_user=learner)
|
||||
disabled_attempt = disabled_collection.get_or_create_cid_user_exam(user_user=learner)
|
||||
|
||||
CaseReviewMessage.objects.create(
|
||||
cid_user_exam=enabled_attempt,
|
||||
case=case,
|
||||
author=learner,
|
||||
message_type=CaseReviewMessage.MessageType.QUERY,
|
||||
message="Enabled message",
|
||||
requires_acknowledgement=True,
|
||||
)
|
||||
CaseReviewMessage.objects.create(
|
||||
cid_user_exam=disabled_attempt,
|
||||
case=case,
|
||||
author=learner,
|
||||
message_type=CaseReviewMessage.MessageType.QUERY,
|
||||
message="Disabled message",
|
||||
requires_acknowledgement=True,
|
||||
)
|
||||
|
||||
client.force_login(moderator)
|
||||
response = client.get(reverse("atlas:user_messages_inbox"))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Enabled messaging collection" in response.content.decode()
|
||||
assert "Disabled messaging collection" not in response.content.decode()
|
||||
+47
-2
@@ -4024,6 +4024,7 @@ def collection_viva_series(request, pk, series_id):
|
||||
def collection_detail(request, pk):
|
||||
collection = get_object_or_404(CaseCollection, pk=pk)
|
||||
collection_content_type = ContentType.objects.get_for_model(collection)
|
||||
messaging_enabled = _collection_case_query_messaging_enabled(collection)
|
||||
|
||||
# cases = collection.cases.all().order_by("casedetail__sort_order")
|
||||
casedetails = (
|
||||
@@ -4034,6 +4035,7 @@ def collection_detail(request, pk):
|
||||
.order_by("sort_order")
|
||||
)
|
||||
|
||||
if messaging_enabled:
|
||||
outstanding_messages = CaseReviewMessage.objects.filter(
|
||||
cid_user_exam__content_type=collection_content_type,
|
||||
cid_user_exam__object_id=collection.pk,
|
||||
@@ -4077,6 +4079,10 @@ def collection_detail(request, pk):
|
||||
.filter(Q(outstanding_message_count__gt=0) | Q(total_review_message_count__gt=0))
|
||||
.order_by("-outstanding_message_count", "-total_review_message_count", "-start_time")
|
||||
)
|
||||
else:
|
||||
total_outstanding_messages = 0
|
||||
user_message_rows = []
|
||||
cid_message_rows = []
|
||||
|
||||
return render(
|
||||
request,
|
||||
@@ -4100,6 +4106,7 @@ def collection_history(request, exam_id: int):
|
||||
|
||||
casedetails = list(CaseDetail.objects.filter(collection=collection).order_by("sort_order"))
|
||||
|
||||
if _collection_case_query_messaging_enabled(collection):
|
||||
userexams = list(
|
||||
collection.get_user_exams()
|
||||
.select_related("user_user")
|
||||
@@ -4132,6 +4139,15 @@ def collection_history(request, exam_id: int):
|
||||
)
|
||||
.order_by("-outstanding_feedback_count", "completed", "-start_time")
|
||||
)
|
||||
else:
|
||||
userexams = list(collection.get_user_exams().select_related("user_user").order_by("completed", "-start_time"))
|
||||
cidexams = list(collection.get_cid_exams().select_related("cid_user").order_by("completed", "-start_time"))
|
||||
for userexam in userexams:
|
||||
userexam.outstanding_feedback_count = 0
|
||||
userexam.total_review_message_count = 0
|
||||
for cidexam in cidexams:
|
||||
cidexam.outstanding_feedback_count = 0
|
||||
cidexam.total_review_message_count = 0
|
||||
|
||||
total_outstanding_messages = sum(item.outstanding_feedback_count for item in userexams) + sum(
|
||||
item.outstanding_feedback_count for item in cidexams
|
||||
@@ -4292,6 +4308,7 @@ def collection_history_user(request, exam_id: int, user_pk: int):
|
||||
case_review_stats, _, _ = _get_case_review_stats_for_exam(
|
||||
cid_user_exam=cid_user_exam,
|
||||
casedetails=casedetails,
|
||||
messaging_enabled=_collection_case_query_messaging_enabled(collection),
|
||||
)
|
||||
for casedetail in casedetails:
|
||||
user_answer = casedetail.get_user_answers(user)
|
||||
@@ -4348,6 +4365,7 @@ def collection_history_ciduser(request, exam_id: int, cid: int):
|
||||
case_review_stats, _, _ = _get_case_review_stats_for_exam(
|
||||
cid_user_exam=cid_user_exam,
|
||||
casedetails=casedetails,
|
||||
messaging_enabled=_collection_case_query_messaging_enabled(collection),
|
||||
)
|
||||
for casedetail in casedetails:
|
||||
user_answer = casedetail.get_cid_answers(cid)
|
||||
@@ -4396,6 +4414,7 @@ def collection_user_messages(request, exam_id: int, user_pk: int):
|
||||
"""
|
||||
|
||||
collection = get_object_or_404(CaseCollection, pk=exam_id)
|
||||
_enforce_collection_case_query_messaging_enabled(collection)
|
||||
user = get_object_or_404(User, pk=user_pk)
|
||||
cid_user_exam = collection.get_or_create_cid_user_exam(user_user=user)
|
||||
|
||||
@@ -4436,6 +4455,7 @@ def collection_cid_messages(request, exam_id: int, cid: int):
|
||||
"""Supervisor page showing case conversations for one CID attempt."""
|
||||
|
||||
collection = get_object_or_404(CaseCollection, pk=exam_id)
|
||||
_enforce_collection_case_query_messaging_enabled(collection)
|
||||
cid_user_exam = collection.get_or_create_cid_user_exam(cid=cid)
|
||||
|
||||
casedetails = list(
|
||||
@@ -4517,10 +4537,31 @@ def user_messages_inbox(request):
|
||||
)
|
||||
|
||||
attempts = [item["attempt"] for item in attempts_map.values()]
|
||||
attempt_ids = [attempt.pk for attempt in attempts]
|
||||
collection_ids = sorted({attempt.object_id for attempt in attempts})
|
||||
|
||||
collections_by_id = CaseCollection.objects.in_bulk(collection_ids)
|
||||
enabled_collection_ids = {
|
||||
collection_id
|
||||
for collection_id, collection in collections_by_id.items()
|
||||
if collection is not None and _collection_case_query_messaging_enabled(collection)
|
||||
}
|
||||
|
||||
attempts = [attempt for attempt in attempts if attempt.object_id in enabled_collection_ids]
|
||||
if not attempts:
|
||||
return render(
|
||||
request,
|
||||
"atlas/user_messages_inbox.html",
|
||||
{
|
||||
"inbox_rows": [],
|
||||
"total_outstanding": 0,
|
||||
"total_needs_your_ack": 0,
|
||||
"total_waiting_on_other": 0,
|
||||
"show_all": request.GET.get("show") == "all",
|
||||
},
|
||||
)
|
||||
|
||||
attempt_ids = [attempt.pk for attempt in attempts]
|
||||
collection_ids = sorted(enabled_collection_ids)
|
||||
|
||||
casedetails_by_collection = defaultdict(list)
|
||||
all_case_ids = set()
|
||||
@@ -4575,7 +4616,7 @@ def user_messages_inbox(request):
|
||||
attempt = item["attempt"]
|
||||
viewer_role = item["viewer_role"]
|
||||
collection = collections_by_id.get(attempt.object_id)
|
||||
if collection is None:
|
||||
if collection is None or not _collection_case_query_messaging_enabled(collection):
|
||||
continue
|
||||
|
||||
casedetails = casedetails_by_collection.get(collection.pk, [])
|
||||
@@ -4822,6 +4863,7 @@ def collection_case_review_thread(request, exam_id: int, cid_user_exam_id: int,
|
||||
"""
|
||||
|
||||
collection = get_object_or_404(CaseCollection, pk=exam_id)
|
||||
_enforce_collection_case_query_messaging_enabled(collection)
|
||||
collection_content_type = ContentType.objects.get_for_model(collection)
|
||||
cid_user_exam = get_object_or_404(
|
||||
CidUserExam,
|
||||
@@ -5517,6 +5559,7 @@ def collection_feedback_overview(
|
||||
):
|
||||
"""Show all case conversations for one learner attempt with outstanding items first."""
|
||||
collection = get_object_or_404(CaseCollection, pk=pk)
|
||||
_enforce_collection_case_query_messaging_enabled(collection)
|
||||
|
||||
try:
|
||||
collection.check_user_can_take(cid, passcode, request.user)
|
||||
@@ -5620,6 +5663,7 @@ def collection_take_overview(
|
||||
case_review_stats, outstanding_case_count, total_outstanding_feedback = _get_case_review_stats_for_exam(
|
||||
cid_user_exam=cid_user_exam,
|
||||
casedetails=casedetails,
|
||||
messaging_enabled=_collection_case_query_messaging_enabled(collection),
|
||||
)
|
||||
|
||||
question_answer_tuples = []
|
||||
@@ -5940,6 +5984,7 @@ def collection_case_view_take(
|
||||
case_review_stats, _, _ = _get_case_review_stats_for_exam(
|
||||
cid_user_exam=cid_user_exam,
|
||||
casedetails=[casedetail],
|
||||
messaging_enabled=_collection_case_query_messaging_enabled(collection),
|
||||
)
|
||||
return render(
|
||||
request,
|
||||
|
||||
Reference in New Issue
Block a user