From 2c30145d3ae6fd85e8e594cd2a7cba4415e1c5a1 Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 13 Oct 2025 14:14:17 +0100 Subject: [PATCH] Add sync functionality for prerequisite users in collection detail --- atlas/templates/atlas/collection_detail.html | 66 ++++++++++++------ .../atlas/collection_take_start.html | 1 + .../atlas/prerequisite_required.html | 7 +- atlas/urls.py | 5 ++ atlas/views.py | 68 +++++++++++++++++-- 5 files changed, 118 insertions(+), 29 deletions(-) diff --git a/atlas/templates/atlas/collection_detail.html b/atlas/templates/atlas/collection_detail.html index 6275710f..0efb370d 100644 --- a/atlas/templates/atlas/collection_detail.html +++ b/atlas/templates/atlas/collection_detail.html @@ -4,30 +4,30 @@ {% partialdef casedetails-management-links %} - ( + ( - {% if casedetail.default_viewerstate %} - - {% endif %} - ) + {% if casedetail.default_viewerstate %} + + {% endif %} + ) - () + () - {% if collection.collection_type == "QUE" %} - ( - {% if casedetail.question_schema %} - - {% else %} - - {% endif %} - - ) - {% endif %} + {% if collection.collection_type == "QUE" %} + ( + {% if casedetail.question_schema %} + + {% else %} + + {% endif %} + + ) + {% endif %} - {% if casedetail.case.previous_case %} - ( - ) - {% endif %} + {% if casedetail.case.previous_case %} + ( + ) + {% endif %} {% endpartialdef %} {% block content %} @@ -42,6 +42,17 @@ Collection Type: {{collection.get_collection_type_display}}
Self review: {{collection.self_review}}
Open access: {{collection.open_access}}
+ {% if collection.prerequisites.exists %} + Prerequisite collections: + + {% endif %} @@ -58,14 +69,17 @@

Review collection here {% endif %} + + +

