diff --git a/generic/templates/generic/partials/user_content_review.html b/generic/templates/generic/partials/user_content_review.html
new file mode 100644
index 00000000..97a7a13b
--- /dev/null
+++ b/generic/templates/generic/partials/user_content_review.html
@@ -0,0 +1,62 @@
+{% load static %}
+{% if user_obj %}
+
+
Content by {{ user_obj.get_full_name }} ({{ user_obj.username }})
+
+ {% for app_key, info in content.items %}
+
+
{{ info.label }}
+
+ Questions: {{ info.questions|length }} Exams: {{ info.exams|length }}
+
+
+ {% if info.questions %}
+
+ {% for q in info.questions %}
+ -
+ {% comment %} Try to link to app question detail if available {% endcomment %}
+ {{ q }}
+
+ {% endfor %}
+
+ {% endif %}
+
+ {% if info.exams %}
+
+ {% for ex in info.exams %}
+ - {{ ex }}
+ {% endfor %}
+
+ {% endif %}
+
+
+ {% endfor %}
+
+
+
Atlas Cases
+
Cases: {{ atlas_cases|length }}
+ {% if atlas_cases %}
+
+ {% for case in atlas_cases %}
+ - {{ case }}
+ {% endfor %}
+
+ {% endif %}
+
+
+
+
Atlas Collections
+
Collections: {{ atlas_collections|length }}
+ {% if atlas_collections %}
+
+ {% for coll in atlas_collections %}
+ - {{ coll }}
+ {% endfor %}
+
+ {% endif %}
+
+
+
+{% else %}
+ No user found
+{% endif %}
diff --git a/generic/templates/generic/user_content_list.html b/generic/templates/generic/user_content_list.html
new file mode 100644
index 00000000..44ea2523
--- /dev/null
+++ b/generic/templates/generic/user_content_list.html
@@ -0,0 +1,35 @@
+{% extends 'generic/base.html' %}
+
+{% block content %}
+ User content
+
+ Click "Show content" to load items created by a user.
+
+
+
+
+ | Name |
+ Username |
+ Content count |
+ Actions |
+
+
+
+ {% for user in users %}
+
+ | {{ user.first_name }} {{ user.last_name }} |
+ {{ user.username }} |
+ {{ user.content_count }} |
+
+
+ |
+
+ {% endfor %}
+
+
+
+
+{% endblock %}
diff --git a/generic/urls.py b/generic/urls.py
index 62ffefa0..4da768a0 100755
--- a/generic/urls.py
+++ b/generic/urls.py
@@ -8,6 +8,9 @@ from generic.views import ExamViews as GenericExamViews, GenericViewBase
app_name = "generic"
urlpatterns = [
+ # User content review views
+ path("user-content/", views.user_content_list, name="user_content_list"),
+ path("user-content//", views.user_content_review, name="user_content_review"),
path("examination/", views.ExaminationView.as_view(), name="examination_view"),
path("examination/", views.examination_detail, name="examination_detail"),
path(
diff --git a/generic/views.py b/generic/views.py
index 6adab348..6d82ade0 100644
--- a/generic/views.py
+++ b/generic/views.py
@@ -6877,3 +6877,87 @@ def supervisor_search(request):
return render(request, "generic/partials/supervisor_search_results.html", {"results": results, "row": row})
+
+@login_required
+@user_passes_test(lambda u: u.is_superuser or u.groups.filter(name__in=["content_moderator", "atlas_editor"]).exists())
+def user_content_list(request):
+ """
+ Shows a list of all users with the ability to load the content they have created via htmx requests
+ """
+ users = User.objects.annotate(
+ content_count=Count("user_anatomy_exams", filter=Q(user_anatomy_exams__archive=False))
+ + Count("user_longs_exams", filter=Q(user_longs_exams__archive=False))
+ + Count("user_physics_exams", filter=Q(user_physics_exams__archive=False))
+ + Count("user_rapid_exams", filter=Q(user_rapid_exams__archive=False))
+ + Count("user_sba_exams", filter=Q(user_sba_exams__archive=False))
+ + Count("user_shorts_exams", filter=Q(user_shorts_exams__archive=False))
+ ).filter(content_count__gt=0)
+
+ return render(request, "generic/user_content_list.html", {"users": users})
+
+
+
+@login_required
+@user_passes_test(lambda u: u.is_superuser or u.groups.filter(name__in=["content_moderator", "atlas_editor"]).exists())
+def user_content_review(request, user_id):
+ """
+ HTMX partial to show content authored by a given user.
+ Includes authored questions and authored exams for each app,
+ plus atlas cases and case collections.
+ """
+ user_obj = get_object_or_404(User, pk=user_id)
+
+ # Mapping of app -> (question_related_name, exam_author_related_name, display_label)
+ APP_MAP = {
+ "anatomy": ("anatomy_authored_questions", "anatomy_exam_author", "Anatomy"),
+ "physics": ("physics_authored_questions", "physics_exam_author", "Physics"),
+ "rapids": ("rapid_authored_questions", "rapid_exam_author", "Rapids"),
+ "shorts": ("shorts_authored_questions", "shorts_exam_author", "Shorts"),
+ "longs": ("long_authored_questions", "long_exam_author", "Longs"),
+ "sbas": ("sbas_authored_questions", "sba_exam_author", "SBAs"),
+ }
+
+ content = {}
+
+ for app_key, (q_rel, e_rel, label) in APP_MAP.items():
+ questions = []
+ exams = []
+ try:
+ questions = list(getattr(user_obj, q_rel).all().order_by("-pk")[:50])
+ except Exception:
+ questions = []
+
+ try:
+ exams = list(getattr(user_obj, e_rel).all().filter(archive=False).order_by("-pk")[:50])
+ except Exception:
+ exams = []
+
+ content[app_key] = {
+ "label": label,
+ "questions": questions,
+ "exams": exams,
+ }
+
+ # Atlas authored content
+ atlas_cases = []
+ atlas_collections = []
+ try:
+ atlas_cases = list(user_obj.atlas_authored_cases.all().order_by("-pk")[:50])
+ except Exception:
+ atlas_cases = []
+ try:
+ atlas_collections = list(user_obj.casecollection_authored_cases.all().order_by("-pk")[:50])
+ except Exception:
+ atlas_collections = []
+
+ context = {
+ "user_obj": user_obj,
+ "content": content,
+ "atlas_cases": atlas_cases,
+ "atlas_collections": atlas_collections,
+ }
+
+ # Render HTMX partial (or full page if not HTMX)
+ template = "generic/partials/user_content_review.html"
+ return render(request, template, context)
+