diff --git a/generic/forms.py b/generic/forms.py
index 35399f47..546bff21 100755
--- a/generic/forms.py
+++ b/generic/forms.py
@@ -807,3 +807,17 @@ class ExamCollectionCloneForm(ModelForm):
class Meta:
model = ExamCollection
fields = ("name", "date")
+
+from autocomplete import widgets as htmx_widgets, Autocomplete, AutocompleteWidget, ModelAutocomplete, register as autocomplete_register
+
+class UserAutocomplete(ModelAutocomplete):
+ model = User
+ search_attrs = ["first_name", "last_name", "username"]
+
+class UsersAutocompleteForm(Form):
+ users = ModelChoiceField(queryset=User.objects.all(), widget=AutocompleteWidget(
+ ac_class=UserAutocomplete, options={"multiselect": True}
+
+ ))
+
+autocomplete_register(UserAutocomplete)
\ No newline at end of file
diff --git a/generic/mixins.py b/generic/mixins.py
index 84f31228..9b3c9393 100644
--- a/generic/mixins.py
+++ b/generic/mixins.py
@@ -51,6 +51,10 @@ class AuthorMixin():
return "None"
return authors
+ def add_authors(self, users : List[User]):
+ """Add an author to the object"""
+ self.author.add(*users)
+
def add_author(self, user: User):
"""Add an author to the object"""
self.author.add(user)
diff --git a/generic/models.py b/generic/models.py
index 665773db..729a6444 100644
--- a/generic/models.py
+++ b/generic/models.py
@@ -882,6 +882,12 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
return cloned_model
+ def add_markers(self, users):
+ self.markers.add(*users)
+
+ def add_marker(self, user):
+ self.markers.add(user)
+
class ExamBase(ExamOrCollectionGenericBase):
exam_mode = models.BooleanField(
@@ -1747,6 +1753,43 @@ class ExamCollection(models.Model, AuthorMixin):
def get_absolute_url(self):
return reverse("generic:examcollection_detail", kwargs={"pk": self.pk})
+ def get_exams(self):
+ return {
+ "anatomy": self.anatomy_exams.filter(archive=False),
+ "longs": self.longs_exams.filter(archive=False),
+ "physics": self.physics_exams.filter(archive=False),
+ "rapids": self.rapids_exams.filter(archive=False),
+ "sbas": self.sbas_exams.filter(archive=False),
+ }
+
+ def add_author_to_exams(self, users, exam_types: None | list[str] =None):
+
+ for exam_type, exams in self.get_exams().items():
+ if exam_types is None:
+ pass
+ elif exam_type not in exam_types:
+ continue
+ else:
+ raise ValueError(f"Invalid exam type {exam_type}")
+
+ for exam in exams:
+ exam.add_authors(users)
+ #return True
+
+ def add_marker_to_exams(self, users, exam_types: None | list[str] =None):
+
+ for exam_type, exams in self.get_exams().items():
+ if exam_types is None:
+ pass
+ elif exam_type not in exam_types:
+ continue
+ else:
+ pass
+
+ for exam in exams:
+ exam.add_markers(users)
+ #return True
+
#class Presentation(models.Model, AuthorMixin):
#
diff --git a/generic/templates/generic/examcollection_detail.html b/generic/templates/generic/examcollection_detail.html
index 77228411..746a5632 100755
--- a/generic/templates/generic/examcollection_detail.html
+++ b/generic/templates/generic/examcollection_detail.html
@@ -29,6 +29,11 @@
{% endwith %}
View exam list
+
+
+
+
+
{% endif %}
{% if object.longs_exams.all %}
@@ -37,6 +42,9 @@
{% include "exam_list.html#exam-list" %}
{% endwith %}
View exam list
+
+
+
{% endif %}
{% if object.rapids_exams.all %}
@@ -45,6 +53,9 @@
{% include "exam_list.html#exam-list" %}
{% endwith %}
View exam list
+
+
+
{% endif %}
{% if object.physics_exams.all %}
@@ -63,11 +74,19 @@
View exam list
{% endif %}
+
+
+
+
{% include 'exam_overview_js.html' %}
{% endblock %}
-{% block js %}
-
-{% endblock %}
+{% block css %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/generic/templates/generic/marker_form.html b/generic/templates/generic/marker_form.html
new file mode 100755
index 00000000..c7748422
--- /dev/null
+++ b/generic/templates/generic/marker_form.html
@@ -0,0 +1,12 @@
+{% load crispy_forms_tags %}
+
+{{form.media}}
+
\ No newline at end of file
diff --git a/generic/templates/generic/user_form.html b/generic/templates/generic/user_form.html
new file mode 100755
index 00000000..a055d5aa
--- /dev/null
+++ b/generic/templates/generic/user_form.html
@@ -0,0 +1,12 @@
+{% load crispy_forms_tags %}
+
+{{form.media}}
+
\ No newline at end of file
diff --git a/generic/urls.py b/generic/urls.py
index 5e887bc9..a7336577 100755
--- a/generic/urls.py
+++ b/generic/urls.py
@@ -236,6 +236,8 @@ urlpatterns = [
views.ExamCollectionDelete.as_view(),
name="examcollection_delete",
),
+ path("collection//add_author", views.exam_collection_add_author, name="exam_collection_add_author"),
+ path("collection//add_marker/", views.exam_collection_add_marker, name="exam_collection_add_marker"),
path(
"share_with_supervisor_toggle//",
views.toggle_share_with_supervisor,
@@ -395,7 +397,7 @@ def generic_exam_urls(generic_exam_view: GenericExamViews):
name="exam_answers_submit",
),
path("exam/", generic_exam_view.exam_list, name="exam_list"),
- path("exam//collection", generic_exam_view.exam_list_collection, name="exam_list_collection"),
+ path("collection//", generic_exam_view.exam_list_collection, name="exam_list_collection"),
path("exam/all", generic_exam_view.exam_list_all, name="exam_list_all"),
path(
"exam//order_questions",
diff --git a/generic/views.py b/generic/views.py
index 29495a3e..22a122e9 100644
--- a/generic/views.py
+++ b/generic/views.py
@@ -77,6 +77,7 @@ from .forms import (
CidGroupExamForm,
UserUserForm,
UserUserGroupForm,
+ UsersAutocompleteForm, # Added import
)
from .models import (
@@ -769,6 +770,7 @@ class ExamViews(View, LoginRequiredMixin):
return self.exam_list(request, all=True, collection=collection)
+
@method_decorator(login_required)
def exam_list(self, request, all=False, collection=None):
if collection is None:
@@ -4032,3 +4034,53 @@ def toggle_share_with_supervisor(request, pk):
)
else:
raise PermissionDenied()
+
+
+def exam_collection_add_author(request, collection_id):
+ if request.method != "POST":
+ form = UsersAutocompleteForm()
+
+ return render(
+ request,
+ "generic/user_form.html",
+ {"form": form, "collection_id": collection_id},
+ )
+ return
+
+
+ collection = get_object_or_404(ExamCollection, pk=collection_id)
+
+ if not request.user in collection.author.all():
+ raise PermissionDenied
+
+ #user = get_object_or_404(User, pk=request.POST.get("user"))
+ users = User.objects.filter(pk__in=request.POST.getlist("users"))
+
+ collection.add_author_to_exams(users)
+
+ return HttpResponse("Author added to all exams")
+
+
+def exam_collection_add_marker(request, collection_id, exam_type):
+ if request.method != "POST":
+ form = UsersAutocompleteForm()
+
+ return render(
+ request,
+ "generic/marker_form.html",
+ {"form": form, "collection_id": collection_id, "exam_type": exam_type},
+ )
+ return
+
+
+ collection = get_object_or_404(ExamCollection, pk=collection_id)
+
+ if not request.user in collection.author.all():
+ raise PermissionDenied
+
+ #user = get_object_or_404(User, pk=request.POST.get("user"))
+ users = User.objects.filter(pk__in=request.POST.getlist("users"))
+
+ collection.add_marker_to_exams(users, exam_types=(exam_type,))
+
+ return HttpResponse("Marker added to exams")
\ No newline at end of file