Cases

    {% for casedetail in casesdetails %}
  1. - + Case {{forloop.counter}} - : + : {% if casedetail.case.series.count == 0 %}    {% endif %} @@ -114,6 +128,14 @@
    +

    + +

    {% endif %} {% include 'exam_overview_js.html' %} {% endblock %} diff --git a/atlas/templates/atlas/collection_take_start.html b/atlas/templates/atlas/collection_take_start.html index c09f4b46..90ca5c4d 100644 --- a/atlas/templates/atlas/collection_take_start.html +++ b/atlas/templates/atlas/collection_take_start.html @@ -6,6 +6,7 @@ {% if request.user.is_authenticated and valid_user %} User: {{request.user}}
    + {% if cid_exam %} Started: {{cid_exam.start_time}}
    diff --git a/atlas/templates/atlas/prerequisite_required.html b/atlas/templates/atlas/prerequisite_required.html index cc13e0a9..a03cfc86 100644 --- a/atlas/templates/atlas/prerequisite_required.html +++ b/atlas/templates/atlas/prerequisite_required.html @@ -5,7 +5,12 @@

    {{ message }}

    {% if prereq %}

    - You must complete the collection: {{ prereq.name }} + You must complete the collection: + {% if cid and passcode %} + {{ prereq.name }} + {% else %} + {{ prereq.name }} + {% endif %}

    {% endif %}

    diff --git a/atlas/urls.py b/atlas/urls.py index 13e136cc..e61c4adf 100755 --- a/atlas/urls.py +++ b/atlas/urls.py @@ -80,6 +80,11 @@ urlpatterns = [ views.collection_take_start, name="collection_take_start", ), + path( + "collection//sync_prerequisite_users", + views.collection_sync_prerequisite_users, + name="collection_sync_prerequisite_users", + ), path("collection//authors", views.CaseCollectionAuthorUpdate.as_view(), name="collection_authors"), path( "collection//toggle_results_published", diff --git a/atlas/views.py b/atlas/views.py index 3f109380..552aba99 100755 --- a/atlas/views.py +++ b/atlas/views.py @@ -2189,9 +2189,7 @@ def collection_take_start(request, pk, cid=None, passcode=None): Returns: _type_: _description_ """ - print(f"Collection take start: {pk}, cid: {cid}, passcode: {passcode}") collection = get_object_or_404(CaseCollection, pk=pk) - print(f"Collection: {collection}") try: collection.check_user_can_take(cid, passcode, request.user) @@ -2204,7 +2202,7 @@ def collection_take_start(request, pk, cid=None, passcode=None): return render( request, 'atlas/prerequisite_required.html', - {'message': str(e), 'prereq': prereq}, + {'message': str(e), 'prereq': prereq, 'cid': cid, 'passcode': passcode}, ) @@ -2626,7 +2624,7 @@ def collection_take_overview( collection.check_user_can_take(cid, passcode, request.user) except PrerequisiteRequired as e: prereq = getattr(e, 'prereq', None) - return render(request, 'atlas/prerequisite_required.html', {'message': str(e), 'prereq': prereq}) + return render(request, 'atlas/prerequisite_required.html', {'message': str(e), 'prereq': prereq, 'cid': cid, 'passcode': passcode}) cid_user_exam = collection.get_or_create_cid_user_exam( cid=cid, user_user=request.user @@ -2703,7 +2701,7 @@ def collection_case_view_take_answers( collection.check_user_can_take(cid, passcode, request.user) except PrerequisiteRequired as e: prereq = getattr(e, 'prereq', None) - return render(request, 'atlas/prerequisite_required.html', {'message': str(e), 'prereq': prereq}) + return render(request, 'atlas/prerequisite_required.html', {'message': str(e), 'prereq': prereq, 'cid': cid, 'passcode': passcode}) case, case_count = collection.get_case_by_index(case_number, case_count=True) @@ -2756,7 +2754,7 @@ def collection_case_view_take( collection.check_user_can_take(cid, passcode, request.user) except PrerequisiteRequired as e: prereq = getattr(e, 'prereq', None) - return render(request, 'atlas/prerequisite_required.html', {'message': str(e), 'prereq': prereq}) + return render(request, 'atlas/prerequisite_required.html', {'message': str(e), 'prereq': prereq, 'cid': cid, 'passcode': passcode}) case, case_count = collection.get_case_by_index(case_number, case_count=True) @@ -3563,6 +3561,64 @@ def collection_question_schemas(request, exam_id: int): ) +@user_is_collection_author_or_atlas_editor +def collection_sync_prerequisite_users(request, pk: int): + """Copy valid cid_users and user_users from all prerequisite collections into this collection. + + This endpoint is intended to be called via HTMX from the collection detail page. + """ + if not request.htmx: + raise Http404("Invalid request") + + collection = get_object_or_404(CaseCollection, pk=pk) + + # Collect the closure of collections: the target collection and all prerequisites (recursively) + to_visit = [collection] + visited = {} + while to_visit: + col = to_visit.pop() + if col.pk in visited: + continue + visited[col.pk] = col + for p in col.prerequisites.all(): + if p.pk not in visited: + to_visit.append(p) + + collections = list(visited.values()) + + # Compute the union of all cid users and user users across the set + union_cid_users = set() + union_user_users = set() + for col in collections: + union_cid_users.update(list(col.valid_cid_users.all())) + union_user_users.update(list(col.valid_user_users.all())) + + # Apply the union to each collection and count additions + total_added_cid = 0 + total_added_user = 0 + per_collection_added = [] + for col in collections: + added_cid = 0 + added_user = 0 + for cid_user in union_cid_users: + if not col.valid_cid_users.filter(pk=cid_user.pk).exists(): + col.valid_cid_users.add(cid_user) + added_cid += 1 + for user in union_user_users: + if not col.valid_user_users.filter(pk=user.pk).exists(): + col.valid_user_users.add(user) + added_user += 1 + if added_cid or added_user: + col.save() + total_added_cid += added_cid + total_added_user += added_user + per_collection_added.append((col.pk, added_cid, added_user)) + + return HttpResponse( + f"Two-way sync complete — total added: {total_added_cid} cid(s), {total_added_user} user(s)." + ) + + def collection_reset_answers_user_list(request, exam_id: int): if request.htmx: collection = get_object_or_404(CaseCollection, pk=exam_id)