Add question review list view and template with filtering options
This commit is contained in:
@@ -296,6 +296,11 @@ def generic_view_urls(generic_views: GenericViewBase):
|
|||||||
generic_views.question_review_next,
|
generic_views.question_review_next,
|
||||||
name="question_review_next",
|
name="question_review_next",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"question/review/list",
|
||||||
|
generic_views.question_review_list,
|
||||||
|
name="question_review_list",
|
||||||
|
),
|
||||||
path("question/<int:pk>/thumbnail/fail", generic_views.question_thumbnail_fail, name="series_thumbnail_fail"),
|
path("question/<int:pk>/thumbnail/fail", generic_views.question_thumbnail_fail, name="series_thumbnail_fail"),
|
||||||
path("question/<int:pk>/thumbnail", generic_views.question_thumbnail, name="series_thumbnail"),
|
path("question/<int:pk>/thumbnail", generic_views.question_thumbnail, name="series_thumbnail"),
|
||||||
path(
|
path(
|
||||||
|
|||||||
@@ -3139,6 +3139,65 @@ class GenericViewBase:
|
|||||||
return render(request, f"{self.app_name}/question_review_complete.html", {"app_name": self.app_name})
|
return render(request, f"{self.app_name}/question_review_complete.html", {"app_name": self.app_name})
|
||||||
|
|
||||||
|
|
||||||
|
def question_review_list(self, request):
|
||||||
|
"""Return a page listing questions that match the supplied filters.
|
||||||
|
|
||||||
|
Accepts query parameters similar to question_review_next: category, status.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Read filters
|
||||||
|
category = request.GET.get("category") or request.POST.get("category")
|
||||||
|
status = request.GET.get("status") or request.POST.get("status") or "ANY"
|
||||||
|
|
||||||
|
# Build base queryset of questions for this app
|
||||||
|
qs = self.question_object.objects.all().order_by("pk")
|
||||||
|
|
||||||
|
# Restrict by category if provided
|
||||||
|
if category:
|
||||||
|
try:
|
||||||
|
qs = qs.filter(category__id=int(category))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Apply permission filter similar to question_review_next
|
||||||
|
if not request.user.groups.filter(name=self.checker_group).exists():
|
||||||
|
try:
|
||||||
|
qs = qs.filter(open_access=True) | qs.filter(author__id=request.user.id)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Optionally filter by review status
|
||||||
|
results = []
|
||||||
|
for question in qs:
|
||||||
|
try:
|
||||||
|
if question.authors_only and not (
|
||||||
|
request.user.is_superuser or request.user in question.author.all()
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
ct = ContentType.objects.get_for_model(question)
|
||||||
|
latest = (
|
||||||
|
QuestionReview.objects.filter(content_type=ct, object_id=question.pk)
|
||||||
|
.order_by("-created_on").first()
|
||||||
|
)
|
||||||
|
|
||||||
|
match = False
|
||||||
|
if status == "ANY":
|
||||||
|
match = True
|
||||||
|
elif status == "UNREVIEWED":
|
||||||
|
match = latest is None
|
||||||
|
else:
|
||||||
|
if latest is not None and latest.status == status:
|
||||||
|
match = True
|
||||||
|
|
||||||
|
if match:
|
||||||
|
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}})
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(user_passes_test(lambda u: u.is_superuser))
|
@method_decorator(user_passes_test(lambda u: u.is_superuser))
|
||||||
def user_answer_delete_multiple(self, request):
|
def user_answer_delete_multiple(self, request):
|
||||||
print(request.POST["answer_ids"])
|
print(request.POST["answer_ids"])
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
{% extends 'sbas/base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Filtered questions</h2>
|
||||||
|
|
||||||
|
<p>Showing questions matching the selected filters.</p>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
Active filters:
|
||||||
|
{% if filters.category %}
|
||||||
|
<span class="badge bg-primary me-1">Category: {{ filters.category }}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary me-1">Category: Any</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if filters.status %}
|
||||||
|
<span class="badge bg-primary me-1">Status: {{ filters.status }}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary me-1">Status: Any</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if questions %}
|
||||||
|
<ul class="list-group">
|
||||||
|
{% for q in questions %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<a href="{% url app_name|add:':question_detail' q.pk %}">{{ q.title|default:q.stem|truncatechars:80 }}</a>
|
||||||
|
<div class="small text-muted">ID: {{ q.pk }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group">
|
||||||
|
<a class="btn btn-sm btn-outline-secondary" href="{% url app_name|add:':question_detail' q.pk %}">View</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-info">No questions match the selected filters.</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<a href="{% url app_name|add:':question_review_start' %}" class="btn btn-secondary">Back to filters</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -24,6 +24,9 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">Start review</button>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="id_status" class="form-label">Review status</label>
|
<label for="id_status" class="form-label">Review status</label>
|
||||||
<select name="status" id="id_status" class="form-select">
|
<select name="status" id="id_status" class="form-select">
|
||||||
@@ -32,8 +35,28 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
<button type="submit" class="btn btn-primary">Start review</button>
|
<p>You can also view the list of questions matching the filters below.</p>
|
||||||
|
<button id="view-list-btn" type="button" class="btn btn-outline-primary">View list</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// When View list is clicked, read the values from the main filter form and navigate
|
||||||
|
(function(){
|
||||||
|
const btn = document.getElementById('view-list-btn');
|
||||||
|
btn.addEventListener('click', function(){
|
||||||
|
const base = "{% url 'sbas:question_review_list' %}";
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
const categoryEl = document.getElementById('id_category');
|
||||||
|
const statusEl = document.getElementById('id_status');
|
||||||
|
if (categoryEl && categoryEl.value) params.set('category', categoryEl.value);
|
||||||
|
if (statusEl && statusEl.value) params.set('status', statusEl.value);
|
||||||
|
const url = base + (params.toString() ? ('?' + params.toString()) : '');
|
||||||
|
window.location.href = url;
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user