Merge branch 'master' of ssh://git.xkjq.uk:30001/ross/rad

This commit is contained in:
Ross
2021-07-26 10:38:27 +01:00
37 changed files with 735 additions and 200 deletions
+2 -1
View File
@@ -5,4 +5,5 @@ __pycache__
.vscode/
.DS_Store
rad/settings_local.py
venv
venv
.venv/
+1
View File
@@ -44,6 +44,7 @@ class AnatomyAdmin(VersionAdmin):
#IncorrectAnatomyAnswersInline,
ExamInline,
]
view_on_site = True
exclude = [ "image_annotations" ]
@@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2021-07-06 19:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0039_answer_proposed'),
]
operations = [
migrations.AlterField(
model_name='exam',
name='exam_mode',
field=models.BooleanField(default=False, help_text='If an exam should be taken in exam mode (users results will be submited to the server for marking)'),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2021-07-09 11:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0040_alter_exam_exam_mode'),
]
operations = [
migrations.AlterField(
model_name='exam',
name='name',
field=models.CharField(help_text='Name of the exam', max_length=200),
),
]
+12 -11
View File
@@ -130,7 +130,7 @@ class AnatomyQuestion(models.Model):
def __str__(self):
# Get first answer
return "{} [{}, {}]".format(
self.GetPrimaryAnswer(), self.modality, self.structure
self.get_primary_answer(), self.modality, self.structure
)
# Get associated exams
e = self.exams.all().values_list("name", flat=True)
@@ -146,19 +146,19 @@ class AnatomyQuestion(models.Model):
def get_absolute_url(self):
return reverse("anatomy:question_detail", kwargs={"pk": self.pk})
def GetPrimaryAnswer(self):
def get_primary_answer(self):
if len(self.answers.filter(proposed=False)) > 0:
return self.answers.filter(proposed=False).first().answer
else:
return "None yet..."
def GetExams(self):
def get_exams(self):
e = self.exams.all().values_list("name", flat=True)
exams = ", ".join(e)
return exams
def GetUnmarkedAnswersString(self):
unmarked_answers = self.GetUnmarkedAnswers()
def get_unmarked_answer_string(self):
unmarked_answers = self.get_unmarked_answers()
if not unmarked_answers:
return "No answers to mark"
@@ -168,18 +168,18 @@ class AnatomyQuestion(models.Model):
)
)
def GetUnmarkedAnswers(self):
def get_unmarked_answers(self):
user_answers = set(
[i.answer_compare for i in self.cid_user_answers.all()]
)
unmarked_answers = user_answers - self.GetMarkedAnswers()
unmarked_answers = user_answers - self.get_marked_answers()
return unmarked_answers
def GetUnmarkedAnswerCount(self):
return len(self.GetUnmarkedAnswers())
def get_unmarked_answer_count(self):
return len(self.get_unmarked_answers())
def GetMarkedAnswers(self):
def get_marked_answers(self):
return set(
[
i.answer_compare
@@ -205,6 +205,7 @@ class AnatomyQuestion(models.Model):
@reversion.register
class Answer(models.Model):
question = models.ForeignKey(
AnatomyQuestion, related_name="answers", on_delete=models.CASCADE
@@ -350,7 +351,7 @@ class CidUserAnswer(models.Model):
except (Exam.DoesNotExist, KeyError) as e:
exam = "None"
return "{}/{}/{}: {}".format(
exam, self.cid, self.question.GetPrimaryAnswer(), self.answer
exam, self.cid, self.question.get_primary_answer(), self.answer
)
def save(self, *args, **kwargs):
+7
View File
@@ -78,6 +78,7 @@ $(document).ready(function () {
var jqxhr = $.get(evt.target.dataset.exam_list_url, function (data) {
console.log(data);
$("#exam-options").empty();
data.forEach((obj, n) => {
$("#exam-options").append($(document.createElement('button')).prop({
type: 'button',
@@ -106,6 +107,12 @@ $(document).ready(function () {
$("table input:checked").each((n, el) => {
ids.push(el.value)
})
// if no question selected
if (ids.length < 1) {
toastr.info('No question selected.');
return
}
let post_url = document.getElementById("button-select-add-exam").dataset.exam_json_edit_url;
let type = document.getElementById("button-select-add-exam").dataset.type;
let csrf = document.getElementById("button-select-add-exam").dataset.csrf;
+4 -2
View File
@@ -26,12 +26,14 @@
{% for question in questions.all %}
<li data-question_pk={{question.pk}}>
<a href="{% url 'anatomy:exam_question_detail' pk=exam.pk sk=forloop.counter0 %}">
<img src="{{ question.image|thumbnail_url:'exam-list' }}" alt="thumbail" />
</a>
{{ question.description }}
<br />
{{ question.question_type }}: {{ question.GetPrimaryAnswer }}
{{ question.question_type }}: {{ question.get_primary_answer }}
<br />
Modality: {{ question.modality }}, <a href="{% url 'anatomy:exam_question_detail' pk=exam.pk sk=forloop.counter0 %}">View</a>, <a href="{% url 'anatomy:mark' pk=exam.pk sk=forloop.counter0 %}">Mark</a>
Modality: {{ question.modality }}, <a href="{% url 'anatomy:question_detail' pk=question.pk %}">View</a>, <a href="{% url 'anatomy:mark' pk=exam.pk sk=forloop.counter0 %}">Mark</a>
</li>
{% endfor %}
</ol>
+2 -2
View File
@@ -12,8 +12,8 @@
<ul id="question-mark-list">
{% for question in questions.all %}
<li data-markcount={{question.GetUnmarkedAnswerCount}}><a href="{% url 'anatomy:mark' pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter }}:
{{ question }}</a><br /> {{ question.GetUnmarkedAnswersString }}</li>
<li data-markcount={{question.get_unmarked_answer_count}}><a href="{% url 'anatomy:mark' pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter }}:
{{ question }}</a><br /> {{ question.get_unmarked_answer_string }}</li>
{% endfor %}
</ul>
</div>
@@ -29,7 +29,7 @@
<div class="date">
Created: {{ question.created_date }}
</div>
<h1>{{ question.GetPrimaryAnswer }}</h1>
<h1>{{ question.get_primary_answer }}</h1>
<h2>{{question.question_type}}</h2>
Answers (score): {% for answer in question.answers.all %}
{{ answer }} ({{answer.status}}),
+2 -2
View File
@@ -333,7 +333,7 @@ def mark(request, pk, sk):
# i.answer.lower() for i in question.incorrect_answers.all()
# ]
unmarked_user_answers = question.GetUnmarkedAnswers()
unmarked_user_answers = question.get_unmarked_answers()
if request.method == "POST":
@@ -576,7 +576,7 @@ def exam_scores_cid_user(request, pk, sk):
answer_score = user_answer.get_answer_score()
correct_answer = q.GetPrimaryAnswer()
correct_answer = q.get_primary_answer()
if not exam.publish_results:
correct_answer = "*****"
+2 -2
View File
@@ -53,7 +53,7 @@ class Sign(tagulous.models.TagModel):
class ExamBase(models.Model):
name = models.CharField(max_length=200)
name = models.CharField(max_length=200, help_text="Name of the exam")
#exam_questions = SortedManyToManyField(Long, related_name="exams", blank="true")
active = models.BooleanField(
@@ -61,7 +61,7 @@ class ExamBase(models.Model):
)
exam_mode = models.BooleanField(
help_text="If an exam should be taken in exam mode", default=True
help_text="If an exam should be taken in exam mode (users results will be submited to the server for marking)", default=False
)
publish_results = models.BooleanField(
+3
View File
@@ -406,6 +406,9 @@ class ExamViews(View, LoginRequiredMixin):
if self.question_type == "long":
h = {}
for question in exam.exam_questions.all():
# Generate json if needed
if question.json_creation_time is None:
question.get_json(question.pk)
h[question.pk] = question.json_creation_time.isoformat()
obj["multi_question_json"] = h
active_exams["exams"].append( obj )
@@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2021-07-06 19:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('longs', '0035_longseries_open_access'),
]
operations = [
migrations.AlterField(
model_name='exam',
name='exam_mode',
field=models.BooleanField(default=False, help_text='If an exam should be taken in exam mode (users results will be submited to the server for marking)'),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2021-07-06 19:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('longs', '0036_alter_exam_exam_mode'),
]
operations = [
migrations.AlterField(
model_name='exam',
name='time_limit',
field=models.IntegerField(default=4500, help_text='Exam time limit (in seconds). Default is 4500 seconds (75 minutes)'),
),
]
+18
View File
@@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2021-07-09 11:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('longs', '0037_alter_exam_time_limit'),
]
operations = [
migrations.AlterField(
model_name='exam',
name='name',
field=models.CharField(help_text='Name of the exam', max_length=200),
),
]
+10 -9
View File
@@ -145,12 +145,12 @@ class Long(models.Model):
examinations = [series.get_examination() for series in self.series.all()]
return "{} : {}".format(self.description, ", ".join(examinations))
def GetExams(self):
def get_exams(self):
e = self.exams.all().values_list("name", flat=True)
exams = ", ".join(e)
return exams
def GetImages(self, feedback=False):
def get_images(self, feedback=False):
qs = self.images.all()
if not feedback:
@@ -160,16 +160,16 @@ class Long(models.Model):
return images
def GetImageUrls(self):
def get_image_urls(self):
return ",".join(
["https://penracourses.org.uk{}".format(i.url) for i in self.GetImages()]
["https://penracourses.org.uk{}".format(i.url) for i in self.get_images()]
)
def GetUnmarkedAnswerCount(self):
def get_unmarked_answer_count(self):
answers = self.cid_user_answers.all()
return len([ans for ans in answers if not ans.is_marked()])
def GetUnmarkedAnswers(self):
def get_unmarked_answers(self):
answers = self.cid_user_answers.all()
return [ans for ans in answers if not ans.is_marked()]
@@ -266,7 +266,7 @@ class Long(models.Model):
# def GetNonFeedbackQuestionImages(self):
# return self.GetImages()
# return self.get_images()
def test_image_validator(file):
@@ -527,7 +527,7 @@ class Exam(ExamBase):
exam_questions = SortedManyToManyField(Long, related_name="exams", blank="true")
time_limit = models.IntegerField(
help_text="Exam time limit (in seconds). Default is 4500 secondse (75 minutes)",
help_text="Exam time limit (in seconds). Default is 4500 seconds (75 minutes)",
default=4500,
)
@@ -568,7 +568,7 @@ class Exam(ExamBase):
return exam_question
def get_exam_json(self, based=False):
def get_exam_json(self, based=True):
questions = self.exam_questions.all()
exam_questions = defaultdict(dict)
@@ -619,6 +619,7 @@ class Exam(ExamBase):
"exam_mode": True,
"exam_order": exam_order,
"questions": exam_questions,
"based": based,
#"question_requests": exam_questions,
}
@@ -40,7 +40,7 @@
<p class="pre-whitespace"><b>Mark Scheme:</b> <pre>{{ question.mark_scheme }}</pre></p>
</div>
</p>
<p><b>Exam(s):</b> {{question.GetExams}}</p>
<p><b>Exam(s):</b> {{question.get_exams}}</p>
</div>
<div>
Notes:
+2 -2
View File
@@ -12,8 +12,8 @@
<ul id="question-mark-list">
{% for question in questions.all %}
<li data-markcount={{question.GetUnmarkedAnswerCount}}><a href="{% url 'longs:mark' pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter }}:
{{ question }}</a><br /> Answers to mark: {{question.GetUnmarkedAnswerCount}}</li>
<li data-markcount={{question.get_unmarked_answer_count}}><a href="{% url 'longs:mark' pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter }}:
{{ question }}</a><br /> Answers to mark: {{question.get_unmarked_answer_count}}</li>
{% endfor %}
</ul>
</div>
+1 -1
View File
@@ -655,7 +655,7 @@ def mark_answer(request, pk, sk, tk):
raise Http404("User answer does not exist")
try:
unmarked = question.GetUnmarkedAnswers()
unmarked = question.get_unmarked_answers()
next_unmarked_id = unmarked[0].cid
if next_unmarked_id == tk:
next_unmarked_id = unmarked[1].cid
@@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2021-07-06 19:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('physics', '0007_exam_author'),
]
operations = [
migrations.AlterField(
model_name='exam',
name='exam_mode',
field=models.BooleanField(default=False, help_text='If an exam should be taken in exam mode (users results will be submited to the server for marking)'),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2021-07-09 11:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('physics', '0008_alter_exam_exam_mode'),
]
operations = [
migrations.AlterField(
model_name='exam',
name='name',
field=models.CharField(help_text='Name of the exam', max_length=200),
),
]
+1 -1
View File
@@ -59,9 +59,9 @@ INSTALLED_APPS = [
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.security.SecurityMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
+2
View File
@@ -37,6 +37,7 @@ router.register(r'longs/exams', long_views.ExamViewSet, basename="long-exams")
router.register(r'rapids/answers', rapid_views.QuestionAnswerViewSet, basename="rapid-question-answers")
router.register(r'rapids/user_answer', rapid_views.UserAnswerViewSet, basename="rapid-question-answers")
router.register(r'rapids', rapid_views.RapidViewSet, basename="rapid")
#router.register(r'rapids/laterality', rapid_views.RapidLateralityViewSet, basename="rapid")
urlpatterns = [
path("admin/", admin.site.urls, name="admin"),
@@ -60,6 +61,7 @@ urlpatterns = [
path("feedback/note/<int:pk>/complete", views.feedback_mark_complete, name="feedback_mark_complete"),
#path("feedback/", views.AddQuestionNote.as_view(), name="feedback"),
path("feedback/answer", views.answer_suggestion_submit, name="answer_suggestion_submit"),
path("feedback/answer_submit", views.answer_submit, name="answer_submit"),
path("feedback/answer/confirm", views.answer_suggestion_confirm, name="answer_suggestion_confirm"),
path("feedback/view", views.view_feedback, name="view_feedback"),
+31
View File
@@ -248,6 +248,37 @@ def answer_suggestion_submit(request):
# postExamAnswers
return JsonResponse({"success": False, "error": "Invalid data"})
@login_required
def answer_submit(request):
if request.is_ajax and request.method == "POST":
qid = request.POST.get("qid")
question_type = request.POST.get("question_type")
answer_string = request.POST.get("answer")
status = request.POST.get("status")
if str(status) not in "012":
return JsonResponse({"success": False, "error": "Invalid status"})
if question_type == "anatomy":
AnswerModel = AnatomyAnswer
question = AnatomyQuestion
elif question_type == "rapid":
AnswerModel = RapidAnswer
question = Rapid
else:
return JsonResponse({"success": False, "error": "Invalid question type"})
q = get_object_or_404(question, pk=qid)
if AnswerModel.objects.filter(answer=answer_string, question=q):
return JsonResponse({"success": False, "error": "Answer already exists"})
a = AnswerModel(question=q, answer=answer_string, status=status)
a.save()
return JsonResponse({"success": True, "error": "Answer added"})
return JsonResponse({"success": False, "error": "Invalid data"})
@login_required
def answer_suggestion_confirm(request):
if request.is_ajax and request.method == "POST":
+2 -1
View File
@@ -41,7 +41,8 @@ class RapidAdminForm(ModelForm):
fields = [
"normal", "abnormality", "region", "laterality", "examination",
#"site",
"sign", "condition", "feedback", "author"
#"sign", "condition",
"feedback", "author"
]
widgets = {
@@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2021-07-06 19:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rapids', '0023_answer_proposed'),
]
operations = [
migrations.AlterField(
model_name='exam',
name='exam_mode',
field=models.BooleanField(default=False, help_text='If an exam should be taken in exam mode (users results will be submited to the server for marking)'),
),
]
@@ -0,0 +1,26 @@
# Generated by Django 3.2.4 on 2021-07-09 11:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rapids', '0024_alter_exam_exam_mode'),
]
operations = [
migrations.RemoveField(
model_name='rapid',
name='condition',
),
migrations.RemoveField(
model_name='rapid',
name='sign',
),
migrations.AlterField(
model_name='exam',
name='name',
field=models.CharField(help_text='Name of the exam', max_length=200),
),
]
+69 -33
View File
@@ -37,6 +37,10 @@ def image_directory_path(instance, filename):
return u"rapids/picture/{0}".format(filename)
def get_answer_compare(s):
s = s.strip().lower()
s = s.translate(str.maketrans('', '', string.punctuation.replace("#", "")))
return s
class Abnormality(models.Model):
name = models.CharField(max_length=200,
@@ -101,15 +105,9 @@ class Answer(models.Model):
def clean(self):
if self.answer:
self.answer = self.answer.strip()
s = self.answer.lower()
s = s.translate(str.maketrans('', '', string.punctuation.replace("#", "")))
self.answer_compare = s
self.answer_compare = get_answer_compare(self.answer)
@reversion.register
class Rapid(models.Model):
#author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
#image = models.ImageField()
@@ -146,14 +144,14 @@ class Rapid(models.Model):
choices=LATERALITY_CHOICES,
default=NONE,
help_text="Applies to the answer, not the examination")
condition = tagulous.models.TagField(
to=Condition,
blank=True,
help_text=
"Associated condition. Will allow searching / filtering and tips / hints to be displayed. Conditions with spaces must be enclosed in quotes \"...\""
)
sign = tagulous.models.TagField(
to=Sign, blank=True, help_text='Radiological signs in the question')
#condition = tagulous.models.TagField(
# to=Condition,
# blank=True,
# help_text=
# "Associated condition. Will allow searching / filtering and tips / hints to be displayed. Conditions with spaces must be enclosed in quotes \"...\""
#)
#sign = tagulous.models.TagField(
# to=Sign, blank=True, help_text='Radiological signs in the question')
DEFAULT_SITE_ID = 1
#site = models.ManyToManyField(
@@ -216,7 +214,7 @@ class Rapid(models.Model):
lat = self.laterality
return "{} / {} {}".format(exams, lat, n)
def GetPrimaryAnswer(self):
def get_primary_answer(self):
if self.normal:
return "Normal"
elif len(self.answers.filter(proposed=False)) > 0:
@@ -224,13 +222,13 @@ class Rapid(models.Model):
else:
return "None yet..."
def GetExams(self):
def get_exams(self):
e = self.exams.all().values_list("name", flat=True)
exams = ", ".join(e)
return exams
def GetUnmarkedAnswersString(self):
unmarked_answers = self.GetUnmarkedAnswers()
def get_unmarked_answer_string(self):
unmarked_answers = self.get_unmarked_answers()
if not unmarked_answers:
return "No answers to mark"
@@ -240,7 +238,7 @@ class Rapid(models.Model):
)
)
def GetUnmarkedAnswers(self):
def get_unmarked_answers(self):
# If normal no answers to mark
if self.normal:
return []
@@ -248,14 +246,22 @@ class Rapid(models.Model):
user_answers = set(
[i.answer_compare for i in self.cid_user_answers.all() if i.normal == False]
)
unmarked_answers = user_answers - self.GetMarkedAnswers()
unmarked_answers = user_answers - self.get_marked_answers()
return unmarked_answers
def GetUnmarkedAnswerCount(self):
return len(self.GetUnmarkedAnswers())
def get_unmarked_answer_count(self):
return len(self.get_unmarked_answers())
def GetMarkedAnswers(self):
def get_compare_answers(self):
return set(
[
i.answer_compare
for i in self.answers.filter()
]
)
def get_marked_answers(self):
return set(
[
i.answer_compare
@@ -276,7 +282,7 @@ class Rapid(models.Model):
marked_answers = correct_answers | half_mark_answers | incorrect_answers
return marked_answers
def GetCorrectUnstrippedAnswers(self):
def get_correct_unstripped_answers(self):
return set(
[
str(i)
@@ -285,7 +291,7 @@ class Rapid(models.Model):
]
)
def GetImages(self, feedback=False):
def get_images(self, feedback=False):
qs = self.images.all()
if not feedback:
@@ -295,12 +301,42 @@ class Rapid(models.Model):
return images
def GetImageUrls(self):
return ",".join(["https://penracourses.org.uk{}".format(i.url) for i in self.GetImages()])
def get_image_urls(self):
return ",".join(["https://penracourses.org.uk{}".format(i.url) for i in self.get_images()])
#def GetNonFeedbackQuestionImages(self):
#return self.GetImages()
#return self.get_images()
def get_laterality_string(self):
if self.laterality == self.NONE:
s = ""
elif self.laterality == self.BILATERAL:
s = "bilateral "
elif self.laterality == self.RIGHT:
s = "right "
elif self.laterality == self.LEFT:
s = "left "
return s
def get_suggested_answers(self):
answers = []
for r in self.region.all():
laterality = self.get_laterality_string()
for a in self.abnormality.all():
answers.append("{}{} {}".format(laterality, r.name, a.name))
answers.append("{} {}{}".format(a.name, laterality, r.name))
answers.append("{} {}".format(r.name, a.name))
answers.append("{} {}".format(a.name, r.name))
compare_answers = self.get_compare_answers()
new_answers = [a for a in answers if get_answer_compare(a) not in compare_answers]
return new_answers
#reversion.register(Rapid, follow=["images"])
#@reversion.register
class RapidImage(models.Model):
rapid = models.ForeignKey(Rapid,
related_name="images",
@@ -357,7 +393,7 @@ class RapidCreationDefault(models.Model):
def get_absolute_url(self):
return reverse('rapids:rapid_create')
@reversion.register
#@reversion.register
class Exam(ExamBase):
app_name = "rapids"
@@ -419,15 +455,15 @@ class Exam(ExamBase):
exam_questions[q.id] = {
"images": images,
"normal": q.normal,
#"feedback_image": [],
#"annotations": [str(q.image_annotations)],
"type": "rapid",
}
if not self.exam_mode:
exam_questions[q.id]["normal"] = q.normal
exam_questions[q.id]["feedback_images"] = feedback_images
exam_questions[q.id]["answers"] = list(q.GetCorrectUnstrippedAnswers())
exam_questions[q.id]["answers"] = list(q.get_correct_unstripped_answers())
#if feedback_images:
@@ -482,7 +518,7 @@ class CidUserAnswer(models.Model):
except (Exam.DoesNotExist, KeyError) as e:
exam = "None"
return "{}/{}/{}: {}".format(
exam, self.cid, self.question.GetPrimaryAnswer(), self.answer
exam, self.cid, self.question.get_primary_answer(), self.answer
)
def save(self, *args, **kwargs):
+8 -2
View File
@@ -29,5 +29,11 @@ class UserAnswerSerializer(serializers.HyperlinkedModelSerializer):
class RapidSerializer(serializers.ModelSerializer):
class Meta:
model = Rapid
fields = ('id', 'question', 'normal', "abnormality", "region", "examination", "exams", "author", "open_access")
view_name = "rapid-detail"
fields = ('id', 'question', 'normal', "abnormality", "region", "laterality", "examination", "exams", "author", "open_access")
view_name = "rapid-detail"
class RapidLateralitySerializer(serializers.ModelSerializer):
class Meta:
model = Rapid
fields = ('id', "laterality")
view_name = "rapid-laterality"
+4
View File
@@ -62,6 +62,10 @@ class RapidUserAnswerTable(tables.Table):
delete = tables.LinkColumn(
"rapids:rapid_user_answer_delete", text="Delete", args=[A("pk")], orderable=False
)
view = tables.LinkColumn('rapids:rapid_user_answer_view',
text='View',
args=[A('pk')],
orderable=False)
class Meta:
model = CidUserAnswer
template_name = "django_tables2/bootstrap4.html"
@@ -0,0 +1,8 @@
{% extends 'rapids/base.html' %}
{% block content %}
{{ciduseranswer.question}}
{{ciduseranswer.normal}}
{{ciduseranswer.cid}}
{% endblock content %}
+5 -3
View File
@@ -26,18 +26,20 @@
{% for question in questions.all %}
<li data-question_pk={{question.pk}}>
{% for image in question.GetImages %}
<a href="{% url 'rapids:exam_question_detail' pk=exam.pk sk=forloop.counter0 %}">
{% for image in question.get_images %}
<img src="{{ image|thumbnail_url:'exam-list' }}" alt="thumbail" />
{% endfor %}
</a>
{% if not question.normal %}
<b>Abnormality:</b> {{ question.get_abnormalities }} <b>Region:</b> {{ question.get_regions }}
<br />
{{ question.GetPrimaryAnswer }}
{{ question.get_primary_answer }}
{% else %}
<b>Normal</b>
{% endif %}
<br />
Examination: {{ question.get_examinations }}, <a href="{% url 'rapids:exam_question_detail' pk=exam.pk sk=forloop.counter0 %}">View</a>, <a href="{% url 'rapids:mark' pk=exam.pk sk=forloop.counter0 %}">Mark</a>
Examination: {{ question.get_examinations }}, <a href="{% url 'rapids:question_detail' pk=question.pk %}">View</a>, <a href="{% url 'rapids:mark' pk=exam.pk sk=forloop.counter0 %}">Mark</a>
</li>
{% endfor %}
</ol>
+1 -1
View File
@@ -13,7 +13,7 @@ Answers will be automatically marked.
Answers marked as normal will be automatically marked.<br/>
Region: {{ question.get_regions }}, Abnormalities: {{ question.get_abnormalities }}
{% endif %}
<div id="single-dicom-viewer" class="marking-dicom" data-images="{{question.GetImageUrls}}"
<div id="single-dicom-viewer" class="marking-dicom" data-images="{{question.get_image_urls}}"
data-annotations='{{question.image_annotations}}'>
</div>
<div class="marking">
+2 -2
View File
@@ -12,8 +12,8 @@
<ul id="question-mark-list">
{% for question in questions.all %}
<li data-markcount={{question.GetUnmarkedAnswerCount}}><a href="{% url 'rapids:mark' pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter }}:
{{ question }}</a><br /> {{ question.GetUnmarkedAnswersString }}</li>
<li data-markcount={{question.get_unmarked_answer_count}}><a href="{% url 'rapids:mark' pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter }}:
{{ question }}</a><br /> {{ question.get_unmarked_answer_string }}</li>
{% endfor %}
</ul>
</div>
@@ -4,22 +4,33 @@
</div>
<p class="pre-whitespace"><b>Rapid:</b> {{ question }}</p>
<p class="pre-whitespace"><b>Normal:</b> {{ question.normal }} <button id="toggle-normal-button"
class="toggle-button">toggle</button></p>
<p class="pre-whitespace"><b>Region:</b> {{ question.get_regions }}</p>
<p class="pre-whitespace"><b>Examination:</b> {{ question.get_examinations }}</p>
<p class="pre-whitespace"><b>Laterality:</b> {{ question.laterality }}</p>
<p class="pre-whitespace"><b>Laterality:</b> {{ question.laterality }}
<button class="toggle-laterality-button toggle-button" data-lat="LEFT">Left</button>
<button class="toggle-laterality-button toggle-button" data-lat="RIGHT">Right</button>
<button class="toggle-laterality-button toggle-button" data-lat="BILAT">Bilateral</button>
<button class="toggle-laterality-button toggle-button" data-lat="NONE">None</button>
</p>
<p class="pre-whitespace"><b>Abnormality:</b> {{ question.get_abnormalities }}</p>
<div class="pre-whitespace multi-image-block"><b>Images:</b>
<div class="pre-whitespace multi-image-block"><b>Images:</b>
{% for image in question.images.all %}
<span class="image-block">
Image {{ forloop.counter }}{% if image.feedback_image %} [feedback image]{% endif %}:
<div class="dicom-image rapid-img {% if image.feedback_image %}feedback-img{% endif %}" data-url="https://penracourses.org.uk{{ image.image.url}}"></div>
Image {{ forloop.counter }}{% if image.feedback_image %} [feedback image]{% endif %}:
<div class="dicom-image rapid-img {% if image.feedback_image %}feedback-img{% endif %}"
data-url="https://penracourses.org.uk{{ image.image.url}}"></div>
</span>
{% endfor %}
</div>
<div>
Exams: {% for exam in question.exams.all %}
<a href="{% url 'rapids:exam_overview' pk=exam.pk %}">{{ exam.name }}</a>,
<a href="{% url 'rapids:exam_overview' pk=exam.pk %}">{{ exam.name }}</a>,
{% endfor %}
<button id="add-to-exam" data-exam_json_edit_url="{% url 'generic:generic_exam_json_edit' %}" data-exam_list_url="{% url 'rapid-exam-list' %}" data-type="rapid" data-csrf="{{ csrf_token}}" data-qid="{{question.pk}}">Add to exam</button>
<span id="exam-options"></span>
</div>
<p class="pre-whitespace"><b>Open Access:</b> {{ question.open_access }}</p>
<p class="pre-whitespace"><b>Feedback:</b> {{ question.feedback }}</p>
@@ -28,16 +39,17 @@
<p><b>Checked by:</b> {% for verified in question.verified.all %} <a
href="{% url 'rapids:verified_detail' pk=verified.pk %}">{{verified}}</a>, {% endfor %}</p>
<p><b>Scrapped:</b> {{ question.scrapped }} <a href="{% url 'rapids:rapid_scrap' pk=question.pk %}">(toggle)</a>
<p class="pre-whitespace">
Answers (score): {% for answer in question.answers.all %}
<span {% if answer.proposed %}class="proposed-answer" data-aid="{{answer.pk}}" data-question-type="rapid" {% endif %}>
{{ answer }} ({{answer.status}}),
</span>
{% endfor %}
</p>
<p class="pre-whitespace">
Answers (score): {% for answer in question.answers.all %}
<span {% if answer.proposed %}class="proposed-answer" data-aid="{{answer.pk}}" data-question-type="rapid"
{% endif %}>
{{ answer }} ({{answer.status}}),
</span>
{% endfor %}
</p>
</div>
<div>
{% if view_feedback %}
Notes:
<ul>
@@ -51,85 +63,266 @@
<ul class="notes">
{% for note in question.anon_notes.all %}
<li {% if note.complete %}class='complete' {% endif %}>
{{ note.created_on }} by {{ note.author }}: {{note.note_type}} / {{ note.note }}
{% if not note.complete %}
(<a href="{% url 'feedback_mark_complete' pk=note.pk %}">Mark complete</a>)
{% endif %}
{{ note.created_on }} by {{ note.author }}: {{note.note_type}} / {{ note.note }}
{% if not note.complete %}
(<a href="{% url 'feedback_mark_complete' pk=note.pk %}">Mark complete</a>)
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
<div id="single-dicom-viewer" class="dicom-viewer" data-images="{{ question.GetImageUrls }}" data-annotations=''></div>
<details>
<summary>
Suggested answers
</summary>
<ul class="suggested_answers">
{% for ans in question.get_suggested_answers %}
<li data-string="{{ans}}">{{ans}}</li>
{% endfor %}
</ul>
</details>
</div>
<details open>
<summary>
Image viewer
</summary>
<div id="single-dicom-viewer" class="dicom-viewer" data-images="{{ question.get_image_urls }}" data-annotations=''>
</div>
</details>
<script>
$(document).ready(function () {
$("#add-to-exam").click(function (evt) {
var jqxhr = $.get(evt.target.dataset.exam_list_url, function (data) {
console.log(data);
$("#exam-options").empty();
data.forEach((obj, n) => {
$("#exam-options").append($(document.createElement('button')).prop({
type: 'button',
innerHTML: obj.name,
class: '',
}).data("exam-id", obj.id).click((evt) => {
addToExam(evt)
}))
})
})
.done(function () {
//alert( "second success" );
})
.fail(function () {
//alert( "error" );
})
.always(function () {
//alert( "finished" );
});
return;
function addToExam(evt) {
let exam_button = document.getElementById("add-to-exam");
let ids = [];
ids.push(exam_button.dataset.qid);
// if no question selected
if (ids.length < 1) {
toastr.info('No question selected.');
return
}
let post_url = exam_button.dataset.exam_json_edit_url;
let type = exam_button.dataset.type;
let csrf = exam_button.dataset.csrf;
$.ajax({
url: post_url,
data: {
csrfmiddlewaretoken: csrf,
add_exam_questions: JSON.stringify(ids),
type: type,
exam_id: $(evt.target).data("exam-id"),
},
type: "POST",
dataType: "json",
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
if (data.status == "success") {
toastr.info('Questions added to exams.')
}
})
.always(function () {
console.log('[Done]');
$("#exam-options").empty();
})
}
});
//var $crf_token = $('[name="csrfmiddlewaretoken"]').attr('value');
/* beautify ignore:start */
n = !{{ question.normal | lower }}
/* beautify ignore:end */
$("#toggle-normal-button").click(function () {
$.ajax({
url: "{% url 'rapid-detail' question.id %}",
type: 'PATCH',
headers: {
"X-CSRFToken": "{{ csrf_token }}"
},
timeout: 3000,
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
normal: n,
}
})
.done(function (data) {
console.log(data);
toastr.info('Answer updated')
})
.fail(function () {
alert('Error updating this model instance.');
});
});
$(".toggle-laterality-button").each((n, el) => {
$(el).click(function () {
$.ajax({
url: "{% url 'rapid-detail' question.id %}",
type: 'PATCH',
headers: {
"X-CSRFToken": "{{ csrf_token }}"
},
timeout: 3000,
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
laterality: el.dataset.lat,
}
})
.done(function (data) {
console.log(data);
toastr.info('Answer updated')
})
.fail(function () {
alert('Error updating this model instance.');
});
});
});
$(".suggested_answers li").each((n, el) => {
$(el).append($("<span class='correct'>[Add Correct]</span>").on("click", function () {
$.ajax({
url: "{% url 'answer_submit' %}",
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
//active: this.checked // true if checked else false
question_type: "rapid",
qid: "{{question.pk}}",
status: 2,
answer: el.dataset.string,
},
type: "POST",
dataType: "json",
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
if (data.success) {
toastr.info('Answer saved')
$(el).find(".correct").remove()
}
// show some message according to the response.
// For eg. A message box showing that the status has been changed
})
.always(function () {
console.log('[Done]');
})
}))
});
// send request to change the is_private state on customSwitches toggle
$(".proposed-answer").each((n, el) => {
// Add button to confirm answer is correct
$(el).append($("<span class='confirm'>[Correct]</span>").on("click", function () {
$.ajax({
url: "{% url 'answer_suggestion_confirm' %}",
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
//active: this.checked // true if checked else false
question_type: "rapid",
aid: el.dataset.aid,
status: 2,
},
type: "POST",
dataType: "json",
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
if (data.success) {
toastr.info('Answer saved')
$(el).find(".confirm").remove()
$(el).removeClass("proposed-answer")
}
// show some message according to the response.
// For eg. A message box showing that the status has been changed
})
.always(function () {
console.log('[Done]');
})
}))
// Add button to confirm answer is correct
$(el).append($("<span class='confirm'>[Add Correct]</span>").on("click", function () {
$.ajax({
url: "{% url 'answer_suggestion_confirm' %}",
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
//active: this.checked // true if checked else false
question_type: "rapid",
aid: el.dataset.aid,
status: 2,
},
type: "POST",
dataType: "json",
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
// Add button to confirm answer is incorrect
$(el).append($("<span class='confirm'>[Incorrect]</span>").on("click", function () {
$.ajax({
url: "{% url 'answer_suggestion_confirm' %}",
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
//active: this.checked // true if checked else false
question_type: "rapid",
aid: el.dataset.aid,
status: 0,
},
type: "POST",
dataType: "json",
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
if (data.success) {
toastr.info('Answer saved')
$(el).find(".confirm").remove()
$(el).removeClass("proposed-answer")
}
// show some message according to the response.
// For eg. A message box showing that the status has been changed
})
.always(function () {
console.log('[Done]');
})
}))
if (data.success) {
toastr.info('Answer saved')
$(el).find(".confirm").remove()
$(el).removeClass("proposed-answer")
}
// show some message according to the response.
// For eg. A message box showing that the status has been changed
})
.always(function () {
console.log('[Done]');
})
}))
// Add button to confirm answer is incorrect
$(el).append($("<span class='confirm'>[Incorrect]</span>").on("click", function () {
$.ajax({
url: "{% url 'answer_suggestion_confirm' %}",
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
//active: this.checked // true if checked else false
question_type: "rapid",
aid: el.dataset.aid,
status: 0,
},
type: "POST",
dataType: "json",
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
if (data.success) {
toastr.info('Answer saved')
$(el).find(".confirm").remove()
$(el).removeClass("proposed-answer")
}
// show some message according to the response.
// For eg. A message box showing that the status has been changed
})
.always(function () {
console.log('[Done]');
})
}))
});
});
});
</script>
</script>
<style>
.toggle-button {
font-size: x-small;
opacity: 10%;
}
.toggle-button:hover {
font-size: x-small;
opacity: 100%;
}
</style>
+2 -1
View File
@@ -66,6 +66,7 @@ urlpatterns = [
name="rapid_update",
),
path("<int:pk>/add_note", views.AddNote.as_view(), name="rapid_add_note"),
path("user_answers/", views.RapidUserAnswerView.as_view(), name="rapid_user_answer_view"),
path("user_answers/", views.RapidUserAnswerTableView.as_view(), name="rapid_user_answer_table_view"),
path("user_answers/<int:pk>", views.RapidUserAnswerView.as_view(), name="rapid_user_answer_view"),
path("user_answers/<int:pk>/delete", views.UserAnswerDelete.as_view(), name="rapid_user_answer_delete"),
]
+106 -41
View File
@@ -8,6 +8,7 @@ from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic import ListView
@@ -28,7 +29,7 @@ from .forms import (
ExaminationForm,
AnswerFormSet,
AnswerUpdateFormSet,
ExamForm
ExamForm,
)
from .models import (
Rapid,
@@ -69,10 +70,16 @@ from rapids.forms import RapidCreationDefaultForm
from rapids.models import RapidCreationDefault
from rest_framework import viewsets, permissions
from rest_framework import viewsets, permissions, mixins
from rest_framework.pagination import PageNumberPagination
from .serializers import ExamSerializer, QuestionAnswerSerializer, RapidSerializer, UserAnswerSerializer
from .serializers import (
ExamSerializer,
QuestionAnswerSerializer,
RapidLateralitySerializer,
RapidSerializer,
UserAnswerSerializer,
)
from generic.mixins import SuperuserRequiredMixin
@@ -89,32 +96,44 @@ class AuthorOrCheckerRequiredMixin(object):
return obj
@login_required
def question_detail(request, pk):
rapid = get_object_or_404(Rapid, pk=pk)
if not rapid.open_access:
if not request.user.groups.filter(name="rapid_checker").exists() and request.user not in rapid.author.all():
if (
not request.user.groups.filter(name="rapid_checker").exists()
and request.user not in rapid.author.all()
):
raise PermissionDenied()
# if request.user not in rapid.author.all():
# raise PermissionDenied
view_feedback = False
if request.user.groups.filter(name="rapid_checker").exists() or request.user.groups.filter(name="feedback_checker").exists() or request.user in rapid.author.all():
if (
request.user.groups.filter(name="rapid_checker").exists()
or request.user.groups.filter(name="feedback_checker").exists()
or request.user in rapid.author.all()
):
view_feedback = True
# logging.debug(rapid.rapid_notes.first())
# logging.debug(rapid.subspecialty.first().name.all())
return render(request, "rapids/question_detail.html", {"question": rapid, "view_feedback" : view_feedback})
return render(
request,
"rapids/question_detail.html",
{"question": rapid, "view_feedback": view_feedback},
)
@login_required
def rapid_split(request, pk):
rapid = get_object_or_404(Rapid, pk=pk)
if not request.user.groups.filter(name="rapid_checker").exists() and request.user not in question.author.all():
if (
not request.user.groups.filter(name="rapid_checker").exists()
and request.user not in question.author.all()
):
raise PermissionDenied()
images = rapid.images.all()
@@ -281,7 +300,7 @@ class RapidCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
# Sending user object to the form, to verify which fields to display/remove (depending on group)
def get_form_kwargs(self):
kwargs = super(RapidCreateBase, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
kwargs.update({"user": self.request.user})
return kwargs
def get_context_data(self, **kwargs):
@@ -328,24 +347,24 @@ class RapidCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
# @login_required
class RapidCreate(RapidCreateBase):
#initial = {"laterality": Rapid.NONE}
# initial = {"laterality": Rapid.NONE}
def get_initial(self):
if "pk" in self.kwargs:
initial = super().get_initial()
exam = get_object_or_404(Exam, pk=self.kwargs["pk"])
initial['exams'] = [exam.id]
initial["exams"] = [exam.id]
return initial
## There has to be a better way...
#try:
# try:
# s = (i.pk for i in self.request.user.rapid_default.site.all())
# self.initial.update({"site": s})
#except AttributeError:
# except AttributeError:
# pass
#return self.initial
# return self.initial
# fields = '__all__'
# #fields = [ 'condition' ]
@@ -359,7 +378,9 @@ class RapidCreate(RapidCreateBase):
# return super().form_valid(form)
class RapidUpdate(RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView):
class RapidUpdate(
RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView
):
model = Rapid
form_class = RapidForm
@@ -369,7 +390,7 @@ class RapidUpdate(RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixi
# exclude = [ 'created_date', 'published_date' ]
def get_form_kwargs(self):
kwargs = super(RapidUpdate, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
kwargs.update({"user": self.request.user})
return kwargs
def get_context_data(self, **kwargs):
@@ -445,7 +466,10 @@ def rapid_scrap(request, pk):
except Rapid.DoesNotExist:
raise Http404("Rapid does not exist")
if not request.user.groups.filter(name="rapid_checker").exists() and request.user not in question.author.all():
if (
not request.user.groups.filter(name="rapid_checker").exists()
and request.user not in question.author.all()
):
raise PermissionDenied()
rapid.scrapped = not rapid.scrapped
@@ -578,9 +602,9 @@ def loadJsonAnswer(answer):
posted_answer = answer["ans"]
# qidn 1 does not hold answer data (but should always arrive prior to 2)
if answer["qidn"] == "1":
posted_answer = ""
## qidn 1 does not hold answer data (but should always arrive prior to 2)
# if answer["qidn"] == "1":
# posted_answer = ""
# Normal answers are just posted with the answer "Normal"
normal = False
@@ -601,7 +625,8 @@ def loadJsonAnswer(answer):
ans = exiting_answers[0]
if answer["qidn"] == "1":
ans.normal = normal
elif answer["qidn"] == "2":
ans.answer = ""
elif answer["qidn"] == "2" and not ans.normal:
ans.answer = posted_answer
ans.full_clean()
ans.save()
@@ -643,7 +668,7 @@ def mark(request, pk, sk):
# i.answer.lower() for i in question.incorrect_answers.all()
# ]
unmarked_user_answers = question.GetUnmarkedAnswers()
unmarked_user_answers = question.get_unmarked_answers()
if request.method == "POST":
@@ -878,11 +903,10 @@ def exam_scores_cid_user(request, pk, sk):
# Get user answer
user_answer = q.cid_user_answers.filter(cid=cid).first()
if not user_answer or user_answer is None:
# skip if no answer
answers_marks.append(0)
#answers.append("")
# answers.append("")
answer_score = 0
ans = "Not answered"
else:
@@ -892,7 +916,7 @@ def exam_scores_cid_user(request, pk, sk):
ans = user_answer.answer
answer_score = user_answer.get_answer_score()
correct_answer = q.GetPrimaryAnswer()
correct_answer = q.get_primary_answer()
if not exam.publish_results:
correct_answer = "*****"
@@ -931,6 +955,7 @@ class QuestionDelete(AuthorOrCheckerRequiredMixin, DeleteView):
model = Rapid
success_url = reverse_lazy("rapids:rapid_view")
RapidExamViews = ExamViews(Exam, Rapid, "rapids", "rapid", loadJsonAnswer)
@@ -945,7 +970,10 @@ class ExamCreate(RevisionMixin, LoginRequiredMixin, CreateView):
form.instance.author.add(self.request.user.id)
return super().form_valid(form)
class ExamUpdate(RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView):
class ExamUpdate(
RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView
):
model = Exam
form_class = ExamForm
@@ -956,28 +984,38 @@ class ExamUpdate(RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin
form.instance.author.add(self.request.user.id)
return super().form_valid(form)
class RapidUserAnswerView(LoginRequiredMixin, SingleTableMixin, FilterView):
class RapidUserAnswerView(LoginRequiredMixin, DetailView):
model = CidUserAnswer
class RapidUserAnswerTableView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = CidUserAnswer
table_class = RapidUserAnswerTable
template_name = "rapids/rapid_question_view.html"
filterset_class = RapidUserAnswerFilter
class UserAnswerDelete(SuperuserRequiredMixin, DeleteView):
model = CidUserAnswer
success_url = reverse_lazy("rapids:rapid_user_answer_view")
success_url = reverse_lazy("rapids:rapid_user_answer_table_view")
class LargeResultsSetPagination(PageNumberPagination):
page_size = 1000
page_size_query_param = 'page_size'
page_size_query_param = "page_size"
max_page_size = 10000
class StandardResultsSetPagination(PageNumberPagination):
page_size = 100
page_size_query_param = 'page_size'
page_size_query_param = "page_size"
max_page_size = 1000
class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
#queryset = Exam.objects.all().order_by('name')
# queryset = Exam.objects.all().order_by('name')
serializer_class = ExamSerializer
def get_queryset(self):
@@ -987,35 +1025,62 @@ class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
user = self.request.user
if user.groups.filter(name="rapid_checker").exists():
return Exam.objects.all()
return Exam.objects.filter(author__id=user.id)
class QuestionAnswerViewSet(viewsets.ModelViewSet):
queryset = Answer.objects.all()#.order_by('name')
queryset = Answer.objects.all() # .order_by('name')
serializer_class = QuestionAnswerSerializer
permission_classes = [permissions.IsAuthenticated]
class UserAnswerViewSet(viewsets.ModelViewSet):
queryset = CidUserAnswer.objects.all()#.order_by('name')
queryset = CidUserAnswer.objects.all() # .order_by('name')
serializer_class = UserAnswerSerializer
permission_classes = [permissions.IsAuthenticated]
class RapidViewSet(RevisionMixin, viewsets.ModelViewSet):
queryset = Rapid.objects.all()#.order_by('name')
class RapidViewSet(
RevisionMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
mixins.DestroyModelMixin,
viewsets.GenericViewSet,
):
queryset = Rapid.objects.all() # .order_by('name')
serializer_class = RapidSerializer
permission_classes = [permissions.IsAuthenticated]
pagination_class = StandardResultsSetPagination
class RapidLateralityViewSet(
RevisionMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet,
):
queryset = Rapid.objects.all() # .order_by('name')
serializer_class = RapidLateralitySerializer
permission_classes = [permissions.IsAuthenticated]
pagination_class = StandardResultsSetPagination
class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
model = Exam
success_url = reverse_lazy("rapids:index")
#class AddSuggestedAnswer(LoginRequiredMixin, CreateView):
# class AddSuggestedAnswer(LoginRequiredMixin, CreateView):
# model = QuestionNote
# form_class = QuestionNoteForm
#
# #fields = ("author", "note_type", "note")
#
#
#
# def get_form_kwargs(self):
# kwargs = super(AddQuestionNote, self).get_form_kwargs()
@@ -1037,7 +1102,7 @@ class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
# content_type = ContentType.objects.get(model="long")
# else:
# raise PermissionError()
#
#
# get_object_or_404(question, pk=pk)
# return { "content_type" : content_type,
# "object_id" : pk }
# "object_id" : pk }