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 %} + + {% endif %} +
+ +
+ Atlas Collections +
Collections: {{ atlas_collections|length }}
+ {% if atlas_collections %} + + {% 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.

+ + + + + + + + + + + + {% for user in users %} + + + + + + + {% endfor %} + +
NameUsernameContent countActions
{{ user.first_name }} {{ user.last_name }}{{ user.username }}{{ user.content_count }} + +
+ + +{% 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) +