Add exam collection link to the navigation menu and clean up dropdown toggle

This commit is contained in:
Ross
2025-11-06 21:30:14 +00:00
parent ccb27af697
commit 2d963259ca
2 changed files with 66 additions and 3 deletions
+61
View File
@@ -4709,6 +4709,67 @@ class ExamCollectionClone(CreateView, AuthorRequiredMixin):
object.author.set(self.author)
object.save()
# Clone exams linked to the original collection into the new one.
# For each supported exam app, find exams related to the old collection
# and create shallow copies (questions/authors re-applied, but
# active/archive/publish flags cleared).
if hasattr(self, "initial_object") and self.initial_object:
for _label, rel_name, ExamModel in GROUP_TYPES:
try:
old_exams = list(getattr(self.initial_object, rel_name).all())
except Exception:
old_exams = []
for old_exam in old_exams:
# Build a dict of field values excluding the PK
data = model_to_dict(old_exam, exclude=["id"])
# Clear group membership and flags for cloned exams
data["valid_user_users"] = [] if "valid_user_users" in data else []
data["valid_cid_users"] = [] if "valid_cid_users" in data else []
data["cid_user_groups"] = [] if "cid_user_groups" in data else []
data["user_user_groups"] = [] if "user_user_groups" in data else []
data["active"] = False
# some models use `archive` or `archived` - try both safely
if "archive" in data:
data["archive"] = False
if "archived" in data:
data["archived"] = False
data["publish_results"] = False if "publish_results" in data else data.get("publish_results", False)
# Keep a copy of M2M fields to reapply after create
m2m_backup = {}
for m2m_field in ("exam_questions", "author", "markers", "valid_user_users", "valid_cid_users", "cid_user_groups", "user_user_groups"):
if m2m_field in data:
m2m_backup[m2m_field] = data.pop(m2m_field)
# Set the new examcollection foreign key to the newly created collection
# model_to_dict returns the FK id; replace with instance for create()
data["examcollection"] = object
try:
new_exam = ExamModel.objects.create(**data)
except Exception:
# If creation failed for any model-specific field, skip cloning this exam
continue
# Reapply M2M relationships where possible
for field_name, ids in m2m_backup.items():
try:
getattr(new_exam, field_name).set(ids)
except Exception:
# If the field doesn't exist on this model, ignore
pass
try:
# Some exam models provide an ordering helper
if hasattr(new_exam, "order_questions"):
new_exam.order_questions()
except Exception:
pass
return HttpResponseRedirect(object.get_absolute_url())
+5 -3
View File
@@ -201,7 +201,7 @@
<a class="nav-link active" href="{% url 'atlas:index' %}">Atlas</a>
</li>
<li class="nav-item dropdown pt-0">
<a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
FRCR
</a>
<ul class="dropdown-menu">
@@ -223,6 +223,9 @@
<li>
<a class="dropdown-item" href="{% url 'sbas:index' %}">SBAs</a>
</li>
<li>
<a class="dropdown-item" href="{% url 'generic:examcollection_list' %}">Exam Collection</a>
</li>
</ul>
</li>
{% if request.user|has_group:"cid_user_manager" %}
@@ -334,7 +337,6 @@ document.addEventListener('DOMContentLoaded', function() {
//}
// fallback to any table on the page that looks like it has selection
var any = document.querySelector('table.js-row-selectable') //|| Array.from(document.querySelectorAll('table')).find(function(t){ return t.querySelector && (t.querySelector('input[name="selection"]') || t.querySelector('input[type="checkbox"]')); });
console.log('findTableForControl fallback', any);
return any || null;
}
@@ -375,7 +377,7 @@ document.addEventListener('DOMContentLoaded', function() {
container.parentNode.insertBefore(wrapper, container);
}
//// Create controls for any table that looks selectable but lacks a control block
// Create controls for any table that looks selectable but lacks a control block
Array.from(document.querySelectorAll('table')).forEach(function(t){
if (t.querySelector && (t.querySelector('input[name="selection"]') || t.querySelector('input[type="checkbox"]'))) {
ensureControlsForTable(t);