diff --git a/AGENTS.md b/AGENTS.md index 0869004f..eef35c9c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,6 +48,7 @@ Django-based radiology education platform. Multi-app, HTMX-driven, PostgreSQL + - **Template tags**: always load `{% load static %}`, `{% load django_htmx %}`, `{% load crispy_forms_tags %}` as needed at the top of the file. - **Bootstrap 5** (dark theme). Use existing utility classes; do not add inline styles. - To include a partial: `{% include 'app/partials/_fragment.html' %}`. Pass context explicitly when using `with` keyword. +- **Multi line comment** NEVER use {# ... #} style comments for multiline comments as it will render into the html. ## Forms diff --git a/atlas/templates/atlas/partials/_sharing_panel.html b/atlas/templates/atlas/partials/_sharing_panel.html index 080ef1af..4860f75e 100644 --- a/atlas/templates/atlas/partials/_sharing_panel.html +++ b/atlas/templates/atlas/partials/_sharing_panel.html @@ -8,7 +8,7 @@
Result Sharing - Shared with me + Supervision
diff --git a/atlas/templates/atlas/shared_with_me.html b/atlas/templates/atlas/shared_with_me.html deleted file mode 100644 index 3dca8f89..00000000 --- a/atlas/templates/atlas/shared_with_me.html +++ /dev/null @@ -1,71 +0,0 @@ -{% extends 'atlas/exams.html' %} -{% load static %} - -{% block content %} -
-

Shared with me

-

- Collection attempts shared with you as an author, marker, supervisor, or by direct invitation. -

-
- - {% if attempt_list %} -
- - - - - - - - - - - - {% for attempt, collection in attempt_list %} - - - - - - - - {% endfor %} - -
CollectionLearnerStartedStatus
- {% if collection %} - {{ collection.name }} - {% else %} - Unknown collection - {% endif %} - - {% if attempt.user_user %} - {{ attempt.user_user.get_full_name|default:attempt.user_user.username }} - {% if attempt.user_user.email %} -
{{ attempt.user_user.email }} - {% endif %} - {% elif attempt.cid_user %} - CID {{ attempt.cid_user.cid }} - {% if attempt.cid_user.name %}
{{ attempt.cid_user.name }}{% endif %} - {% else %} - Unknown - {% endif %} -
{{ attempt.start_time|default:"-" }} - {% if attempt.completed %} - Completed - {% else %} - In progress - {% endif %} - - - View attempt - -
-
- {% else %} -
- No collection attempts have been shared with you yet. -
- {% endif %} -{% endblock %} diff --git a/atlas/templates/atlas/supervision.html b/atlas/templates/atlas/supervision.html index a6bc2560..709bf6c3 100644 --- a/atlas/templates/atlas/supervision.html +++ b/atlas/templates/atlas/supervision.html @@ -2,135 +2,155 @@ {% load static %} {% block content %} -
-
-

Supervision

-

- Collection attempts shared with you as author, marker, supervisor or by direct invitation. - {{ total_attempts }} attempt{{ total_attempts|pluralize }} found. -

-
-
- -{# ---- Filter / group toolbar ---- #} -
-
- -
- - By learner - - - By collection - +
+
+

Supervision

+

+ Collection attempts shared with you as author, marker, supervisor or by direct invitation. + {{ total_attempts }} attempt{{ total_attempts|pluralize }} found. +

-
- - -
-
- - -
- -
- - Clear -
- -{# ---- Grouped results ---- #} -{% if groups %} - {% for group_label, attempt_rows in groups %} -
-
- {% if group_by == 'user' %} - - {% else %} - - {% endif %} - {{ group_label }} - {{ attempt_rows|length }} attempt{{ attempt_rows|length|pluralize }} -
-
- - - - {% if group_by == 'user' %} - - {% else %} - - {% endif %} - - - - - - - - - {% for attempt, collection in attempt_rows %} - - {% if group_by == 'user' %} - - {% else %} - - {% endif %} - - - - - - {% endfor %} - -
CollectionLearnerStartedEndedStatusProgress
- {% if collection %} - {{ collection.name }} - {% else %} - Unknown - {% endif %} - - {% if attempt.user_user %} - {{ attempt.user_user.get_full_name|default:attempt.user_user.username }} - {% if attempt.user_user.email %} -
{{ attempt.user_user.email }} - {% endif %} - {% elif attempt.cid_user %} - CID {{ attempt.cid_user.cid }} - {% if attempt.cid_user.name %}
{{ attempt.cid_user.name }}{% endif %} - {% else %} - Unknown - {% endif %} -
{{ attempt.start_time|default:"-" }}{{ attempt.end_time|default:"-" }} - {% if attempt.completed %} - Completed - {% else %} - In progress - {% endif %} - - {% if attempt.user_user_id %} - - View attempt - - {% else %} - - View attempt - - {% endif %} -
+ {# ---- Filter / group toolbar ---- #} +
+ {# Group by #} + - {% endfor %} -{% else %} -
- No collection attempts have been shared with you yet. Try adjusting the date filter. -
-{% endif %} + + {# Learner search #} +
+ + +
+ + {# Date range #} +
+ + +
+
+ + +
+ + + + {# Supervisor-only toggle (only visible when user is a supervisor) #} + {% if is_supervisor %} +
+
+ + +
+
+ {% endif %} + +
+ + Clear +
+
+ + {# ---- Grouped results ---- #} + {% if groups %} + {% for group_label, attempt_rows in groups %} +
+
+ {% if group_by == 'user' %} + + {% else %} + + {% endif %} + {{ group_label }} + {{ attempt_rows|length }} attempt{{ attempt_rows|length|pluralize }} +
+
+ + + + {% if group_by == 'user' %} + + {% else %} + + {% endif %} + + + + + + + + {% for attempt, collection in attempt_rows %} + + {% if group_by == 'user' %} + + {% else %} + + {% endif %} + + + + + + {% endfor %} + +
CollectionLearnerStartedEndedStatus
+ {% if collection %}{{ collection.name }}{% else %}Unknown{% endif %} + + {% if attempt.user_user %} + {{ attempt.user_user.get_full_name|default:attempt.user_user.username }} + {% if attempt.user_user.email %} +
{{ attempt.user_user.email }} + {% endif %} + {% elif attempt.cid_user %} + CID {{ attempt.cid_user.cid }} + {% if attempt.cid_user.name %}
{{ attempt.cid_user.name }}{% endif %} + {% else %} + Unknown + {% endif %} +
{{ attempt.start_time|default:"-" }}{{ attempt.end_time|default:"-" }} + {% if attempt.completed %} + Completed + {% else %} + In progress + {% endif %} + + {% if attempt.user_user_id and collection %} + + View attempt + + {% else %} + + View attempt + + {% endif %} +
+
+
+ {% endfor %} + {% else %} +
+ No collection attempts found. Try adjusting the filters. +
+ {% endif %} {% endblock %} diff --git a/atlas/views.py b/atlas/views.py index d1613cbc..4ff06c84 100755 --- a/atlas/views.py +++ b/atlas/views.py @@ -5844,72 +5844,9 @@ def collection_exam_user_search(request, pk): @login_required def collection_shared_with_me(request): - """Full page listing all collection attempts that have been shared with the current user. + """Legacy redirect — the shared-with-me page is now part of the supervision page.""" + return redirect(reverse("atlas:collection_supervision")) - Includes attempts where the user is: - - An author or marker of the collection - - A supervisor of the attempt owner (share_with_supervisor=True or exam results_supervisor_visible) - - Manually listed in CidUserExam.shared_with_users - - The user's own attempts are excluded. - """ - collection_ct = ContentType.objects.get_for_model(CaseCollection) - - # Manual shares - manual_qs = CidUserExam.objects.filter( - shared_with_users=request.user, - content_type=collection_ct, - ).exclude(user_user=request.user) - - # Author or marker of the collection - managed_collection_ids = list( - CaseCollection.objects.filter( - Q(author=request.user) | Q(markers=request.user) - ).values_list("pk", flat=True) - ) - authored_qs = CidUserExam.objects.filter( - content_type=collection_ct, - object_id__in=managed_collection_ids, - ).exclude(user_user=request.user) - - # Supervisor (per-attempt flag) - supervisor_qs = CidUserExam.objects.none() - supervisor_exam_qs = CidUserExam.objects.none() - try: - supervisor = request.user.supervisor - trainee_users = User.objects.filter(userprofile__supervisor=supervisor) - trainee_cid_users = CidUser.objects.filter(supervisor=supervisor) - supervisor_qs = CidUserExam.objects.filter( - content_type=collection_ct, - share_with_supervisor=True, - ).filter( - Q(user_user__in=trainee_users) | Q(cid_user__in=trainee_cid_users) - ).exclude(user_user=request.user) - - # Exam-level supervisor visibility - visible_collection_ids = list( - CaseCollection.objects.filter(results_supervisor_visible=True).values_list("pk", flat=True) - ) - supervisor_exam_qs = CidUserExam.objects.filter( - content_type=collection_ct, - object_id__in=visible_collection_ids, - ).filter( - Q(user_user__in=trainee_users) | Q(cid_user__in=trainee_cid_users) - ).exclude(user_user=request.user) - except Exception: - pass - - all_attempts = ( - manual_qs | authored_qs | supervisor_qs | supervisor_exam_qs - ).distinct().select_related("user_user", "cid_user").order_by("-start_time") - - attempt_list = [(attempt, attempt.exam) for attempt in all_attempts] - - return render( - request, - "atlas/shared_with_me.html", - {"attempt_list": attempt_list}, - ) @login_required @@ -5993,13 +5930,31 @@ def collection_supervision(request): - GET param ``group_by``: ``user`` (default) or ``collection`` - GET params ``date_from`` / ``date_to``: ISO date strings to filter by attempt start_time (inclusive). + - GET param ``q``: free-text search over learner name / username / email. + - GET param ``supervisor_only``: ``1`` to restrict to attempts where the current + user is the assigned supervisor (only shown when the user is a supervisor). """ from django.db.models import Q from django.contrib.contenttypes.models import ContentType + from django.utils.dateparse import parse_date collection_ct = ContentType.objects.get_for_model(CaseCollection) - # ---- build base queryset of accessible attempts ---- + # ---- determine supervisor status ---- + is_supervisor = False + trainee_users = User.objects.none() + trainee_cid_users = CidUser.objects.none() + try: + supervisor = request.user.supervisor + is_supervisor = True + trainee_users = User.objects.filter(userprofile__supervisor=supervisor) + trainee_cid_users = CidUser.objects.filter(supervisor=supervisor) + except Exception: + pass + + supervisor_only = request.GET.get("supervisor_only", "") == "1" and is_supervisor + + # ---- build base querysets of accessible attempts ---- manual_qs = CidUserExam.objects.filter( shared_with_users=request.user, content_type=collection_ct, @@ -6017,10 +5972,7 @@ def collection_supervision(request): supervisor_qs = CidUserExam.objects.none() supervisor_exam_qs = CidUserExam.objects.none() - try: - supervisor = request.user.supervisor - trainee_users = User.objects.filter(userprofile__supervisor=supervisor) - trainee_cid_users = CidUser.objects.filter(supervisor=supervisor) + if is_supervisor: supervisor_qs = CidUserExam.objects.filter( content_type=collection_ct, share_with_supervisor=True, @@ -6037,21 +5989,21 @@ def collection_supervision(request): ).filter( Q(user_user__in=trainee_users) | Q(cid_user__in=trainee_cid_users) ).exclude(user_user=request.user) - except Exception: - pass - all_attempts = ( - manual_qs | authored_qs | supervisor_qs | supervisor_exam_qs - ).distinct().select_related("user_user", "cid_user") + if supervisor_only: + all_attempts = (supervisor_qs | supervisor_exam_qs).distinct() + else: + all_attempts = ( + manual_qs | authored_qs | supervisor_qs | supervisor_exam_qs + ).distinct() + + all_attempts = all_attempts.select_related("user_user", "cid_user") # ---- date filter ---- date_from_raw = request.GET.get("date_from", "").strip() date_to_raw = request.GET.get("date_to", "").strip() - date_from = None - date_to = None try: if date_from_raw: - from django.utils.dateparse import parse_date date_from = parse_date(date_from_raw) if date_from: all_attempts = all_attempts.filter(start_time__date__gte=date_from) @@ -6059,13 +6011,24 @@ def collection_supervision(request): pass try: if date_to_raw: - from django.utils.dateparse import parse_date date_to = parse_date(date_to_raw) if date_to: all_attempts = all_attempts.filter(start_time__date__lte=date_to) except Exception: pass + # ---- user / learner search ---- + q = request.GET.get("q", "").strip() + if q: + all_attempts = all_attempts.filter( + Q(user_user__first_name__icontains=q) + | Q(user_user__last_name__icontains=q) + | Q(user_user__username__icontains=q) + | Q(user_user__email__icontains=q) + | Q(cid_user__name__icontains=q) + | Q(cid_user__email__icontains=q) + ) + all_attempts = all_attempts.order_by("-start_time") # ---- grouping ---- @@ -6094,7 +6057,6 @@ def collection_supervision(request): key = (key_id, key_label) grouped.setdefault(key, []).append((attempt, collection_obj)) - # Convert to sorted list of (key_label, [(attempt, collection), ...]) groups = sorted(grouped.items(), key=lambda x: str(x[0][1]).lower()) groups = [(label, rows) for (_, label), rows in groups] @@ -6106,6 +6068,9 @@ def collection_supervision(request): "group_by": group_by, "date_from": date_from_raw, "date_to": date_to_raw, + "q": q, + "is_supervisor": is_supervisor, + "supervisor_only": supervisor_only, "total_attempts": all_attempts.count(), }, ) diff --git a/templates/base.html b/templates/base.html index a87a665e..db192389 100644 --- a/templates/base.html +++ b/templates/base.html @@ -214,9 +214,6 @@
  • Supervision
  • -
  • - Shared with me -