From e46f8432849a867b76d19eec068e95fd1b62eccb Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 5 Aug 2024 10:45:38 +0100 Subject: [PATCH] add markers to exams (move away from group based to content based permissions) --- anatomy/forms.py | 8 ++++-- anatomy/migrations/0019_exam_markers.py | 20 ++++++++++++++ anatomy/models.py | 7 +++++ anatomy/urls.py | 1 + anatomy/views.py | 9 +++++++ .../migrations/0058_casecollection_markers.py | 20 ++++++++++++++ atlas/models.py | 7 +++++ generic/forms.py | 11 ++++++++ .../templates/generic/exam_link_headers.html | 1 + longs/forms.py | 8 ++++-- longs/migrations/0026_exam_markers.py | 20 ++++++++++++++ longs/models.py | 7 +++++ longs/tests/test_longs_exams.py | 26 +++++++++--------- longs/urls.py | 1 + longs/views.py | 9 +++++++ physics/forms.py | 6 ++++- physics/migrations/0012_exam_markers.py | 20 ++++++++++++++ physics/models.py | 7 +++++ physics/urls.py | 1 + physics/views.py | 9 ++++++- rad/tests/test_helpers.py | 8 ++++-- rapids/forms.py | 6 ++++- rapids/migrations/0011_exam_markers.py | 20 ++++++++++++++ rapids/models.py | 7 +++++ rapids/urls.py | 1 + rapids/views.py | 9 +++++++ sbas/forms.py | 5 +++- sbas/migrations/0012_exam_markers.py | 20 ++++++++++++++ sbas/models.py | 7 +++++ sbas/urls.py | 1 + sbas/views.py | 9 +++++++ templates/author_form.html | 4 +++ templates/markers_form.html | 27 +++++++++++++++++++ 33 files changed, 299 insertions(+), 23 deletions(-) create mode 100644 anatomy/migrations/0019_exam_markers.py create mode 100644 atlas/migrations/0058_casecollection_markers.py create mode 100644 longs/migrations/0026_exam_markers.py create mode 100644 physics/migrations/0012_exam_markers.py create mode 100644 rapids/migrations/0011_exam_markers.py create mode 100644 sbas/migrations/0012_exam_markers.py create mode 100644 templates/markers_form.html diff --git a/anatomy/forms.py b/anatomy/forms.py index 4db06025..2f5d64cf 100644 --- a/anatomy/forms.py +++ b/anatomy/forms.py @@ -10,7 +10,7 @@ from django.forms import inlineformset_factory from django.contrib.postgres.forms import SimpleArrayField, SplitArrayField , SplitArrayWidget -from generic.forms import ExamAuthorFormMixin, ExamFormMixin +from generic.forms import ExamAuthorFormMixin, ExamFormMixin, ExamMarkerFormMixin from .models import ( Answer, @@ -231,4 +231,8 @@ class ExamForm(ExamFormMixin, ModelForm): class ExamAuthorForm(ExamAuthorFormMixin): class Meta(ExamAuthorFormMixin.Meta): - model = Exam \ No newline at end of file + model = Exam + +class ExamMarkerForm(ExamMarkerFormMixin): + class Meta(ExamMarkerFormMixin.Meta): + model = Exam diff --git a/anatomy/migrations/0019_exam_markers.py b/anatomy/migrations/0019_exam_markers.py new file mode 100644 index 00000000..9d4be730 --- /dev/null +++ b/anatomy/migrations/0019_exam_markers.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.2 on 2024-08-05 09:43 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('anatomy', '0018_alter_exam_end_date_alter_exam_exam_results_emailed_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='exam', + name='markers', + field=models.ManyToManyField(blank=True, help_text='Authorised markers for the exam', related_name='anatomy_exam_markers', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/anatomy/models.py b/anatomy/models.py index da2a5921..aff59a3d 100644 --- a/anatomy/models.py +++ b/anatomy/models.py @@ -387,6 +387,13 @@ class Exam(ExamBase): related_name="anatomy_exam_author", ) + markers = models.ManyToManyField( + settings.AUTH_USER_MODEL, + blank=True, + help_text="Authorised markers for the exam", + related_name="anatomy_exam_markers", + ) + valid_cid_users = models.ManyToManyField( CidUser, blank=True, related_name="anatomy_exams" ) diff --git a/anatomy/urls.py b/anatomy/urls.py index be660773..8e2b08ea 100644 --- a/anatomy/urls.py +++ b/anatomy/urls.py @@ -52,6 +52,7 @@ urlpatterns = [ path( "exam///mark/review", views.mark_review, name="mark_review" ), + path("exam//markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"), path("exam//authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"), path("exam/create", views.ExamCreate.as_view(), name="exam_create"), path("exam//clone", views.ExamClone.as_view(), name="exam_clone"), diff --git a/anatomy/views.py b/anatomy/views.py index 0a4d07ce..d02330a2 100644 --- a/anatomy/views.py +++ b/anatomy/views.py @@ -33,6 +33,7 @@ from .forms import ( AnswerUpdateFormSet, BodyPartForm, ExamAuthorForm, + ExamMarkerForm, ExaminationForm, MarkAnatomyQuestionForm, AnatomyQuestionForm, @@ -836,6 +837,14 @@ class ExamAuthorUpdate(RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, Aut template_name = "author_form.html" +class ExamMarkersUpdate( + RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView +): + model = Exam + form_class = ExamMarkerForm + + template_name = "markers_form.html" + GenericViews = GenericViewBase("anatomy", AnatomyQuestion, UserAnswer, Exam) diff --git a/atlas/migrations/0058_casecollection_markers.py b/atlas/migrations/0058_casecollection_markers.py new file mode 100644 index 00000000..c59c3f45 --- /dev/null +++ b/atlas/migrations/0058_casecollection_markers.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.2 on 2024-08-05 09:43 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0057_alter_caseprior_prior_case'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='casecollection', + name='markers', + field=models.ManyToManyField(blank=True, help_text='Authorised markers for the exam', related_name='casecollection_markers', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/atlas/models.py b/atlas/models.py index 081ef5cc..2163a9b2 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -780,6 +780,13 @@ class CaseCollection(ExamOrCollectionGenericBase): related_name="casecollection_authored_cases", ) + markers = models.ManyToManyField( + settings.AUTH_USER_MODEL, + blank=True, + help_text="Authorised markers for the exam", + related_name="casecollection_markers", + ) + valid_cid_users = models.ManyToManyField( CidUser, blank=True, related_name="casecollection_exams" ) diff --git a/generic/forms.py b/generic/forms.py index 0c9b6204..5e48aef7 100755 --- a/generic/forms.py +++ b/generic/forms.py @@ -185,6 +185,17 @@ class ExamAuthorFormMixin(ModelForm): widget=FilteredSelectMultiple(verbose_name="Authors", is_stacked=False), ) +class ExamMarkerFormMixin(ModelForm): + class Meta: + fields = ["markers"] + + def __init__(self, *args, **kwargs): + ModelForm.__init__(self, *args, **kwargs) + self.fields["markers"] = ModelMultipleChoiceField( + queryset=User.objects.all(), + widget=FilteredSelectMultiple(verbose_name="Markers", is_stacked=False), + ) + class ExaminationForm(ModelForm): def __init__(self, *args, **kwargs): diff --git a/generic/templates/generic/exam_link_headers.html b/generic/templates/generic/exam_link_headers.html index f21dee48..dbda0d0a 100644 --- a/generic/templates/generic/exam_link_headers.html +++ b/generic/templates/generic/exam_link_headers.html @@ -5,6 +5,7 @@ \ Delete \ Clone \ Authors + \ Markers {% endif %} {% if can_edit and request.user.is_superuser %} \ diff --git a/longs/forms.py b/longs/forms.py index b5d544f1..46c69ec2 100755 --- a/longs/forms.py +++ b/longs/forms.py @@ -8,7 +8,7 @@ from django.forms import ( CharField, ) from django.forms import inlineformset_factory -from generic.forms import ExamAuthorFormMixin, ExamFormMixin +from generic.forms import ExamAuthorFormMixin, ExamFormMixin, ExamMarkerFormMixin from longs.models import ( # Examination, @@ -313,4 +313,8 @@ class ExamForm(ExamFormMixin, ModelForm): class ExamAuthorForm(ExamAuthorFormMixin): class Meta(ExamAuthorFormMixin.Meta): model = Exam - #fields = ExamAuthorFormMixin.Meta.fields.append("double_mark") \ No newline at end of file + #fields = ExamAuthorFormMixin.Meta.fields.append("double_mark") + +class ExamMarkerForm(ExamMarkerFormMixin): + class Meta(ExamMarkerFormMixin.Meta): + model = Exam diff --git a/longs/migrations/0026_exam_markers.py b/longs/migrations/0026_exam_markers.py new file mode 100644 index 00000000..280fde9a --- /dev/null +++ b/longs/migrations/0026_exam_markers.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.2 on 2024-08-05 09:43 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('longs', '0025_remove_answermarks_candidate_feedback_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='exam', + name='markers', + field=models.ManyToManyField(blank=True, help_text='Authorised markers for the exam', related_name='long_exam_markers', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/longs/models.py b/longs/models.py index 8e72a345..8aaf6e12 100644 --- a/longs/models.py +++ b/longs/models.py @@ -445,6 +445,13 @@ class Exam(ExamBase): help_text="Author of exam", related_name="long_exam_author", ) + + markers = models.ManyToManyField( + settings.AUTH_USER_MODEL, + blank=True, + help_text="Authorised markers for the exam", + related_name="long_exam_markers", + ) double_mark = models.BooleanField( default=True, help_text="Defines if an exam is expected to be double marked" diff --git a/longs/tests/test_longs_exams.py b/longs/tests/test_longs_exams.py index 32a40837..85e5763b 100644 --- a/longs/tests/test_longs_exams.py +++ b/longs/tests/test_longs_exams.py @@ -176,7 +176,6 @@ def test_exams(db, client): active_exams_cid = client.get(active_exams_url) - print(cid_user) assert active_exams_cid.status_code == 200 assert len(json.loads(active_exams_cid.content)["exams"]) == 1 @@ -202,7 +201,6 @@ def test_exams(db, client): assert exam_metadata["name"] == "Cid Exam" assert exam_metadata["exam_active"] == True assert exam_metadata["exam_mode"] == True - print(f"{exam_metadata=}") res = client.get(exam_metadata["url"]) assert res.status_code == 200 @@ -224,7 +222,6 @@ def test_exams(db, client): ) for q in exam_json["question_requests"]: - print("check question:", q) # check redirects to the json file work # TODO: sort out testing of the saved JSON files try: @@ -258,8 +255,6 @@ def test_exams(db, client): assert question_json_res.redirect_chain[1][0].endswith(f"{q}.json") - print(cid_user, testing_cid_user) - if testing_cid_user: question_json_unbased_res = client.get( reverse( @@ -281,7 +276,6 @@ def test_exams(db, client): "sk": q, }, ) - print(test) question_json_unbased_res = client.get( reverse( @@ -516,8 +510,6 @@ def test_exams(db, client): assert len(answer_lis) == len(valid_answers_by_question) - pprint(valid_answers_by_question) - print(cid_user) for n in range( len(valid_answers_by_question) @@ -526,7 +518,6 @@ def test_exams(db, client): #n = str(n + 1) # questions are not zero indexed (lists are) for qidn, section_title in sections: - print(n, qidn) answer = valid_answers_by_question[n][qidn] li = lis.find("li", attrs={"data-qidn": qidn}) @@ -558,6 +549,19 @@ def test_exams(db, client): assert "Not answered" in str(answer_lis[-1]) assert str(answer_lis[-1]).count("Not answered") == 5 + + ## Load the mark overview page + #mark_overview_res = client.get(reverse( + # f"{exam.app_name}:mark_overview", + # kwargs={"pk": exam.pk}, + # )) + #print(mark_overview_res.content) + #mark_overview_soup = BeautifulSoup(mark_overview_res.content, "html.parser") + + #to_mark = mark_overview_soup.find("ul", {"id": "question-mark-list"}).find_all("li") + #assert len(to_mark) == 5 + + # Publish the results! exam.publish_results = True exam.save() @@ -594,8 +598,6 @@ def test_exams(db, client): assert answer["ans"] in str(li) # Check that the saved answer is shown - print(cid_exam_scores_soup.find("div", {"class": "score-overview"})) - # For long cases an unmarked answers is given a score of 4 # Each user needs marking individually assert ( @@ -609,10 +611,8 @@ def test_exams(db, client): # For longs this needs to be done for each user question: Question for question in exam.exam_questions.all(): - print(question) answers = question.get_unmarked_user_answers() - print(answers) for answer in answers: answer.score = UserAnswer.ScoreOptions.EIGHT answer.save() diff --git a/longs/urls.py b/longs/urls.py index 5009dbf1..f24373b2 100755 --- a/longs/urls.py +++ b/longs/urls.py @@ -82,6 +82,7 @@ urlpatterns = [ views.mark, name="mark", ), + path("exam//markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"), path("exam//authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"), path( "exam//question_json_refresh", diff --git a/longs/views.py b/longs/views.py index a668a00f..94b3ac99 100755 --- a/longs/views.py +++ b/longs/views.py @@ -26,6 +26,7 @@ from django.http import HttpResponseRedirect, HttpResponse from .forms import ( ExamAuthorForm, + ExamMarkerForm, LongForm, LongSeriesForm, LongSeriesImageFormSet, @@ -977,6 +978,14 @@ class ExamAuthorUpdate( form_class = ExamAuthorForm template_name = "author_form.html" +class ExamMarkersUpdate( + RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView +): + model = Exam + form_class = ExamMarkerForm + + template_name = "markers_form.html" + # class ExamViewSet(RevisionMixin, viewsets.ModelViewSet): # queryset = Exam.objects.all().order_by("name") diff --git a/physics/forms.py b/physics/forms.py index ab099de1..3048725d 100755 --- a/physics/forms.py +++ b/physics/forms.py @@ -8,7 +8,7 @@ from django.forms import ( ) from django.forms import inlineformset_factory -from generic.forms import ExamAuthorFormMixin, ExamFormMixin +from generic.forms import ExamAuthorFormMixin, ExamFormMixin, ExamMarkerFormMixin from .models import ( Question, @@ -38,4 +38,8 @@ class ExamForm(ExamFormMixin, ModelForm): class ExamAuthorForm(ExamAuthorFormMixin): class Meta(ExamAuthorFormMixin.Meta): + model = Exam + +class ExamMarkerForm(ExamMarkerFormMixin): + class Meta(ExamMarkerFormMixin.Meta): model = Exam \ No newline at end of file diff --git a/physics/migrations/0012_exam_markers.py b/physics/migrations/0012_exam_markers.py new file mode 100644 index 00000000..bca5f7f4 --- /dev/null +++ b/physics/migrations/0012_exam_markers.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.2 on 2024-08-05 09:43 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('physics', '0011_alter_exam_end_date_alter_exam_exam_results_emailed_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='exam', + name='markers', + field=models.ManyToManyField(blank=True, help_text='Authorised markers for the exam', related_name='physics_exam_markers', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/physics/models.py b/physics/models.py index b0fde47a..a94f44ef 100644 --- a/physics/models.py +++ b/physics/models.py @@ -167,6 +167,13 @@ class Exam(ExamBase): related_name="physics_exam_author", ) + markers = models.ManyToManyField( + settings.AUTH_USER_MODEL, + blank=True, + help_text="Authorised markers for the exam", + related_name="physics_exam_markers", + ) + valid_cid_users = models.ManyToManyField( CidUser, blank=True, related_name="physics_exams" ) diff --git a/physics/urls.py b/physics/urls.py index cf6c29c3..a6c79385 100644 --- a/physics/urls.py +++ b/physics/urls.py @@ -33,6 +33,7 @@ urlpatterns.extend( views.exam_take_overview, name="exam_take_overview_user", ), + path("exam//markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"), path( "exam//authors", views.ExamAuthorUpdate.as_view(), diff --git a/physics/views.py b/physics/views.py index 09fa99c5..e9e42d6a 100644 --- a/physics/views.py +++ b/physics/views.py @@ -58,7 +58,7 @@ from rest_framework.pagination import PageNumberPagination from django.core.exceptions import PermissionDenied -from .forms import UserAnswerForm, ExamAuthorForm, ExamForm +from .forms import ExamMarkerForm, UserAnswerForm, ExamAuthorForm, ExamForm from .decorators import ( user_is_author_or_physics_checker, user_is_exam_author_or_physics_checker, @@ -359,6 +359,13 @@ class ExamAuthorUpdate( form_class = ExamAuthorForm template_name = "author_form.html" +class ExamMarkersUpdate( + RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView +): + model = Exam + form_class = ExamMarkerForm + + template_name = "markers_form.html" class QuestionView( LoginRequiredMixin, SingleTableMixin, FilterView, AuthorOrCheckerRequiredMixin diff --git a/rad/tests/test_helpers.py b/rad/tests/test_helpers.py index 46919778..12ff2449 100644 --- a/rad/tests/test_helpers.py +++ b/rad/tests/test_helpers.py @@ -7,7 +7,7 @@ import pytest from generic.models import CidUser, CidUserGroup, UserUserGroup, ExamBase from generic.views import ExamViews -from django.contrib.auth.models import User +from django.contrib.auth.models import User, Group from django.urls import reverse from rich.pretty import pprint @@ -20,7 +20,7 @@ def AssertNotFound(client, url): ), f"Response content: {response.content}" -def create_cid_user_and_groups(db): +def create_cid_user_and_groups(db, marker=None): group1 = CidUserGroup.objects.create(name="Group1") group2 = CidUserGroup.objects.create(name="Group2") @@ -43,6 +43,10 @@ def create_cid_user_and_groups(db): usergroup2 = UserUserGroup.objects.create(name="Group2") usergroup2.users.add(user2) + #if marker is not None: + # marker_group = Group.objects.create(name=f"{marker}_marker") + # #marker_group = Group.objects.create(name=f"{marker}_marker") + def create_exam(db, ExamViews: ExamViews): exam: ExamBase = ExamViews.Exam.objects.create( diff --git a/rapids/forms.py b/rapids/forms.py index 4bb7fbae..37b35b40 100755 --- a/rapids/forms.py +++ b/rapids/forms.py @@ -7,7 +7,7 @@ from django.forms import ( CharField, ) from django.forms import inlineformset_factory -from generic.forms import ExamAuthorFormMixin, ExamFormMixin +from generic.forms import ExamAuthorFormMixin, ExamFormMixin, ExamMarkerFormMixin from rapids.models import ( Abnormality, @@ -240,4 +240,8 @@ class ExamForm(ExamFormMixin, ModelForm): class ExamAuthorForm(ExamAuthorFormMixin): class Meta(ExamAuthorFormMixin.Meta): + model = Exam + +class ExamMarkerForm(ExamMarkerFormMixin): + class Meta(ExamMarkerFormMixin.Meta): model = Exam \ No newline at end of file diff --git a/rapids/migrations/0011_exam_markers.py b/rapids/migrations/0011_exam_markers.py new file mode 100644 index 00000000..88edfc14 --- /dev/null +++ b/rapids/migrations/0011_exam_markers.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.2 on 2024-08-05 09:43 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rapids', '0010_alter_exam_end_date_alter_exam_exam_results_emailed_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='exam', + name='markers', + field=models.ManyToManyField(blank=True, help_text='Authorised markers for the exam', related_name='rapid_exam_markers', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/rapids/models.py b/rapids/models.py index 558baacc..e8a2e705 100644 --- a/rapids/models.py +++ b/rapids/models.py @@ -629,6 +629,13 @@ class Exam(ExamBase): related_name="rapid_exam_author", ) + markers = models.ManyToManyField( + settings.AUTH_USER_MODEL, + blank=True, + help_text="Authorised markers for the exam", + related_name="rapid_exam_markers", + ) + valid_cid_users = models.ManyToManyField( CidUser, blank=True, related_name="rapid_exams" ) diff --git a/rapids/urls.py b/rapids/urls.py index f43e8f42..fa14723b 100755 --- a/rapids/urls.py +++ b/rapids/urls.py @@ -50,6 +50,7 @@ urlpatterns = [ path( "exam///mark/review", views.mark_review, name="mark_review" ), + path("exam//markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"), path("exam//authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"), path("exam/create", views.ExamCreate.as_view(), name="exam_create"), path("exam//clone", views.ExamClone.as_view(), name="exam_clone"), diff --git a/rapids/views.py b/rapids/views.py index a43ee4f2..bc10309c 100755 --- a/rapids/views.py +++ b/rapids/views.py @@ -21,6 +21,7 @@ from django.http import HttpResponseRedirect, HttpResponse from .forms import ( ExamAuthorForm, + ExamMarkerForm, RapidForm, MarkRapidQuestionForm, ImageFormSet, @@ -761,6 +762,14 @@ class ExamAuthorUpdate( template_name = "author_form.html" +class ExamMarkersUpdate( + RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView +): + model = Exam + form_class = ExamMarkerForm + + template_name = "markers_form.html" + class UserAnswerView(LoginRequiredMixin, DetailView): model = UserAnswer diff --git a/sbas/forms.py b/sbas/forms.py index f6a2ac07..ed69fbf9 100755 --- a/sbas/forms.py +++ b/sbas/forms.py @@ -8,7 +8,7 @@ from django.forms import ( ) from django.forms import inlineformset_factory -from generic.forms import ExamAuthorFormMixin, ExamFormMixin +from generic.forms import ExamAuthorFormMixin, ExamFormMixin, ExamMarkerFormMixin from .models import ( Question, @@ -41,6 +41,9 @@ class ExamAuthorForm(ExamAuthorFormMixin): class Meta(ExamAuthorFormMixin.Meta): model = Exam +class ExamMarkerForm(ExamMarkerFormMixin): + class Meta(ExamMarkerFormMixin.Meta): + model = Exam class QuestionForm(ModelForm): diff --git a/sbas/migrations/0012_exam_markers.py b/sbas/migrations/0012_exam_markers.py new file mode 100644 index 00000000..a8dba93b --- /dev/null +++ b/sbas/migrations/0012_exam_markers.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.2 on 2024-08-05 09:43 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sbas', '0011_alter_exam_end_date_alter_exam_exam_results_emailed_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='exam', + name='markers', + field=models.ManyToManyField(blank=True, help_text='Authorised markers for the exam', related_name='sba_exam_markers', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/sbas/models.py b/sbas/models.py index 290d3b8c..6157f0b4 100644 --- a/sbas/models.py +++ b/sbas/models.py @@ -185,6 +185,13 @@ class Exam(ExamBase): related_name="sba_exam_author", ) + markers = models.ManyToManyField( + settings.AUTH_USER_MODEL, + blank=True, + help_text="Authorised markers for the exam", + related_name="sba_exam_markers", + ) + valid_cid_users = models.ManyToManyField(CidUser, blank=True, related_name="sba_exams") valid_user_users = models.ManyToManyField( diff --git a/sbas/urls.py b/sbas/urls.py index 6bc93db6..50f86cb2 100644 --- a/sbas/urls.py +++ b/sbas/urls.py @@ -49,6 +49,7 @@ urlpatterns = [ name="exam_take_overview_user", ), + path("exam//markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"), path( "exam//authors", views.ExamAuthorUpdate.as_view(), name="exam_authors" ), diff --git a/sbas/views.py b/sbas/views.py index f9eca2c5..649fdb69 100644 --- a/sbas/views.py +++ b/sbas/views.py @@ -61,6 +61,7 @@ from .tables import QuestionTable, UserAnswerTable from .filters import QuestionFilter, UserAnswerFilter from .forms import ( + ExamMarkerForm, QuestionForm, ExamForm, ) @@ -429,6 +430,14 @@ class ExamAuthorUpdate( template_name = "author_form.html" +class ExamMarkersUpdate( + RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView +): + model = Exam + form_class = ExamMarkerForm + + template_name = "markers_form.html" + class ExamClone(ExamCloneMixin, ExamCreate): """Clone exam view""" diff --git a/templates/author_form.html b/templates/author_form.html index 7b92080a..2778286a 100644 --- a/templates/author_form.html +++ b/templates/author_form.html @@ -15,6 +15,10 @@ {% block content %}

Edit Authors

+

Authors have full access to access and edit the exam/collection (including the ability to add/remove other authors).

+ +

If you just want a user to be able to mark questions add them as a marker instead.

+
{% csrf_token %} diff --git a/templates/markers_form.html b/templates/markers_form.html new file mode 100644 index 00000000..1a856bd2 --- /dev/null +++ b/templates/markers_form.html @@ -0,0 +1,27 @@ + +{% extends object.get_base_template %} + +{% block js %} + {{ form.media }} +{% endblock %} + + +{% block navigation %} + {{ block.super }} + {% include object.get_link_headers %} +{% endblock navigation %} + + +{% block content %} + +

Edit Markers

+ Markers have access to mark the exam, but they cannot otherwise edit it. + + {% csrf_token %} +
+ {{ form.as_table }} +
+ + Cancel +
+{% endblock %}