numerous updates
This commit is contained in:
@@ -46,6 +46,7 @@ class RapidUserAnswerFilter(django_filters.FilterSet):
|
||||
model = CidUserAnswer
|
||||
fields = (
|
||||
"cid",
|
||||
"user",
|
||||
"normal",
|
||||
"answer",
|
||||
#"answer_compare",
|
||||
|
||||
@@ -243,6 +243,7 @@ class ExamForm(ExamFormMixin, ModelForm):
|
||||
"publish_results",
|
||||
"archive",
|
||||
"cid_user_groups",
|
||||
"user_user_groups",
|
||||
#"author",
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.13 on 2022-05-20 10:45
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('rapids', '0053_ciduseranswer_user'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='exam',
|
||||
old_name='valid_users',
|
||||
new_name='valid_cid_users',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 3.2.13 on 2022-05-20 10:59
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('rapids', '0054_rename_valid_users_exam_valid_cid_users'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exam',
|
||||
name='valid_user_users',
|
||||
field=models.ManyToManyField(blank=True, related_name='user_rapid_exams', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.2.13 on 2022-05-20 17:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('generic', '0029_userusergroup'),
|
||||
('rapids', '0055_exam_valid_user_users'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exam',
|
||||
name='user_user_groups',
|
||||
field=models.ManyToManyField(blank=True, help_text='These groups define which candidates are able to be added to the exams/collection.', to='generic.UserUserGroup'),
|
||||
),
|
||||
]
|
||||
+48
-17
@@ -26,7 +26,16 @@ from helpers.images import image_as_base64
|
||||
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
|
||||
from generic.models import CidUser, CidUserGroup, Site, Condition, Sign, ExamBase, QuestionNote
|
||||
from generic.models import (
|
||||
CidUser,
|
||||
CidUserGroup,
|
||||
Site,
|
||||
Condition,
|
||||
Sign,
|
||||
ExamBase,
|
||||
QuestionNote,
|
||||
UserUserGroup,
|
||||
)
|
||||
|
||||
import reversion
|
||||
|
||||
@@ -245,7 +254,10 @@ class Rapid(models.Model):
|
||||
if self.normal:
|
||||
return "Normal"
|
||||
else:
|
||||
if hasattr(self, "prefetched_primary_answer") and self.prefetched_primary_answer:
|
||||
if (
|
||||
hasattr(self, "prefetched_primary_answer")
|
||||
and self.prefetched_primary_answer
|
||||
):
|
||||
ans = self.prefetched_primary_answer[0]
|
||||
else:
|
||||
ans = self.answers.filter(
|
||||
@@ -257,7 +269,7 @@ class Rapid(models.Model):
|
||||
else:
|
||||
return ans.answer
|
||||
|
||||
#def get_primary_answer(self):
|
||||
# def get_primary_answer(self):
|
||||
# if self.normal:
|
||||
# return "Normal"
|
||||
# elif (
|
||||
@@ -302,7 +314,6 @@ class Rapid(models.Model):
|
||||
|
||||
marked_answers = self.get_marked_answers()
|
||||
|
||||
|
||||
unmarked_answers = set(
|
||||
[
|
||||
i
|
||||
@@ -449,7 +460,7 @@ class Rapid(models.Model):
|
||||
feedback_images.append(
|
||||
"{}/{}".format(settings.REMOTE_URL, i.image.url)
|
||||
)
|
||||
#feedback_images.append("{}/{}".format(settings.REMOTE_URL, i.image.url))
|
||||
# feedback_images.append("{}/{}".format(settings.REMOTE_URL, i.image.url))
|
||||
else:
|
||||
if based:
|
||||
images.append(image_as_base64(i.image))
|
||||
@@ -471,7 +482,6 @@ class Rapid(models.Model):
|
||||
if feedback:
|
||||
json["feedback"] = self.feedback
|
||||
|
||||
|
||||
return json
|
||||
|
||||
def anonymise_images(self):
|
||||
@@ -487,6 +497,7 @@ class Rapid(models.Model):
|
||||
except pydicom.errors.InvalidDicomError:
|
||||
pass
|
||||
|
||||
|
||||
# reversion.register(Rapid, follow=["images"])
|
||||
# @reversion.register
|
||||
class RapidImage(models.Model):
|
||||
@@ -501,7 +512,12 @@ class RapidImage(models.Model):
|
||||
|
||||
feedback_image = models.BooleanField(default=False)
|
||||
|
||||
filename = models.CharField(max_length=255, null=True, blank=True, help_text="Name of the original file when uploaded")
|
||||
filename = models.CharField(
|
||||
max_length=255,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Name of the original file when uploaded",
|
||||
)
|
||||
|
||||
description = models.CharField(max_length=255, null=True, blank=True)
|
||||
|
||||
@@ -591,11 +607,25 @@ class Exam(ExamBase):
|
||||
related_name="rapid_exam_author",
|
||||
)
|
||||
|
||||
valid_users = models.ManyToManyField(
|
||||
valid_cid_users = models.ManyToManyField(
|
||||
CidUser, blank=True, related_name="rapid_exams"
|
||||
)
|
||||
|
||||
cid_user_groups = models.ManyToManyField(CidUserGroup, blank=True, help_text="These groups define which candidates are able to be added to the exams/collection.")
|
||||
valid_user_users = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL, blank=True, related_name="user_rapid_exams"
|
||||
)
|
||||
|
||||
cid_user_groups = models.ManyToManyField(
|
||||
CidUserGroup,
|
||||
blank=True,
|
||||
help_text="These groups define which candidates are able to be added to the exams/collection.",
|
||||
)
|
||||
|
||||
user_user_groups = models.ManyToManyField(
|
||||
UserUserGroup,
|
||||
blank=True,
|
||||
help_text="These groups define which candidates are able to be added to the exams/collection.",
|
||||
)
|
||||
|
||||
def get_normal_abnormal_breakdown(self):
|
||||
# Inefficient but more extendible
|
||||
@@ -638,7 +668,7 @@ class Exam(ExamBase):
|
||||
images.append(image_as_base64(i.image))
|
||||
else:
|
||||
images.append("{}/{}".format(settings.REMOTE_URL, i.image.url))
|
||||
|
||||
|
||||
if i.description:
|
||||
image_titles.append(i.description)
|
||||
else:
|
||||
@@ -664,8 +694,6 @@ class Exam(ExamBase):
|
||||
if any(image_titles):
|
||||
exam_questions[q.id]["image_titles"] = image_titles
|
||||
|
||||
|
||||
|
||||
# if feedback_images:
|
||||
# exam_questions[q.id]["feedback_image"] = feedback_images
|
||||
|
||||
@@ -709,10 +737,12 @@ class CidUserAnswer(models.Model):
|
||||
)
|
||||
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
blank=True,
|
||||
null=True, # , related_name="user_rapid_user_answers"
|
||||
)
|
||||
|
||||
|
||||
# Each user answer is associated with a particular exam
|
||||
exam = models.ForeignKey(
|
||||
Exam, related_name="cid_user_answers", on_delete=models.CASCADE, null=True
|
||||
@@ -752,7 +782,9 @@ class CidUserAnswer(models.Model):
|
||||
self.answer = self.answer.strip()
|
||||
|
||||
s = self.answer.lower()
|
||||
s = s.translate(str.maketrans("", "", string.punctuation.replace("#", ""))).strip()
|
||||
s = s.translate(
|
||||
str.maketrans("", "", string.punctuation.replace("#", ""))
|
||||
).strip()
|
||||
|
||||
self.answer_compare = s
|
||||
else:
|
||||
@@ -805,7 +837,6 @@ class CidUserAnswer(models.Model):
|
||||
self.save()
|
||||
return 0
|
||||
|
||||
|
||||
# TODO: this should be cleaned up as we don't want duplicates...
|
||||
# try:
|
||||
# marked_ans = q.answers.get(answer_compare__iexact=ans)
|
||||
@@ -825,4 +856,4 @@ class CidUserAnswer(models.Model):
|
||||
elif marked_ans.status == Answer.MarkOptions.INCORRECT:
|
||||
mark = 0
|
||||
|
||||
return mark
|
||||
return mark
|
||||
|
||||
@@ -24,7 +24,7 @@ class UserAnswerSerializer(serializers.HyperlinkedModelSerializer):
|
||||
)
|
||||
class Meta:
|
||||
model = CidUserAnswer
|
||||
fields = ('id', 'question', 'normal', "answer", "answer_compare", "cid", "exam", "created", "updated")
|
||||
fields = ('id', 'question', 'normal', "answer", "answer_compare", "cid", "user", "exam", "created", "updated")
|
||||
|
||||
class RapidSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
|
||||
@@ -80,6 +80,7 @@ class RapidUserAnswerTable(tables.Table):
|
||||
template_name = "django_tables2/bootstrap4.html"
|
||||
fields = (
|
||||
"cid",
|
||||
"user",
|
||||
"question",
|
||||
"normal",
|
||||
"answer",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
{% block content %}
|
||||
CID: {{ciduseranswer.cid}}<br/>
|
||||
User: {{ciduseranswer.user}}<br/>
|
||||
Exam: {{ciduseranswer.exam}}<br/>
|
||||
{{ciduseranswer.question}}<br/>
|
||||
Normal: {{ciduseranswer.normal}}<br/>
|
||||
|
||||
@@ -33,7 +33,11 @@
|
||||
</tr>
|
||||
{% for cid in cids %}
|
||||
<tr>
|
||||
{% if cid|slice:":1" == "u" %}
|
||||
<td>{{cid}}</td>
|
||||
{% else %}
|
||||
<td><a href="{% url 'cid_scores_admin' cid %}">{{cid}}</a></td>
|
||||
{% endif %}
|
||||
<td>{{user_scores|get_item:cid}}</td>
|
||||
<td>{{user_scores_normalised|get_item:cid}}</td>
|
||||
</tr>
|
||||
|
||||
@@ -113,6 +113,7 @@ urlpatterns = [
|
||||
path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"),
|
||||
path("exam/<int:exam_id>/cids", views.GenericExamViews.exam_cids, name="exam_cids"),
|
||||
path("exam/<int:exam_id>/cids/edit", views.GenericExamViews.exam_cids_edit, name="exam_cids_edit"),
|
||||
path("exam/<int:exam_id>/users/edit", views.GenericExamViews.exam_users_edit, name="exam_users_edit"),
|
||||
path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"),
|
||||
path("exam/<int:pk>/delete", views.ExamDelete.as_view(), name="exam_delete"),
|
||||
path("exam/json/", views.GenericExamViews.active_exams, name="active_exams"),
|
||||
|
||||
+49
-18
@@ -509,26 +509,48 @@ class RapidView(LoginRequiredMixin, SingleTableMixin, FilterView):
|
||||
filterset_class = RapidFilter
|
||||
|
||||
|
||||
def loadJsonAnswer(answer):
|
||||
def loadJsonAnswer(answer, user=None):
|
||||
eid = int(answer["eid"].split("/")[1])
|
||||
cid = int(answer["cid"])
|
||||
uid = False
|
||||
|
||||
# As access is not restricted make sure the data appears valid
|
||||
if (not isinstance(cid, int)) or (
|
||||
not isinstance(eid, int) or (not isinstance(answer["ans"], str))
|
||||
):
|
||||
return False, JsonResponse(
|
||||
{"success": False, "error": "cid or eid or answers not defined"}
|
||||
)
|
||||
try:
|
||||
cid = int(answer["cid"])
|
||||
except ValueError:
|
||||
if answer["cid"].startswith("u-"):
|
||||
cid = False
|
||||
uid = int(answer["cid"][2:])
|
||||
|
||||
# The model should catch invalid data but this should be less intensive
|
||||
max_int = 999999999999999999999
|
||||
if cid > max_int or eid > max_int:
|
||||
return False, JsonResponse({"success": False, "error": "invalid cid"})
|
||||
print(f"{uid=}")
|
||||
print(f"{uid=}")
|
||||
if not uid == user.id:
|
||||
return False, JsonResponse(
|
||||
{"success": False, "error": "user / uid mismatch"}
|
||||
)
|
||||
|
||||
else:
|
||||
return False, JsonResponse(
|
||||
{"success": False, "error": "cid or eid or answers not defined"}
|
||||
)
|
||||
|
||||
#uid = int(answer["uid"])
|
||||
|
||||
if not uid:
|
||||
# As access is not restricted make sure the data appears valid
|
||||
if (not isinstance(cid, int)) or (
|
||||
not isinstance(eid, int) or (not isinstance(answer["ans"], str))
|
||||
):
|
||||
return False, JsonResponse(
|
||||
{"success": False, "error": "cid or eid or answers not defined"}
|
||||
)
|
||||
|
||||
## The model should catch invalid data but this should be less intensive
|
||||
#max_int = 999999999999999999999
|
||||
#if cid > max_int or eid > max_int:
|
||||
# return False, JsonResponse({"success": False, "error": "invalid cid"})
|
||||
|
||||
exam = get_object_or_404(Exam, pk=eid)
|
||||
|
||||
if not exam.check_cid_user(cid, answer["passcode"]):
|
||||
if not exam.check_cid_user(cid, answer["passcode"], user_id=uid):
|
||||
return False, JsonResponse(
|
||||
{"success": False, "error": "invalid cid / passcode"}
|
||||
)
|
||||
@@ -538,9 +560,14 @@ def loadJsonAnswer(answer):
|
||||
# {"success": False, "error": "No active exam: {}".format(eid)}
|
||||
# )
|
||||
|
||||
exiting_answers = CidUserAnswer.objects.filter(
|
||||
question__id=answer["qid"], exam__id=eid, cid=cid
|
||||
)
|
||||
if uid:
|
||||
exiting_answers = CidUserAnswer.objects.filter(
|
||||
question__id=answer["qid"], exam__id=eid, user__id=uid
|
||||
)
|
||||
else:
|
||||
exiting_answers = CidUserAnswer.objects.filter(
|
||||
question__id=answer["qid"], exam__id=eid, cid=cid
|
||||
)
|
||||
|
||||
posted_answer = answer["ans"]
|
||||
|
||||
@@ -554,7 +581,11 @@ def loadJsonAnswer(answer):
|
||||
normal = True
|
||||
|
||||
if not exiting_answers:
|
||||
ans = CidUserAnswer(answer=posted_answer, normal=normal, cid=cid)
|
||||
if uid:
|
||||
ans = CidUserAnswer(answer=posted_answer, normal=normal, user=User.objects.get(id=uid))
|
||||
else:
|
||||
ans = CidUserAnswer(answer=posted_answer, normal=normal, cid=cid)
|
||||
|
||||
ans.question_id = answer["qid"]
|
||||
ans.exam_id = eid
|
||||
|
||||
|
||||
Reference in New Issue
Block a user