clone rapids app into longs

This commit is contained in:
Ross
2021-02-02 09:33:50 +00:00
parent 4c2fbdb6f7
commit 38134e5fbc
32 changed files with 2900 additions and 0 deletions
View File
Executable
+73
View File
@@ -0,0 +1,73 @@
from django.contrib import admin
from .models import Rapid, RapidImage, Examination, Site, Abnormality, Region, Note, RapidCreationDefault, Answer, Exam
import tagulous.admin
from django.forms import ModelForm
from reversion.admin import VersionAdmin
from django.forms.widgets import RadioSelect
# Register your models here.
admin.site.register(Examination)
admin.site.register(Site)
admin.site.register(Exam)
admin.site.register(Abnormality)
admin.site.register(Region)
admin.site.register(Note)
admin.site.register(RapidCreationDefault)
admin.site.register(Answer)
class RapidAnswersInline(admin.TabularInline):
model = Answer
extra = 1
class RapidImageInline(admin.TabularInline):
model = RapidImage
extra = 1
readonly_fields = [
"image_tag",
]
class ExamInline(admin.TabularInline):
model = Rapid.exams.through
extra = 1
class RapidAdminForm(ModelForm):
class Meta:
model = Rapid
fields = [
"normal", "abnormality", "region", "laterality", "examination",
"site", "sign", "condition", "feedback", "author"
]
widgets = {
"laterality": RadioSelect(attrs={'style': 'display: inline-flex'}),
}
class RapidAdmin(VersionAdmin):
form = RapidAdminForm
filter_horizontal = (
"abnormality",
"region",
#"examination",
)
save_on_top = True
save_as = True
view_on_site = True
inlines = [
RapidImageInline,
RapidAnswersInline,
ExamInline,
]
admin.site.register(Rapid, RapidAdmin)
#tagulous.admin.register(Rapid.condition)
Executable
+5
View File
@@ -0,0 +1,5 @@
from django.apps import AppConfig
class RapidsConfig(AppConfig):
name = 'rapids'
+13
View File
@@ -0,0 +1,13 @@
from django.core.exceptions import PermissionDenied
from .models import Rapid
def user_is_author_or_rapid_checker(function):
def wrap(request, *args, **kwargs):
rapid = Rapid.objects.get(pk=kwargs['pk'])
if request.user in rapid.author.all() or request.user.groups.filter(name='rapid_checker').exists():
return function(request, *args, **kwargs)
else:
raise PermissionDenied
wrap.__doc__ = function.__doc__
wrap.__name__ = function.__name__
return wrap
+10
View File
@@ -0,0 +1,10 @@
import django_filters
from .models import Rapid
class RapidFilter(django_filters.FilterSet):
class Meta:
model = Rapid
fields = ("normal", "abnormality", "region", "examination",
"laterality", "site", "created_date", "author")
Executable
+169
View File
@@ -0,0 +1,169 @@
from django.forms import (
Form,
ModelForm,
ModelMultipleChoiceField,
ModelChoiceField,
ChoiceField,
CharField,
)
from django.forms import inlineformset_factory
from rapids.models import (
Abnormality,
Examination,
Note,
Rapid,
RapidCreationDefault,
RapidImage,
Region,
Answer,
CidUserAnswer,
Exam,
)
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.forms.widgets import RadioSelect, TextInput, Textarea
class RapidAnswerForm(ModelForm):
class Meta:
model = CidUserAnswer
fields = ("answer",)
class MarkRapidQuestionForm(Form):
# correct = forms.CharField(required=False)
# half_correct = forms.CharField(required=False)
# incorrect = forms.CharField(required=False)
marked_answers = CharField(required=False)
class ExaminationForm(ModelForm):
class Meta:
model = Examination
fields = ["examination"]
class NoteForm(ModelForm):
class Meta:
model = Note
fields = ["note"]
class RegionForm(ModelForm):
class Meta:
model = Region
fields = ["name"]
class AbnormalityForm(ModelForm):
class Meta:
model = Abnormality
fields = ["name"]
class RapidCreationDefaultForm(ModelForm):
class Meta:
model = RapidCreationDefault
fields = ["site"]
exclude = ["author"]
class RapidForm(ModelForm):
class Media:
# Django also includes a few javascript files necessary
# for the operation of this form element. You need to
# include <script src="/admin/jsi18n"></script>
# in the template.
css = {
"all": ["css/widgets.css"],
}
# Adding this javascript is crucial
js = ["jsi18n.js", "tesseract.min.js"]
def __init__(self, *args, **kwargs):
super(RapidForm, self).__init__(*args, **kwargs)
# self.fields['question'].widget.attrs = {'class': 'question-form', 'rows': 10, 'cols': 100}
# self.fields['feedback'].widget.attrs = {'class': 'feedback-form', 'rows': 10, 'cols': 100}
self.fields["abnormality"] = ModelMultipleChoiceField(
required=False,
queryset=Abnormality.objects.all(),
widget=FilteredSelectMultiple(verbose_name="Abnormality", is_stacked=False),
)
self.fields["region"] = ModelMultipleChoiceField(
required=False,
queryset=Region.objects.all(),
widget=FilteredSelectMultiple(verbose_name="Region", is_stacked=False),
)
self.fields["examination"] = ModelMultipleChoiceField(
queryset=Examination.objects.all(),
widget=FilteredSelectMultiple(verbose_name="Examination", is_stacked=False),
)
self.fields["laterality"] = ChoiceField(
choices=Rapid.LATERALITY_CHOICES, required=True, widget=RadioSelect()
)
class Meta:
model = Rapid
# fields = ['due_back']
# fields = '__all__'
fields = [
"normal",
"abnormality",
"region",
"laterality",
"examination",
"site",
"feedback",
]
# fields = ['question', 'feedback', 'subspecialty', 'references']
widgets = {
# "normal": RadioSelect(
# choices=[(True, 'Yes'),
# (False, 'No')])
}
ImageFormSet = inlineformset_factory(
Rapid,
RapidImage,
fields=["image", "feedback_image"],
exclude=[],
can_delete=True,
extra=4,
max_num=10,
field_classes="testing",
)
AnswerFormSet = inlineformset_factory(
Rapid,
Answer,
fields=["answer", "status"],
widgets={
"answer": Textarea(
attrs={"required": "true", "minlength": 2, "maxlength": 500, "rows": 3}
)
},
exclude=[],
can_delete=True,
extra=1,
max_num=10,
)
AnswerUpdateFormSet = inlineformset_factory(
Rapid,
Answer,
fields=["answer", "status"],
widgets={
"answer": Textarea(
attrs={"required": "true", "minlength": 2, "maxlength": 500, "rows": 3}
)
},
exclude=[],
can_delete=True,
extra=0,
max_num=10,
)
+475
View File
@@ -0,0 +1,475 @@
from django.db import models
from django.utils import timezone
import tagulous
import tagulous.models
from django.core.files.storage import FileSystemStorage
from django.conf import settings
from django.utils.html import format_html
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from django.utils.html import mark_safe
from sortedm2m.fields import SortedManyToManyField
import string
image_storage = FileSystemStorage(
# Physical file location ROOT
location=u"{0}/rapids/".format(settings.MEDIA_ROOT),
# Url for file
base_url=u"{0}rapids/".format(settings.MEDIA_URL),
)
def image_directory_path(instance, filename):
return u"picture/{0}".format(filename)
class Abnormality(models.Model):
name = models.CharField(max_length=200,
unique=True,
help_text="Primary abnormality on the film(s)")
def __str__(self):
return self.name
class Meta:
ordering = ('name', )
class Region(models.Model):
name = models.CharField(
max_length=200,
unique=True,
help_text="Region of the abnormality (not including lateralitiy)",
blank=True)
def __str__(self):
return self.name
class Meta:
ordering = ('name', )
class Examination(models.Model):
examination = models.CharField(max_length=200)
def __str__(self):
return self.examination
class Meta:
ordering = ('examination', )
class Site(models.Model):
site = models.CharField(max_length=200)
initials = models.CharField(max_length=200)
def __str__(self):
return self.site
class Condition(tagulous.models.TagModel):
pass
class Sign(tagulous.models.TagModel):
pass
class Answer(models.Model):
question = models.ForeignKey(
"Rapid", related_name="answers", on_delete=models.CASCADE
)
answer = models.TextField(max_length=500)
answer_compare = models.TextField(max_length=500)
class MarkOptions(models.TextChoices):
UNMARKED = "", _("Unmarked")
INCORRECT = "0", _("Incorrect")
HALF_MARK = "1", _("Half mark")
CORRECT = "2", _("Correct")
status = models.CharField(
max_length=1, choices=MarkOptions.choices, default=MarkOptions.UNMARKED
)
def __str__(self):
return self.answer
def save(self, *args, **kwargs):
self.clean()
return super(Answer, self).save(*args, **kwargs)
def clean(self):
if self.answer:
self.answer = self.answer.strip()
s = self.answer.lower()
s = s.translate(str.maketrans('', '', string.punctuation))
self.answer_compare = s
class Rapid(models.Model):
#author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
#image = models.ImageField()
question = models.TextField(null=True, blank=True)
feedback = models.TextField(null=True, blank=True)
normal = models.BooleanField(default=False, help_text="Tick if true")
NONE = "NONE"
LEFT = "LEFT"
RIGHT = "RIGHT"
BILATERAL = "BILAT"
LATERALITY_CHOICES = (
(NONE, "None"),
(LEFT, "Left"),
(RIGHT, "Right"),
(BILATERAL, "Bilateral"),
)
abnormality = models.ManyToManyField(
Abnormality,
blank=True,
help_text="The abnormality (laterality and region independent). Used for categorisation but does not affect the answer",
)
region = models.ManyToManyField(
Region,
blank=True,
help_text="Region of the abnormality (laterality independent)")
examination = models.ManyToManyField(
Examination, help_text="Name of the (primary) examination")
laterality = models.CharField(
max_length=20,
choices=LATERALITY_CHOICES,
default=NONE,
help_text="Applies to the answer, not the examination")
condition = models.CharField(max_length=255, blank=True)
#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 = models.CharField(max_length=255, blank=True)
#sign = tagulous.models.TagField(
# to=Sign, blank=True, help_text='Radiological signs in the question')
DEFAULT_SITE_ID = 1
site = models.ManyToManyField(
Site,
blank=True,
default=DEFAULT_SITE_ID,
help_text=
"If we know the source of the image")
verified = models.BooleanField(default=False)
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
author = models.ManyToManyField(settings.AUTH_USER_MODEL,
blank=True,
help_text='Author of question',
related_name="rapid_authored_questions")
scrapped = models.BooleanField(
default=False,
help_text='Question has been scrapped and will not be shown')
open_access = models.BooleanField(
help_text="If an question should be freely available to browse", default=True
)
def get_absolute_url(self):
return reverse('rapids:rapid_detail', kwargs={'pk': self.pk})
def get_authors(self):
"""Returns a comma seperated text list of authors"""
authors = ", ".join([i.username for i in self.author.all()])
return authors
def get_regions(self):
"""Returns a comma seperated text list of regions"""
regions = ", ".join([i.name for i in self.region.all()])
return regions
def get_abnormalities(self):
"""Returns a comma seperated text list of regions"""
abnormalities = ", ".join([i.name for i in self.abnormality.all()])
return abnormalities
def get_examinations(self):
"""Returns a comma seperated text list of regions"""
examinations = ", ".join([i.examination for i in self.examination.all()])
return examinations
def __str__(self):
n = "Normal"
if not self.normal:
#n = self.answers.first()
n = "{} / {}".format(self.abnormality.first(), self.region.first())
exams = self.get_examinations()
lat = ""
if self.laterality != "NONE":
lat = self.laterality
return "{} / {} {}".format(exams, lat, n)
def GetPrimaryAnswer(self):
if len(self.answers.all()) > 0:
return self.answers.all().first().answer
elif self.normal:
return "Normal"
else:
return "None yet..."
def GetExams(self):
e = self.exams.all().values_list("name", flat=True)
exams = ", ".join(e)
return exams
def GetUnmarkedAnswersString(self):
unmarked_answers = self.GetUnmarkedAnswers()
if not unmarked_answers:
return "No answers to mark"
return format_html(
"<span class='warn'>{} answer unmarked:</span> {}".format(
len(unmarked_answers), ", ".join(unmarked_answers)
)
)
def GetUnmarkedAnswers(self):
# If normal no answers to mark
if self.normal:
return []
user_answers = set(
[i.answer_compare for i in self.cid_user_answers.all() if i.normal == False]
)
unmarked_answers = user_answers - self.GetMarkedAnswers()
return unmarked_answers
def GetUnmarkedAnswerCount(self):
return len(self.GetUnmarkedAnswers())
def GetMarkedAnswers(self):
return set(
[
i.answer_compare
for i in self.answers.all()
if i.status != i.MarkOptions.UNMARKED
]
)
correct_answers = set(
[i.answer.get_compare_string() for i in self.answers.all()]
)
half_mark_answers = set(
[i.answer.get_compare_string() for i in self.half_mark_answers.all()]
)
incorrect_answers = set(
[i.answer.get_compare_string() for i in self.incorrect_answers.all()]
)
marked_answers = correct_answers | half_mark_answers | incorrect_answers
return marked_answers
def GetImages(self, feedback=False):
qs = self.images.all()
if not feedback:
images = [i.image for i in qs if not i.feedback_image]
else:
images = [i.image for i in qs]
return images
def GetImageUrls(self):
return ",".join(["http://penracourses.org.uk{}".format(i.url) for i in self.GetImages()])
#def GetNonFeedbackQuestionImages(self):
#return self.GetImages()
class RapidImage(models.Model):
rapid = models.ForeignKey(Rapid,
related_name="images",
on_delete=models.CASCADE)
image = models.ImageField(upload_to=image_directory_path, storage=image_storage)
feedback_image = models.BooleanField(default=False)
def image_tag(self):
if self.image:
return mark_safe(
'<img src="{}" class="admin-rapid-image" /><span class="admin-rapid-image-info">Click and hold to zoom<span>'
.format(self.image.url))
else:
return ""
image_tag.short_description = 'Image'
class Note(models.Model):
rapid = models.ForeignKey(Rapid,
related_name="rapid_notes",
on_delete=models.CASCADE,
null=True)
#author = models.ForeignKey(User,
author = models.ForeignKey( settings.AUTH_USER_MODEL,
related_name="rapid_notes",
on_delete=models.CASCADE)
note = models.TextField()
created_on = models.DateTimeField(auto_now_add=True)
def get_absolute_url(self):
self.pk = self.rapid_id
return reverse('rapids:rapid_detail', kwargs={'pk': self.pk})
def __str__(self):
return "{} [{}] {}".format(self.rapid, self.author, self.created_on)
class RapidCreationDefault(models.Model):
#author = models.OneToOneField(User,
author = models.OneToOneField(
settings.AUTH_USER_MODEL,
related_name="rapid_default",
on_delete=models.CASCADE)
site = models.ManyToManyField(
Site,
blank=True,
default=1,
help_text="Default site to use when creating a new rapid")
def get_absolute_url(self):
return reverse('rapids:rapid_create')
class Exam(models.Model):
name = models.CharField(max_length=200)
exam_questions = SortedManyToManyField(
Rapid, related_name="exams", blank="true"
)
active = models.BooleanField(
help_text="If an exam should be available", default=True
)
publish_results = models.BooleanField(
help_text="If an exams results should be available", default=True
)
recreate_json = models.BooleanField(
help_text="If the json cache needs updating", default=False
)
time_limit = models.IntegerField(
help_text="Exam time limit (in seconds). Default is 2100 secondse (35 minutes)", default=2100
)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("rapids:exam_overview", kwargs={"pk": self.pk})
def get_exam_name(self):
return self.name
def get_json_url(self):
return reverse("rapids:exam_json", args=(self.pk,))
def get_question_index(self, question):
return list(self.exam_questions.all()).index(question)
class CidUserAnswer(models.Model):
"""User answers by candidate"""
question = models.ForeignKey(
Rapid, related_name="cid_user_answers", on_delete=models.CASCADE
)
# For rapids the answer can be normal in which case the below field is true
normal = models.BooleanField(default=False)
answer = models.TextField(max_length=500, blank=True)
answer_compare = models.TextField(max_length=500, blank=True)
cid = models.BigIntegerField(blank=True, null=True, help_text="Candidate ID (limitied by BigIntegerField size)")
# Each user answer is associated with a particular exam
exam = models.ForeignKey(
Exam, related_name="cid_user_answers", on_delete=models.CASCADE, null=True
)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
try:
exam = self.exam
except (Exam.DoesNotExist, KeyError) as e:
exam = "None"
return "{}/{}/{}: {}".format(
exam, self.cid, self.question.GetPrimaryAnswer(), self.answer
)
def save(self, *args, **kwargs):
self.clean()
return super(CidUserAnswer, self).save(*args, **kwargs)
def clean(self):
if self.answer:
self.answer = self.answer.strip()
s = self.answer.lower()
s = s.translate(str.maketrans('', '', string.punctuation))
self.answer_compare = s
# def get_compare_string(self):
# # strip here should be unneccasry (providing clean is now working)
# s = self.answer.lower().strip()
# s = s.translate(str.maketrans('', '', string.punctuation))
# return s
def get_answer_score(self):
q = self.question
# First step we check that the normal/abnormal states match
if q.normal != self.normal:
# If they don't match the answer is wrong (score is 0)
return 0
# If both are normal full marks
elif q.normal and self.normal:
return 2
# Then compare answer strings (as per anatomy questions)
ans = self.answer_compare
if (
q.answers.filter(
answer_compare__iexact=ans, status=Answer.MarkOptions.CORRECT
).first()
is not None
):
mark = 2
elif (
q.answers.filter(
answer_compare__iexact=ans, status=Answer.MarkOptions.HALF_MARK
).first()
is not None
):
mark = 1
elif q.answers.filter(
answer_compare__iexact=ans, status=Answer.MarkOptions.INCORRECT
).first():
mark = 0
else:
mark = "unmarked"
return mark
+48
View File
@@ -0,0 +1,48 @@
import django_tables2 as tables
from django_tables2.utils import A
from .models import Rapid
from django.utils.html import format_html
from easy_thumbnails.files import get_thumbnailer
from easy_thumbnails.exceptions import InvalidImageFormatError
class ImageColumn(tables.Column):
def render(self, value):
obj = value.first()
if obj is None:
return format_html('<span>No image<span>')
image_object = obj.image
try:
thumbnailer = get_thumbnailer(image_object)
return format_html('<img src="/media/{}" />', thumbnailer["exam-list"])
except InvalidImageFormatError:
return format_html('<span title="{}">Invalid image url<span>', image_object)
class RapidTable(tables.Table):
edit = tables.LinkColumn('rapids:rapid_update',
text='Edit',
args=[A('pk')],
orderable=False)
view = tables.LinkColumn('rapids:rapid_detail',
text='View',
args=[A('pk')],
orderable=False)
clone = tables.LinkColumn('rapids:rapid_clone',
text='Clone',
args=[A('pk')],
orderable=False)
images = ImageColumn("images", orderable=False)
class Meta:
model = Rapid
template_name = "django_tables2/bootstrap4.html"
fields = ("normal", "abnormality", "region", "examination",
"laterality", "site", "created_date", "author")
sequence = ("view", "images")
+5
View File
@@ -0,0 +1,5 @@
{% extends "admin/base_site.html" %}
{% load static %}
{% block extrahead %}
<link rel="stylesheet" href="{% static 'css/admin.css' %}">
{% endblock %}
+8
View File
@@ -0,0 +1,8 @@
{% extends 'rapids/base.html' %}
{% block content %}
{% for author in authors %}
<p>Author: <a href="{% url 'rapids:author_detail' pk=author.pk %}">{{author.username}}</a>, </p>
{% endfor %}
{% endblock %}
+27
View File
@@ -0,0 +1,27 @@
{% extends 'base.html' %}
{% block title %}
Rapids
{% endblock %}
{% block css %}
{% endblock %}
{% block js %}
{% endblock %}
{% block content %}
{% endblock %}
{% block navigation %}
Rapids:
{% if request.user.is_authenticated %}
<a href="{% url 'rapids:exam_list' %}">Exams</a> /
<a href="{% url 'rapids:rapid_view' %}">Questions</a> /
<a href="{% url 'rapids:rapid_create' %}" title="Create a new question">Create Question</a>
{% endif %}
{% comment %} </br>
Questions by:
<span id="authors-link"><a href="{% url 'rapids:author_list' %}">author</a></span> {% endcomment %}
{% endblock %}
+13
View File
@@ -0,0 +1,13 @@
{% extends 'rapids/base.html' %}
{% block content %}
{{category}}:
<ul>
{% for rapid in rapids %}
<li><a href="{% url 'rapids:rapid_detail' pk=rapid.pk %}">{{rapid}}
[{{rapid.get_authors}}]</a></li>
{% endfor %}
</ul>
{% endblock %}
+5
View File
@@ -0,0 +1,5 @@
<h2>Add new {{name}}</h2>
<form method="POST" class="post-form">{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Save</button>
</form>
+79
View File
@@ -0,0 +1,79 @@
{% extends 'rapids/base.html' %}
{% block content %}
<script>
$(document).ready(function () {
$("#flagged-button").click(function () {
$.ajax({
url: "{% url 'rapids:flag_question' %}",
data: {
'pk': {{ question.pk }}
},
dataType: 'json',
success: function (data) {
console.log(data.flagged);
if(data.flagged) {
$("#flagged-button").text("Flagged");
$("#flagged-button").attr("class", "flagged");
} else {
$("#flagged-button").text("Not Flagged");
$("#flagged-button").attr("class", "not-flagged");
}
}
});
});
$("#id_answer").keyup(function (event) {
if(event.keyCode == 13) {
$(".btn-default").click();
}
});
});
</script>
<h1>Question {{question_details.current}} of {{question_details.total}}</h1>
<p>{{ question.question_type.first }}</p>
<table>
<tr>
<td>
<div id="dicom-image" class="dicom-image" data-url="{{ question.image.url}}" data-annotations='{{question.image_annotations}}'>
</div>
<form method="POST" class="post-form">{% csrf_token %}
{{ form.as_p }}
{% if question_details.current > 1 %}
<button type="submit" name="previous" class="btn btn-previous">Previous</button>
{% endif %}
{% if question_details.current >= question_details.total %}
<button type="submit" name="save" class="save btn btn-default">Save</button>
{% else %}
<button type="submit" name="next" class="save btn btn-default">Next</button>
{% endif %}
<button id=flagged-button type="button"
class={% if answer.flagged == True %}flagged{% else %}not-flagged{% endif %}>{% if answer.flagged == True %}Flagged{% else %}Not
Flagged{% endif %}</button>
</form>
</td>
<td>
<div class=exam-navigation />
<ul id=question-list>
{% for question in questions %}
<li><a class={% if question.pk in answered_questions %}answered{% else %}unanswered{% endif %}
href="{% url 'rapids:exam_take' pk=exam.pk sk=forloop.counter0 %}">{{ forloop.counter }}{% if question.pk in flagged_questions %}!{% endif %}</a>
</li>
{% endfor %}
</ul>
</div>
</td>
</tr>
</table>
{% endblock %}
+30
View File
@@ -0,0 +1,30 @@
{% extends 'rapids/base.html' %}
{% block content %}
<h1>Examinations</h1>
<div class="rapids">
Active exams:<br/>
<ul>
{% for exam in exams %}
{% if exam.active %}
<li>
<a href="{% url 'rapids:exam_overview' pk=exam.pk %}">{{exam.name}}</a> <a href="{% url 'rapids:mark_overview' pk=exam.pk %}">(mark)</a> <a href="{% url 'rapids:exam_scores_cid' pk=exam.pk %}">(scores)</a> [Results are {% if not exam.publish_results %} not {% endif %}published]
</li>
{% endif %}
{% endfor %}
</ul>
Inactive exams:<br/>
<ul>
{% for exam in exams %}
{% if not exam.active %}
<li>
<a href="{% url 'rapids:exam_overview' pk=exam.pk %}">{{exam.name}}</a> <a href="{% url 'rapids:mark_overview' pk=exam.pk %}">(mark)</a> <a href="{% url 'rapids:exam_scores_cid' pk=exam.pk %}">(scores)</a> [Results are {% if not exam.publish_results %} not {% endif %}published]
</li>
{% endif %}
{% endfor %}
</ul>
</div>
{% endblock %}
+141
View File
@@ -0,0 +1,141 @@
{% extends 'rapids/exams.html' %}
{% block content %}
{% load thumbnail %}
<div class="rapids">
<a href="{% url 'admin:rapids_exam_change' exam.id %}" title="Edit the Exam using the admin interface">Admin Edit</a>
<h1>Exam: {{ exam.name }}</h1>
This exam has {{question_number}} questions. Time limit: {{exam.time_limit}} seconds.
<div class="parent-help" title="Click to enable / disable the exam">
Exam active: <input type="checkbox" id="exam-active-switch" {% if exam.active %}checked{% endif %}> <span class="help-text">[When checked the exam will be available to take in the test system]</span>
</div>
<div class="parent-help" title="Click to enable / disable the exam results">
Publish results: <input type="checkbox" id="exam-publish-results-switch" {% if exam.publish_results %}checked{% endif %}> <span class="help-text">[When checked the exam results will be available on this site]</span>
</div>
<p><a href="{% url 'rapids:mark_overview' pk=exam.pk %}"><button>Mark exam</button></a></p>
<ol id="full-question-list">
{% for question in questions.all %}
<li>
{% for image in question.GetImages %}
<img src="{{ image|thumbnail_url:'exam-list' }}" alt="thumbail" />
{% endfor %}
{% if not question.normal %}
<b>Abnormality:</b> {{ question.get_abnormalities }} <b>Region:</b> {{ question.get_regions }}
<br />
{{ question.GetPrimaryAnswer }}
{% 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>
</li>
{% endfor %}
</ol>
<a href="{% url 'rapids:exam_json' pk=exam.pk %}">JSON</a>
<a href="{% url 'rapids:exam_json_recreate' pk=exam.pk %}">Refresh JSON cache</a>
<button id='button-open-access'>Make questions open access</button>
<button id='button-closed-access'>Make questions closed access</button>
</div>
<script type="text/javascript">
$(document).ready(function () {
// send request to change the is_private state on customSwitches toggle
$("#exam-active-switch").on("change", function () {
$.ajax({
url: "{% url 'rapids:exam_toggle_active' pk=exam.pk %}",
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
active: this.checked // true if checked else false
},
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('Exam state changed.')
}
// show some message according to the response.
// For eg. A message box showing that the status has been changed
})
.always(function () {
console.log('[Done]');
})
})
$("#exam-publish-results-switch").on("change", function () {
$.ajax({
url: "{% url 'rapids:exam_toggle_results_published' pk=exam.pk %}",
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
publish_results: this.checked // true if checked else false
},
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('Publish results state changed.')
}
// show some message according to the response.
// For eg. A message box showing that the status has been changed
})
.always(function () {
console.log('[Done]');
})
})
$("#button-open-access").click(function () {
$.ajax({
url: "{% url 'rapids:exam_json_edit' pk=exam.pk %}",
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
set_open_access: true,
},
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('Question access state changed.')
}
})
.always(function () {
console.log('[Done]');
})
})
$("#button-closed-access").click(function () {
$.ajax({
url: "{% url 'rapids:exam_json_edit' pk=exam.pk %}",
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
set_open_access: false,
},
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('Question access state changed.')
}
})
.always(function () {
console.log('[Done]');
})
})
});
</script>
{% endblock %}
+65
View File
@@ -0,0 +1,65 @@
{% extends 'rapids/exams.html' %}
{% block content %}
<div class="rapids">
<h2>{{ exam.name }}</h2>
{% if unmarked %}
The following questions need marking
{% for exam_index in unmarked %}
<a href="{% url 'rapids:mark' exam.pk exam_index %}">{{ exam_index|add:1 }}</a>
{% endfor %}
{% endif %}
</div>
<div id="stats-block">
<h3>Stats</h3>
Candidates: {{cids|length}}<br />
Max score: {{max_score}}<br/>
Mean: {{mean}}, Median {{median}}, Mode {{mode}}
<div id="stats-plot">{{plot|safe}}</div>
</div>
<div>
<table>
<tr>
<th>Candidate ID</th>
<th>Score</th>
</tr>
{% for user, value in user_answers_marks.items %}
<tr>
<td><a href="{% url 'cid_scores' user %}">{{user}}</a></td>
<td>{{user_scores|get_item:user}}</td>
</tr>
{% endfor %}
</table>
</div>
<div>
<h3>Answers as a table</h3>
<table>
<tr>
<th>Candidate</th>
{% for cid in cids %}
<th>{{cid}}</th>
{% endfor %}
</tr>
{% for question in questions %}
<tr>
<td>Question {{forloop.counter}}</td>
{% for ans, score in by_question|get_item:question %}
<td class="user-answer-score-{{score}}" title="answer score: {{score}}">{{ans}}</td>
{% endfor %}
</tr>
{% endfor %}
<tr>
<td>Score:</td>
{% for score in user_scores_list %}
<td>{{score}}</td>
{% endfor %}
<tr>
</table>
</div>
{% endblock %}
@@ -0,0 +1,18 @@
{% extends 'rapids/base.html' %}
{% block content %}
<div class="rapids">
<h2>Exam: {{ exam.name }}</h2>
<h3>Candidate: {{ cid }}</h3>
Answers:
<ul>{% for ans, score, correct_answer in answers_and_marks %}
<li class="user-answer-li">Question {{forloop.counter}} - Correct answer: <span class="correct-answer">{{ correct_answer }}</span></li>
<span class="user-answer-score user-answer-score-{{score}}"><pre>{{ans}}</pre> ({{score}})</span>
{% endfor %}
</ul>
<br /> Total mark: {{ total_score }} / {{max_score}}
<div>
<a href="{% url 'cid_scores' cid %}">Other exams</a>
</div>
</div>
{% endblock %}
+7
View File
@@ -0,0 +1,7 @@
{% extends 'rapids/base.html' %}
{% block navigation %}
{{block.super}}
<br/>
Exams: {{exam.name}}-> <a href="{% url 'rapids:exam_overview' pk=exam.pk %}">Overview</a> / <a href="{% url 'rapids:mark_overview' pk=exam.pk %}">Mark</a> / <a href="{% url 'rapids:exam_scores_cid' pk=exam.pk %}">Scores</a>
{% endblock %}
+11
View File
@@ -0,0 +1,11 @@
{% extends 'rapids/base.html' %}
{% block content %}
{% for exam in exams %}
<div class="rapids">
<h1><a href="{% url 'rapids:exam_overview' pk=exam.pk %}">Exam: {{ exam.name }} </a></h1>
{% if request.user.is_staff %}<a href="{% url 'rapids:mark' pk=exam.pk sk=0 %}">Mark answers</a>{% endif %}
{% if request.user.is_staff %}<a href="{% url 'rapids:exam_scores_cid' pk=exam.pk %}">Scores</a>{% endif %}
</div>
{% endfor %}
{% endblock %}
+23
View File
@@ -0,0 +1,23 @@
{% extends 'rapids/base.html' %}
{% block content %}
My Questions:
<ul>
{% for rapid in user_rapids %}
<li{% if rapid.scrapped %} class='rapid-scrapped' {% endif %}><a
href="{% url 'rapids:rapid_detail' pk=rapid.pk %}">{{rapid}}
[{{rapid.get_authors}}]</a></li>
{% endfor %}
</ul>
Other Questions:
<ul>
{% for rapid in other_rapids %}
<li{% if rapid.scrapped %} class='rapid-scrapped' {% endif %}><a
href="{% url 'rapids:rapid_detail' pk=rapid.pk %}">{{rapid}} [{{rapid.get_authors}}]</a></li>
{% endfor %}
</ul>
{% endblock %}
+67
View File
@@ -0,0 +1,67 @@
{% extends 'rapids/exams.html' %}
{% block content %}
<h2>Marking question {{question_details.current}} of {{question_details.total}}</h2>
<a href="{% url 'rapids:rapid_update' question.id %}" title="Edit the Question">Edit</a> <a
href="{% url 'admin:rapids_rapid_change' question.id %}" title="Edit the Question using the admin interface">Admin
Edit</a>
{% if question.normal %}
<h3>This question is normal</h3>
Answers will be automatically marked.
{% else %}
<h3>This question is abnormal</h3>
Answers marked as normal will be automatically marked.
{% endif %}
<div id="single-dicom-viewer" class="marking-dicom" data-images="{{question.GetImageUrls}}"
data-annotations='{{question.image_annotations}}'>
</div>
<div class="marking">
<form method="POST" class="post-form">{% csrf_token %}
{% if not question.normal %}
Click each answer to toggle through marks awarded (as per colour)
<div class="marking-list">
Unmarked:
<ul id="new-answer-list" class="answer-list">
{% for answer in user_answers %}
<li>
<pre><span class="answer not-marked">{{ answer }}</span></pre>
</li>
{% endfor %}
</ul>
Marked:
<ul id="marked-answer-list" class="answer-list">
{% for answer in correct_answers %}
<li>
<pre><span class="answer correct">{{ answer }}</span></pre>
</li>
{% endfor %}
{% for answer in half_mark_answers %}
<li>
<pre><span class="answer half-correct">{{ answer }}</span></pre>
</li>
{% endfor %}
{% for answer in incorrect_answers %}
<li>
<pre><span class="answer incorrect">{{ answer }}</span></pre>
</li>
{% endfor %}
</ul>
<div class="answer-list key">Key: <span class="correct">2 Marks</span>, <span class="half-correct">1
Mark</span>, <span class="incorrect">0 Marks</span></div>
</div>
{% endif %}
{% if question_details.current > 1 %}
<button type="submit" name="previous" class="save btn btn-default">Previous</button>
{% endif %}
<button type="submit" name="save" class="save btn btn-default">Save</button>
{% if question_details.current >= question_details.total %}
{% else %}
<button type="submit" name="next" class="save btn btn-default">Next</button>
<button type="submit" name="skip" class="save btn btn-default">Skip</button>
{% endif %}
<span class=hide>
{{ form.as_p }}
</span>
</form>
</div>
{% endblock %}
+20
View File
@@ -0,0 +1,20 @@
{% extends 'rapids/exams.html' %}
{% block content %}
<div class="rapids">
<h2>Marking exam: {{ exam.name }}</h2>
You can start marking from a particular question by clicking on it below.
<div>
<button class="show-all-button">Show all</button><button class="show-unmarked-button">Show unmarked</button>
</div>
<div id="stark-marking-button"><a href="{% url 'rapids:mark' pk=exam.pk sk=0 %}"><button>Click here to start marking</button></a></div>
<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>
{% endfor %}
</ul>
</div>
{% endblock %}
+12
View File
@@ -0,0 +1,12 @@
{% extends "rapids/base.html" %}
{% block content %}
<h2>Add Note</h2>
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit">
</form>
{% endblock %}
+26
View File
@@ -0,0 +1,26 @@
{% extends 'rapids/base.html' %}
{% block content %}
{% if exam %}
<div>
{% if previous > -1 %}
<a href="{% url 'rapids:exam_question_detail' exam.id previous %}">Previous question</a>
{% endif %}
This question is part of exam: {{exam.name}} [{{pos}}/{{exam_length}}]
{% if next %}
<a href="{% url 'rapids:exam_question_detail' exam.id next %}">Next question</a>
{% endif %}
</div>
{% endif %}
<a href="{% url 'rapids:rapid_update' pk=question.pk %}" title="Edit the Rapid">Edit</a>
<a href="{% url 'rapids:rapid_clone' pk=question.pk %}" title="Clone the Rapid (duplicate everything but the images)">Clone</a>
<a href="{% url 'rapids:rapid_add_note' pk=question.pk %}"> Add Note</a>
{% if request.user.is_superuser %}
<a href="{% url 'admin:rapids_rapid_change' question.id %}" title="Edit the Rapid using the admin interface">Admin Edit</a>
{% endif %}
{% include 'rapids/rapid_display_block.html' %}
{% endblock %}
+38
View File
@@ -0,0 +1,38 @@
<div class="rapid {% if question.scrapped %}rapid-scrapped{% endif %}">
<div class="date">
{{ question.created_date }}
</div>
<p class="pre-whitespace"><b>Rapid:</b> {{ question }}</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>Abnormality:</b> {{ question.get_abnormalities }}</p>
<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="http://penracourses.org.uk{{ image.image.url}}"></div>
</span>
{% endfor %}
</div>
<p class="pre-whitespace"><b>Feedback:</b> {{ question.feedback }}</p>
<p><b>Author(s):</b> {% for author in question.author.all %} <a
href="{% url 'rapids:author_detail' pk=author.pk %}">{{author}}</a>, {% endfor %}</p>
<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"><b>Answers:</b> {{ question.GetMarkedAnswers }}</p>
</p>
</div>
<div>
Notes:
<ul>
{% for note in question.rapid_notes.all %}
<li>
{{ note.created_on }} by {{ note.author }}: {{ note.note }}
</li>
{% endfor %}
</ul>
</div>
+292
View File
@@ -0,0 +1,292 @@
{% extends "rapids/base.html" %}
{% load static from static %}
{% block js %}
<!--<script type="text/javascript" src="/admin/jsi18n/"></script>-->
<script type="text/javascript">
function showEditPopup(url) {
var win = window.open(url, "Edit",
'height=500,width=800,resizable=yes,scrollbars=yes');
return false;
}
function showAddPopup(triggeringLink) {
var name = triggeringLink.id.replace(/^add_/, '');
href = triggeringLink.href;
var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
win.focus();
return false;
}
function closePopup(win, newID, newRepr, id) {
console.log(id)
$(id + "_to").append('<option value=' + newID + ' title=' + newRepr + ' >' + newRepr + '</option>')
win.close();
}
document.addEventListener('drop', function (e) { e.preventDefault(); }, false);
function add_input_form() {
var form_idx = $('#id_images-TOTAL_FORMS').val();
$('#image_form_set').append($('#empty_form').html().replace(/__prefix__/g, form_idx));
$('#id_images-TOTAL_FORMS').val(parseInt(form_idx) + 1);
}
function add_answers_input_form() {
var form_idx = $('#id_answers-TOTAL_FORMS').val();
$('#answer_form_set').append($('#answer_empty_form').html().replace(/__prefix__/g, form_idx));
$('#id_answers-TOTAL_FORMS').val(parseInt(form_idx) + 1);
}
$(document).ready(function () {
$("#add_abnormality").appendTo($("label[for='id_abnormality']"));
$("#add_examination").appendTo($("label[for='id_examination']"));
$("#add_region").appendTo($("label[for='id_region']"));
dropContainer = document.getElementById("drop-container");
dropContainer.ondragover = function (evt) {
evt.preventDefault();
evt.stopPropagation();
};
dropContainer.ondragenter = function (evt) {
console.log("ENTER")
console.log(evt)
$(evt.target).addClass("drop-target-active")
evt.preventDefault();
evt.stopPropagation();
};
dropContainer.ondragleave = function (evt) {
console.log("ENTER")
console.log(evt)
$(evt.target).removeClass("drop-target-active")
evt.preventDefault();
evt.stopPropagation();
};
dropContainer.ondrop = function (evt) {
console.log("SHIT", evt);
$(evt.target).removeClass("drop-target-active");
// Get all input elements
inputs = $("#rapid-form input[type=file][id^=id_images-]");
//fileInput = document.getElementById("id_images-0-image");
// Make sure we have enough input targets
input_diff = (evt.dataTransfer.files.length - inputs.length)
console.log("diff", input_diff)
if(input_diff > 0) {
for(let j = 0; j < input_diff; j++) {
add_input_form()
}
// Need to make sure we include the new ones...
inputs = $("#rapid-form input[type=file][id^=id_images-]");
}
// Loop through each dropped file and try to assign to an
// input element
[...evt.dataTransfer.files].forEach((f) => {
let dT = new DataTransfer();
dT.clearData();
dT.items.add(f);
for(let i = 0; i < inputs.length; i++) {
el = inputs.get(i)
if(el.files.length == 0) {
el.files = dT.files;
ocr(el)
if(evt.target.id == "feedback-drop-target") {
arr = el.name.split("-");
arr.splice(2, 1, "feedback_image");
feedback_el_name = arr.join("-");
$(`[name=${feedback_el_name}]`).prop("checked", true);
}
return false
}
}
})
// // If you want to use some of the dropped files
// const dT = new DataTransfer();
// dT.items.add(evt.dataTransfer.files[0]);
// dT.items.add(evt.dataTransfer.files[3]);
// fileInput.files = dT.files;
evt.preventDefault();
evt.stopPropagation();
updateFileList();
};
$('#add_more_images').click(() => { add_input_form() });
$('#add_more_answers').click(() => { add_answers_input_form() });
function updateFileList() {
$("#drop-filenames").empty()
$("#image_form_set input[type=file]").each((n, el) => {
console.log(el);
if(el.files.length > 0) {
extra_class = " image-ident-loading";
if($(el).hasClass("image-ident-warning")) {
extra_class = " image-ident-warning"
} else if($(el).hasClass("image-ident-ok")) {
extra_class = " image-ident-ok"
}
$("#drop-filenames").append(
`<span>${n}: ${el.files[0].name}</span><img class='uploading${extra_class}' data-input-id='${el.id}' src=${URL.createObjectURL(el.files[0])}>`
)
}
})
}
$("input[type=file]").on('change', function () {
$(this).removeClass("image-ident-warning");
$(this).removeClass("image-ident-ok");
updateFileList();
console.log("input change1");
console.log("input change", this);
ocr(this);
});
function ifNormal(){
if (document.getElementById("id_normal").checked){
$("#answer_form_set textarea").attr("required", false)
} else {
$("#answer_form_set textarea").attr("required", true)
}
}
$("#id_normal").change(() => ifNormal());
ifNormal();
});
function ocr(input) {
// Tesseract.recognize(f, "eng",{ logger: m => console.log(m) }
// ).then(({ data: { text } }) => {
// console.log(text);
// l = text.toLowerCase();
// if (l.includes("accesion") || l.includes("number") || l.search(/(REF|RK9|RH8|RA9)\d+/g)) {
// console.log("SHIT", this);
// }
// })
console.log('{% static "worker.min.js" %}');
console.log('{{ STATIC_URL }}/tesseract-core.wasm.js %}');
const worker = Tesseract.createWorker({
workerPath: '{% static "worker.min.js" %}',
langPath: '{% static "" %}',
//langPath: '{{ STATIC_URL }}',
corePath: '{% static "tesseract-core.wasm.js" %}',
});
const url = URL.createObjectURL(input.files[0]);
let img = new Image;
img.src = url;
img.onload = function () {
width = img.width;
// const rectangle = { left: 0, top: 0, width: width, height: 10 }
(async () => {
await worker.load();
await worker.loadLanguage('eng');
await worker.initialize('eng');
// const { data: { text } } = await worker.recognize(input.files[0], { rectangle });
const { data: { text } } = await worker.recognize(input.files[0]);
//console.log(text);
l = text.toLowerCase();
$(`img[data-input-id='${input.id}'`).removeClass("image-ident-loading");
console.log(l);
if(l.includes("accesion") || l.includes("number") || l.search(/(ref|rk9|rh8|ra9|rbz)\d+/g) > -1) {
console.log("SHIT", input);
$(input).addClass("image-ident-warning");
$(`img[data-input-id='${input.id}'`).addClass("image-ident-warning");
} else {
$(input).addClass("image-ident-ok");
}
await worker.terminate();
})();
}
}
</script>
{{ form.media }}
{% endblock %}
{% block content %}
<h2>Submit Rapid</h2>
<a href="{% url 'rapids:rapid_create_defaults' %}">Edit defaults</a>
<form action="" method="post" enctype="multipart/form-data" id="rapid-form">
{% csrf_token %}
<a href="/rapids/abnormality/create" id="add_abnormality" class="add-popup"
onclick="return showAddPopup(this);"><img src="{% static '/img/icon-addlink.svg' %}"></a>
<a href="/rapids/examination/create" id="add_examination" class="add-popup"
onclick="return showAddPopup(this);"><img src="{% static '/img/icon-addlink.svg' %}"></a>
<a href="/rapids/region/create" id="add_region" class="add-popup" onclick="return showAddPopup(this);"><img
src="{% static '/img/icon-addlink.svg' %}"></a>
<table>
{{ form.as_table }}
</table>
<h3>Answers:</h3>
<input type="button" value="Add More Answers" id="add_more_answers">
<div id="answer_form_set">
{% for form in answer_formset %}
<ul class="no-error answer-formset">
{{form.non_field_errors}}
{{form.errors}}
{{ form.as_ul }}
</ul>
{% endfor %}
</div>
{{ answer_formset.management_form }}
<h3>Images:</h3>
<input type="button" value="Add More Images" id="add_more_images">
<div id="drop-container" class="drop-target">Drop images here (or use the buttons below)<div
id="feedback-drop-target">Feedback image?<br />drop those here</div>
<div id="drop-filenames"></div>
</div>
<div id="image_form_set">
{% for form in image_formset %}
<ul class="no-error image-formset">
{{form.non_field_errors}}
{{form.errors}}
{{ form.as_ul }}
</ul>
{% endfor %}
</div>
{{ image_formset.management_form }}
<input type="submit" class="submit-button" value="Submit" name="submit">
<input type="submit" class="submit-button" value="Submit and Clone" name="submit-clone">
</form>
<div id="empty_form" style="display:none">
<ul class='no_error image-formset'>
{{ image_formset.empty_form.as_ul }}
</ul>
</div>
<div id="answer_empty_form" style="display:none">
<ul class='no_error answer-formset'>
{{ answer_formset.empty_form.as_ul }}
</ul>
</div>
{% endblock %}
+10
View File
@@ -0,0 +1,10 @@
{% extends "rapids/base.html" %}
{% block content %}
<h2>Defaults for creating new rapids</h2>
<form method="POST" class="post-form">{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-primary">Save</button>
</form>
{% endblock %}
+18
View File
@@ -0,0 +1,18 @@
{% extends 'rapids/base.html' %}
{% load render_table from django_tables2 %}
{% block css %}
{% endblock %}
{% block content %}
<div id="view-filter-options">
<h3>Filter Rapids</h3>
<form action="" method="get">
{{ filter.form }}
<input class="btn btn-primary btn-sm mt-1 mb-1" type="submit" />
</form>
</div>
{% render_table table %}
{% endblock %}
Executable
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
Executable
+63
View File
@@ -0,0 +1,63 @@
from django.urls import path, include
from . import views
app_name = "rapids"
urlpatterns = [
# path('', views.question_list, name='question_list'),
path("", views.index, name="index"),
path("author/<int:pk>/", views.author_detail, name="author_detail"),
path("author/", views.author_list, name="author_list"),
path("question/", views.RapidView.as_view(), name="rapid_view"),
#path("unchecked/", views.unchecked_list, name="unchecked_list"),
#path("verified/<int:pk>/", views.verified_detail, name="verified_detail"),
path("question/<int:pk>/", views.rapid_detail, name="rapid_detail"),
path("question/<int:pk>/split", views.rapid_split, name="rapid_split"),
path("question/<int:pk>/clone", views.RapidClone.as_view(), name="rapid_clone"),
#path("verified/", views.verified, name="verified"),
#path("all_questions/", views.all_questions, name="all_questions"),
path("question/<int:pk>/scrap", views.rapid_scrap, name="rapid_scrap"),
path("exam/<int:pk>/<int:sk>/mark", views.mark, name="mark"),
path("exam/<int:pk>/mark", views.mark_overview, name="mark_overview"),
# path("exam/<int:pk>/<int:sk>/", views.exam_take, name="exam_take"),
path("exam/<int:pk>/question/<int:sk>/", views.exam_question_detail, name="exam_question_detail"),
path("exam/<int:pk>/", views.exam_overview, name="exam_overview"),
path("exam/<int:pk>/json_edit", views.exam_json_edit, name="exam_json_edit"),
path("exam/<int:pk>/scores", views.exam_scores_cid,
name="exam_scores_cid"),
path("exam/<int:pk>/scores/<int:sk>/", views.exam_scores_cid_user,
name="exam_scores_cid_user"),
path("exam/<int:pk>/toggle_active", views.exam_toggle_active, name="exam_toggle_active"),
path("exam/<int:pk>/toggle_results_published", views.exam_toggle_results_published, name="exam_toggle_results_published"),
path("exam/submit", views.postExamAnswers, name="exam_answers_submit"),
path("exam/", views.exam_list, name="exam_list"),
path("exam/json/", views.active_exams, name="active_exams"),
path("exam/json/<int:pk>", views.exam_json, name="exam_json"),
path("exam/json/<int:pk>/recreate", views.exam_json_recreate, name="exam_json_recreate"),
path("create/", views.RapidCreate.as_view(), name="rapid_create"),
path("create/defaults",
views.RapidCreationDefaultView.as_view(),
name="rapid_create_defaults"),
path("region/create/", views.create_region, name="create_region"),
path("region/ajax/get_region_id",
views.get_region_id,
name="get_region_id"),
path("abnormality/create/",
views.create_abnormality,
name="create_abnormality"),
path("abnormality/ajax/get_abnormality_id",
views.get_abnormality_id,
name="get_abnormality_id"),
path("examination/create/",
views.create_examination,
name="create_examination"),
path("examination/ajax/get_examination_id",
views.get_examination_id,
name="get_examination_id"),
path(
"question/<int:pk>/update",
views.RapidUpdate.as_view(),
name="rapid_update",
),
path("<int:pk>/add_note", views.AddNote.as_view(), name="rapid_add_note"),
]
Executable
+1126
View File
File diff suppressed because it is too large Load Diff