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}} +
+ {% csrf_token %} + + {{ form|crispy }} + {% comment %} {% endcomment %} + + + +
\ 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}} +
+ {% csrf_token %} + + {{ form|crispy }} + {% comment %} {% endcomment %} + + + +
\ 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