add markers to exams (move away from group based to content based permissions)

This commit is contained in:
Ross
2024-08-05 10:45:38 +01:00
parent cff5a46e4c
commit e46f843284
33 changed files with 299 additions and 23 deletions
+6 -2
View File
@@ -10,7 +10,7 @@ from django.forms import inlineformset_factory
from django.contrib.postgres.forms import SimpleArrayField, SplitArrayField , SplitArrayWidget 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 ( from .models import (
Answer, Answer,
@@ -231,4 +231,8 @@ class ExamForm(ExamFormMixin, ModelForm):
class ExamAuthorForm(ExamAuthorFormMixin): class ExamAuthorForm(ExamAuthorFormMixin):
class Meta(ExamAuthorFormMixin.Meta): class Meta(ExamAuthorFormMixin.Meta):
model = Exam model = Exam
class ExamMarkerForm(ExamMarkerFormMixin):
class Meta(ExamMarkerFormMixin.Meta):
model = Exam
+20
View File
@@ -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),
),
]
+7
View File
@@ -387,6 +387,13 @@ class Exam(ExamBase):
related_name="anatomy_exam_author", 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( valid_cid_users = models.ManyToManyField(
CidUser, blank=True, related_name="anatomy_exams" CidUser, blank=True, related_name="anatomy_exams"
) )
+1
View File
@@ -52,6 +52,7 @@ urlpatterns = [
path( path(
"exam/<int:exam_pk>/<int:sk>/mark/review", views.mark_review, name="mark_review" "exam/<int:exam_pk>/<int:sk>/mark/review", views.mark_review, name="mark_review"
), ),
path("exam/<int:pk>/markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"),
path("exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"), path("exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"),
path("exam/create", views.ExamCreate.as_view(), name="exam_create"), path("exam/create", views.ExamCreate.as_view(), name="exam_create"),
path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"), path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"),
+9
View File
@@ -33,6 +33,7 @@ from .forms import (
AnswerUpdateFormSet, AnswerUpdateFormSet,
BodyPartForm, BodyPartForm,
ExamAuthorForm, ExamAuthorForm,
ExamMarkerForm,
ExaminationForm, ExaminationForm,
MarkAnatomyQuestionForm, MarkAnatomyQuestionForm,
AnatomyQuestionForm, AnatomyQuestionForm,
@@ -836,6 +837,14 @@ class ExamAuthorUpdate(RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, Aut
template_name = "author_form.html" 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) GenericViews = GenericViewBase("anatomy", AnatomyQuestion, UserAnswer, Exam)
@@ -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),
),
]
+7
View File
@@ -780,6 +780,13 @@ class CaseCollection(ExamOrCollectionGenericBase):
related_name="casecollection_authored_cases", 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( valid_cid_users = models.ManyToManyField(
CidUser, blank=True, related_name="casecollection_exams" CidUser, blank=True, related_name="casecollection_exams"
) )
+11
View File
@@ -185,6 +185,17 @@ class ExamAuthorFormMixin(ModelForm):
widget=FilteredSelectMultiple(verbose_name="Authors", is_stacked=False), 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): class ExaminationForm(ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -5,6 +5,7 @@
\ <a href="{% url exam.get_app_name|add:':exam_delete' exam.id %}" title="Delete the Exam">Delete</a> \ <a href="{% url exam.get_app_name|add:':exam_delete' exam.id %}" title="Delete the Exam">Delete</a>
\ <a href="{% url exam.get_app_name|add:':exam_clone' exam.id %}" title="Clone the Exam">Clone</a> \ <a href="{% url exam.get_app_name|add:':exam_clone' exam.id %}" title="Clone the Exam">Clone</a>
\ <a href="{% url exam.get_app_name|add:':exam_authors' exam.id %}" title="Edit Exam Authors">Authors</a> \ <a href="{% url exam.get_app_name|add:':exam_authors' exam.id %}" title="Edit Exam Authors">Authors</a>
\ <a href="{% url exam.get_app_name|add:':exam_markers' exam.id %}" title="Edit Exam Markers">Markers</a>
{% endif %} {% endif %}
{% if can_edit and request.user.is_superuser %} {% if can_edit and request.user.is_superuser %}
\ \
+6 -2
View File
@@ -8,7 +8,7 @@ from django.forms import (
CharField, CharField,
) )
from django.forms import inlineformset_factory from django.forms import inlineformset_factory
from generic.forms import ExamAuthorFormMixin, ExamFormMixin from generic.forms import ExamAuthorFormMixin, ExamFormMixin, ExamMarkerFormMixin
from longs.models import ( from longs.models import (
# Examination, # Examination,
@@ -313,4 +313,8 @@ class ExamForm(ExamFormMixin, ModelForm):
class ExamAuthorForm(ExamAuthorFormMixin): class ExamAuthorForm(ExamAuthorFormMixin):
class Meta(ExamAuthorFormMixin.Meta): class Meta(ExamAuthorFormMixin.Meta):
model = Exam model = Exam
#fields = ExamAuthorFormMixin.Meta.fields.append("double_mark") #fields = ExamAuthorFormMixin.Meta.fields.append("double_mark")
class ExamMarkerForm(ExamMarkerFormMixin):
class Meta(ExamMarkerFormMixin.Meta):
model = Exam
+20
View File
@@ -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),
),
]
+7
View File
@@ -445,6 +445,13 @@ class Exam(ExamBase):
help_text="Author of exam", help_text="Author of exam",
related_name="long_exam_author", 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( double_mark = models.BooleanField(
default=True, help_text="Defines if an exam is expected to be double marked" default=True, help_text="Defines if an exam is expected to be double marked"
+13 -13
View File
@@ -176,7 +176,6 @@ def test_exams(db, client):
active_exams_cid = client.get(active_exams_url) active_exams_cid = client.get(active_exams_url)
print(cid_user)
assert active_exams_cid.status_code == 200 assert active_exams_cid.status_code == 200
assert len(json.loads(active_exams_cid.content)["exams"]) == 1 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["name"] == "Cid Exam"
assert exam_metadata["exam_active"] == True assert exam_metadata["exam_active"] == True
assert exam_metadata["exam_mode"] == True assert exam_metadata["exam_mode"] == True
print(f"{exam_metadata=}")
res = client.get(exam_metadata["url"]) res = client.get(exam_metadata["url"])
assert res.status_code == 200 assert res.status_code == 200
@@ -224,7 +222,6 @@ def test_exams(db, client):
) )
for q in exam_json["question_requests"]: for q in exam_json["question_requests"]:
print("check question:", q)
# check redirects to the json file work # check redirects to the json file work
# TODO: sort out testing of the saved JSON files # TODO: sort out testing of the saved JSON files
try: try:
@@ -258,8 +255,6 @@ def test_exams(db, client):
assert question_json_res.redirect_chain[1][0].endswith(f"{q}.json") assert question_json_res.redirect_chain[1][0].endswith(f"{q}.json")
print(cid_user, testing_cid_user)
if testing_cid_user: if testing_cid_user:
question_json_unbased_res = client.get( question_json_unbased_res = client.get(
reverse( reverse(
@@ -281,7 +276,6 @@ def test_exams(db, client):
"sk": q, "sk": q,
}, },
) )
print(test)
question_json_unbased_res = client.get( question_json_unbased_res = client.get(
reverse( reverse(
@@ -516,8 +510,6 @@ def test_exams(db, client):
assert len(answer_lis) == len(valid_answers_by_question) assert len(answer_lis) == len(valid_answers_by_question)
pprint(valid_answers_by_question)
print(cid_user)
for n in range( for n in range(
len(valid_answers_by_question) 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) #n = str(n + 1) # questions are not zero indexed (lists are)
for qidn, section_title in sections: for qidn, section_title in sections:
print(n, qidn)
answer = valid_answers_by_question[n][qidn] answer = valid_answers_by_question[n][qidn]
li = lis.find("li", attrs={"data-qidn": 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 "Not answered" in str(answer_lis[-1])
assert str(answer_lis[-1]).count("Not answered") == 5 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! # Publish the results!
exam.publish_results = True exam.publish_results = True
exam.save() exam.save()
@@ -594,8 +598,6 @@ def test_exams(db, client):
assert answer["ans"] in str(li) # Check that the saved answer is shown 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 # For long cases an unmarked answers is given a score of 4
# Each user needs marking individually # Each user needs marking individually
assert ( assert (
@@ -609,10 +611,8 @@ def test_exams(db, client):
# For longs this needs to be done for each user # For longs this needs to be done for each user
question: Question question: Question
for question in exam.exam_questions.all(): for question in exam.exam_questions.all():
print(question)
answers = question.get_unmarked_user_answers() answers = question.get_unmarked_user_answers()
print(answers)
for answer in answers: for answer in answers:
answer.score = UserAnswer.ScoreOptions.EIGHT answer.score = UserAnswer.ScoreOptions.EIGHT
answer.save() answer.save()
+1
View File
@@ -82,6 +82,7 @@ urlpatterns = [
views.mark, views.mark,
name="mark", name="mark",
), ),
path("exam/<int:pk>/markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"),
path("exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"), path("exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"),
path( path(
"exam/<int:pk>/question_json_refresh", "exam/<int:pk>/question_json_refresh",
+9
View File
@@ -26,6 +26,7 @@ from django.http import HttpResponseRedirect, HttpResponse
from .forms import ( from .forms import (
ExamAuthorForm, ExamAuthorForm,
ExamMarkerForm,
LongForm, LongForm,
LongSeriesForm, LongSeriesForm,
LongSeriesImageFormSet, LongSeriesImageFormSet,
@@ -977,6 +978,14 @@ class ExamAuthorUpdate(
form_class = ExamAuthorForm form_class = ExamAuthorForm
template_name = "author_form.html" 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): # class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
# queryset = Exam.objects.all().order_by("name") # queryset = Exam.objects.all().order_by("name")
+5 -1
View File
@@ -8,7 +8,7 @@ from django.forms import (
) )
from django.forms import inlineformset_factory from django.forms import inlineformset_factory
from generic.forms import ExamAuthorFormMixin, ExamFormMixin from generic.forms import ExamAuthorFormMixin, ExamFormMixin, ExamMarkerFormMixin
from .models import ( from .models import (
Question, Question,
@@ -38,4 +38,8 @@ class ExamForm(ExamFormMixin, ModelForm):
class ExamAuthorForm(ExamAuthorFormMixin): class ExamAuthorForm(ExamAuthorFormMixin):
class Meta(ExamAuthorFormMixin.Meta): class Meta(ExamAuthorFormMixin.Meta):
model = Exam
class ExamMarkerForm(ExamMarkerFormMixin):
class Meta(ExamMarkerFormMixin.Meta):
model = Exam model = Exam
+20
View File
@@ -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),
),
]
+7
View File
@@ -167,6 +167,13 @@ class Exam(ExamBase):
related_name="physics_exam_author", 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( valid_cid_users = models.ManyToManyField(
CidUser, blank=True, related_name="physics_exams" CidUser, blank=True, related_name="physics_exams"
) )
+1
View File
@@ -33,6 +33,7 @@ urlpatterns.extend(
views.exam_take_overview, views.exam_take_overview,
name="exam_take_overview_user", name="exam_take_overview_user",
), ),
path("exam/<int:pk>/markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"),
path( path(
"exam/<int:pk>/authors", "exam/<int:pk>/authors",
views.ExamAuthorUpdate.as_view(), views.ExamAuthorUpdate.as_view(),
+8 -1
View File
@@ -58,7 +58,7 @@ from rest_framework.pagination import PageNumberPagination
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from .forms import UserAnswerForm, ExamAuthorForm, ExamForm from .forms import ExamMarkerForm, UserAnswerForm, ExamAuthorForm, ExamForm
from .decorators import ( from .decorators import (
user_is_author_or_physics_checker, user_is_author_or_physics_checker,
user_is_exam_author_or_physics_checker, user_is_exam_author_or_physics_checker,
@@ -359,6 +359,13 @@ class ExamAuthorUpdate(
form_class = ExamAuthorForm form_class = ExamAuthorForm
template_name = "author_form.html" template_name = "author_form.html"
class ExamMarkersUpdate(
RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView
):
model = Exam
form_class = ExamMarkerForm
template_name = "markers_form.html"
class QuestionView( class QuestionView(
LoginRequiredMixin, SingleTableMixin, FilterView, AuthorOrCheckerRequiredMixin LoginRequiredMixin, SingleTableMixin, FilterView, AuthorOrCheckerRequiredMixin
+6 -2
View File
@@ -7,7 +7,7 @@ import pytest
from generic.models import CidUser, CidUserGroup, UserUserGroup, ExamBase from generic.models import CidUser, CidUserGroup, UserUserGroup, ExamBase
from generic.views import ExamViews 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 django.urls import reverse
from rich.pretty import pprint from rich.pretty import pprint
@@ -20,7 +20,7 @@ def AssertNotFound(client, url):
), f"Response content: {response.content}" ), 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") group1 = CidUserGroup.objects.create(name="Group1")
group2 = CidUserGroup.objects.create(name="Group2") group2 = CidUserGroup.objects.create(name="Group2")
@@ -43,6 +43,10 @@ def create_cid_user_and_groups(db):
usergroup2 = UserUserGroup.objects.create(name="Group2") usergroup2 = UserUserGroup.objects.create(name="Group2")
usergroup2.users.add(user2) 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): def create_exam(db, ExamViews: ExamViews):
exam: ExamBase = ExamViews.Exam.objects.create( exam: ExamBase = ExamViews.Exam.objects.create(
+5 -1
View File
@@ -7,7 +7,7 @@ from django.forms import (
CharField, CharField,
) )
from django.forms import inlineformset_factory from django.forms import inlineformset_factory
from generic.forms import ExamAuthorFormMixin, ExamFormMixin from generic.forms import ExamAuthorFormMixin, ExamFormMixin, ExamMarkerFormMixin
from rapids.models import ( from rapids.models import (
Abnormality, Abnormality,
@@ -240,4 +240,8 @@ class ExamForm(ExamFormMixin, ModelForm):
class ExamAuthorForm(ExamAuthorFormMixin): class ExamAuthorForm(ExamAuthorFormMixin):
class Meta(ExamAuthorFormMixin.Meta): class Meta(ExamAuthorFormMixin.Meta):
model = Exam
class ExamMarkerForm(ExamMarkerFormMixin):
class Meta(ExamMarkerFormMixin.Meta):
model = Exam model = Exam
+20
View File
@@ -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),
),
]
+7
View File
@@ -629,6 +629,13 @@ class Exam(ExamBase):
related_name="rapid_exam_author", 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( valid_cid_users = models.ManyToManyField(
CidUser, blank=True, related_name="rapid_exams" CidUser, blank=True, related_name="rapid_exams"
) )
+1
View File
@@ -50,6 +50,7 @@ urlpatterns = [
path( path(
"exam/<int:exam_pk>/<int:sk>/mark/review", views.mark_review, name="mark_review" "exam/<int:exam_pk>/<int:sk>/mark/review", views.mark_review, name="mark_review"
), ),
path("exam/<int:pk>/markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"),
path("exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"), path("exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"),
path("exam/create", views.ExamCreate.as_view(), name="exam_create"), path("exam/create", views.ExamCreate.as_view(), name="exam_create"),
path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"), path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"),
+9
View File
@@ -21,6 +21,7 @@ from django.http import HttpResponseRedirect, HttpResponse
from .forms import ( from .forms import (
ExamAuthorForm, ExamAuthorForm,
ExamMarkerForm,
RapidForm, RapidForm,
MarkRapidQuestionForm, MarkRapidQuestionForm,
ImageFormSet, ImageFormSet,
@@ -761,6 +762,14 @@ class ExamAuthorUpdate(
template_name = "author_form.html" 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): class UserAnswerView(LoginRequiredMixin, DetailView):
model = UserAnswer model = UserAnswer
+4 -1
View File
@@ -8,7 +8,7 @@ from django.forms import (
) )
from django.forms import inlineformset_factory from django.forms import inlineformset_factory
from generic.forms import ExamAuthorFormMixin, ExamFormMixin from generic.forms import ExamAuthorFormMixin, ExamFormMixin, ExamMarkerFormMixin
from .models import ( from .models import (
Question, Question,
@@ -41,6 +41,9 @@ class ExamAuthorForm(ExamAuthorFormMixin):
class Meta(ExamAuthorFormMixin.Meta): class Meta(ExamAuthorFormMixin.Meta):
model = Exam model = Exam
class ExamMarkerForm(ExamMarkerFormMixin):
class Meta(ExamMarkerFormMixin.Meta):
model = Exam
class QuestionForm(ModelForm): class QuestionForm(ModelForm):
+20
View File
@@ -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),
),
]
+7
View File
@@ -185,6 +185,13 @@ class Exam(ExamBase):
related_name="sba_exam_author", 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_cid_users = models.ManyToManyField(CidUser, blank=True, related_name="sba_exams")
valid_user_users = models.ManyToManyField( valid_user_users = models.ManyToManyField(
+1
View File
@@ -49,6 +49,7 @@ urlpatterns = [
name="exam_take_overview_user", name="exam_take_overview_user",
), ),
path("exam/<int:pk>/markers", views.ExamMarkersUpdate.as_view(), name="exam_markers"),
path( path(
"exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors" "exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"
), ),
+9
View File
@@ -61,6 +61,7 @@ from .tables import QuestionTable, UserAnswerTable
from .filters import QuestionFilter, UserAnswerFilter from .filters import QuestionFilter, UserAnswerFilter
from .forms import ( from .forms import (
ExamMarkerForm,
QuestionForm, QuestionForm,
ExamForm, ExamForm,
) )
@@ -429,6 +430,14 @@ class ExamAuthorUpdate(
template_name = "author_form.html" 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): class ExamClone(ExamCloneMixin, ExamCreate):
"""Clone exam view""" """Clone exam view"""
+4
View File
@@ -15,6 +15,10 @@
{% block content %} {% block content %}
<h2>Edit Authors</h2> <h2>Edit Authors</h2>
<p>Authors have full access to access and edit the exam/collection (including the ability to add/remove other authors).</p>
<p>If you just want a user to be able to mark questions add them as a marker instead.</p>
<form action="" method="post"> <form action="" method="post">
{% csrf_token %} {% csrf_token %}
<table> <table>
+27
View File
@@ -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 %}
<h2>Edit Markers</h2>
Markers have access to mark the exam, but they cannot otherwise edit it.
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit">
<a href="{{ object.get_absolute_url }}" class="btn btn-default">Cancel</a>
</form>
{% endblock %}