Enhance question review functionality with latest review status annotation and display in templates
This commit is contained in:
+31
-9
@@ -3199,6 +3199,20 @@ class GenericViewBase:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Annotate with latest review status using a DB subquery for efficiency
|
||||||
|
try:
|
||||||
|
from django.db.models import OuterRef, Subquery
|
||||||
|
ct = ContentType.objects.get_for_model(self.question_object)
|
||||||
|
latest_status_subq = (
|
||||||
|
QuestionReview.objects.filter(content_type=ct, object_id=OuterRef("pk"))
|
||||||
|
.order_by("-created_on")
|
||||||
|
.values("status")[:1]
|
||||||
|
)
|
||||||
|
qs = qs.annotate(latest_review_status=Subquery(latest_status_subq))
|
||||||
|
except Exception:
|
||||||
|
# annotation failed — we'll fall back to per-object lookup below
|
||||||
|
pass
|
||||||
|
|
||||||
# Optionally filter by review status
|
# Optionally filter by review status
|
||||||
results = []
|
results = []
|
||||||
for question in qs:
|
for question in qs:
|
||||||
@@ -3210,19 +3224,15 @@ class GenericViewBase:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
ct = ContentType.objects.get_for_model(question)
|
# Determine latest status — prefer annotated value if present
|
||||||
latest = (
|
latest_status = getattr(question, "latest_review_status", None)
|
||||||
QuestionReview.objects.filter(content_type=ct, object_id=question.pk)
|
|
||||||
.order_by("-created_on").first()
|
|
||||||
)
|
|
||||||
|
|
||||||
match = False
|
match = False
|
||||||
if status == "ANY":
|
if status == "ANY":
|
||||||
match = True
|
match = True
|
||||||
elif status == "UNREVIEWED":
|
elif status == "UNREVIEWED":
|
||||||
match = latest is None
|
match = latest_status is None
|
||||||
else:
|
else:
|
||||||
if latest is not None and latest.status == status:
|
if latest_status is not None and latest_status == status:
|
||||||
match = True
|
match = True
|
||||||
|
|
||||||
if match:
|
if match:
|
||||||
@@ -3241,7 +3251,19 @@ class GenericViewBase:
|
|||||||
|
|
||||||
results.append(question)
|
results.append(question)
|
||||||
|
|
||||||
return render(request, f"{self.app_name}/question_review_list.html", {"questions": results, "app_name": self.app_name, "filters": {"category": category, "status": status}})
|
# Provide human-readable labels for statuses to the template
|
||||||
|
status_choices = {code: label for code, label in QuestionReview.StatusChoices.choices}
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
f"{self.app_name}/question_review_list.html",
|
||||||
|
{
|
||||||
|
"questions": results,
|
||||||
|
"app_name": self.app_name,
|
||||||
|
"filters": {"category": category, "status": status},
|
||||||
|
"status_choices": status_choices,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(user_passes_test(lambda u: u.is_superuser))
|
@method_decorator(user_passes_test(lambda u: u.is_superuser))
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<h5 class="h6">Feedback</h5>
|
<h5 class="h6">Feedback</h5>
|
||||||
<div>{{ question.feedback|linebreaks }}</div>
|
<div>{{ question.feedback|safe|linebreaks }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{% extends 'sbas/base.html' %}
|
{% extends 'sbas/base.html' %}
|
||||||
|
|
||||||
|
{% load partials %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Filtered questions</h2>
|
<h2>Filtered questions</h2>
|
||||||
|
|
||||||
@@ -36,6 +38,16 @@
|
|||||||
<div>
|
<div>
|
||||||
<a href="{% url app_name|add:':question_detail' q.pk %}">{{ q.title|default:q.stem|truncatechars:80|safe }}</a>
|
<a href="{% url app_name|add:':question_detail' q.pk %}">{{ q.title|default:q.stem|truncatechars:80|safe }}</a>
|
||||||
<div class="small text-muted">ID: {{ q.pk }}</div>
|
<div class="small text-muted">ID: {{ q.pk }}</div>
|
||||||
|
{% comment %} Show latest review status if available {% endcomment %}
|
||||||
|
{% with status_code=q.latest_review_status %}
|
||||||
|
{% if status_code %}
|
||||||
|
{% with label=status_choices|get_item:status_code %}
|
||||||
|
<div class="mt-1"><span class="badge bg-secondary">Latest review: {{ label }}</span></div>
|
||||||
|
{% endwith %}
|
||||||
|
{% else %}
|
||||||
|
<div class="mt-1"><span class="text-muted small">Latest review: Unreviewed</span></div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a class="btn btn-sm btn-outline-secondary" href="{% url app_name|add:':question_detail' q.pk %}">View</a>
|
<a class="btn btn-sm btn-outline-secondary" href="{% url app_name|add:':question_detail' q.pk %}">View</a>
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Start review</button>
|
<button type="submit" class="btn btn-primary">Start review</button>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -40,7 +42,13 @@
|
|||||||
<button id="view-list-btn" type="button" class="btn btn-outline-primary">View list</button>
|
<button id="view-list-btn" type="button" class="btn btn-outline-primary">View list</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Checkbox outside the POST form so it only affects the View List flow -->
|
||||||
|
<div class="form-check mb-2">
|
||||||
|
<input class="form-check-input" type="checkbox" value="me" id="id_reviewed_me">
|
||||||
|
<label class="form-check-label" for="id_reviewed_me">Only questions I've reviewed (applies to View list)</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// When View list is clicked, read the values from the main filter form and navigate
|
// When View list is clicked, read the values from the main filter form and navigate
|
||||||
@@ -51,8 +59,10 @@
|
|||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
const categoryEl = document.getElementById('id_category');
|
const categoryEl = document.getElementById('id_category');
|
||||||
const statusEl = document.getElementById('id_status');
|
const statusEl = document.getElementById('id_status');
|
||||||
|
const reviewedEl = document.getElementById('id_reviewed_me');
|
||||||
if (categoryEl && categoryEl.value) params.set('category', categoryEl.value);
|
if (categoryEl && categoryEl.value) params.set('category', categoryEl.value);
|
||||||
if (statusEl && statusEl.value) params.set('status', statusEl.value);
|
if (statusEl && statusEl.value) params.set('status', statusEl.value);
|
||||||
|
if (reviewedEl && reviewedEl.checked) params.set('reviewed', reviewedEl.value || 'me');
|
||||||
const url = base + (params.toString() ? ('?' + params.toString()) : '');
|
const url = base + (params.toString() ? ('?' + params.toString()) : '');
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user