.
This commit is contained in:
+215
-215
@@ -1,215 +1,215 @@
|
|||||||
from django.forms import (
|
from django.forms import (
|
||||||
Form,
|
Form,
|
||||||
ModelForm,
|
ModelForm,
|
||||||
ModelMultipleChoiceField,
|
ModelMultipleChoiceField,
|
||||||
ModelChoiceField,
|
ModelChoiceField,
|
||||||
ChoiceField,
|
ChoiceField,
|
||||||
CharField,
|
CharField,
|
||||||
)
|
)
|
||||||
from django.forms import inlineformset_factory
|
from django.forms import inlineformset_factory
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Answer,
|
Answer,
|
||||||
CidUserAnswer,
|
CidUserAnswer,
|
||||||
AnatomyQuestion,
|
AnatomyQuestion,
|
||||||
# Examination,
|
# Examination,
|
||||||
BodyPart,
|
BodyPart,
|
||||||
Structure,
|
Structure,
|
||||||
Region,
|
Region,
|
||||||
Modality,
|
Modality,
|
||||||
QuestionType,
|
QuestionType,
|
||||||
Exam,
|
Exam,
|
||||||
)
|
)
|
||||||
from generic.models import Examination
|
from generic.models import Examination
|
||||||
|
|
||||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||||
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
||||||
|
|
||||||
from dal import autocomplete
|
from dal import autocomplete
|
||||||
|
|
||||||
|
|
||||||
class AnatomyAnswerForm(ModelForm):
|
class AnatomyAnswerForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CidUserAnswer
|
model = CidUserAnswer
|
||||||
fields = ("answer",)
|
fields = ("answer",)
|
||||||
|
|
||||||
|
|
||||||
class MarkAnatomyQuestionForm(Form):
|
class MarkAnatomyQuestionForm(Form):
|
||||||
# correct = forms.CharField(required=False)
|
# correct = forms.CharField(required=False)
|
||||||
# half_correct = forms.CharField(required=False)
|
# half_correct = forms.CharField(required=False)
|
||||||
# incorrect = forms.CharField(required=False)
|
# incorrect = forms.CharField(required=False)
|
||||||
marked_answers = CharField(required=False)
|
marked_answers = CharField(required=False)
|
||||||
|
|
||||||
|
|
||||||
class AnatomyQuestionForm(ModelForm):
|
class AnatomyQuestionForm(ModelForm):
|
||||||
|
|
||||||
# exams = ModelMultipleChoiceField(required=False, queryset=Exam.objects.all())
|
# exams = ModelMultipleChoiceField(required=False, queryset=Exam.objects.all())
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
# Django also includes a few javascript files necessary
|
# Django also includes a few javascript files necessary
|
||||||
# for the operation of this form element. You need to
|
# for the operation of this form element. You need to
|
||||||
# include <script src="/admin/jsi18n"></script>
|
# include <script src="/admin/jsi18n"></script>
|
||||||
# in the template.
|
# in the template.
|
||||||
css = {
|
css = {
|
||||||
"all": ["css/widgets.css"],
|
"all": ["css/widgets.css"],
|
||||||
}
|
}
|
||||||
# Adding this javascript is crucial
|
# Adding this javascript is crucial
|
||||||
js = ["jsi18n.js", "tesseract.min.js"]
|
js = ["jsi18n.js", "tesseract.min.js"]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.user = kwargs.pop(
|
self.user = kwargs.pop(
|
||||||
"user"
|
"user"
|
||||||
) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
|
) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
|
||||||
if kwargs.get("instance"):
|
if kwargs.get("instance"):
|
||||||
# We get the 'initial' keyword argument or initialize it
|
# We get the 'initial' keyword argument or initialize it
|
||||||
# as a dict if it didn't exist.
|
# as a dict if it didn't exist.
|
||||||
initial = kwargs.setdefault("initial", {})
|
initial = kwargs.setdefault("initial", {})
|
||||||
# The widget for a ModelMultipleChoiceField expects
|
# The widget for a ModelMultipleChoiceField expects
|
||||||
# a list of primary key for the selected data.
|
# a list of primary key for the selected data.
|
||||||
initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()]
|
initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()]
|
||||||
|
|
||||||
ModelForm.__init__(self, *args, **kwargs)
|
ModelForm.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
super(AnatomyQuestionForm, self).__init__(*args, **kwargs)
|
super(AnatomyQuestionForm, self).__init__(*args, **kwargs)
|
||||||
# self.fields['question'].widget.attrs = {'class': 'question-form', 'rows': 10, 'cols': 100}
|
# 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['feedback'].widget.attrs = {'class': 'feedback-form', 'rows': 10, 'cols': 100}
|
||||||
self.fields["question_type"] = ModelChoiceField(
|
self.fields["question_type"] = ModelChoiceField(
|
||||||
required=True,
|
required=True,
|
||||||
queryset=QuestionType.objects.all(),
|
queryset=QuestionType.objects.all(),
|
||||||
initial=1,
|
initial=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.fields["region"] = ModelChoiceField(
|
self.fields["region"] = ModelChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.fields["modality"] = ModelChoiceField(
|
self.fields["modality"] = ModelChoiceField(
|
||||||
required=True,
|
required=True,
|
||||||
queryset=Modality.objects.all(),
|
queryset=Modality.objects.all(),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.fields["examination"] = ModelChoiceField(
|
self.fields["examination"] = ModelChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=Examination.objects.all(),
|
queryset=Examination.objects.all(),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.fields["body_part"] = ModelChoiceField(
|
self.fields["body_part"] = ModelChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=BodyPart.objects.all(),
|
queryset=BodyPart.objects.all(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.user.groups.filter(name="anatomy_checker").exists():
|
if self.user.groups.filter(name="anatomy_checker").exists():
|
||||||
exam_queryset = Exam.objects.all()
|
exam_queryset = Exam.objects.all()
|
||||||
else:
|
else:
|
||||||
exam_queryset = Exam.objects.filter(
|
exam_queryset = Exam.objects.filter(
|
||||||
author__id=self.user.id
|
author__id=self.user.id
|
||||||
) | Exam.objects.filter(open_access=True)
|
) | Exam.objects.filter(open_access=True)
|
||||||
|
|
||||||
self.fields["exams"] = ModelMultipleChoiceField(
|
self.fields["exams"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=exam_queryset,
|
queryset=exam_queryset,
|
||||||
widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False),
|
widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
# Get the unsaved Pizza instance
|
# Get the unsaved Pizza instance
|
||||||
instance = ModelForm.save(self, False)
|
instance = ModelForm.save(self, False)
|
||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
old_exams = instance.exams.all()
|
old_exams = instance.exams.all()
|
||||||
|
|
||||||
new_exams = self.cleaned_data["exams"]
|
new_exams = self.cleaned_data["exams"]
|
||||||
|
|
||||||
for exam in old_exams:
|
for exam in old_exams:
|
||||||
if exam not in new_exams:
|
if exam not in new_exams:
|
||||||
exam.exam_questions.remove(instance)
|
exam.exam_questions.remove(instance)
|
||||||
|
|
||||||
for exam in new_exams:
|
for exam in new_exams:
|
||||||
exam.exam_questions.add(instance)
|
exam.exam_questions.add(instance)
|
||||||
|
|
||||||
self.save_m2m()
|
self.save_m2m()
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AnatomyQuestion
|
model = AnatomyQuestion
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
"question_type",
|
"question_type",
|
||||||
"image",
|
"image",
|
||||||
"region",
|
"region",
|
||||||
"modality",
|
"modality",
|
||||||
"structure",
|
"structure",
|
||||||
"examination",
|
"examination",
|
||||||
"body_part",
|
"body_part",
|
||||||
"description",
|
"description",
|
||||||
"open_access",
|
"open_access",
|
||||||
]
|
]
|
||||||
|
|
||||||
widgets = {
|
widgets = {
|
||||||
"structure": autocomplete.ModelSelect2(url="anatomy:structure-autocomplete")
|
"structure": autocomplete.ModelSelect2(url="anatomy:structure-autocomplete")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
AnswerFormSet = inlineformset_factory(
|
AnswerFormSet = inlineformset_factory(
|
||||||
AnatomyQuestion,
|
AnatomyQuestion,
|
||||||
Answer,
|
Answer,
|
||||||
fields=["answer", "status"],
|
fields=["answer", "status"],
|
||||||
widgets={
|
widgets={
|
||||||
"answer": Textarea(
|
"answer": Textarea(
|
||||||
attrs={"required": "true", "minlength": 2, "maxlength": 500, "rows": 3}
|
attrs={"required": "true", "minlength": 2, "maxlength": 500, "rows": 3}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
exclude=[],
|
exclude=[],
|
||||||
can_delete=True,
|
can_delete=True,
|
||||||
extra=1,
|
extra=1,
|
||||||
max_num=10,
|
max_num=10,
|
||||||
)
|
)
|
||||||
|
|
||||||
AnswerUpdateFormSet = inlineformset_factory(
|
AnswerUpdateFormSet = inlineformset_factory(
|
||||||
AnatomyQuestion,
|
AnatomyQuestion,
|
||||||
Answer,
|
Answer,
|
||||||
fields=["answer", "status"],
|
fields=["answer", "status"],
|
||||||
widgets={
|
widgets={
|
||||||
"answer": Textarea(
|
"answer": Textarea(
|
||||||
attrs={"required": "true", "minlength": 2, "maxlength": 500, "rows": 3}
|
attrs={"required": "true", "minlength": 2, "maxlength": 500, "rows": 3}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
exclude=[],
|
exclude=[],
|
||||||
can_delete=True,
|
can_delete=True,
|
||||||
extra=0,
|
extra=0,
|
||||||
max_num=10,
|
max_num=10,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ExaminationForm(ModelForm):
|
class ExaminationForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Examination
|
model = Examination
|
||||||
fields = ["examination"]
|
fields = ["examination"]
|
||||||
|
|
||||||
|
|
||||||
class StructureForm(ModelForm):
|
class StructureForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Structure
|
model = Structure
|
||||||
fields = ["structure"]
|
fields = ["structure"]
|
||||||
|
|
||||||
|
|
||||||
class BodyPartForm(ModelForm):
|
class BodyPartForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = BodyPart
|
model = BodyPart
|
||||||
fields = ["bodypart"]
|
fields = ["bodypart"]
|
||||||
|
|
||||||
|
|
||||||
class ExamForm(ModelForm):
|
class ExamForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Exam
|
model = Exam
|
||||||
fields = [
|
fields = [
|
||||||
"name",
|
"name",
|
||||||
"time_limit",
|
"time_limit",
|
||||||
"open_access",
|
"open_access",
|
||||||
"exam_mode",
|
"exam_mode",
|
||||||
"active",
|
"active",
|
||||||
"publish_results",
|
"publish_results",
|
||||||
"archive",
|
"archive",
|
||||||
]
|
]
|
||||||
|
|||||||
+830
-830
File diff suppressed because it is too large
Load Diff
+527
-526
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,20 @@
|
|||||||
{% extends 'anatomy/exams.html' %}
|
{% extends 'anatomy/exams.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="anatomy">
|
<div class="anatomy">
|
||||||
<h2>Marking exam: {{ exam.name }}</h2>
|
<h2>Marking exam: {{ exam.name }}</h2>
|
||||||
You can start marking from a particular question by clicking on it below.
|
You can start marking from a particular question by clicking on it below.
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button class="show-all-button">Show all</button><button class="show-unmarked-button">Show unmarked</button>
|
<button class="show-all-button">Show all</button><button class="show-unmarked-button">Show unmarked</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="stark-marking-button"><a href="{% url 'anatomy:mark' exam_pk=exam.pk sk=0 %}"><button>Click here to start marking</button></a></div>
|
<div id="stark-marking-button"><a href="{% url 'anatomy:mark' exam_pk=exam.pk sk=0 %}"><button>Click here to start marking</button></a></div>
|
||||||
|
|
||||||
<ul id="question-mark-list">
|
<ul id="question-mark-list">
|
||||||
{% for question, unmarked_count, unmarked_count2 in question_unmarked_map %}
|
{% for question, unmarked_count, unmarked_count2 in question_unmarked_map %}
|
||||||
<li data-markcount={{unmarked_count}} {% if unmarked_count %}class="unmarked" {% endif %}><a href="{% url 'anatomy:mark' exam_pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter }}:
|
<li data-markcount={{unmarked_count}} {% if unmarked_count %}class="unmarked" {% endif %}><a href="{% url 'anatomy:mark' exam_pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter }}:
|
||||||
{{ question }}</a><br />Unmarked answers: {{ unmarked_count }}</li>
|
{{ question }}</a><br />Unmarked answers: {{ unmarked_count }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
+1111
-1111
File diff suppressed because it is too large
Load Diff
+112
-112
@@ -1,112 +1,112 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import (
|
from .models import (
|
||||||
Case,
|
Case,
|
||||||
CaseDetail,
|
CaseDetail,
|
||||||
CaseCollection,
|
CaseCollection,
|
||||||
Differential,
|
Differential,
|
||||||
Series,
|
Series,
|
||||||
SeriesImage,
|
SeriesImage,
|
||||||
Condition,
|
Condition,
|
||||||
Subspecialty,
|
Subspecialty,
|
||||||
Finding,
|
Finding,
|
||||||
SeriesFinding,
|
SeriesFinding,
|
||||||
Structure,
|
Structure,
|
||||||
PathologicalProcess,
|
PathologicalProcess,
|
||||||
Presentation,
|
Presentation,
|
||||||
)
|
)
|
||||||
|
|
||||||
import tagulous.admin
|
import tagulous.admin
|
||||||
|
|
||||||
from django.forms import ModelForm
|
from django.forms import ModelForm
|
||||||
|
|
||||||
from reversion.admin import VersionAdmin
|
from reversion.admin import VersionAdmin
|
||||||
from django.forms.widgets import RadioSelect
|
from django.forms.widgets import RadioSelect
|
||||||
|
|
||||||
from tinymce.widgets import TinyMCE
|
from tinymce.widgets import TinyMCE
|
||||||
|
|
||||||
# from generic.models import Examination, Sign, Site, Condition
|
# from generic.models import Examination, Sign, Site, Condition
|
||||||
from generic.models import Examination
|
from generic.models import Examination
|
||||||
|
|
||||||
# Register your models here.
|
# Register your models here.
|
||||||
# admin.site.register(Examination)
|
# admin.site.register(Examination)
|
||||||
# admin.site.register(Site)
|
# admin.site.register(Site)
|
||||||
admin.site.register(SeriesImage)
|
admin.site.register(SeriesImage)
|
||||||
admin.site.register(Condition)
|
admin.site.register(Condition)
|
||||||
admin.site.register(Subspecialty)
|
admin.site.register(Subspecialty)
|
||||||
admin.site.register(Finding)
|
admin.site.register(Finding)
|
||||||
admin.site.register(Structure)
|
admin.site.register(Structure)
|
||||||
admin.site.register(SeriesFinding)
|
admin.site.register(SeriesFinding)
|
||||||
admin.site.register(Differential)
|
admin.site.register(Differential)
|
||||||
admin.site.register(PathologicalProcess)
|
admin.site.register(PathologicalProcess)
|
||||||
admin.site.register(Presentation)
|
admin.site.register(Presentation)
|
||||||
admin.site.register(CaseDetail)
|
admin.site.register(CaseDetail)
|
||||||
|
|
||||||
|
|
||||||
class DifferentialInline(admin.TabularInline):
|
class DifferentialInline(admin.TabularInline):
|
||||||
model = Differential
|
model = Differential
|
||||||
|
|
||||||
class CasesInline(admin.TabularInline):
|
class CasesInline(admin.TabularInline):
|
||||||
model = CaseDetail
|
model = CaseDetail
|
||||||
|
|
||||||
class CaseCollectionAdminForm(ModelForm):
|
class CaseCollectionAdminForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CaseCollection
|
model = CaseCollection
|
||||||
|
|
||||||
exclude = []
|
exclude = []
|
||||||
|
|
||||||
|
|
||||||
class CaseCollectionAdmin(VersionAdmin):
|
class CaseCollectionAdmin(VersionAdmin):
|
||||||
form = CaseCollectionAdminForm
|
form = CaseCollectionAdminForm
|
||||||
|
|
||||||
save_on_top = True
|
save_on_top = True
|
||||||
save_as = True
|
save_as = True
|
||||||
view_on_site = True
|
view_on_site = True
|
||||||
|
|
||||||
inlines = (CasesInline,)
|
inlines = (CasesInline,)
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(CaseCollection, CaseCollectionAdmin)
|
admin.site.register(CaseCollection, CaseCollectionAdmin)
|
||||||
|
|
||||||
class AtlasAdminForm(ModelForm):
|
class AtlasAdminForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Case
|
model = Case
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
"title",
|
"title",
|
||||||
"description",
|
"description",
|
||||||
"history",
|
"history",
|
||||||
"discussion",
|
"discussion",
|
||||||
"condition",
|
"condition",
|
||||||
"differential",
|
"differential",
|
||||||
"subspecialty",
|
"subspecialty",
|
||||||
"author",
|
"author",
|
||||||
"series",
|
"series",
|
||||||
]
|
]
|
||||||
|
|
||||||
widgets = {
|
widgets = {
|
||||||
# "normal": RadioSelect(
|
# "normal": RadioSelect(
|
||||||
# choices=[(True, 'Yes'),
|
# choices=[(True, 'Yes'),
|
||||||
# (False, 'No')])
|
# (False, 'No')])
|
||||||
"history": TinyMCE(attrs={"cols": 80, "rows": 30}),
|
"history": TinyMCE(attrs={"cols": 80, "rows": 30}),
|
||||||
"discussion": TinyMCE(attrs={"cols": 80, "rows": 30}),
|
"discussion": TinyMCE(attrs={"cols": 80, "rows": 30}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class AtlasAdmin(VersionAdmin):
|
class AtlasAdmin(VersionAdmin):
|
||||||
form = AtlasAdminForm
|
form = AtlasAdminForm
|
||||||
|
|
||||||
save_on_top = True
|
save_on_top = True
|
||||||
save_as = True
|
save_as = True
|
||||||
view_on_site = True
|
view_on_site = True
|
||||||
|
|
||||||
inlines = (DifferentialInline,)
|
inlines = (DifferentialInline,)
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Case, AtlasAdmin)
|
admin.site.register(Case, AtlasAdmin)
|
||||||
|
|
||||||
|
|
||||||
class SeriesAdmin(VersionAdmin):
|
class SeriesAdmin(VersionAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Series, SeriesAdmin)
|
admin.site.register(Series, SeriesAdmin)
|
||||||
|
|||||||
@@ -1,36 +1,36 @@
|
|||||||
# Generated by Django 3.2.10 on 2022-03-21 16:02
|
# Generated by Django 3.2.10 on 2022-03-21 16:02
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('atlas', '0024_alter_case_previous_case'),
|
('atlas', '0024_alter_case_previous_case'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='CaseCollection',
|
name='CaseCollection',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(max_length=255, unique=True)),
|
('name', models.CharField(max_length=255, unique=True)),
|
||||||
('open_access', models.BooleanField(default=True, help_text='If a collection should be freely available to browse')),
|
('open_access', models.BooleanField(default=True, help_text='If a collection should be freely available to browse')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='CaseDetail',
|
name='CaseDetail',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('sort_order', models.IntegerField()),
|
('sort_order', models.IntegerField()),
|
||||||
('case', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='atlas.case')),
|
('case', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='atlas.case')),
|
||||||
('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='atlas.casecollection')),
|
('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='atlas.casecollection')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='casecollection',
|
model_name='casecollection',
|
||||||
name='cases',
|
name='cases',
|
||||||
field=models.ManyToManyField(through='atlas.CaseDetail', to='atlas.Case'),
|
field=models.ManyToManyField(through='atlas.CaseDetail', to='atlas.Case'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
# Generated by Django 3.2.10 on 2022-03-21 16:12
|
# Generated by Django 3.2.10 on 2022-03-21 16:12
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('atlas', '0025_auto_20220321_1602'),
|
('atlas', '0025_auto_20220321_1602'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='case',
|
model_name='case',
|
||||||
name='open_access',
|
name='open_access',
|
||||||
field=models.BooleanField(default=True, help_text='If a case should be freely available to browse'),
|
field=models.BooleanField(default=True, help_text='If a case should be freely available to browse'),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='series',
|
model_name='series',
|
||||||
name='open_access',
|
name='open_access',
|
||||||
field=models.BooleanField(default=True, help_text='If a series should be freely available to browse'),
|
field=models.BooleanField(default=True, help_text='If a series should be freely available to browse'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
+575
-568
File diff suppressed because it is too large
Load Diff
@@ -1,125 +1,125 @@
|
|||||||
<div class="atlas {% if case.scrapped %}atlas-scrapped{% endif %}">
|
<div class="atlas {% if case.scrapped %}atlas-scrapped{% endif %}">
|
||||||
<div class="date">
|
<div class="date">
|
||||||
{{ case.created_date|date:"d/m/Y" }}
|
{{ case.created_date|date:"d/m/Y" }}
|
||||||
</div>
|
</div>
|
||||||
<div class="id">
|
<div class="id">
|
||||||
ID: {{ case.id }}
|
ID: {{ case.id }}
|
||||||
</div>
|
</div>
|
||||||
<p class="pre-whitespace"><b>Title:</b> {{ case.title }}</p>
|
<p class="pre-whitespace"><b>Title:</b> {{ case.title }}</p>
|
||||||
|
|
||||||
<p class="pre-whitespace"><b>Description:</b> {{ case.description }}</p>
|
<p class="pre-whitespace"><b>Description:</b> {{ case.description }}</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<b>Presentation:</b>
|
<b>Presentation:</b>
|
||||||
<ul>
|
<ul>
|
||||||
{% for presentation in case.presentation.all %}
|
{% for presentation in case.presentation.all %}
|
||||||
<li>{{presentation.get_link}}</li>
|
<li>{{presentation.get_link}}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="pre-whitespace"><b>History:</b> {{ case.history }}</p>
|
<p class="pre-whitespace"><b>History:</b> {{ case.history }}</p>
|
||||||
|
|
||||||
<p class="pre-whitespace"><b>Discussion:</b> {{ case.discussion }}</p>
|
<p class="pre-whitespace"><b>Discussion:</b> {{ case.discussion }}</p>
|
||||||
|
|
||||||
<p class="pre-whitespace"><b>Report:</b> {{ case.report }}</p>
|
<p class="pre-whitespace"><b>Report:</b> {{ case.report }}</p>
|
||||||
|
|
||||||
<div class="pre-whitespace multi-image-block"><b>Series:</b>
|
<div class="pre-whitespace multi-image-block"><b>Series:</b>
|
||||||
{% for series in case.series.all %}
|
{% for series in case.series.all %}
|
||||||
<span class="series-block">
|
<span class="series-block">
|
||||||
<span>
|
<span>
|
||||||
Series {{ forloop.counter }}:
|
Series {{ forloop.counter }}:
|
||||||
<a href="{% url 'atlas:series_detail' pk=series.pk %}">
|
<a href="{% url 'atlas:series_detail' pk=series.pk %}">
|
||||||
{{series.get_block}}
|
{{series.get_block}}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="#"
|
<a href="#"
|
||||||
onclick="return window.create_popup_window('/atlas/series/{{series.pk}}', 'Series')">Popup</a>
|
onclick="return window.create_popup_window('/atlas/series/{{series.pk}}', 'Series')">Popup</a>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<a href="{% url 'atlas:series_id_create' pk=case.pk %}">Add new series</a><br />
|
<a href="{% url 'atlas:series_id_create' pk=case.pk %}">Add new series</a><br />
|
||||||
</div>
|
</div>
|
||||||
{% comment %} <p><b>Scrapped:</b> {{ case.scrapped }} <a
|
{% comment %} <p><b>Scrapped:</b> {{ case.scrapped }} <a
|
||||||
href="{% url 'atlas:case_scrap' pk=case.pk %}">(toggle)</a> {% endcomment %}
|
href="{% url 'atlas:case_scrap' pk=case.pk %}">(toggle)</a> {% endcomment %}
|
||||||
|
|
||||||
<div class="atlas-answer">
|
<div class="atlas-answer">
|
||||||
<p class="pre-whitespace">
|
<p class="pre-whitespace">
|
||||||
Condition:
|
Condition:
|
||||||
<ul>
|
<ul>
|
||||||
{% for con in case.condition.all %}
|
{% for con in case.condition.all %}
|
||||||
<li>{{con.get_link}}</li>
|
<li>{{con.get_link}}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<details>
|
<details>
|
||||||
<summary><b>Findings</b></summary>
|
<summary><b>Findings</b></summary>
|
||||||
{% if case.series.all %}
|
{% if case.series.all %}
|
||||||
See individual series for more details.<br/>
|
See individual series for more details.<br/>
|
||||||
{% for series in case.series.all %}
|
{% for series in case.series.all %}
|
||||||
{% for finding in series.findings.all %}
|
{% for finding in series.findings.all %}
|
||||||
<div class="finding-box">
|
<div class="finding-box">
|
||||||
<span>
|
<span>
|
||||||
<a href="{{series.get_absolute_url}}">Series {{series.pk}}</a>: {{series.get_examination_full}} |
|
<a href="{{series.get_absolute_url}}">Series {{series.pk}}</a>: {{series.get_examination_full}} |
|
||||||
</span>
|
</span>
|
||||||
{% if finding.findings.all %}
|
{% if finding.findings.all %}
|
||||||
<span>
|
<span>
|
||||||
Findings: {% for f in finding.findings.all %}
|
Findings: {% for f in finding.findings.all %}
|
||||||
{{f.get_link}},
|
{{f.get_link}},
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if finding.structures.all %}
|
{% if finding.structures.all %}
|
||||||
<span>
|
<span>
|
||||||
Structure: {% for s in finding.structures.all %}
|
Structure: {% for s in finding.structures.all %}
|
||||||
{{s.get_link}},
|
{{s.get_link}},
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if finding.description %}
|
{% if finding.description %}
|
||||||
<span>
|
<span>
|
||||||
Description: {{finding.description}}
|
Description: {{finding.description}}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
No findings associated with case.
|
No findings associated with case.
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</details>
|
</details>
|
||||||
<details open>
|
<details open>
|
||||||
<summary><b>Differentials</b></summary>
|
<summary><b>Differentials</b></summary>
|
||||||
<ul>
|
<ul>
|
||||||
{% for diff in case.differentialcase.all %}
|
{% for diff in case.differentialcase.all %}
|
||||||
<li>{{diff.condition.get_link}} - {{diff.text}}</li>
|
<li>{{diff.condition.get_link}} - {{diff.text}}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
Subspecialty:
|
Subspecialty:
|
||||||
<ul>
|
<ul>
|
||||||
{% for sub in case.subspecialty.all %}
|
{% for sub in case.subspecialty.all %}
|
||||||
<li>{{sub.get_link}}</li>
|
<li>{{sub.get_link}}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<b>Pathological Process:</b>
|
<b>Pathological Process:</b>
|
||||||
<ul>
|
<ul>
|
||||||
{% for p in case.pathological_process.all %}
|
{% for p in case.pathological_process.all %}
|
||||||
<li>{{p.get_link}}</li>
|
<li>{{p.get_link}}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
<p class="pre-whitespace"><b>Previous case:</b> {{ case.previous_case }}</p>
|
<p class="pre-whitespace"><b>Previous case:</b> {{ case.previous_case }}</p>
|
||||||
<p class="pre-whitespace"><b>Next case:</b> {{ case.next_case }}</p>
|
<p class="pre-whitespace"><b>Next case:</b> {{ case.next_case }}</p>
|
||||||
<div>
|
<div>
|
||||||
{% include 'question_notes.html' %}
|
{% include 'question_notes.html' %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p><b>Checked by:</b> {% for verified in case.verified.all %} <a
|
<p><b>Checked by:</b> {% for verified in case.verified.all %} <a
|
||||||
href="{% url 'atlas:verified_detail' pk=verified.pk %}">{{verified}}</a>, {% endfor %}</p>
|
href="{% url 'atlas:verified_detail' pk=verified.pk %}">{{verified}}</a>, {% endfor %}</p>
|
||||||
|
|
||||||
<p><b>Author(s):</b> {% for author in case.author.all %} <a
|
<p><b>Author(s):</b> {% for author in case.author.all %} <a
|
||||||
href="{% url 'atlas:author_detail' pk=author.pk %}">{{author}}</a>, {% endfor %}</p>
|
href="{% url 'atlas:author_detail' pk=author.pk %}">{{author}}</a>, {% endfor %}</p>
|
||||||
|
|||||||
@@ -1,40 +1,52 @@
|
|||||||
{% extends 'atlas/base.html' %}
|
{% extends 'atlas/base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Case {{case_number|add:1}}</h2>
|
<h2>Case {{case_number|add:1}}</h2>
|
||||||
|
|
||||||
<div class="pre-whitespace multi-image-block">
|
<div class="pre-whitespace multi-image-block">
|
||||||
{% for series in series_list %}
|
{% for series in series_list %}
|
||||||
<span class="series-block">
|
<span class="series-block">
|
||||||
<span>
|
<span>
|
||||||
Series {{ forloop.counter }}:
|
Series {{ forloop.counter }}:
|
||||||
<a href="{% url 'atlas:series_detail' pk=series.pk %}">
|
<a href="{% url 'atlas:series_detail' pk=series.pk %}">
|
||||||
{{series.get_block}}
|
{{series.get_block}}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="#"
|
<a href="#"
|
||||||
onclick="return window.create_popup_window('/atlas/series/{{series.pk}}', 'Series')">Popup</a>
|
onclick="return window.create_popup_window('/atlas/series/{{series.pk}}', 'Series')">Popup</a>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for series in series_list %}
|
<div>
|
||||||
<div>
|
<div id="single-dicom-viewer" class="dicom-viewer" data-images="{{ series.get_image_url_array }}" data-annotations=''>
|
||||||
<div id="single-dicom-viewer" class="dicom-viewer" data-images="{{ series.get_image_url_array }}" data-annotations=''>
|
</div>
|
||||||
</div>
|
|
||||||
|
</div>
|
||||||
</div>
|
|
||||||
|
<div>
|
||||||
{% endfor %}
|
{% if previous %}
|
||||||
<div>
|
<a href="{% url 'atlas:collection_case_view' pk=collection.pk case_number=case_number|add:-1 %}">Previous</a>
|
||||||
{% if previous %}
|
{% endif %}
|
||||||
<a href="{% url 'atlas:collection_case_view' pk=collection.pk case_number=case_number|add:-1 %}">Previous</a>
|
{% if next %}
|
||||||
{% endif %}
|
<a href="{% url 'atlas:collection_case_view' pk=collection.pk case_number=case_number|add:1 %}">Next</a>
|
||||||
{% if next %}
|
{% endif %}
|
||||||
<a href="{% url 'atlas:collection_case_view' pk=collection.pk case_number=case_number|add:1 %}">Next</a>
|
</div>
|
||||||
{% endif %}
|
<br/>
|
||||||
</div>
|
Return to <a href='{{collection.get_absolute_url}}'>collection</a>
|
||||||
<br/>
|
{% endblock %}
|
||||||
Return to <a href='{{collection.get_absolute_url}}'>collection</a>
|
{% block js %}
|
||||||
{% endblock %}
|
<script type="text/javascript">
|
||||||
|
window.images = {
|
||||||
|
{% for series in series_list %}
|
||||||
|
{{forloop.counter0}} : ["{{ series.get_image_url_array_not_json }}"],
|
||||||
|
{% endfor %}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
loadDicomViewer(window.images[0])
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock js %}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{% extends 'atlas/base.html' %}
|
{% extends 'atlas/base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{{collection.name}}
|
<h2>{{collection.name}}
|
||||||
<ul>
|
<ul>
|
||||||
{% for case in collection.cases.all %}
|
{% for case in collection.cases.all %}
|
||||||
<li><a href="{% url 'atlas:collection_case_view' pk=collection.pk case_number=forloop.counter0 %}">Case {{forloop.counter}}</a></li>
|
<li><a href="{% url 'atlas:collection_case_view' pk=collection.pk case_number=forloop.counter0 %}">Case {{forloop.counter}}</a></li>
|
||||||
|
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
{% extends 'atlas/base.html' %}
|
{% extends 'atlas/base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for collection in collections %}
|
{% for collection in collections %}
|
||||||
<li><a href='{{collection.get_absolute_url}}'>{{collection.name}}</a></li>
|
<li><a href='{{collection.get_absolute_url}}'>{{collection.name}}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -1,172 +1,172 @@
|
|||||||
<div>{{ series.modality}}, {{ series.examination }}, {{ series.plane }}, {{ series.contrast }}</div>
|
<div>{{ series.modality}}, {{ series.examination }}, {{ series.plane }}, {{ series.contrast }}</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="single-dicom-viewer" class="dicom-viewer" data-images="{{ series.get_image_url_array }}" data-annotations=''>
|
<div id="single-dicom-viewer" class="dicom-viewer" data-images="{{ series.get_image_url_array }}" data-annotations=''>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if editing_finding < 1 %}
|
{% if editing_finding < 1 %}
|
||||||
<button id="add-finding-button">Add finding</button>
|
<button id="add-finding-button">Add finding</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button id="reset-viewport-button">Reset viewport</button>
|
<button id="reset-viewport-button">Reset viewport</button>
|
||||||
<div id="finding-form">
|
<div id="finding-form">
|
||||||
<div class="hide" id="hidden-form">
|
<div class="hide" id="hidden-form">
|
||||||
<form method="post" id="series_finding_form">
|
<form method="post" id="series_finding_form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{series_finding_form.management_form}}
|
{{series_finding_form.management_form}}
|
||||||
<div>
|
<div>
|
||||||
<div>Description: {{ series_finding_form.description }}</div>
|
<div>Description: {{ series_finding_form.description }}</div>
|
||||||
<div>Findings: {{ series_finding_form.findings }}</div>
|
<div>Findings: {{ series_finding_form.findings }}</div>
|
||||||
<div>Structures: {{ series_finding_form.structures }}</div>
|
<div>Structures: {{ series_finding_form.structures }}</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="submit" value="Submit">
|
<input type="submit" value="Submit">
|
||||||
<button id="cancel-add-finding-button">Cancel</button>
|
<button id="cancel-add-finding-button">Cancel</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<details open>
|
<details open>
|
||||||
<summary>Findings</summary>
|
<summary>Findings</summary>
|
||||||
|
|
||||||
{% for finding in series.findings.all %}
|
{% for finding in series.findings.all %}
|
||||||
<div class="finding-box">
|
<div class="finding-box">
|
||||||
<button class="view-finding-button" data-annotationjson={{finding.annotation_json}}
|
<button class="view-finding-button" data-annotationjson={{finding.annotation_json}}
|
||||||
data-viewportjson={{finding.viewport_json}} data-findingid={{finding.id}}>Click to view</button>
|
data-viewportjson={{finding.viewport_json}} data-findingid={{finding.id}}>Click to view</button>
|
||||||
<span class="view-finding-details">
|
<span class="view-finding-details">
|
||||||
Finding(s): {% for f in finding.findings.all %}{{f.get_link}}{% endfor %}<br />
|
Finding(s): {% for f in finding.findings.all %}{{f.get_link}}{% endfor %}<br />
|
||||||
Structure(s): {% for s in finding.structures.all %}{{s.get_link}}{% endfor %}<br />
|
Structure(s): {% for s in finding.structures.all %}{{s.get_link}}{% endfor %}<br />
|
||||||
Description: {{finding.description}}<br />
|
Description: {{finding.description}}<br />
|
||||||
</span>
|
</span>
|
||||||
<a href="{% url 'atlas:series_edit_finding' pk=series.pk finding_pk=finding.pk %}" class="edit-finding-link">Edit</a>
|
<a href="{% url 'atlas:series_edit_finding' pk=series.pk finding_pk=finding.pk %}" class="edit-finding-link">Edit</a>
|
||||||
<a href="{% url 'atlas:delete_finding' pk=finding.pk %}" class="delete-finding-link">Delete</a>
|
<a href="{% url 'atlas:delete_finding' pk=finding.pk %}" class="delete-finding-link">Delete</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
<div>Author: {{ series.get_author_display }}</div>
|
<div>Author: {{ series.get_author_display }}</div>
|
||||||
<details>
|
<details>
|
||||||
<summary>Image info</summary>
|
<summary>Image info</summary>
|
||||||
<div>
|
<div>
|
||||||
<a href="{% url 'atlas:series_order_dicom' pk=series.pk %}" title="orders dicom by slice location">Order dicoms
|
<a href="{% url 'atlas:series_order_dicom' pk=series.pk %}" title="orders dicom by slice location">Order dicoms
|
||||||
by slice location</a>
|
by slice location</a>
|
||||||
<a href="{% url 'atlas:series_order_dicom_instance' pk=series.pk %}"
|
<a href="{% url 'atlas:series_order_dicom_instance' pk=series.pk %}"
|
||||||
title="orders dicom by instance number">Order dicoms by instance number</a>
|
title="orders dicom by instance number">Order dicoms by instance number</a>
|
||||||
<a href="{% url 'atlas:series_order_dicom_SeriesInstanceUID' pk=series.pk %}"
|
<a href="{% url 'atlas:series_order_dicom_SeriesInstanceUID' pk=series.pk %}"
|
||||||
title="orders dicom by instance number">Order dicoms by SeriesInstanceUID</a>
|
title="orders dicom by instance number">Order dicoms by SeriesInstanceUID</a>
|
||||||
<a href="{% url 'atlas:series_order_upload_filename' pk=series.pk %}"
|
<a href="{% url 'atlas:series_order_upload_filename' pk=series.pk %}"
|
||||||
title="orders dicom by uploaded filename">Order by uploaded filename</a>
|
title="orders dicom by uploaded filename">Order by uploaded filename</a>
|
||||||
</div>
|
</div>
|
||||||
{% for image in series.images.all %}
|
{% for image in series.images.all %}
|
||||||
{{image.image.url}}, pos: {{image.position}}, {{image.upload_filename}} [{{image.image.size|filesizeformat}}]<br />
|
{{image.image.url}}, pos: {{image.position}}, {{image.upload_filename}} [{{image.image.size|filesizeformat}}]<br />
|
||||||
{% comment %} {{image.get_dicom_info|safe}}<br /> {% endcomment %}
|
{% comment %} {{image.get_dicom_info|safe}}<br /> {% endcomment %}
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<p>Total image size: {{series.get_total_image_size|filesizeformat}}</p>
|
<p>Total image size: {{series.get_total_image_size|filesizeformat}}</p>
|
||||||
<p><a href="{% url 'atlas:series_download' pk=series.pk %}">Download images</a></p>
|
<p><a href="{% url 'atlas:series_download' pk=series.pk %}">Download images</a></p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$("#add-finding-button").click(() => {
|
$("#add-finding-button").click(() => {
|
||||||
dicom_element = $(".cornerstone-element").get(0);
|
dicom_element = $(".cornerstone-element").get(0);
|
||||||
cornerstoneTools.globalImageIdSpecificToolStateManager.clear(dicom_element);
|
cornerstoneTools.globalImageIdSpecificToolStateManager.clear(dicom_element);
|
||||||
cornerstone.reset(dicom_element);
|
cornerstone.reset(dicom_element);
|
||||||
cornerstone.resize(dicom_element, true);
|
cornerstone.resize(dicom_element, true);
|
||||||
//$("#finding-form").empty().append(
|
//$("#finding-form").empty().append(
|
||||||
// $("#hidden-form form").clone()
|
// $("#hidden-form form").clone()
|
||||||
//);
|
//);
|
||||||
$("#hidden-form").show()
|
$("#hidden-form").show()
|
||||||
|
|
||||||
});
|
});
|
||||||
$("#cancel-add-finding-button").click((e) => {
|
$("#cancel-add-finding-button").click((e) => {
|
||||||
$("#hidden-form").hide()
|
$("#hidden-form").hide()
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
$("#reset-viewport-button").click(() => {
|
$("#reset-viewport-button").click(() => {
|
||||||
dicom_element = $(".cornerstone-element").get(0);
|
dicom_element = $(".cornerstone-element").get(0);
|
||||||
//annotationjson = JSON.parse(e.currentTarget.dataset.annotationjson);
|
//annotationjson = JSON.parse(e.currentTarget.dataset.annotationjson);
|
||||||
cornerstoneTools.globalImageIdSpecificToolStateManager.clear(dicom_element);
|
cornerstoneTools.globalImageIdSpecificToolStateManager.clear(dicom_element);
|
||||||
cornerstone.reset(dicom_element);
|
cornerstone.reset(dicom_element);
|
||||||
cornerstone.resize(dicom_element, true);
|
cornerstone.resize(dicom_element, true);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".view-finding-button").each((n, el) => {
|
$(".view-finding-button").each((n, el) => {
|
||||||
$(el).click((e) => {
|
$(el).click((e) => {
|
||||||
dicom_element = $(".cornerstone-element").get(0);
|
dicom_element = $(".cornerstone-element").get(0);
|
||||||
annotationjson = JSON.parse(e.currentTarget.dataset.annotationjson);
|
annotationjson = JSON.parse(e.currentTarget.dataset.annotationjson);
|
||||||
viewport = JSON.parse(e.currentTarget.dataset.viewportjson);
|
viewport = JSON.parse(e.currentTarget.dataset.viewportjson);
|
||||||
loadAnnotationAndViewportOnElement(annotationjson, viewport, dicom_element);
|
loadAnnotationAndViewportOnElement(annotationjson, viewport, dicom_element);
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
{% if editing_finding > 0 %}
|
{% if editing_finding > 0 %}
|
||||||
$("#hidden-form").show()
|
$("#hidden-form").show()
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
$(".view-finding-button[data-findingid={{editing_finding}}]").trigger("click");
|
$(".view-finding-button[data-findingid={{editing_finding}}]").trigger("click");
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
});
|
});
|
||||||
|
|
||||||
function loadAnnotationAndViewportOnElement(annotation, viewport, dicom_element) {
|
function loadAnnotationAndViewportOnElement(annotation, viewport, dicom_element) {
|
||||||
cornerstoneTools.globalImageIdSpecificToolStateManager.clear(dicom_element);
|
cornerstoneTools.globalImageIdSpecificToolStateManager.clear(dicom_element);
|
||||||
cornerstone.getEnabledElement(dicom_element).viewport = viewport
|
cornerstone.getEnabledElement(dicom_element).viewport = viewport
|
||||||
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
|
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
|
||||||
annotation);
|
annotation);
|
||||||
console.log(annotation)
|
console.log(annotation)
|
||||||
//cornerstone.getEnabledElement(dicom_element).toolStateManager.restoreToolState(annotationjson)
|
//cornerstone.getEnabledElement(dicom_element).toolStateManager.restoreToolState(annotationjson)
|
||||||
cornerstone.resize(dicom_element);
|
cornerstone.resize(dicom_element);
|
||||||
|
|
||||||
dicomViewer.getNextAnnotationImage(dicom_element);
|
dicomViewer.getNextAnnotationImage(dicom_element);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).on('submit', '#series_finding_form', function (e) {
|
$(document).on('submit', '#series_finding_form', function (e) {
|
||||||
dicom_element = $(".cornerstone-element").get(0);
|
dicom_element = $(".cornerstone-element").get(0);
|
||||||
c = cornerstone.getEnabledElement(dicom_element);
|
c = cornerstone.getEnabledElement(dicom_element);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: '{% url "atlas:add_finding" %}',
|
url: '{% url "atlas:add_finding" %}',
|
||||||
data: {
|
data: {
|
||||||
series_finding_id: {{ editing_finding }},
|
series_finding_id: {{ editing_finding }},
|
||||||
description: $('#id_description').val(),
|
description: $('#id_description').val(),
|
||||||
series: {{ series.pk }},
|
series: {{ series.pk }},
|
||||||
//annotation_json: JSON.stringify(c.toolStateManager.saveToolState()),
|
//annotation_json: JSON.stringify(c.toolStateManager.saveToolState()),
|
||||||
annotation_json: JSON.stringify(cornerstoneTools.globalImageIdSpecificToolStateManager
|
annotation_json: JSON.stringify(cornerstoneTools.globalImageIdSpecificToolStateManager
|
||||||
.saveToolState()),
|
.saveToolState()),
|
||||||
viewport_json: JSON.stringify(c.viewport),
|
viewport_json: JSON.stringify(c.viewport),
|
||||||
findings: JSON.stringify($('#finding-form select[name="findings"]').find(":selected")
|
findings: JSON.stringify($('#finding-form select[name="findings"]').find(":selected")
|
||||||
.map((i, el) => {
|
.map((i, el) => {
|
||||||
return $(el).val()
|
return $(el).val()
|
||||||
}).toArray()),
|
}).toArray()),
|
||||||
structures: JSON.stringify($('#finding-form select[name="structures"]').find(
|
structures: JSON.stringify($('#finding-form select[name="structures"]').find(
|
||||||
":selected")
|
":selected")
|
||||||
.map((i, el) => {
|
.map((i, el) => {
|
||||||
return $(el).val()
|
return $(el).val()
|
||||||
}).toArray()),
|
}).toArray()),
|
||||||
csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
|
csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
|
||||||
action: 'post'
|
action: 'post'
|
||||||
},
|
},
|
||||||
success: function (json) {
|
success: function (json) {
|
||||||
$("#finding-form").empty();
|
$("#finding-form").empty();
|
||||||
toastr.info('Finding added.');
|
toastr.info('Finding added.');
|
||||||
location.href='{{series.get_absolute_url}}';
|
location.href='{{series.get_absolute_url}}';
|
||||||
},
|
},
|
||||||
error: function (xhr, errmsg, err) {
|
error: function (xhr, errmsg, err) {
|
||||||
console.log(xhr.status + ": " + xhr
|
console.log(xhr.status + ": " + xhr
|
||||||
.responseText); // provide a bit more info about the error to the console
|
.responseText); // provide a bit more info about the error to the console
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
+151
-151
@@ -1,151 +1,151 @@
|
|||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
|
||||||
from atlas.models import Condition, Finding, Presentation, Structure
|
from atlas.models import Condition, Finding, Presentation, Structure
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
app_name = "atlas"
|
app_name = "atlas"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", views.CaseView.as_view(), name="index"),
|
path("", views.CaseView.as_view(), name="index"),
|
||||||
path("author/<int:pk>/", views.author_detail, name="author_detail"),
|
path("author/<int:pk>/", views.author_detail, name="author_detail"),
|
||||||
path("author/", views.author_list, name="author_list"),
|
path("author/", views.author_list, name="author_list"),
|
||||||
path("case/", views.CaseView.as_view(), name="case_view"),
|
path("case/", views.CaseView.as_view(), name="case_view"),
|
||||||
path("collection/", views.collection_index_view, name="collection_index_view"),
|
path("collection/", views.collection_index_view, name="collection_index_view"),
|
||||||
path("collections/<int:pk>", views.collection_detail_view, name="collection_detail_view"),
|
path("collections/<int:pk>", views.collection_detail_view, name="collection_detail_view"),
|
||||||
path("collections/<int:pk>/<int:case_number>", views.collection_case_view, name="collection_case_view"),
|
path("collections/<int:pk>/<int:case_number>", views.collection_case_view, name="collection_case_view"),
|
||||||
path("condition/", views.ConditionView.as_view(), name="condition_view"),
|
path("condition/", views.ConditionView.as_view(), name="condition_view"),
|
||||||
path("categories/", views.categories_list, name="categories_list"),
|
path("categories/", views.categories_list, name="categories_list"),
|
||||||
path("condition/<int:pk>", views.condition_detail, name="condition_detail"),
|
path("condition/<int:pk>", views.condition_detail, name="condition_detail"),
|
||||||
path(
|
path(
|
||||||
"condition/<int:pk>/delete",
|
"condition/<int:pk>/delete",
|
||||||
views.ConditionDelete.as_view(),
|
views.ConditionDelete.as_view(),
|
||||||
name="condition_delete",
|
name="condition_delete",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"condition/<int:pk>/update",
|
"condition/<int:pk>/update",
|
||||||
views.ConditionUpdate.as_view(),
|
views.ConditionUpdate.as_view(),
|
||||||
name="condition_update",
|
name="condition_update",
|
||||||
),
|
),
|
||||||
path("subspecialty/", views.SubspecialtyView.as_view(), name="subspecialty_view"),
|
path("subspecialty/", views.SubspecialtyView.as_view(), name="subspecialty_view"),
|
||||||
path("subspecialty/<int:pk>", views.subspecialty_detail, name="subspecialty_detail"),
|
path("subspecialty/<int:pk>", views.subspecialty_detail, name="subspecialty_detail"),
|
||||||
path("condition/create", views.ConditionCreate.as_view(), name="condition_create"),
|
path("condition/create", views.ConditionCreate.as_view(), name="condition_create"),
|
||||||
path("finding/", views.FindingView.as_view(), name="finding_view"),
|
path("finding/", views.FindingView.as_view(), name="finding_view"),
|
||||||
path("finding/<int:pk>", views.finding_detail, name="finding_detail"),
|
path("finding/<int:pk>", views.finding_detail, name="finding_detail"),
|
||||||
path(
|
path(
|
||||||
"finding/<int:pk>/delete", views.FindingDelete.as_view(), name="finding_delete"
|
"finding/<int:pk>/delete", views.FindingDelete.as_view(), name="finding_delete"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"finding/<int:pk>/update", views.FindingUpdate.as_view(), name="finding_update"
|
"finding/<int:pk>/update", views.FindingUpdate.as_view(), name="finding_update"
|
||||||
),
|
),
|
||||||
path("finding/create", views.FindingCreate.as_view(), name="finding_create"),
|
path("finding/create", views.FindingCreate.as_view(), name="finding_create"),
|
||||||
path("structure/", views.StructureView.as_view(), name="structure_view"),
|
path("structure/", views.StructureView.as_view(), name="structure_view"),
|
||||||
path("structure/<int:pk>", views.structure_detail, name="structure_detail"),
|
path("structure/<int:pk>", views.structure_detail, name="structure_detail"),
|
||||||
path(
|
path(
|
||||||
"structure/<int:pk>/delete",
|
"structure/<int:pk>/delete",
|
||||||
views.StructureDelete.as_view(),
|
views.StructureDelete.as_view(),
|
||||||
name="structure_delete",
|
name="structure_delete",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"structure/<int:pk>/update",
|
"structure/<int:pk>/update",
|
||||||
views.StructureUpdate.as_view(),
|
views.StructureUpdate.as_view(),
|
||||||
name="structure_update",
|
name="structure_update",
|
||||||
),
|
),
|
||||||
path("structure/create", views.StructureCreate.as_view(), name="structure_create"),
|
path("structure/create", views.StructureCreate.as_view(), name="structure_create"),
|
||||||
path("series/<int:pk>", views.series_detail, name="series_detail"),
|
path("series/<int:pk>", views.series_detail, name="series_detail"),
|
||||||
path("series/", views.SeriesView.as_view(), name="series_view"),
|
path("series/", views.SeriesView.as_view(), name="series_view"),
|
||||||
path("series/<int:pk>", views.series_detail, name="series_detail"),
|
path("series/<int:pk>", views.series_detail, name="series_detail"),
|
||||||
path(
|
path(
|
||||||
"series/<int:pk>/<int:finding_pk>",
|
"series/<int:pk>/<int:finding_pk>",
|
||||||
views.series_detail,
|
views.series_detail,
|
||||||
name="series_edit_finding",
|
name="series_edit_finding",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"series/<int:pk>/order_dicom",
|
"series/<int:pk>/order_dicom",
|
||||||
views.series_order_dicom,
|
views.series_order_dicom,
|
||||||
name="series_order_dicom",
|
name="series_order_dicom",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"series/<int:pk>/download",
|
"series/<int:pk>/download",
|
||||||
views.SeriesImagesZipView.as_view(),
|
views.SeriesImagesZipView.as_view(),
|
||||||
name="series_download",
|
name="series_download",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"series/<int:pk>/order_dicom_instance",
|
"series/<int:pk>/order_dicom_instance",
|
||||||
views.series_order_dicom_instance,
|
views.series_order_dicom_instance,
|
||||||
name="series_order_dicom_instance",
|
name="series_order_dicom_instance",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"series/<int:pk>/order_dicom_SeriesInstanceUID",
|
"series/<int:pk>/order_dicom_SeriesInstanceUID",
|
||||||
views.series_order_dicom_SeriesInstanceUID,
|
views.series_order_dicom_SeriesInstanceUID,
|
||||||
name="series_order_dicom_SeriesInstanceUID",
|
name="series_order_dicom_SeriesInstanceUID",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"series/<int:pk>/order_upload_filename",
|
"series/<int:pk>/order_upload_filename",
|
||||||
views.series_order_upload_filename,
|
views.series_order_upload_filename,
|
||||||
name="series_order_upload_filename",
|
name="series_order_upload_filename",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"series/<int:pk>/delete",
|
"series/<int:pk>/delete",
|
||||||
views.SeriesDelete.as_view(),
|
views.SeriesDelete.as_view(),
|
||||||
name="series_delete",
|
name="series_delete",
|
||||||
),
|
),
|
||||||
# path("unchecked/", views.unchecked_list, name="unchecked_list"),
|
# path("unchecked/", views.unchecked_list, name="unchecked_list"),
|
||||||
# path("verified/<int:pk>/", views.verified_detail, name="verified_detail"),
|
# path("verified/<int:pk>/", views.verified_detail, name="verified_detail"),
|
||||||
path("case/<int:pk>/", views.case_detail, name="case_detail"),
|
path("case/<int:pk>/", views.case_detail, name="case_detail"),
|
||||||
path("case/<int:pk>/clone", views.AtlasClone.as_view(), name="case_clone"),
|
path("case/<int:pk>/clone", views.AtlasClone.as_view(), name="case_clone"),
|
||||||
# path("verified/", views.verified, name="verified"),
|
# path("verified/", views.verified, name="verified"),
|
||||||
# path("all_questions/", views.all_questions, name="all_questions"),
|
# path("all_questions/", views.all_questions, name="all_questions"),
|
||||||
path("case/<int:pk>/scrap", views.atlas_scrap, name="case_scrap"),
|
path("case/<int:pk>/scrap", views.atlas_scrap, name="case_scrap"),
|
||||||
path("case/<int:pk>/delete", views.CaseDelete.as_view(), name="case_delete"),
|
path("case/<int:pk>/delete", views.CaseDelete.as_view(), name="case_delete"),
|
||||||
path("case/create/", views.AtlasCreate.as_view(), name="create"),
|
path("case/create/", views.AtlasCreate.as_view(), name="create"),
|
||||||
path("series/create", views.SeriesCreate.as_view(), name="series_create"),
|
path("series/create", views.SeriesCreate.as_view(), name="series_create"),
|
||||||
path(
|
path(
|
||||||
"series/<int:pk>/create",
|
"series/<int:pk>/create",
|
||||||
views.SeriesCreate.as_view(),
|
views.SeriesCreate.as_view(),
|
||||||
name="series_id_create",
|
name="series_id_create",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"case/<int:pk>/update",
|
"case/<int:pk>/update",
|
||||||
views.AtlasUpdate.as_view(),
|
views.AtlasUpdate.as_view(),
|
||||||
name="case_update",
|
name="case_update",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"series/<int:pk>/update",
|
"series/<int:pk>/update",
|
||||||
views.SeriesUpdate.as_view(),
|
views.SeriesUpdate.as_view(),
|
||||||
name="series_update",
|
name="series_update",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"series/add_finding",
|
"series/add_finding",
|
||||||
views.create_series_findings,
|
views.create_series_findings,
|
||||||
name="add_finding",
|
name="add_finding",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"series/delete_finding/<int:pk>",
|
"series/delete_finding/<int:pk>",
|
||||||
views.SeriesFindingDelete.as_view(),
|
views.SeriesFindingDelete.as_view(),
|
||||||
name="delete_finding",
|
name="delete_finding",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"condition-autocomplete",
|
"condition-autocomplete",
|
||||||
views.ConditionAutocomplete.as_view(model=Condition, create_field="name"),
|
views.ConditionAutocomplete.as_view(model=Condition, create_field="name"),
|
||||||
name="condition-autocomplete",
|
name="condition-autocomplete",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"presentation-autocomplete",
|
"presentation-autocomplete",
|
||||||
views.PresentationAutocomplete.as_view(model=Presentation, create_field="name"),
|
views.PresentationAutocomplete.as_view(model=Presentation, create_field="name"),
|
||||||
name="presentation-autocomplete",
|
name="presentation-autocomplete",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"finding-autocomplete",
|
"finding-autocomplete",
|
||||||
views.FindingAutocomplete.as_view(model=Finding, create_field="name"),
|
views.FindingAutocomplete.as_view(model=Finding, create_field="name"),
|
||||||
name="finding-autocomplete",
|
name="finding-autocomplete",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"structure-autocomplete",
|
"structure-autocomplete",
|
||||||
views.StructureAutocomplete.as_view(model=Structure, create_field="name"),
|
views.StructureAutocomplete.as_view(model=Structure, create_field="name"),
|
||||||
name="structure-autocomplete",
|
name="structure-autocomplete",
|
||||||
),
|
),
|
||||||
path("presentation/", views.PresentationView.as_view(), name="presentation_view"),
|
path("presentation/", views.PresentationView.as_view(), name="presentation_view"),
|
||||||
path("presentation/<int:pk>", views.presentation_detail, name="presentation_detail"),
|
path("presentation/<int:pk>", views.presentation_detail, name="presentation_detail"),
|
||||||
path("process/", views.PathologicalProcessView.as_view(), name="pathological_process_view"),
|
path("process/", views.PathologicalProcessView.as_view(), name="pathological_process_view"),
|
||||||
path("process/<int:pk>", views.pathological_process_detail, name="pathological_process_detail"),
|
path("process/<int:pk>", views.pathological_process_detail, name="pathological_process_detail"),
|
||||||
]
|
]
|
||||||
|
|||||||
+918
-918
File diff suppressed because it is too large
Load Diff
+188
-188
@@ -1,189 +1,189 @@
|
|||||||
from django.forms import (
|
from django.forms import (
|
||||||
Form,
|
Form,
|
||||||
ModelForm,
|
ModelForm,
|
||||||
ModelMultipleChoiceField,
|
ModelMultipleChoiceField,
|
||||||
ModelChoiceField,
|
ModelChoiceField,
|
||||||
ChoiceField,
|
ChoiceField,
|
||||||
CharField,
|
CharField,
|
||||||
HiddenInput,
|
HiddenInput,
|
||||||
)
|
)
|
||||||
from django.forms import inlineformset_factory
|
from django.forms import inlineformset_factory
|
||||||
|
|
||||||
from generic.models import CidUser, CidUserGroup, Examination, QuestionNote
|
from generic.models import CidUser, CidUserGroup, Examination, QuestionNote
|
||||||
|
|
||||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||||
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
||||||
|
|
||||||
from rapids.models import Rapid
|
from rapids.models import Rapid
|
||||||
from anatomy.models import AnatomyQuestion
|
from anatomy.models import AnatomyQuestion
|
||||||
from longs.models import Long
|
from longs.models import Long
|
||||||
|
|
||||||
from django.shortcuts import render, get_object_or_404, redirect
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
|
|
||||||
from physics.models import CidUserAnswer as PhysicsCidUserAnswer, Question
|
from physics.models import CidUserAnswer as PhysicsCidUserAnswer, Question
|
||||||
from physics.models import Exam as PhysicsExam
|
from physics.models import Exam as PhysicsExam
|
||||||
from physics.models import Question as SbasQuestion
|
from physics.models import Question as SbasQuestion
|
||||||
|
|
||||||
from anatomy.models import CidUserAnswer as AnatomyCidUserAnswer
|
from anatomy.models import CidUserAnswer as AnatomyCidUserAnswer
|
||||||
from anatomy.models import Exam as AnatomyExam
|
from anatomy.models import Exam as AnatomyExam
|
||||||
from anatomy.models import AnatomyQuestion
|
from anatomy.models import AnatomyQuestion
|
||||||
from anatomy.models import Answer as AnatomyAnswer
|
from anatomy.models import Answer as AnatomyAnswer
|
||||||
|
|
||||||
from rapids.models import CidUserAnswer as RapidsCidUserAnswer
|
from rapids.models import CidUserAnswer as RapidsCidUserAnswer
|
||||||
from rapids.models import Exam as RapidsExam
|
from rapids.models import Exam as RapidsExam
|
||||||
from rapids.models import Rapid
|
from rapids.models import Rapid
|
||||||
from rapids.models import Answer as RapidAnswer
|
from rapids.models import Answer as RapidAnswer
|
||||||
|
|
||||||
from longs.models import CidUserAnswer as LongsCidUserAnswer
|
from longs.models import CidUserAnswer as LongsCidUserAnswer
|
||||||
from longs.models import Exam as LongsExam
|
from longs.models import Exam as LongsExam
|
||||||
from longs.models import Long
|
from longs.models import Long
|
||||||
|
|
||||||
from sbas.models import CidUserAnswer as SbasCidUserAnswer
|
from sbas.models import CidUserAnswer as SbasCidUserAnswer
|
||||||
from sbas.models import Exam as SbasExam
|
from sbas.models import Exam as SbasExam
|
||||||
from sbas.models import Question as SbasQuestion
|
from sbas.models import Question as SbasQuestion
|
||||||
|
|
||||||
class ExaminationForm(ModelForm):
|
class ExaminationForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Examination
|
model = Examination
|
||||||
fields = ["examination"]
|
fields = ["examination"]
|
||||||
|
|
||||||
|
|
||||||
class QuestionNoteForm(ModelForm):
|
class QuestionNoteForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = QuestionNote
|
model = QuestionNote
|
||||||
fields = ["content_type", "object_id", "author", "note_type", "note"]
|
fields = ["content_type", "object_id", "author", "note_type", "note"]
|
||||||
widgets = {"content_type": HiddenInput(), "object_id": HiddenInput()}
|
widgets = {"content_type": HiddenInput(), "object_id": HiddenInput()}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
question_type = kwargs.pop("question_type")
|
question_type = kwargs.pop("question_type")
|
||||||
pk = kwargs.pop("pk")
|
pk = kwargs.pop("pk")
|
||||||
user = kwargs.pop("user")
|
user = kwargs.pop("user")
|
||||||
|
|
||||||
# if question_type == "rapid":
|
# if question_type == "rapid":
|
||||||
# question = Rapid
|
# question = Rapid
|
||||||
# content_type = ContentType.objects.get(model="rapid")
|
# content_type = ContentType.objects.get(model="rapid")
|
||||||
# elif question_type == "anatomy":
|
# elif question_type == "anatomy":
|
||||||
# question = AnatomyQuestion
|
# question = AnatomyQuestion
|
||||||
# content_type = ContentType.objects.get(model="anatomyquestion")
|
# content_type = ContentType.objects.get(model="anatomyquestion")
|
||||||
# elif question_type == "long":
|
# elif question_type == "long":
|
||||||
# question = Long
|
# question = Long
|
||||||
# content_type = ContentType.objects.get(model="long")
|
# content_type = ContentType.objects.get(model="long")
|
||||||
# else:
|
# else:
|
||||||
# raise PermissionError()
|
# raise PermissionError()
|
||||||
|
|
||||||
# get_object_or_404(question, pk=pk)
|
# get_object_or_404(question, pk=pk)
|
||||||
# We get the 'initial' keyword argument or initialize it
|
# We get the 'initial' keyword argument or initialize it
|
||||||
# as a dict if it didn't exist.
|
# as a dict if it didn't exist.
|
||||||
# if kwargs.get("instance"):
|
# if kwargs.get("instance"):
|
||||||
ModelForm.__init__(self, *args, **kwargs)
|
ModelForm.__init__(self, *args, **kwargs)
|
||||||
super(QuestionNoteForm, self).__init__(*args, **kwargs)
|
super(QuestionNoteForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
# initial = kwargs.setdefault("initial", {})
|
# initial = kwargs.setdefault("initial", {})
|
||||||
## The widget for a ModelMultipleChoiceField expects
|
## The widget for a ModelMultipleChoiceField expects
|
||||||
## a list of primary key for the selected data.
|
## a list of primary key for the selected data.
|
||||||
##initial["content_type"] = question
|
##initial["content_type"] = question
|
||||||
##initial["content_id"] = pk
|
##initial["content_id"] = pk
|
||||||
# self.fields["content_type"].initial = content_type
|
# self.fields["content_type"].initial = content_type
|
||||||
# self.fields["object_id"].initial = pk
|
# self.fields["object_id"].initial = pk
|
||||||
|
|
||||||
|
|
||||||
class CidUserForm(ModelForm):
|
class CidUserForm(ModelForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
if kwargs.get("instance"):
|
if kwargs.get("instance"):
|
||||||
# We get the 'initial' keyword argument or initialize it
|
# We get the 'initial' keyword argument or initialize it
|
||||||
# as a dict if it didn't exist.
|
# as a dict if it didn't exist.
|
||||||
initial = kwargs.setdefault("initial", {})
|
initial = kwargs.setdefault("initial", {})
|
||||||
# The widget for a ModelMultipleChoiceField expects
|
# The widget for a ModelMultipleChoiceField expects
|
||||||
# a list of primary key for the selected data.
|
# a list of primary key for the selected data.
|
||||||
initial["physics_exams"] = [t.pk for t in kwargs["instance"].physics_exams.all()]
|
initial["physics_exams"] = [t.pk for t in kwargs["instance"].physics_exams.all()]
|
||||||
initial["anatomy_exams"] = [t.pk for t in kwargs["instance"].anatomy_exams.all()]
|
initial["anatomy_exams"] = [t.pk for t in kwargs["instance"].anatomy_exams.all()]
|
||||||
initial["sba_exams"] = [t.pk for t in kwargs["instance"].sba_exams.all()]
|
initial["sba_exams"] = [t.pk for t in kwargs["instance"].sba_exams.all()]
|
||||||
initial["rapid_exams"] = [t.pk for t in kwargs["instance"].rapid_exams.all()]
|
initial["rapid_exams"] = [t.pk for t in kwargs["instance"].rapid_exams.all()]
|
||||||
initial["longs_exams"] = [t.pk for t in kwargs["instance"].longs_exams.all()]
|
initial["longs_exams"] = [t.pk for t in kwargs["instance"].longs_exams.all()]
|
||||||
|
|
||||||
ModelForm.__init__(self, *args, **kwargs)
|
ModelForm.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
super(CidUserForm, self).__init__(*args, **kwargs)
|
super(CidUserForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.fields["physics_exams"] = ModelMultipleChoiceField(
|
self.fields["physics_exams"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=PhysicsExam.objects.filter(archive=False),
|
queryset=PhysicsExam.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(verbose_name="Physics Exams", is_stacked=False),
|
widget=FilteredSelectMultiple(verbose_name="Physics Exams", is_stacked=False),
|
||||||
)
|
)
|
||||||
self.fields["anatomy_exams"] = ModelMultipleChoiceField(
|
self.fields["anatomy_exams"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=AnatomyExam.objects.filter(archive=False),
|
queryset=AnatomyExam.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(verbose_name="Anatomy Exams", is_stacked=False),
|
widget=FilteredSelectMultiple(verbose_name="Anatomy Exams", is_stacked=False),
|
||||||
)
|
)
|
||||||
self.fields["sba_exams"] = ModelMultipleChoiceField(
|
self.fields["sba_exams"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=SbasExam.objects.filter(archive=False),
|
queryset=SbasExam.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(verbose_name="SBA Exams", is_stacked=False),
|
widget=FilteredSelectMultiple(verbose_name="SBA Exams", is_stacked=False),
|
||||||
)
|
)
|
||||||
self.fields["rapid_exams"] = ModelMultipleChoiceField(
|
self.fields["rapid_exams"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=RapidsExam.objects.filter(archive=False),
|
queryset=RapidsExam.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(verbose_name="Rapid Exams", is_stacked=False),
|
widget=FilteredSelectMultiple(verbose_name="Rapid Exams", is_stacked=False),
|
||||||
)
|
)
|
||||||
self.fields["longs_exams"] = ModelMultipleChoiceField(
|
self.fields["longs_exams"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=LongsExam.objects.filter(archive=False),
|
queryset=LongsExam.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(verbose_name="Long Exams", is_stacked=False),
|
widget=FilteredSelectMultiple(verbose_name="Long Exams", is_stacked=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
# Get the unsaved Long instance
|
# Get the unsaved Long instance
|
||||||
instance = ModelForm.save(self, False)
|
instance = ModelForm.save(self, False)
|
||||||
|
|
||||||
# Prepare a 'save_m2m' method for the form,
|
# Prepare a 'save_m2m' method for the form,
|
||||||
old_save_m2m = self.save_m2m
|
old_save_m2m = self.save_m2m
|
||||||
|
|
||||||
def save_m2m():
|
def save_m2m():
|
||||||
old_save_m2m()
|
old_save_m2m()
|
||||||
instance.physics_exams.clear()
|
instance.physics_exams.clear()
|
||||||
for physics_exams in self.cleaned_data["physics_exams"]:
|
for physics_exams in self.cleaned_data["physics_exams"]:
|
||||||
instance.physics_exams.add(physics_exams.pk)
|
instance.physics_exams.add(physics_exams.pk)
|
||||||
instance.anatomy_exams.clear()
|
instance.anatomy_exams.clear()
|
||||||
for anatomy_exams in self.cleaned_data["anatomy_exams"]:
|
for anatomy_exams in self.cleaned_data["anatomy_exams"]:
|
||||||
instance.anatomy_exams.add(anatomy_exams.pk)
|
instance.anatomy_exams.add(anatomy_exams.pk)
|
||||||
instance.sba_exams.clear()
|
instance.sba_exams.clear()
|
||||||
for sba_exams in self.cleaned_data["sba_exams"]:
|
for sba_exams in self.cleaned_data["sba_exams"]:
|
||||||
instance.sba_exams.add(sba_exams.pk)
|
instance.sba_exams.add(sba_exams.pk)
|
||||||
instance.rapid_exams.clear()
|
instance.rapid_exams.clear()
|
||||||
for rapid_exams in self.cleaned_data["rapid_exams"]:
|
for rapid_exams in self.cleaned_data["rapid_exams"]:
|
||||||
instance.rapid_exams.add(rapid_exams.pk)
|
instance.rapid_exams.add(rapid_exams.pk)
|
||||||
instance.longs_exams.clear()
|
instance.longs_exams.clear()
|
||||||
for longs_exams in self.cleaned_data["longs_exams"]:
|
for longs_exams in self.cleaned_data["longs_exams"]:
|
||||||
instance.longs_exams.add(longs_exams.pk)
|
instance.longs_exams.add(longs_exams.pk)
|
||||||
|
|
||||||
self.save_m2m = save_m2m
|
self.save_m2m = save_m2m
|
||||||
|
|
||||||
# Do we need to save all changes now?
|
# Do we need to save all changes now?
|
||||||
# if commit:
|
# if commit:
|
||||||
instance.save()
|
instance.save()
|
||||||
self.save_m2m()
|
self.save_m2m()
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CidUser
|
model = CidUser
|
||||||
fields = [
|
fields = [
|
||||||
"passcode",
|
"passcode",
|
||||||
"active",
|
"active",
|
||||||
"internal_candidate",
|
"internal_candidate",
|
||||||
"name",
|
"name",
|
||||||
"email",
|
"email",
|
||||||
"supervisor_email",
|
"supervisor_email",
|
||||||
"login_email_sent",
|
"login_email_sent",
|
||||||
"results_email_sent",
|
"results_email_sent",
|
||||||
"group",
|
"group",
|
||||||
]
|
]
|
||||||
|
|
||||||
class CidUserGroupForm(ModelForm):
|
class CidUserGroupForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CidUserGroup
|
model = CidUserGroup
|
||||||
fields = ["name", "archive"]
|
fields = ["name", "archive"]
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
# Generated by Django 3.2.10 on 2022-03-01 18:32
|
# Generated by Django 3.2.10 on 2022-03-01 18:32
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('generic', '0024_auto_20211231_1044'),
|
('generic', '0024_auto_20211231_1044'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='ciduser',
|
model_name='ciduser',
|
||||||
name='group',
|
name='group',
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='generic.cidusergroup'),
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='generic.cidusergroup'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
+469
-469
@@ -1,469 +1,469 @@
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from smtplib import SMTPException
|
from smtplib import SMTPException
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.views.generic.edit import CreateView
|
from django.views.generic.edit import CreateView
|
||||||
from reversion.views import RevisionMixin
|
from reversion.views import RevisionMixin
|
||||||
|
|
||||||
import tagulous
|
import tagulous
|
||||||
import tagulous.models
|
import tagulous.models
|
||||||
|
|
||||||
from sortedm2m.fields import SortedManyToManyField
|
from sortedm2m.fields import SortedManyToManyField
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.core.mail import EmailMessage
|
from django.core.mail import EmailMessage
|
||||||
|
|
||||||
|
|
||||||
class Plane(models.Model):
|
class Plane(models.Model):
|
||||||
plane = models.CharField(max_length=200, unique=True)
|
plane = models.CharField(max_length=200, unique=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.plane
|
return self.plane
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ("plane",)
|
ordering = ("plane",)
|
||||||
|
|
||||||
|
|
||||||
class Contrast(models.Model):
|
class Contrast(models.Model):
|
||||||
contrast = models.CharField(max_length=200, unique=True)
|
contrast = models.CharField(max_length=200, unique=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.contrast
|
return self.contrast
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ("contrast",)
|
ordering = ("contrast",)
|
||||||
|
|
||||||
|
|
||||||
class Examination(models.Model):
|
class Examination(models.Model):
|
||||||
examination = models.CharField(max_length=200, unique=True)
|
examination = models.CharField(max_length=200, unique=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.examination
|
return self.examination
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ("examination",)
|
ordering = ("examination",)
|
||||||
|
|
||||||
|
|
||||||
class Site(models.Model):
|
class Site(models.Model):
|
||||||
site = models.CharField(max_length=200)
|
site = models.CharField(max_length=200)
|
||||||
initials = models.CharField(max_length=200)
|
initials = models.CharField(max_length=200)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.site
|
return self.site
|
||||||
|
|
||||||
|
|
||||||
class Condition(tagulous.models.TagModel):
|
class Condition(tagulous.models.TagModel):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Sign(tagulous.models.TagModel):
|
class Sign(tagulous.models.TagModel):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ExamBase(models.Model):
|
class ExamBase(models.Model):
|
||||||
name = models.CharField(max_length=200, help_text="Name of the exam")
|
name = models.CharField(max_length=200, help_text="Name of the exam")
|
||||||
# exam_questions = SortedManyToManyField(Long, related_name="exams", blank="true")
|
# exam_questions = SortedManyToManyField(Long, related_name="exams", blank="true")
|
||||||
|
|
||||||
active = models.BooleanField(
|
active = models.BooleanField(
|
||||||
help_text="If an exam should be available to take", default=False
|
help_text="If an exam should be available to take", default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
exam_mode = models.BooleanField(
|
exam_mode = models.BooleanField(
|
||||||
help_text="If an exam should be taken in exam mode (users results will be submited to the server for marking)",
|
help_text="If an exam should be taken in exam mode (users results will be submited to the server for marking)",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
publish_results = models.BooleanField(
|
publish_results = models.BooleanField(
|
||||||
help_text="If an exams results should be available", default=False
|
help_text="If an exams results should be available", default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
recreate_json = models.BooleanField(
|
recreate_json = models.BooleanField(
|
||||||
help_text="If the json cache needs updating", default=False
|
help_text="If the json cache needs updating", default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
json_creation_time = models.DateTimeField(blank=True, default=None, null=True)
|
json_creation_time = models.DateTimeField(blank=True, default=None, null=True)
|
||||||
exam_json_id = models.IntegerField(
|
exam_json_id = models.IntegerField(
|
||||||
default=1, help_text="auto incrementing field when json recreated"
|
default=1, help_text="auto incrementing field when json recreated"
|
||||||
)
|
)
|
||||||
|
|
||||||
archive = models.BooleanField(
|
archive = models.BooleanField(
|
||||||
help_text="Archived exams will remain on the test system but will not be displayed by default",
|
help_text="Archived exams will remain on the test system but will not be displayed by default",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
open_access = models.BooleanField(
|
open_access = models.BooleanField(
|
||||||
help_text="If the exam is freely accessible (to view and edit on the test system)",
|
help_text="If the exam is freely accessible (to view and edit on the test system)",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
stats_mean = models.FloatField(default=0)
|
stats_mean = models.FloatField(default=0)
|
||||||
stats_mode = models.CharField(default=0, max_length=25)
|
stats_mode = models.CharField(default=0, max_length=25)
|
||||||
stats_median = models.FloatField(default=0)
|
stats_median = models.FloatField(default=0)
|
||||||
|
|
||||||
stats_candidates = models.FloatField(default=0)
|
stats_candidates = models.FloatField(default=0)
|
||||||
|
|
||||||
stats_min = models.FloatField(default=0)
|
stats_min = models.FloatField(default=0)
|
||||||
stats_max = models.FloatField(default=0)
|
stats_max = models.FloatField(default=0)
|
||||||
|
|
||||||
stats_max_possible = models.FloatField(default=0)
|
stats_max_possible = models.FloatField(default=0)
|
||||||
|
|
||||||
stats_graph = models.TextField(default=0)
|
stats_graph = models.TextField(default=0)
|
||||||
|
|
||||||
user_scores = models.JSONField(default=dict)
|
user_scores = models.JSONField(default=dict)
|
||||||
|
|
||||||
notes = GenericRelation("generic.CidUserExam")
|
notes = GenericRelation("generic.CidUserExam")
|
||||||
|
|
||||||
# time_limit = models.IntegerField(
|
# time_limit = models.IntegerField(
|
||||||
# help_text="Exam time limit (in seconds). Default is 2100 secondse (35 minutes)",
|
# help_text="Exam time limit (in seconds). Default is 2100 secondse (35 minutes)",
|
||||||
# default=2100,
|
# default=2100,
|
||||||
# )
|
# )
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
def get_exam_stats(self):
|
def get_exam_stats(self):
|
||||||
text = f"""{self.name}
|
text = f"""{self.name}
|
||||||
{"-"*len(self.name)}
|
{"-"*len(self.name)}
|
||||||
- Candidates: {int(self.stats_candidates)}
|
- Candidates: {int(self.stats_candidates)}
|
||||||
- Mean: {self.stats_mean:.2f} Median: {self.stats_median} Mode: {self.stats_mode} [Min: {self.stats_min} / Max: {self.stats_max}]
|
- Mean: {self.stats_mean:.2f} Median: {self.stats_median} Mode: {self.stats_mode} [Min: {self.stats_min} / Max: {self.stats_max}]
|
||||||
"""
|
"""
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("{}:exam_overview".format(self.app_name), kwargs={"pk": self.pk})
|
return reverse("{}:exam_overview".format(self.app_name), kwargs={"pk": self.pk})
|
||||||
|
|
||||||
def get_take_url(self):
|
def get_take_url(self):
|
||||||
return f"{settings.REMOTE_URL}/rts"
|
return f"{settings.REMOTE_URL}/rts"
|
||||||
|
|
||||||
def get_exam_name(self):
|
def get_exam_name(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def get_json_url(self):
|
def get_json_url(self):
|
||||||
return reverse("{}:exam_json".format(self.app_name), args=(self.pk,))
|
return reverse("{}:exam_json".format(self.app_name), args=(self.pk,))
|
||||||
|
|
||||||
def get_question_index(self, question):
|
def get_question_index(self, question):
|
||||||
return list(self.exam_questions.all()).index(question)
|
return list(self.exam_questions.all()).index(question)
|
||||||
|
|
||||||
def get_authors(self):
|
def get_authors(self):
|
||||||
"""Returns a comma seperated text list of authors"""
|
"""Returns a comma seperated text list of authors"""
|
||||||
authors = ", ".join([i.username for i in self.author.all()])
|
authors = ", ".join([i.username for i in self.author.all()])
|
||||||
return authors
|
return authors
|
||||||
|
|
||||||
def get_author_objects(self):
|
def get_author_objects(self):
|
||||||
"""Returns a comma seperated text list of authors"""
|
"""Returns a comma seperated text list of authors"""
|
||||||
authors = [i for i in self.author.all()]
|
authors = [i for i in self.author.all()]
|
||||||
return authors
|
return authors
|
||||||
|
|
||||||
def check_cid_user(self, cid, passcode, request=None):
|
def check_cid_user(self, cid, passcode, request=None):
|
||||||
if request is not None and request.user.is_superuser:
|
if request is not None and request.user.is_superuser:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if self.valid_users.exists():
|
if self.valid_users.exists():
|
||||||
|
|
||||||
user = self.valid_users.filter(cid=cid).first()
|
user = self.valid_users.filter(cid=cid).first()
|
||||||
|
|
||||||
if not user or user.passcode != passcode:
|
if not user or user.passcode != passcode:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_or_create_cid_user_exam(self, cid_user, start_time=None):
|
def get_or_create_cid_user_exam(self, cid_user, start_time=None):
|
||||||
content_type = ContentType.objects.get_for_model(self)
|
content_type = ContentType.objects.get_for_model(self)
|
||||||
c = CidUserExam.objects.filter(
|
c = CidUserExam.objects.filter(
|
||||||
content_type=content_type, object_id=self.pk, cid_user=cid_user
|
content_type=content_type, object_id=self.pk, cid_user=cid_user
|
||||||
).first()
|
).first()
|
||||||
print(c)
|
print(c)
|
||||||
if c:
|
if c:
|
||||||
return c
|
return c
|
||||||
|
|
||||||
if start_time is None:
|
if start_time is None:
|
||||||
start_time = timezone.now()
|
start_time = timezone.now()
|
||||||
new = CidUserExam(
|
new = CidUserExam(
|
||||||
content_type=content_type,
|
content_type=content_type,
|
||||||
object_id=self.pk,
|
object_id=self.pk,
|
||||||
cid_user=cid_user,
|
cid_user=cid_user,
|
||||||
start_time=start_time,
|
start_time=start_time,
|
||||||
)
|
)
|
||||||
new.save()
|
new.save()
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def get_cid_user_exams(self, cid_user=None):
|
def get_cid_user_exams(self, cid_user=None):
|
||||||
content_type = ContentType.objects.get_for_model(self)
|
content_type = ContentType.objects.get_for_model(self)
|
||||||
if cid_user is None:
|
if cid_user is None:
|
||||||
return CidUserExam.objects.filter(
|
return CidUserExam.objects.filter(
|
||||||
content_type=content_type,
|
content_type=content_type,
|
||||||
object_id=self.pk,
|
object_id=self.pk,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return CidUserExam.objects.filter(
|
return CidUserExam.objects.filter(
|
||||||
content_type=content_type, object_id=self.pk, cid_user=cid_user
|
content_type=content_type, object_id=self.pk, cid_user=cid_user
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_cid_user_score(self, cid_user):
|
def get_cid_user_score(self, cid_user):
|
||||||
c = str(cid_user)
|
c = str(cid_user)
|
||||||
if c in self.user_scores:
|
if c in self.user_scores:
|
||||||
return self.user_scores[c]
|
return self.user_scores[c]
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class NoteType(models.Model):
|
class NoteType(models.Model):
|
||||||
note_type = models.CharField(max_length=200)
|
note_type = models.CharField(max_length=200)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.note_type
|
return self.note_type
|
||||||
|
|
||||||
|
|
||||||
class QuestionNote(models.Model):
|
class QuestionNote(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
question = GenericForeignKey("content_type", "object_id")
|
question = GenericForeignKey("content_type", "object_id")
|
||||||
# author = models.ForeignKey(User,
|
# author = models.ForeignKey(User,
|
||||||
|
|
||||||
author = models.CharField(max_length=200, blank=True)
|
author = models.CharField(max_length=200, blank=True)
|
||||||
|
|
||||||
user_author = models.ForeignKey(
|
user_author = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True
|
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
note = models.TextField(blank=True)
|
note = models.TextField(blank=True)
|
||||||
created_on = models.DateTimeField(auto_now_add=True)
|
created_on = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
complete = models.BooleanField(default=False)
|
complete = models.BooleanField(default=False)
|
||||||
|
|
||||||
note_type = models.ForeignKey(
|
note_type = models.ForeignKey(
|
||||||
NoteType, on_delete=models.CASCADE, null=True, blank=True
|
NoteType, on_delete=models.CASCADE, null=True, blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# def get_absolute_url(self):
|
# def get_absolute_url(self):
|
||||||
# self.pk = self.rapid_id
|
# self.pk = self.rapid_id
|
||||||
# return reverse('rapids:question_detail', kwargs={'pk': self.pk})
|
# return reverse('rapids:question_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{}: {} [{}] {} / {}".format(
|
return "{}: {} [{}] {} / {}".format(
|
||||||
self.content_type,
|
self.content_type,
|
||||||
self.get_author_str(),
|
self.get_author_str(),
|
||||||
self.created_on,
|
self.created_on,
|
||||||
self.note_type,
|
self.note_type,
|
||||||
self.note,
|
self.note,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
# TODO: return to question if logged in
|
# TODO: return to question if logged in
|
||||||
return "/"
|
return "/"
|
||||||
|
|
||||||
def get_object_url(self):
|
def get_object_url(self):
|
||||||
return self.question.get_absolute_url()
|
return self.question.get_absolute_url()
|
||||||
|
|
||||||
def get_author_str(self):
|
def get_author_str(self):
|
||||||
if self.user_author is not None:
|
if self.user_author is not None:
|
||||||
return self.user_author.username
|
return self.user_author.username
|
||||||
else:
|
else:
|
||||||
return self.author
|
return self.author
|
||||||
|
|
||||||
def get_short_str(self):
|
def get_short_str(self):
|
||||||
return f"{self.note_type} - {self.note}"
|
return f"{self.note_type} - {self.note}"
|
||||||
|
|
||||||
|
|
||||||
class CidUser(models.Model):
|
class CidUser(models.Model):
|
||||||
EXAM_TYPES = (
|
EXAM_TYPES = (
|
||||||
("Physics", "physics_exams"),
|
("Physics", "physics_exams"),
|
||||||
("Rapids", "rapid_exams"),
|
("Rapids", "rapid_exams"),
|
||||||
("SBAs", "sba_exams"),
|
("SBAs", "sba_exams"),
|
||||||
("Anatomy", "anatomy_exams"),
|
("Anatomy", "anatomy_exams"),
|
||||||
("Longs", "longs_exams"),
|
("Longs", "longs_exams"),
|
||||||
)
|
)
|
||||||
|
|
||||||
cid = models.IntegerField(blank=True, null=True, unique=True)
|
cid = models.IntegerField(blank=True, null=True, unique=True)
|
||||||
passcode = models.CharField(blank=True, max_length=50)
|
passcode = models.CharField(blank=True, max_length=50)
|
||||||
active = models.BooleanField(default=True)
|
active = models.BooleanField(default=True)
|
||||||
internal_candidate = models.BooleanField(default=False)
|
internal_candidate = models.BooleanField(default=False)
|
||||||
name = models.CharField(blank=True, max_length=255)
|
name = models.CharField(blank=True, max_length=255)
|
||||||
email = models.EmailField(blank=True)
|
email = models.EmailField(blank=True)
|
||||||
supervisor_email = models.EmailField(blank=True)
|
supervisor_email = models.EmailField(blank=True)
|
||||||
|
|
||||||
login_email_sent = models.BooleanField(default=False)
|
login_email_sent = models.BooleanField(default=False)
|
||||||
results_email_sent = models.BooleanField(default=False)
|
results_email_sent = models.BooleanField(default=False)
|
||||||
|
|
||||||
group = models.ForeignKey("CidUserGroup", on_delete=models.SET_NULL, null=True, blank=True)
|
group = models.ForeignKey("CidUserGroup", on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return str(self.cid)
|
return str(self.cid)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("generic:manage_cids")
|
return reverse("generic:manage_cids")
|
||||||
|
|
||||||
def check_passcode(self, passcode) -> bool:
|
def check_passcode(self, passcode) -> bool:
|
||||||
return self.passcode == passcode
|
return self.passcode == passcode
|
||||||
|
|
||||||
def get_cid_exams(self):
|
def get_cid_exams(self):
|
||||||
available_exams = []
|
available_exams = []
|
||||||
|
|
||||||
for n, t in self.EXAM_TYPES:
|
for n, t in self.EXAM_TYPES:
|
||||||
exam_rel = getattr(self, t)
|
exam_rel = getattr(self, t)
|
||||||
if exam_rel.exists():
|
if exam_rel.exists():
|
||||||
exams = exam_rel.filter(exam_mode=True, archive=False).order_by("name")
|
exams = exam_rel.filter(exam_mode=True, archive=False).order_by("name")
|
||||||
available_exams.append((n, exams))
|
available_exams.append((n, exams))
|
||||||
|
|
||||||
return available_exams
|
return available_exams
|
||||||
|
|
||||||
def generate_exam_report(self):
|
def generate_exam_report(self):
|
||||||
exam_list = self.get_cid_exams()
|
exam_list = self.get_cid_exams()
|
||||||
|
|
||||||
name = ""
|
name = ""
|
||||||
if self.name:
|
if self.name:
|
||||||
name = f"{self.name} "
|
name = f"{self.name} "
|
||||||
|
|
||||||
exam_text = [f"Candidate {self.cid} {name}[{self.email}]"]
|
exam_text = [f"Candidate {self.cid} {name}[{self.email}]"]
|
||||||
|
|
||||||
for exam_type, exams in exam_list:
|
for exam_type, exams in exam_list:
|
||||||
exam_text.append(f"{'='*len(exam_type)}\n{exam_type}\n{'='*len(exam_type)}")
|
exam_text.append(f"{'='*len(exam_type)}\n{exam_type}\n{'='*len(exam_type)}")
|
||||||
|
|
||||||
for exam in exams:
|
for exam in exams:
|
||||||
try:
|
try:
|
||||||
user_score = exam.user_scores[str(self.cid)]
|
user_score = exam.user_scores[str(self.cid)]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
user_score = "Score not generated"
|
user_score = "Score not generated"
|
||||||
t = exam.get_exam_stats()
|
t = exam.get_exam_stats()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
percentage = (
|
percentage = (
|
||||||
f" ({user_score / int(exam.stats_max_possible) * 100:.2f}%)"
|
f" ({user_score / int(exam.stats_max_possible) * 100:.2f}%)"
|
||||||
)
|
)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
percentage = ""
|
percentage = ""
|
||||||
|
|
||||||
exam_text.append(
|
exam_text.append(
|
||||||
f"""{t}
|
f"""{t}
|
||||||
# Score: {user_score} / {int(exam.stats_max_possible)}{percentage}
|
# Score: {user_score} / {int(exam.stats_max_possible)}{percentage}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = "\n\n".join(exam_text)
|
msg = "\n\n".join(exam_text)
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
html_msg = f""""""
|
html_msg = f""""""
|
||||||
|
|
||||||
def email_results(self, resend=False, additional_emails=[]):
|
def email_results(self, resend=False, additional_emails=[]):
|
||||||
if not self.internal_candidate:
|
if not self.internal_candidate:
|
||||||
return False, "Not internal candidate."
|
return False, "Not internal candidate."
|
||||||
|
|
||||||
if self.results_email_sent and not resend:
|
if self.results_email_sent and not resend:
|
||||||
return False, "Already sent."
|
return False, "Already sent."
|
||||||
|
|
||||||
# Get a list of taken exams
|
# Get a list of taken exams
|
||||||
msg = self.generate_exam_report()
|
msg = self.generate_exam_report()
|
||||||
|
|
||||||
emails = [self.email, self.supervisor_email]
|
emails = [self.email, self.supervisor_email]
|
||||||
|
|
||||||
if additional_emails:
|
if additional_emails:
|
||||||
emails.extend(additional_emails)
|
emails.extend(additional_emails)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
send_mail(
|
send_mail(
|
||||||
f"{self.group} results",
|
f"{self.group} results",
|
||||||
msg,
|
msg,
|
||||||
"test@xkjq.uk",
|
"test@xkjq.uk",
|
||||||
emails,
|
emails,
|
||||||
fail_silently=False,
|
fail_silently=False,
|
||||||
# html_message=html_msg,
|
# html_message=html_msg,
|
||||||
)
|
)
|
||||||
except SMTPException as e:
|
except SMTPException as e:
|
||||||
return False, e
|
return False, e
|
||||||
|
|
||||||
self.results_email_sent = True
|
self.results_email_sent = True
|
||||||
self.save()
|
self.save()
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
def email_details(self, resend=False):
|
def email_details(self, resend=False):
|
||||||
if self.login_email_sent and not resend:
|
if self.login_email_sent and not resend:
|
||||||
return False, "Already sent."
|
return False, "Already sent."
|
||||||
|
|
||||||
login_url = reverse("cid_selector")
|
login_url = reverse("cid_selector")
|
||||||
|
|
||||||
msg = f"""
|
msg = f"""
|
||||||
See below for your details for: {self.group}
|
See below for your details for: {self.group}
|
||||||
|
|
||||||
---------
|
---------
|
||||||
CID: {self.cid}
|
CID: {self.cid}
|
||||||
Passcode: {self.passcode}
|
Passcode: {self.passcode}
|
||||||
---------
|
---------
|
||||||
|
|
||||||
You will need these to access to the test system.
|
You will need these to access to the test system.
|
||||||
|
|
||||||
Direct link to login:
|
Direct link to login:
|
||||||
{settings.REMOTE_URL}{login_url}?cid={self.cid}&passcode={self.passcode}
|
{settings.REMOTE_URL}{login_url}?cid={self.cid}&passcode={self.passcode}
|
||||||
|
|
||||||
The above details are unique to you. Please do not share as otherwise others will be able to access and submit your results.
|
The above details are unique to you. Please do not share as otherwise others will be able to access and submit your results.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Please note this inbox is not monitored. Direct all enquiries to plh-tr.radiology-academy@nhs.net
|
Please note this inbox is not monitored. Direct all enquiries to plh-tr.radiology-academy@nhs.net
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# send_mail(
|
# send_mail(
|
||||||
# "Candidate Details",
|
# "Candidate Details",
|
||||||
# msg,
|
# msg,
|
||||||
# "test@xkjq.uk",
|
# "test@xkjq.uk",
|
||||||
# [self.email],
|
# [self.email],
|
||||||
# fail_silently=False,
|
# fail_silently=False,
|
||||||
# )
|
# )
|
||||||
email = EmailMessage(
|
email = EmailMessage(
|
||||||
"Candidate Details",
|
"Candidate Details",
|
||||||
msg,
|
msg,
|
||||||
"test@xkjq.uk",
|
"test@xkjq.uk",
|
||||||
[self.email],
|
[self.email],
|
||||||
["ross.kruger@nhs.net"],
|
["ross.kruger@nhs.net"],
|
||||||
)
|
)
|
||||||
email.send(fail_silently=False)
|
email.send(fail_silently=False)
|
||||||
except SMTPException as e:
|
except SMTPException as e:
|
||||||
return False, e
|
return False, e
|
||||||
|
|
||||||
self.login_email_sent = True
|
self.login_email_sent = True
|
||||||
self.save()
|
self.save()
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
def get_email_results_url(self):
|
def get_email_results_url(self):
|
||||||
return reverse("generic:candidate_email_results", kwargs={"cid": self.cid})
|
return reverse("generic:candidate_email_results", kwargs={"cid": self.cid})
|
||||||
|
|
||||||
def get_email_results_resend_url(self):
|
def get_email_results_resend_url(self):
|
||||||
return reverse("generic:candidate_email_results_resend", kwargs={"cid": self.cid})
|
return reverse("generic:candidate_email_results_resend", kwargs={"cid": self.cid})
|
||||||
|
|
||||||
|
|
||||||
class CidUserExam(models.Model):
|
class CidUserExam(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
exam = GenericForeignKey("content_type", "object_id")
|
exam = GenericForeignKey("content_type", "object_id")
|
||||||
|
|
||||||
start_time = models.DateTimeField(blank=True, null=True)
|
start_time = models.DateTimeField(blank=True, null=True)
|
||||||
end_time = models.DateTimeField(blank=True, null=True)
|
end_time = models.DateTimeField(blank=True, null=True)
|
||||||
|
|
||||||
cid_user = models.ForeignKey(CidUser, on_delete=models.CASCADE)
|
cid_user = models.ForeignKey(CidUser, on_delete=models.CASCADE)
|
||||||
|
|
||||||
manual_submission = models.BooleanField(default=False)
|
manual_submission = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
|
||||||
class CidUserGroup(models.Model):
|
class CidUserGroup(models.Model):
|
||||||
name = models.CharField(blank=True, max_length=50)
|
name = models.CharField(blank=True, max_length=50)
|
||||||
archive = models.BooleanField(default=False)
|
archive = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return str(self.name)
|
return str(self.name)
|
||||||
|
|
||||||
def GetGroupUsers(self):
|
def GetGroupUsers(self):
|
||||||
cid_users = self.ciduser_set.all()
|
cid_users = self.ciduser_set.all()
|
||||||
|
|
||||||
s = ", ".join([f"{user.id} - {user.name}" for user in cid_users])
|
s = ", ".join([f"{user.id} - {user.name}" for user in cid_users])
|
||||||
return s
|
return s
|
||||||
|
|||||||
+1650
-1650
File diff suppressed because it is too large
Load Diff
+280
-280
@@ -1,280 +1,280 @@
|
|||||||
from django.forms import (
|
from django.forms import (
|
||||||
Form,
|
Form,
|
||||||
ModelForm,
|
ModelForm,
|
||||||
ModelMultipleChoiceField,
|
ModelMultipleChoiceField,
|
||||||
ModelChoiceField,
|
ModelChoiceField,
|
||||||
ChoiceField,
|
ChoiceField,
|
||||||
CharField,
|
CharField,
|
||||||
)
|
)
|
||||||
from django.forms import inlineformset_factory
|
from django.forms import inlineformset_factory
|
||||||
|
|
||||||
from longs.models import (
|
from longs.models import (
|
||||||
# Examination,
|
# Examination,
|
||||||
AnswerMarks,
|
AnswerMarks,
|
||||||
Long,
|
Long,
|
||||||
LongSeries,
|
LongSeries,
|
||||||
LongSeriesImage,
|
LongSeriesImage,
|
||||||
LongCreationDefault,
|
LongCreationDefault,
|
||||||
CidUserAnswer,
|
CidUserAnswer,
|
||||||
Exam,
|
Exam,
|
||||||
)
|
)
|
||||||
|
|
||||||
from anatomy.models import Modality
|
from anatomy.models import Modality
|
||||||
|
|
||||||
from generic.models import Examination, Condition, Sign
|
from generic.models import Examination, Condition, Sign
|
||||||
|
|
||||||
# from generic.models import Examination, Site, Condition, Sign
|
# from generic.models import Examination, Site, Condition, Sign
|
||||||
|
|
||||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||||
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
||||||
|
|
||||||
from tinymce.widgets import TinyMCE
|
from tinymce.widgets import TinyMCE
|
||||||
|
|
||||||
|
|
||||||
class LongAnswerForm(ModelForm):
|
class LongAnswerForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CidUserAnswer
|
model = CidUserAnswer
|
||||||
fields = (
|
fields = (
|
||||||
"answer_observations",
|
"answer_observations",
|
||||||
"answer_interpretation",
|
"answer_interpretation",
|
||||||
"answer_principle_diagnosis",
|
"answer_principle_diagnosis",
|
||||||
"answer_differential_diagnosis",
|
"answer_differential_diagnosis",
|
||||||
"answer_management",
|
"answer_management",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MarkLongQuestionSingleForm(ModelForm):
|
class MarkLongQuestionSingleForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CidUserAnswer
|
model = CidUserAnswer
|
||||||
fields = ["score"]
|
fields = ["score"]
|
||||||
|
|
||||||
|
|
||||||
class MarkLongQuestionDoubleForm(ModelForm):
|
class MarkLongQuestionDoubleForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AnswerMarks
|
model = AnswerMarks
|
||||||
fields = ["score", "mark_reason", "candidate_feedback"]
|
fields = ["score", "mark_reason", "candidate_feedback"]
|
||||||
|
|
||||||
|
|
||||||
class ExaminationForm(ModelForm):
|
class ExaminationForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Examination
|
model = Examination
|
||||||
fields = ["examination"]
|
fields = ["examination"]
|
||||||
|
|
||||||
|
|
||||||
class LongCreationDefaultForm(ModelForm):
|
class LongCreationDefaultForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = LongCreationDefault
|
model = LongCreationDefault
|
||||||
# fields = ["site"]
|
# fields = ["site"]
|
||||||
exclude = ["author"]
|
exclude = ["author"]
|
||||||
|
|
||||||
|
|
||||||
class LongSeriesForm(ModelForm):
|
class LongSeriesForm(ModelForm):
|
||||||
class Media:
|
class Media:
|
||||||
# Django also includes a few javascript files necessary
|
# Django also includes a few javascript files necessary
|
||||||
# for the operation of this form element. You need to
|
# for the operation of this form element. You need to
|
||||||
# include <script src="/admin/jsi18n"></script>
|
# include <script src="/admin/jsi18n"></script>
|
||||||
# in the template.
|
# in the template.
|
||||||
css = {
|
css = {
|
||||||
"all": ["css/widgets.css"],
|
"all": ["css/widgets.css"],
|
||||||
}
|
}
|
||||||
# Adding this javascript is crucial
|
# Adding this javascript is crucial
|
||||||
js = ["jsi18n.js", "tesseract.min.js"]
|
js = ["jsi18n.js", "tesseract.min.js"]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.user = kwargs.pop(
|
self.user = kwargs.pop(
|
||||||
"user"
|
"user"
|
||||||
) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
|
) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
|
||||||
if kwargs.get("instance"):
|
if kwargs.get("instance"):
|
||||||
# We get the 'initial' keyword argument or initialize it
|
# We get the 'initial' keyword argument or initialize it
|
||||||
# as a dict if it didn't exist.
|
# as a dict if it didn't exist.
|
||||||
initial = kwargs.setdefault("initial", {})
|
initial = kwargs.setdefault("initial", {})
|
||||||
# The widget for a ModelMultipleChoiceField expects
|
# The widget for a ModelMultipleChoiceField expects
|
||||||
# a list of primary key for the selected data.
|
# a list of primary key for the selected data.
|
||||||
initial["long"] = [t.pk for t in kwargs["instance"].long.all()]
|
initial["long"] = [t.pk for t in kwargs["instance"].long.all()]
|
||||||
|
|
||||||
super(LongSeriesForm, self).__init__(*args, **kwargs)
|
super(LongSeriesForm, self).__init__(*args, **kwargs)
|
||||||
# self.fields["examination"] = ModelChoiceField(
|
# self.fields["examination"] = ModelChoiceField(
|
||||||
# queryset=Examination.objects.all(),
|
# queryset=Examination.objects.all(),
|
||||||
# widget=FilteredSelectMultiple(verbose_name="Examination", is_stacked=False),
|
# widget=FilteredSelectMultiple(verbose_name="Examination", is_stacked=False),
|
||||||
# )
|
# )
|
||||||
|
|
||||||
self.fields["modality"] = ModelChoiceField(
|
self.fields["modality"] = ModelChoiceField(
|
||||||
required=True,
|
required=True,
|
||||||
queryset=Modality.objects.all(),
|
queryset=Modality.objects.all(),
|
||||||
)
|
)
|
||||||
|
|
||||||
ModelForm.__init__(self, *args, **kwargs)
|
ModelForm.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
if self.user.groups.filter(name="long_checker").exists():
|
if self.user.groups.filter(name="long_checker").exists():
|
||||||
long_queryset = Long.objects.all()
|
long_queryset = Long.objects.all()
|
||||||
else:
|
else:
|
||||||
long_queryset = Long.objects.filter(
|
long_queryset = Long.objects.filter(
|
||||||
author__id=self.user.id
|
author__id=self.user.id
|
||||||
) | Long.objects.filter(open_access=True)
|
) | Long.objects.filter(open_access=True)
|
||||||
|
|
||||||
self.fields["long"] = ModelMultipleChoiceField(
|
self.fields["long"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=long_queryset,
|
queryset=long_queryset,
|
||||||
widget=FilteredSelectMultiple(verbose_name="Long", is_stacked=False),
|
widget=FilteredSelectMultiple(verbose_name="Long", is_stacked=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
# Get the unsaved Pizza instance
|
# Get the unsaved Pizza instance
|
||||||
instance = ModelForm.save(self, False)
|
instance = ModelForm.save(self, False)
|
||||||
|
|
||||||
# Prepare a 'save_m2m' method for the form,
|
# Prepare a 'save_m2m' method for the form,
|
||||||
old_save_m2m = self.save_m2m
|
old_save_m2m = self.save_m2m
|
||||||
|
|
||||||
def save_m2m():
|
def save_m2m():
|
||||||
old_save_m2m()
|
old_save_m2m()
|
||||||
# This is where we actually link the pizza with toppings
|
# This is where we actually link the pizza with toppings
|
||||||
instance.long.clear()
|
instance.long.clear()
|
||||||
for long in self.cleaned_data["long"]:
|
for long in self.cleaned_data["long"]:
|
||||||
instance.long.add(long)
|
instance.long.add(long)
|
||||||
|
|
||||||
self.save_m2m = save_m2m
|
self.save_m2m = save_m2m
|
||||||
|
|
||||||
# Do we need to save all changes now?
|
# Do we need to save all changes now?
|
||||||
# if commit:
|
# if commit:
|
||||||
instance.save()
|
instance.save()
|
||||||
self.save_m2m()
|
self.save_m2m()
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = LongSeries
|
model = LongSeries
|
||||||
exclude = ["author"]
|
exclude = ["author"]
|
||||||
|
|
||||||
|
|
||||||
class LongForm(ModelForm):
|
class LongForm(ModelForm):
|
||||||
|
|
||||||
# exams = ModelMultipleChoiceField(required=False, queryset=Exam.objects.all(),widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False))
|
# exams = ModelMultipleChoiceField(required=False, queryset=Exam.objects.all(),widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False))
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
# Django also includes a few javascript files necessary
|
# Django also includes a few javascript files necessary
|
||||||
# for the operation of this form element. You need to
|
# for the operation of this form element. You need to
|
||||||
# include <script src="/admin/jsi18n"></script>
|
# include <script src="/admin/jsi18n"></script>
|
||||||
# in the template.
|
# in the template.
|
||||||
css = {
|
css = {
|
||||||
"all": ["css/widgets.css"],
|
"all": ["css/widgets.css"],
|
||||||
}
|
}
|
||||||
# Adding this javascript is crucial
|
# Adding this javascript is crucial
|
||||||
js = ["jsi18n.js", "tesseract.min.js"]
|
js = ["jsi18n.js", "tesseract.min.js"]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.user = kwargs.pop(
|
self.user = kwargs.pop(
|
||||||
"user"
|
"user"
|
||||||
) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
|
) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
|
||||||
if kwargs.get("instance"):
|
if kwargs.get("instance"):
|
||||||
# We get the 'initial' keyword argument or initialize it
|
# We get the 'initial' keyword argument or initialize it
|
||||||
# as a dict if it didn't exist.
|
# as a dict if it didn't exist.
|
||||||
initial = kwargs.setdefault("initial", {})
|
initial = kwargs.setdefault("initial", {})
|
||||||
# The widget for a ModelMultipleChoiceField expects
|
# The widget for a ModelMultipleChoiceField expects
|
||||||
# a list of primary key for the selected data.
|
# a list of primary key for the selected data.
|
||||||
initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()]
|
initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()]
|
||||||
|
|
||||||
ModelForm.__init__(self, *args, **kwargs)
|
ModelForm.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
super(LongForm, self).__init__(*args, **kwargs)
|
super(LongForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
if self.user.groups.filter(name="long_checker").exists():
|
if self.user.groups.filter(name="long_checker").exists():
|
||||||
exam_queryset = Exam.objects.all()
|
exam_queryset = Exam.objects.all()
|
||||||
else:
|
else:
|
||||||
exam_queryset = Exam.objects.filter(
|
exam_queryset = Exam.objects.filter(
|
||||||
author__id=self.user.id
|
author__id=self.user.id
|
||||||
) | Exam.objects.filter(open_access=True)
|
) | Exam.objects.filter(open_access=True)
|
||||||
|
|
||||||
self.fields["exams"] = ModelMultipleChoiceField(
|
self.fields["exams"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=exam_queryset,
|
queryset=exam_queryset,
|
||||||
widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False),
|
widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Long
|
model = Long
|
||||||
# fields = ['due_back']
|
# fields = ['due_back']
|
||||||
# fields = '__all__'
|
# fields = '__all__'
|
||||||
fields = [
|
fields = [
|
||||||
# "examination",
|
# "examination",
|
||||||
# "site",
|
# "site",
|
||||||
"description",
|
"description",
|
||||||
"history",
|
"history",
|
||||||
"feedback",
|
"feedback",
|
||||||
"condition",
|
"condition",
|
||||||
"sign",
|
"sign",
|
||||||
"model_observations",
|
"model_observations",
|
||||||
"model_interpretation",
|
"model_interpretation",
|
||||||
"model_principle_diagnosis",
|
"model_principle_diagnosis",
|
||||||
"model_differential_diagnosis",
|
"model_differential_diagnosis",
|
||||||
"model_management",
|
"model_management",
|
||||||
"mark_scheme",
|
"mark_scheme",
|
||||||
"open_access",
|
"open_access",
|
||||||
]
|
]
|
||||||
# fields = ['question', 'feedback', 'subspecialty', 'references']
|
# fields = ['question', 'feedback', 'subspecialty', 'references']
|
||||||
widgets = {
|
widgets = {
|
||||||
# "normal": RadioSelect(
|
# "normal": RadioSelect(
|
||||||
# choices=[(True, 'Yes'),
|
# choices=[(True, 'Yes'),
|
||||||
# (False, 'No')])
|
# (False, 'No')])
|
||||||
"feedback": TinyMCE(attrs={"cols": 80, "rows": 20}),
|
"feedback": TinyMCE(attrs={"cols": 80, "rows": 20}),
|
||||||
"mark_scheme": TinyMCE(attrs={"cols": 80, "rows": 30}),
|
"mark_scheme": TinyMCE(attrs={"cols": 80, "rows": 30}),
|
||||||
"description": Textarea(attrs={"cols": 80, "rows": 5}),
|
"description": Textarea(attrs={"cols": 80, "rows": 5}),
|
||||||
"history": Textarea(attrs={"cols": 80, "rows": 5}),
|
"history": Textarea(attrs={"cols": 80, "rows": 5}),
|
||||||
"model_observations": Textarea(attrs={"cols": 80, "rows": 5}),
|
"model_observations": Textarea(attrs={"cols": 80, "rows": 5}),
|
||||||
"model_interpretation": Textarea(attrs={"cols": 80, "rows": 5}),
|
"model_interpretation": Textarea(attrs={"cols": 80, "rows": 5}),
|
||||||
"model_principle_diagnosis": Textarea(attrs={"cols": 80, "rows": 5}),
|
"model_principle_diagnosis": Textarea(attrs={"cols": 80, "rows": 5}),
|
||||||
"model_differential_diagnosis": Textarea(attrs={"cols": 80, "rows": 5}),
|
"model_differential_diagnosis": Textarea(attrs={"cols": 80, "rows": 5}),
|
||||||
"model_management": Textarea(attrs={"cols": 80, "rows": 5}),
|
"model_management": Textarea(attrs={"cols": 80, "rows": 5}),
|
||||||
}
|
}
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
instance = ModelForm.save(self, False)
|
instance = ModelForm.save(self, False)
|
||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
old_exams = instance.exams.all()
|
old_exams = instance.exams.all()
|
||||||
|
|
||||||
new_exams = self.cleaned_data["exams"]
|
new_exams = self.cleaned_data["exams"]
|
||||||
|
|
||||||
for exam in old_exams:
|
for exam in old_exams:
|
||||||
if exam not in new_exams:
|
if exam not in new_exams:
|
||||||
exam.exam_questions.remove(instance)
|
exam.exam_questions.remove(instance)
|
||||||
|
|
||||||
for exam in new_exams:
|
for exam in new_exams:
|
||||||
exam.exam_questions.add(instance)
|
exam.exam_questions.add(instance)
|
||||||
self.save_m2m()
|
self.save_m2m()
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
SeriesFormSet = inlineformset_factory(
|
SeriesFormSet = inlineformset_factory(
|
||||||
Long,
|
Long,
|
||||||
LongSeries.long.through,
|
LongSeries.long.through,
|
||||||
exclude=[],
|
exclude=[],
|
||||||
can_delete=True,
|
can_delete=True,
|
||||||
extra=0,
|
extra=0,
|
||||||
max_num=10,
|
max_num=10,
|
||||||
)
|
)
|
||||||
|
|
||||||
LongSeriesImageFormSet = inlineformset_factory(
|
LongSeriesImageFormSet = inlineformset_factory(
|
||||||
LongSeries,
|
LongSeries,
|
||||||
LongSeriesImage,
|
LongSeriesImage,
|
||||||
exclude=[],
|
exclude=[],
|
||||||
can_delete=True,
|
can_delete=True,
|
||||||
extra=0,
|
extra=0,
|
||||||
max_num=2000,
|
max_num=2000,
|
||||||
field_classes="testing",
|
field_classes="testing",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ExamForm(ModelForm):
|
class ExamForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Exam
|
model = Exam
|
||||||
fields = [
|
fields = [
|
||||||
"name",
|
"name",
|
||||||
"time_limit",
|
"time_limit",
|
||||||
"open_access",
|
"open_access",
|
||||||
"exam_mode",
|
"exam_mode",
|
||||||
"active",
|
"active",
|
||||||
"publish_results",
|
"publish_results",
|
||||||
"double_mark",
|
"double_mark",
|
||||||
"archive",
|
"archive",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Longs
|
Longs
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block navigation %}
|
{% block navigation %}
|
||||||
Longs:
|
Longs:
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<a href="{% url 'longs:exam_list' %}">Exams</a> /
|
<a href="{% url 'longs:exam_list' %}">Exams</a> /
|
||||||
<a href="{% url 'longs:exam_create' %}" title="Create a new exam">Create Exam</a> /
|
<a href="{% url 'longs:exam_create' %}" title="Create a new exam">Create Exam</a> /
|
||||||
<a href="{% url 'longs:long_view' %}">Cases</a> /
|
<a href="{% url 'longs:long_view' %}">Cases</a> /
|
||||||
<a href="{% url 'longs:long_series_view' %}">Series</a> /
|
<a href="{% url 'longs:long_series_view' %}">Series</a> /
|
||||||
<a href="{% url 'longs:long_create' %}" title="Create a new long case">Create Case</a> /
|
<a href="{% url 'longs:long_create' %}" title="Create a new long case">Create Case</a> /
|
||||||
<a href="{% url 'longs:long_series_create' %}" title="Create a new image series">Create Series</a>
|
<a href="{% url 'longs:long_series_create' %}" title="Create a new image series">Create Series</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if request.user.is_superuser %}
|
{% if request.user.is_superuser %}
|
||||||
/ <a href="{% url 'longs:user_answer_table_view' %}" title="User answers">Answers</a>
|
/ <a href="{% url 'longs:user_answer_table_view' %}" title="User answers">Answers</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% comment %} </br>
|
{% comment %} </br>
|
||||||
Questions by:
|
Questions by:
|
||||||
<span id="authors-link"><a href="{% url 'longs:author_list' %}">author</a></span> {% endcomment %}
|
<span id="authors-link"><a href="{% url 'longs:author_list' %}">author</a></span> {% endcomment %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
+1339
-1339
File diff suppressed because it is too large
Load Diff
+268
-268
@@ -1,268 +1,268 @@
|
|||||||
"""
|
"""
|
||||||
Django settings for rad project.
|
Django settings for rad project.
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 1.11.14.
|
Generated by 'django-admin startproject' using Django 1.11.14.
|
||||||
|
|
||||||
For more information on this file, see
|
For more information on this file, see
|
||||||
https://docs.djangoproject.com/en/1.11/topics/settings/
|
https://docs.djangoproject.com/en/1.11/topics/settings/
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
For the full list of settings and their values, see
|
||||||
https://docs.djangoproject.com/en/1.11/ref/settings/
|
https://docs.djangoproject.com/en/1.11/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
with open("/etc/secret_key") as f:
|
with open("/etc/secret_key") as f:
|
||||||
SECRET_KEY = f.read().strip()
|
SECRET_KEY = f.read().strip()
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = [
|
ALLOWED_HOSTS = [
|
||||||
"localhost",
|
"localhost",
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
"161.35.163.87",
|
"161.35.163.87",
|
||||||
"penracourses.org.uk",
|
"penracourses.org.uk",
|
||||||
"www.penracourses.org.uk",
|
"www.penracourses.org.uk",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
"generic",
|
"generic",
|
||||||
"anatomy",
|
"anatomy",
|
||||||
"physics",
|
"physics",
|
||||||
"rapids",
|
"rapids",
|
||||||
"longs",
|
"longs",
|
||||||
"sbas",
|
"sbas",
|
||||||
"wally",
|
"wally",
|
||||||
"atlas",
|
"atlas",
|
||||||
"reversion",
|
"reversion",
|
||||||
"django_tables2",
|
"django_tables2",
|
||||||
"django_filters",
|
"django_filters",
|
||||||
"easy_thumbnails",
|
"easy_thumbnails",
|
||||||
"filer",
|
"filer",
|
||||||
"mptt",
|
"mptt",
|
||||||
"sortedm2m",
|
"sortedm2m",
|
||||||
"dal",
|
"dal",
|
||||||
"dal_select2",
|
"dal_select2",
|
||||||
"dal_queryset_sequence",
|
"dal_queryset_sequence",
|
||||||
"django.contrib.admin",
|
"django.contrib.admin",
|
||||||
"django.contrib.auth",
|
"django.contrib.auth",
|
||||||
"django.contrib.contenttypes",
|
"django.contrib.contenttypes",
|
||||||
"django.contrib.sessions",
|
"django.contrib.sessions",
|
||||||
"django.contrib.messages",
|
"django.contrib.messages",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
"debug_toolbar",
|
"debug_toolbar",
|
||||||
"tagulous",
|
"tagulous",
|
||||||
"dbbackup",
|
"dbbackup",
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
"tinymce",
|
"tinymce",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
"corsheaders.middleware.CorsMiddleware",
|
"corsheaders.middleware.CorsMiddleware",
|
||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
"django.middleware.common.CommonMiddleware",
|
"django.middleware.common.CommonMiddleware",
|
||||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
"django.contrib.messages.middleware.MessageMiddleware",
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
"reversion.middleware.RevisionMiddleware",
|
"reversion.middleware.RevisionMiddleware",
|
||||||
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = "rad.urls"
|
ROOT_URLCONF = "rad.urls"
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
"DIRS": [os.path.join(BASE_DIR, "templates")],
|
"DIRS": [os.path.join(BASE_DIR, "templates")],
|
||||||
"APP_DIRS": True,
|
"APP_DIRS": True,
|
||||||
"OPTIONS": {
|
"OPTIONS": {
|
||||||
"context_processors": [
|
"context_processors": [
|
||||||
"django.template.context_processors.debug",
|
"django.template.context_processors.debug",
|
||||||
"django.template.context_processors.request",
|
"django.template.context_processors.request",
|
||||||
"django.contrib.auth.context_processors.auth",
|
"django.contrib.auth.context_processors.auth",
|
||||||
"django.contrib.messages.context_processors.messages",
|
"django.contrib.messages.context_processors.messages",
|
||||||
"django.template.context_processors.static",
|
"django.template.context_processors.static",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = "rad.wsgi.application"
|
WSGI_APPLICATION = "rad.wsgi.application"
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
|
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.postgresql",
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
"NAME": "rad",
|
"NAME": "rad",
|
||||||
"USER": "django",
|
"USER": "django",
|
||||||
"PASSWORD": "f7bf31dc9bda1256ea827953480d1917",
|
"PASSWORD": "f7bf31dc9bda1256ea827953480d1917",
|
||||||
# "HOST": "xkjq.uk",
|
# "HOST": "xkjq.uk",
|
||||||
"HOST": "161.35.163.87",
|
"HOST": "161.35.163.87",
|
||||||
"PORT": "5432",
|
"PORT": "5432",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{
|
||||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
SERIALIZATION_MODULES = {
|
SERIALIZATION_MODULES = {
|
||||||
"xml": "tagulous.serializers.xml_serializer",
|
"xml": "tagulous.serializers.xml_serializer",
|
||||||
"json": "tagulous.serializers.json",
|
"json": "tagulous.serializers.json",
|
||||||
"python": "tagulous.serializers.python",
|
"python": "tagulous.serializers.python",
|
||||||
"yaml": "tagulous.serializers.pyyaml",
|
"yaml": "tagulous.serializers.pyyaml",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/1.11/topics/i18n/
|
# https://docs.djangoproject.com/en/1.11/topics/i18n/
|
||||||
|
|
||||||
LANGUAGE_CODE = "en-us"
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
TIME_ZONE = "Europe/London"
|
TIME_ZONE = "Europe/London"
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/1.11/howto/static-files/
|
# https://docs.djangoproject.com/en/1.11/howto/static-files/
|
||||||
|
|
||||||
SECURE_SSL_REDIRECT = True
|
SECURE_SSL_REDIRECT = True
|
||||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||||
|
|
||||||
STATIC_URL = "/static/"
|
STATIC_URL = "/static/"
|
||||||
STATIC_ROOT = "static/"
|
STATIC_ROOT = "static/"
|
||||||
|
|
||||||
REMOTE_URL = "https://www.penracourses.org.uk"
|
REMOTE_URL = "https://www.penracourses.org.uk"
|
||||||
#
|
#
|
||||||
# if DEBUG:
|
# if DEBUG:
|
||||||
# STATIC_URL = REMOTE_URL + STATIC_URL
|
# STATIC_URL = REMOTE_URL + STATIC_URL
|
||||||
|
|
||||||
MEDIA_URL = "/media/"
|
MEDIA_URL = "/media/"
|
||||||
MEDIA_ROOT = "/home/django/rad/media/"
|
MEDIA_ROOT = "/home/django/rad/media/"
|
||||||
|
|
||||||
# if DEBUG:
|
# if DEBUG:
|
||||||
# MEDIA_URL = REMOTE_URL + MEDIA_URL
|
# MEDIA_URL = REMOTE_URL + MEDIA_URL
|
||||||
|
|
||||||
# Redirect to home URL after login (Default redirects to /accounts/profile/)
|
# Redirect to home URL after login (Default redirects to /accounts/profile/)
|
||||||
LOGIN_REDIRECT_URL = "/"
|
LOGIN_REDIRECT_URL = "/"
|
||||||
LOGOUT_REDIRECT_URL = "/"
|
LOGOUT_REDIRECT_URL = "/"
|
||||||
|
|
||||||
TAGULOUS_AUTOCOMPLETE_JS = (
|
TAGULOUS_AUTOCOMPLETE_JS = (
|
||||||
"tagulous/lib/jquery.js",
|
"tagulous/lib/jquery.js",
|
||||||
"tagulous/lib/select2-3/select2.min.js",
|
"tagulous/lib/select2-3/select2.min.js",
|
||||||
"tagulous/tagulous.js",
|
"tagulous/tagulous.js",
|
||||||
"tagulous/adaptor/select2-3.js",
|
"tagulous/adaptor/select2-3.js",
|
||||||
)
|
)
|
||||||
|
|
||||||
TAGULOUS_AUTOCOMPLETE_CSS = {"all": ["tagulous/lib/select2-3/select2.css"]}
|
TAGULOUS_AUTOCOMPLETE_CSS = {"all": ["tagulous/lib/select2-3/select2.css"]}
|
||||||
|
|
||||||
INTERNAL_IPS = ["localhost", "127.0.0.1"]
|
INTERNAL_IPS = ["localhost", "127.0.0.1"]
|
||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"disable_existing_loggers": False,
|
"disable_existing_loggers": False,
|
||||||
"incremental": True,
|
"incremental": True,
|
||||||
"root": {
|
"root": {
|
||||||
"level": "DEBUG",
|
"level": "DEBUG",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
CORS_ORIGIN_ALLOW_ALL = True
|
CORS_ORIGIN_ALLOW_ALL = True
|
||||||
|
|
||||||
CACHES = {
|
CACHES = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
|
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
|
||||||
"LOCATION": "127.0.0.1:11211",
|
"LOCATION": "127.0.0.1:11211",
|
||||||
#'OPTIONS': {
|
#'OPTIONS': {
|
||||||
# 'server_max_value_length': 1024 * 1024 * 10
|
# 'server_max_value_length': 1024 * 1024 * 10
|
||||||
# }
|
# }
|
||||||
},
|
},
|
||||||
"filesystem": {
|
"filesystem": {
|
||||||
"BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
|
"BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
|
||||||
"LOCATION": "/var/tmp/django_cache",
|
"LOCATION": "/var/tmp/django_cache",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
THUMBNAIL_ALIASES = {
|
THUMBNAIL_ALIASES = {
|
||||||
"": {
|
"": {
|
||||||
"exam-list": {"size": (50, 50), "crop": True},
|
"exam-list": {"size": (50, 50), "crop": True},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DATA_UPLOAD_MAX_NUMBER_FIELDS = 50000
|
DATA_UPLOAD_MAX_NUMBER_FIELDS = 50000
|
||||||
|
|
||||||
THUMBNAIL_SOURCE_GENERATORS = (
|
THUMBNAIL_SOURCE_GENERATORS = (
|
||||||
"easy_thumbnails.source_generators.pil_image",
|
"easy_thumbnails.source_generators.pil_image",
|
||||||
"helpers.images.pil_dicom_image",
|
"helpers.images.pil_dicom_image",
|
||||||
)
|
)
|
||||||
DBBACKUP_STORAGE = "django.core.files.storage.FileSystemStorage"
|
DBBACKUP_STORAGE = "django.core.files.storage.FileSystemStorage"
|
||||||
DBBACKUP_STORAGE_OPTIONS = {"location": os.path.join(BASE_DIR, "../backups")}
|
DBBACKUP_STORAGE_OPTIONS = {"location": os.path.join(BASE_DIR, "../backups")}
|
||||||
|
|
||||||
DEFAULT_FILE_STORAGE = (
|
DEFAULT_FILE_STORAGE = (
|
||||||
"django_hashedfilenamestorage.storage.HashedFilenameFileSystemStorage"
|
"django_hashedfilenamestorage.storage.HashedFilenameFileSystemStorage"
|
||||||
)
|
)
|
||||||
|
|
||||||
TINYMCE_DEFAULT_CONFIG = {
|
TINYMCE_DEFAULT_CONFIG = {
|
||||||
"theme": "silver",
|
"theme": "silver",
|
||||||
"height": 500,
|
"height": 500,
|
||||||
"menubar": False,
|
"menubar": False,
|
||||||
"plugins": "advlist,autolink,lists,link,image,charmap,print,preview,anchor,"
|
"plugins": "advlist,autolink,lists,link,image,charmap,print,preview,anchor,"
|
||||||
"searchreplace,visualblocks,code,fullscreen,insertdatetime,media,table,paste,"
|
"searchreplace,visualblocks,code,fullscreen,insertdatetime,media,table,paste,"
|
||||||
"code,help,wordcount",
|
"code,help,wordcount",
|
||||||
"toolbar": "undo redo | formatselect | code"
|
"toolbar": "undo redo | formatselect | code"
|
||||||
"bold italic backcolor | alignleft aligncenter "
|
"bold italic backcolor | alignleft aligncenter "
|
||||||
"alignright alignjustify | bullist numlist outdent indent | "
|
"alignright alignjustify | bullist numlist outdent indent | "
|
||||||
"removeformat | code | help",
|
"removeformat | code | help",
|
||||||
"skin": "oxide-dark",
|
"skin": "oxide-dark",
|
||||||
"menubar": "tools",
|
"menubar": "tools",
|
||||||
"content_css": "dark",
|
"content_css": "dark",
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||||
|
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
||||||
|
|
||||||
EMAIL_HOST = "smtp.office365.com "
|
EMAIL_HOST = "smtp.office365.com "
|
||||||
EMAIL_HOST_USER = "Penra.courses@nhs.net"
|
EMAIL_HOST_USER = "Penra.courses@nhs.net"
|
||||||
EMAIL_HOST_PASSWORD = '"got3<}%o"~\J",\Di3<'
|
EMAIL_HOST_PASSWORD = '"got3<}%o"~\J",\Di3<'
|
||||||
EMAIL_PORT = 587
|
EMAIL_PORT = 587
|
||||||
EMAIL_USE_TLS = True
|
EMAIL_USE_TLS = True
|
||||||
EMAIL_USE_SSl = False
|
EMAIL_USE_SSl = False
|
||||||
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .settings_local import *
|
from .settings_local import *
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
+241
-241
@@ -1,241 +1,241 @@
|
|||||||
from django.forms import (
|
from django.forms import (
|
||||||
Form,
|
Form,
|
||||||
ModelForm,
|
ModelForm,
|
||||||
ModelMultipleChoiceField,
|
ModelMultipleChoiceField,
|
||||||
ModelChoiceField,
|
ModelChoiceField,
|
||||||
ChoiceField,
|
ChoiceField,
|
||||||
CharField,
|
CharField,
|
||||||
)
|
)
|
||||||
from django.forms import inlineformset_factory
|
from django.forms import inlineformset_factory
|
||||||
|
|
||||||
from rapids.models import (
|
from rapids.models import (
|
||||||
Abnormality,
|
Abnormality,
|
||||||
Examination,
|
Examination,
|
||||||
Rapid,
|
Rapid,
|
||||||
RapidCreationDefault,
|
RapidCreationDefault,
|
||||||
RapidImage,
|
RapidImage,
|
||||||
Region,
|
Region,
|
||||||
Answer,
|
Answer,
|
||||||
CidUserAnswer,
|
CidUserAnswer,
|
||||||
Exam,
|
Exam,
|
||||||
)
|
)
|
||||||
|
|
||||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||||
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
||||||
|
|
||||||
|
|
||||||
class RapidAnswerForm(ModelForm):
|
class RapidAnswerForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CidUserAnswer
|
model = CidUserAnswer
|
||||||
fields = ("answer",)
|
fields = ("answer",)
|
||||||
|
|
||||||
|
|
||||||
class MarkRapidQuestionForm(Form):
|
class MarkRapidQuestionForm(Form):
|
||||||
# correct = forms.CharField(required=False)
|
# correct = forms.CharField(required=False)
|
||||||
# half_correct = forms.CharField(required=False)
|
# half_correct = forms.CharField(required=False)
|
||||||
# incorrect = forms.CharField(required=False)
|
# incorrect = forms.CharField(required=False)
|
||||||
marked_answers = CharField(required=False)
|
marked_answers = CharField(required=False)
|
||||||
|
|
||||||
|
|
||||||
class ExaminationForm(ModelForm):
|
class ExaminationForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Examination
|
model = Examination
|
||||||
fields = ["examination"]
|
fields = ["examination"]
|
||||||
|
|
||||||
|
|
||||||
class RegionForm(ModelForm):
|
class RegionForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Region
|
model = Region
|
||||||
fields = ["name"]
|
fields = ["name"]
|
||||||
|
|
||||||
|
|
||||||
class AbnormalityForm(ModelForm):
|
class AbnormalityForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Abnormality
|
model = Abnormality
|
||||||
fields = ["name"]
|
fields = ["name"]
|
||||||
|
|
||||||
|
|
||||||
class RapidCreationDefaultForm(ModelForm):
|
class RapidCreationDefaultForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RapidCreationDefault
|
model = RapidCreationDefault
|
||||||
# fields = ["site"]
|
# fields = ["site"]
|
||||||
exclude = ["author"]
|
exclude = ["author"]
|
||||||
|
|
||||||
|
|
||||||
class RapidForm(ModelForm):
|
class RapidForm(ModelForm):
|
||||||
|
|
||||||
# exams = ModelMultipleChoiceField(required=False, queryset=Exam.objects.all(),widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False))
|
# exams = ModelMultipleChoiceField(required=False, queryset=Exam.objects.all(),widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False))
|
||||||
class Media:
|
class Media:
|
||||||
# Django also includes a few javascript files necessary
|
# Django also includes a few javascript files necessary
|
||||||
# for the operation of this form element. You need to
|
# for the operation of this form element. You need to
|
||||||
# include <script src="/admin/jsi18n"></script>
|
# include <script src="/admin/jsi18n"></script>
|
||||||
# in the template.
|
# in the template.
|
||||||
css = {
|
css = {
|
||||||
"all": ["css/widgets.css"],
|
"all": ["css/widgets.css"],
|
||||||
}
|
}
|
||||||
# Adding this javascript is crucial
|
# Adding this javascript is crucial
|
||||||
js = ["jsi18n.js", "tesseract.min.js"]
|
js = ["jsi18n.js", "tesseract.min.js"]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.user = kwargs.pop(
|
self.user = kwargs.pop(
|
||||||
"user"
|
"user"
|
||||||
) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
|
) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
|
||||||
if kwargs.get("instance"):
|
if kwargs.get("instance"):
|
||||||
# We get the 'initial' keyword argument or initialize it
|
# We get the 'initial' keyword argument or initialize it
|
||||||
# as a dict if it didn't exist.
|
# as a dict if it didn't exist.
|
||||||
initial = kwargs.setdefault("initial", {})
|
initial = kwargs.setdefault("initial", {})
|
||||||
# The widget for a ModelMultipleChoiceField expects
|
# The widget for a ModelMultipleChoiceField expects
|
||||||
# a list of primary key for the selected data.
|
# a list of primary key for the selected data.
|
||||||
initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()]
|
initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()]
|
||||||
|
|
||||||
ModelForm.__init__(self, *args, **kwargs)
|
ModelForm.__init__(self, *args, **kwargs)
|
||||||
super(RapidForm, self).__init__(*args, **kwargs)
|
super(RapidForm, self).__init__(*args, **kwargs)
|
||||||
# self.fields['question'].widget.attrs = {'class': 'question-form', 'rows': 10, 'cols': 100}
|
# 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['feedback'].widget.attrs = {'class': 'feedback-form', 'rows': 10, 'cols': 100}
|
||||||
|
|
||||||
self.fields["abnormality"] = ModelMultipleChoiceField(
|
self.fields["abnormality"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=Abnormality.objects.all(),
|
queryset=Abnormality.objects.all(),
|
||||||
widget=FilteredSelectMultiple(verbose_name="Abnormality", is_stacked=False),
|
widget=FilteredSelectMultiple(verbose_name="Abnormality", is_stacked=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.fields["region"] = ModelMultipleChoiceField(
|
self.fields["region"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
widget=FilteredSelectMultiple(verbose_name="Region", is_stacked=False),
|
widget=FilteredSelectMultiple(verbose_name="Region", is_stacked=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.fields["examination"] = ModelMultipleChoiceField(
|
self.fields["examination"] = ModelMultipleChoiceField(
|
||||||
queryset=Examination.objects.all(),
|
queryset=Examination.objects.all(),
|
||||||
widget=FilteredSelectMultiple(verbose_name="Examination", is_stacked=False),
|
widget=FilteredSelectMultiple(verbose_name="Examination", is_stacked=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.fields["laterality"] = ChoiceField(
|
self.fields["laterality"] = ChoiceField(
|
||||||
choices=Rapid.LATERALITY_CHOICES, required=False, widget=RadioSelect()
|
choices=Rapid.LATERALITY_CHOICES, required=False, widget=RadioSelect()
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.user.groups.filter(name="rapid_checker").exists():
|
if self.user.groups.filter(name="rapid_checker").exists():
|
||||||
exam_queryset = Exam.objects.all()
|
exam_queryset = Exam.objects.all()
|
||||||
else:
|
else:
|
||||||
exam_queryset = Exam.objects.filter(
|
exam_queryset = Exam.objects.filter(
|
||||||
author__id=self.user.id
|
author__id=self.user.id
|
||||||
) | Exam.objects.filter(open_access=True)
|
) | Exam.objects.filter(open_access=True)
|
||||||
|
|
||||||
self.fields["exams"] = ModelMultipleChoiceField(
|
self.fields["exams"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=exam_queryset,
|
queryset=exam_queryset,
|
||||||
widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False),
|
widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
instance = ModelForm.save(self, False)
|
instance = ModelForm.save(self, False)
|
||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
old_exams = instance.exams.all()
|
old_exams = instance.exams.all()
|
||||||
|
|
||||||
new_exams = self.cleaned_data["exams"]
|
new_exams = self.cleaned_data["exams"]
|
||||||
|
|
||||||
for exam in old_exams:
|
for exam in old_exams:
|
||||||
if exam not in new_exams:
|
if exam not in new_exams:
|
||||||
exam.exam_questions.remove(instance)
|
exam.exam_questions.remove(instance)
|
||||||
|
|
||||||
for exam in new_exams:
|
for exam in new_exams:
|
||||||
exam.exam_questions.add(instance)
|
exam.exam_questions.add(instance)
|
||||||
#exam.save()
|
#exam.save()
|
||||||
|
|
||||||
# # Prepare a 'save_m2m' method for the form,
|
# # Prepare a 'save_m2m' method for the form,
|
||||||
# old_save_m2m = self.save_m2m
|
# old_save_m2m = self.save_m2m
|
||||||
#
|
#
|
||||||
# def save_m2m():
|
# def save_m2m():
|
||||||
# old_save_m2m()
|
# old_save_m2m()
|
||||||
# # This is where we actually link the pizza with toppings
|
# # This is where we actually link the pizza with toppings
|
||||||
# instance.exams.clear()
|
# instance.exams.clear()
|
||||||
# for exam in self.cleaned_data["exams"]:
|
# for exam in self.cleaned_data["exams"]:
|
||||||
# instance.exams.add(exam)
|
# instance.exams.add(exam)
|
||||||
#
|
#
|
||||||
# self.save_m2m = save_m2m
|
# self.save_m2m = save_m2m
|
||||||
#
|
#
|
||||||
# # Do we need to save all changes now?
|
# # Do we need to save all changes now?
|
||||||
# # if commit:
|
# # if commit:
|
||||||
# instance.save()
|
# instance.save()
|
||||||
self.save_m2m()
|
self.save_m2m()
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Rapid
|
model = Rapid
|
||||||
# fields = ['due_back']
|
# fields = ['due_back']
|
||||||
# fields = '__all__'
|
# fields = '__all__'
|
||||||
fields = [
|
fields = [
|
||||||
"normal",
|
"normal",
|
||||||
"abnormality",
|
"abnormality",
|
||||||
"region",
|
"region",
|
||||||
"laterality",
|
"laterality",
|
||||||
"examination",
|
"examination",
|
||||||
# "site",
|
# "site",
|
||||||
"feedback",
|
"feedback",
|
||||||
"open_access",
|
"open_access",
|
||||||
]
|
]
|
||||||
# fields = ['question', 'feedback', 'subspecialty', 'references']
|
# fields = ['question', 'feedback', 'subspecialty', 'references']
|
||||||
widgets = {
|
widgets = {
|
||||||
# "normal": RadioSelect(
|
# "normal": RadioSelect(
|
||||||
# choices=[(True, 'Yes'),
|
# choices=[(True, 'Yes'),
|
||||||
# (False, 'No')])
|
# (False, 'No')])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ImageFormSet = inlineformset_factory(
|
ImageFormSet = inlineformset_factory(
|
||||||
Rapid,
|
Rapid,
|
||||||
RapidImage,
|
RapidImage,
|
||||||
fields=["image", "feedback_image"],
|
fields=["image", "feedback_image"],
|
||||||
exclude=[],
|
exclude=[],
|
||||||
can_delete=True,
|
can_delete=True,
|
||||||
extra=1,
|
extra=1,
|
||||||
max_num=10,
|
max_num=10,
|
||||||
field_classes="testing",
|
field_classes="testing",
|
||||||
)
|
)
|
||||||
|
|
||||||
AnswerFormSet = inlineformset_factory(
|
AnswerFormSet = inlineformset_factory(
|
||||||
Rapid,
|
Rapid,
|
||||||
Answer,
|
Answer,
|
||||||
fields=["answer", "status"],
|
fields=["answer", "status"],
|
||||||
widgets={
|
widgets={
|
||||||
"answer": Textarea(
|
"answer": Textarea(
|
||||||
attrs={"required": "false", "minlength": 2, "maxlength": 500, "rows": 3}
|
attrs={"required": "false", "minlength": 2, "maxlength": 500, "rows": 3}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
exclude=[],
|
exclude=[],
|
||||||
can_delete=True,
|
can_delete=True,
|
||||||
extra=0,
|
extra=0,
|
||||||
max_num=10,
|
max_num=10,
|
||||||
)
|
)
|
||||||
|
|
||||||
AnswerUpdateFormSet = inlineformset_factory(
|
AnswerUpdateFormSet = inlineformset_factory(
|
||||||
Rapid,
|
Rapid,
|
||||||
Answer,
|
Answer,
|
||||||
fields=["answer", "status"],
|
fields=["answer", "status"],
|
||||||
widgets={
|
widgets={
|
||||||
"answer": Textarea(
|
"answer": Textarea(
|
||||||
attrs={"required": "false", "minlength": 2, "maxlength": 500, "rows": 3}
|
attrs={"required": "false", "minlength": 2, "maxlength": 500, "rows": 3}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
exclude=[],
|
exclude=[],
|
||||||
can_delete=True,
|
can_delete=True,
|
||||||
extra=0,
|
extra=0,
|
||||||
max_num=10,
|
max_num=10,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# This should be made generic?
|
# This should be made generic?
|
||||||
class ExamForm(ModelForm):
|
class ExamForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Exam
|
model = Exam
|
||||||
fields = [
|
fields = [
|
||||||
"name",
|
"name",
|
||||||
"time_limit",
|
"time_limit",
|
||||||
"open_access",
|
"open_access",
|
||||||
"exam_mode",
|
"exam_mode",
|
||||||
"active",
|
"active",
|
||||||
"publish_results",
|
"publish_results",
|
||||||
"archive",
|
"archive",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
{% extends 'rapids/exams.html' %}
|
{% extends 'rapids/exams.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="rapids">
|
<div class="rapids">
|
||||||
<h2>Marking exam: {{ exam.name }}</h2>
|
<h2>Marking exam: {{ exam.name }}</h2>
|
||||||
You can start marking from a particular question by clicking on it below.
|
You can start marking from a particular question by clicking on it below.
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button class="show-all-button">Show all</button><button class="show-unmarked-button">Show unmarked</button>
|
<button class="show-all-button">Show all</button><button class="show-unmarked-button">Show unmarked</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="stark-marking-button"><a href="{% url 'rapids:mark' exam_pk=exam.pk sk=0 %}"><button>Click here to start marking</button></a></div>
|
<div id="stark-marking-button"><a href="{% url 'rapids:mark' exam_pk=exam.pk sk=0 %}"><button>Click here to start marking</button></a></div>
|
||||||
|
|
||||||
<ul id="question-mark-list">
|
<ul id="question-mark-list">
|
||||||
{% for question, unmarked_count, unmarked_count2 in question_unmarked_map %}
|
{% for question, unmarked_count, unmarked_count2 in question_unmarked_map %}
|
||||||
<li data-markcount={{unmarked_count}} {% if unmarked_count %}class="unmarked" {% endif %}><a href="{% url 'rapids:mark' exam_pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter }}:
|
<li data-markcount={{unmarked_count}} {% if unmarked_count %}class="unmarked" {% endif %}><a href="{% url 'rapids:mark' exam_pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter }}:
|
||||||
{{ question }}</a><br />Unmarked answers: {{unmarked_count}}</li>
|
{{ question }}</a><br />Unmarked answers: {{unmarked_count}}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
+1290
-1290
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,16 @@
|
|||||||
{% extends "sbas/base.html" %}
|
{% extends "sbas/base.html" %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
{{ form.media }}
|
{{ form.media }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Add Exam</h2>
|
<h2>Add Exam</h2>
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<table>
|
<table>
|
||||||
{{ form.as_table }}
|
{{ form.as_table }}
|
||||||
</table>
|
</table>
|
||||||
<input type="submit" value="Submit">
|
<input type="submit" value="Submit">
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
+77
-77
@@ -1,77 +1,77 @@
|
|||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
from django.views.decorators.cache import cache_page
|
from django.views.decorators.cache import cache_page
|
||||||
|
|
||||||
app_name = "sbas"
|
app_name = "sbas"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# path('', views.question_list, name='question_list'),
|
# path('', views.question_list, name='question_list'),
|
||||||
path("", views.GenericExamViews.index, name="index"),
|
path("", views.GenericExamViews.index, name="index"),
|
||||||
path("question/", views.QuestionView.as_view(), name="question_view"),
|
path("question/", views.QuestionView.as_view(), name="question_view"),
|
||||||
path("question/<int:pk>/", views.question_detail, name="question_detail"),
|
path("question/<int:pk>/", views.question_detail, name="question_detail"),
|
||||||
# path("question/create/", views.QuestionCreate.as_view(), name="anatomy_question_create"),
|
# path("question/create/", views.QuestionCreate.as_view(), name="anatomy_question_create"),
|
||||||
# path("question/<int:pk>/update", views.QuestionUpdate.as_view(), name="anatomy_question_update"),
|
# path("question/<int:pk>/update", views.QuestionUpdate.as_view(), name="anatomy_question_update"),
|
||||||
# path("exam/<int:pk>/<int:sk>/mark", views.mark, name="mark"),
|
# 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>/mark", views.mark_overview, name="mark_overview"),
|
||||||
path(
|
path(
|
||||||
"exam/<int:pk>/<int:sk>/<str:cid>/<str:passcode>/take",
|
"exam/<int:pk>/<int:sk>/<str:cid>/<str:passcode>/take",
|
||||||
views.exam_take,
|
views.exam_take,
|
||||||
name="exam_take",
|
name="exam_take",
|
||||||
),
|
),
|
||||||
path("exam/<int:pk>/start", views.exam_start, name="exam_start"),
|
path("exam/<int:pk>/start", views.exam_start, name="exam_start"),
|
||||||
path(
|
path(
|
||||||
"exam/<int:pk>/<str:cid>/<str:passcode>/finish",
|
"exam/<int:pk>/<str:cid>/<str:passcode>/finish",
|
||||||
views.exam_take_overview,
|
views.exam_take_overview,
|
||||||
name="exam_take_overview",
|
name="exam_take_overview",
|
||||||
),
|
),
|
||||||
path("exam/<int:pk>/", views.GenericExamViews.exam_overview, name="exam_overview"),
|
path("exam/<int:pk>/", views.GenericExamViews.exam_overview, name="exam_overview"),
|
||||||
path(
|
path(
|
||||||
"exam/<int:pk>/scores",
|
"exam/<int:pk>/scores",
|
||||||
views.GenericExamViews.exam_scores_cid,
|
views.GenericExamViews.exam_scores_cid,
|
||||||
name="exam_scores_cid",
|
name="exam_scores_cid",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"exam/<int:pk>/scores/<int:cid>/<str:passcode>/",
|
"exam/<int:pk>/scores/<int:cid>/<str:passcode>/",
|
||||||
views.exam_scores_cid_user,
|
views.exam_scores_cid_user,
|
||||||
name="exam_scores_cid_user",
|
name="exam_scores_cid_user",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"exam/<int:pk>/toggle_active",
|
"exam/<int:pk>/toggle_active",
|
||||||
views.GenericExamViews.exam_toggle_active,
|
views.GenericExamViews.exam_toggle_active,
|
||||||
name="exam_toggle_active",
|
name="exam_toggle_active",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"exam/<int:pk>/toggle_results_published",
|
"exam/<int:pk>/toggle_results_published",
|
||||||
views.GenericExamViews.exam_toggle_results_published,
|
views.GenericExamViews.exam_toggle_results_published,
|
||||||
name="exam_toggle_results_published",
|
name="exam_toggle_results_published",
|
||||||
),
|
),
|
||||||
# path("exam/submit", views.GenericExamViews.postExamAnswers, name="exam_answers_submit"),
|
# path("exam/submit", views.GenericExamViews.postExamAnswers, name="exam_answers_submit"),
|
||||||
path("exam/", views.GenericExamViews.exam_list, name="exam_list"),
|
path("exam/", views.GenericExamViews.exam_list, name="exam_list"),
|
||||||
path("exam/all", views.GenericExamViews.exam_list_all, name="exam_list_all"),
|
path("exam/all", views.GenericExamViews.exam_list_all, name="exam_list_all"),
|
||||||
path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"),
|
path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"),
|
||||||
path("exam/<int:exam_id>/cids", views.GenericExamViews.exam_cids, name="exam_cids"),
|
path("exam/<int:exam_id>/cids", views.GenericExamViews.exam_cids, name="exam_cids"),
|
||||||
path("exam/available", views.active_exams, name="active_exams"),
|
path("exam/available", views.active_exams, name="active_exams"),
|
||||||
path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"),
|
path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"),
|
||||||
path("exam/<int:pk>/delete", views.ExamDelete.as_view(), name="exam_delete"),
|
path("exam/<int:pk>/delete", views.ExamDelete.as_view(), name="exam_delete"),
|
||||||
# path("exam/json/<int:pk>", views.exam_json, name="exam_json"),
|
# 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("exam/json/<int:pk>/recreate", views.exam_json_recreate, name="exam_json_recreate"),
|
||||||
path(
|
path(
|
||||||
"user_answers/",
|
"user_answers/",
|
||||||
views.UserAnswerTableView.as_view(),
|
views.UserAnswerTableView.as_view(),
|
||||||
name="user_answer_table_view",
|
name="user_answer_table_view",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"user_answers/<int:pk>", views.UserAnswerView.as_view(), name="user_answer_view"
|
"user_answers/<int:pk>", views.UserAnswerView.as_view(), name="user_answer_view"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"user_answers/<int:pk>/delete",
|
"user_answers/<int:pk>/delete",
|
||||||
views.UserAnswerDelete.as_view(),
|
views.UserAnswerDelete.as_view(),
|
||||||
name="user_answer_delete",
|
name="user_answer_delete",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"user_answers/delete",
|
"user_answers/delete",
|
||||||
views.GenericViews.user_answer_delete_multiple,
|
views.GenericViews.user_answer_delete_multiple,
|
||||||
name="user_answer_delete_multiple",
|
name="user_answer_delete_multiple",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
+410
-410
@@ -1,411 +1,411 @@
|
|||||||
from reversion.views import RevisionMixin
|
from reversion.views import RevisionMixin
|
||||||
from generic.mixins import SuperuserRequiredMixin
|
from generic.mixins import SuperuserRequiredMixin
|
||||||
from django.views.generic.detail import DetailView
|
from django.views.generic.detail import DetailView
|
||||||
from generic.models import CidUser
|
from generic.models import CidUser
|
||||||
from sbas.forms import CidUserAnswerForm, ExamForm
|
from sbas.forms import CidUserAnswerForm, ExamForm
|
||||||
from django.shortcuts import render, get_object_or_404, redirect
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
# from django.contrib.auth.models import User
|
# from django.contrib.auth.models import User
|
||||||
from django.contrib.auth.decorators import login_required, user_passes_test
|
from django.contrib.auth.decorators import login_required, user_passes_test
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
|
||||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
|
|
||||||
from django.db.models.functions import Lower
|
from django.db.models.functions import Lower
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
|
||||||
from django.urls import reverse_lazy, reverse
|
from django.urls import reverse_lazy, reverse
|
||||||
|
|
||||||
from django.http import Http404, JsonResponse
|
from django.http import Http404, JsonResponse
|
||||||
|
|
||||||
from django.http import HttpResponseRedirect, HttpResponse
|
from django.http import HttpResponseRedirect, HttpResponse
|
||||||
|
|
||||||
from .models import Question, Category, Exam, CidUserAnswer
|
from .models import Question, Category, Exam, CidUserAnswer
|
||||||
|
|
||||||
from django_tables2 import SingleTableView, SingleTableMixin
|
from django_tables2 import SingleTableView, SingleTableMixin
|
||||||
from django_filters.views import FilterView
|
from django_filters.views import FilterView
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import os
|
import os
|
||||||
import base64
|
import base64
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import json
|
import json
|
||||||
import statistics
|
import statistics
|
||||||
import plotly.express as px
|
import plotly.express as px
|
||||||
|
|
||||||
from generic.views import ExamCloneMixin, ExamViews, GenericViewBase
|
from generic.views import ExamCloneMixin, ExamViews, GenericViewBase
|
||||||
|
|
||||||
from rest_framework import viewsets, permissions
|
from rest_framework import viewsets, permissions
|
||||||
from rest_framework.pagination import PageNumberPagination
|
from rest_framework.pagination import PageNumberPagination
|
||||||
|
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
|
|
||||||
from .tables import QuestionTable, UserAnswerTable
|
from .tables import QuestionTable, UserAnswerTable
|
||||||
from .filters import QuestionFilter, UserAnswerFilter
|
from .filters import QuestionFilter, UserAnswerFilter
|
||||||
|
|
||||||
|
|
||||||
class AuthorOrCheckerRequiredMixin(object):
|
class AuthorOrCheckerRequiredMixin(object):
|
||||||
def get_object(self, *args, **kwargs):
|
def get_object(self, *args, **kwargs):
|
||||||
obj = super().get_object(*args, **kwargs)
|
obj = super().get_object(*args, **kwargs)
|
||||||
if self.request.user.groups.filter(name="sbas_checker").exists():
|
if self.request.user.groups.filter(name="sbas_checker").exists():
|
||||||
return obj
|
return obj
|
||||||
if self.request.user not in obj.author.all():
|
if self.request.user not in obj.author.all():
|
||||||
raise PermissionDenied() # or Http404
|
raise PermissionDenied() # or Http404
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
def question_list(request):
|
def question_list(request):
|
||||||
questions = Question.objects.all()
|
questions = Question.objects.all()
|
||||||
return render(request, "sbas/question_list.html", {"questions": questions})
|
return render(request, "sbas/question_list.html", {"questions": questions})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def question_detail(request, pk):
|
def question_detail(request, pk):
|
||||||
question = get_object_or_404(Question, pk=pk)
|
question = get_object_or_404(Question, pk=pk)
|
||||||
return render(request, "sbas/question_detail.html", {"question": question})
|
return render(request, "sbas/question_detail.html", {"question": question})
|
||||||
|
|
||||||
|
|
||||||
def active_exams(request):
|
def active_exams(request):
|
||||||
exams = Exam.objects.all()
|
exams = Exam.objects.all()
|
||||||
print(exams)
|
print(exams)
|
||||||
|
|
||||||
active_exams = []
|
active_exams = []
|
||||||
|
|
||||||
for exam in exams:
|
for exam in exams:
|
||||||
if exam.active:
|
if exam.active:
|
||||||
active_exams.append(exam)
|
active_exams.append(exam)
|
||||||
return render(request, "sbas/available_exam_list.html", {"exams": active_exams})
|
return render(request, "sbas/available_exam_list.html", {"exams": active_exams})
|
||||||
|
|
||||||
|
|
||||||
def exam_scores_cid_user(request, pk, cid, passcode):
|
def exam_scores_cid_user(request, pk, cid, passcode):
|
||||||
exam = get_object_or_404(Exam, pk=pk)
|
exam = get_object_or_404(Exam, pk=pk)
|
||||||
|
|
||||||
if not exam.check_cid_user(cid, passcode, request):
|
if not exam.check_cid_user(cid, passcode, request):
|
||||||
raise Http404("Error accessing exam")
|
raise Http404("Error accessing exam")
|
||||||
|
|
||||||
questions = exam.exam_questions.all()
|
questions = exam.exam_questions.all()
|
||||||
|
|
||||||
answers_and_marks = []
|
answers_and_marks = []
|
||||||
answers_marks = []
|
answers_marks = []
|
||||||
answers = []
|
answers = []
|
||||||
|
|
||||||
view_all_results = False
|
view_all_results = False
|
||||||
if request.user.groups.filter(name="view_all_results").exists():
|
if request.user.groups.filter(name="view_all_results").exists():
|
||||||
view_all_results = True
|
view_all_results = True
|
||||||
|
|
||||||
for q in questions:
|
for q in questions:
|
||||||
# Get user answer
|
# Get user answer
|
||||||
user_answer = q.cid_user_answers.filter(cid=cid).first()
|
user_answer = q.cid_user_answers.filter(cid=cid).first()
|
||||||
|
|
||||||
if user_answer is None:
|
if user_answer is None:
|
||||||
answer_score = 0
|
answer_score = 0
|
||||||
ans = ""
|
ans = ""
|
||||||
else:
|
else:
|
||||||
answer_score = user_answer.get_answer_score()
|
answer_score = user_answer.get_answer_score()
|
||||||
ans = user_answer.get_answer()
|
ans = user_answer.get_answer()
|
||||||
|
|
||||||
correct_answer = q.get_correct_answer()
|
correct_answer = q.get_correct_answer()
|
||||||
|
|
||||||
if not exam.publish_results and not view_all_results:
|
if not exam.publish_results and not view_all_results:
|
||||||
correct_answer = "*****"
|
correct_answer = "*****"
|
||||||
answer_score = 0
|
answer_score = 0
|
||||||
answers.append(ans)
|
answers.append(ans)
|
||||||
answers_marks.append(answer_score)
|
answers_marks.append(answer_score)
|
||||||
|
|
||||||
merged_ans = (ans, answer_score, correct_answer)
|
merged_ans = (ans, answer_score, correct_answer)
|
||||||
answers_and_marks.append((q, *merged_ans))
|
answers_and_marks.append((q, *merged_ans))
|
||||||
|
|
||||||
total_score = sum(answers_marks)
|
total_score = sum(answers_marks)
|
||||||
|
|
||||||
max_score = len(questions)
|
max_score = len(questions)
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"sbas/exam_scores_user.html",
|
"sbas/exam_scores_user.html",
|
||||||
{
|
{
|
||||||
"exam": exam,
|
"exam": exam,
|
||||||
"cid": cid,
|
"cid": cid,
|
||||||
"passcode": passcode,
|
"passcode": passcode,
|
||||||
"questions": questions,
|
"questions": questions,
|
||||||
"answers": answers,
|
"answers": answers,
|
||||||
"answers_marks": answers_marks,
|
"answers_marks": answers_marks,
|
||||||
"total_score": total_score,
|
"total_score": total_score,
|
||||||
"max_score": max_score,
|
"max_score": max_score,
|
||||||
"answers_and_marks": answers_and_marks,
|
"answers_and_marks": answers_and_marks,
|
||||||
"view_all_results": view_all_results,
|
"view_all_results": view_all_results,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def exam_start(request, pk):
|
def exam_start(request, pk):
|
||||||
exam = get_object_or_404(Exam, pk=pk)
|
exam = get_object_or_404(Exam, pk=pk)
|
||||||
|
|
||||||
if not exam.active:
|
if not exam.active:
|
||||||
raise Http404("Exam not found")
|
raise Http404("Exam not found")
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"sbas/exam_start.html",
|
"sbas/exam_start.html",
|
||||||
{
|
{
|
||||||
"exam": exam,
|
"exam": exam,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def exam_take_overview(request, pk, cid, passcode):
|
def exam_take_overview(request, pk, cid, passcode):
|
||||||
exam = get_object_or_404(Exam, pk=pk)
|
exam = get_object_or_404(Exam, pk=pk)
|
||||||
|
|
||||||
if not exam.active:
|
if not exam.active:
|
||||||
raise Http404("Exam not found")
|
raise Http404("Exam not found")
|
||||||
|
|
||||||
if not exam.check_cid_user(cid, passcode):
|
if not exam.check_cid_user(cid, passcode):
|
||||||
raise Http404("Error accessing exam")
|
raise Http404("Error accessing exam")
|
||||||
|
|
||||||
questions = exam.exam_questions.all()
|
questions = exam.exam_questions.all()
|
||||||
|
|
||||||
answers = CidUserAnswer.objects.filter(cid=cid, exam=exam)
|
answers = CidUserAnswer.objects.filter(cid=cid, exam=exam)
|
||||||
|
|
||||||
answer_question_map = {}
|
answer_question_map = {}
|
||||||
for ans in answers:
|
for ans in answers:
|
||||||
answer_question_map[ans.question] = ans
|
answer_question_map[ans.question] = ans
|
||||||
|
|
||||||
question_answer_tuples = []
|
question_answer_tuples = []
|
||||||
answer_count = 0
|
answer_count = 0
|
||||||
for q in questions:
|
for q in questions:
|
||||||
if q in answer_question_map and answer_question_map[q].answer:
|
if q in answer_question_map and answer_question_map[q].answer:
|
||||||
question_answer_tuples.append((q, answer_question_map[q]))
|
question_answer_tuples.append((q, answer_question_map[q]))
|
||||||
answer_count += 1
|
answer_count += 1
|
||||||
else:
|
else:
|
||||||
question_answer_tuples.append((q, None))
|
question_answer_tuples.append((q, None))
|
||||||
|
|
||||||
c = CidUser.objects.filter(cid=cid).first()
|
c = CidUser.objects.filter(cid=cid).first()
|
||||||
cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c)
|
cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c)
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"sbas/exam_take_overview.html",
|
"sbas/exam_take_overview.html",
|
||||||
{
|
{
|
||||||
"exam": exam,
|
"exam": exam,
|
||||||
"cid": cid,
|
"cid": cid,
|
||||||
"question_answer_tuples": question_answer_tuples,
|
"question_answer_tuples": question_answer_tuples,
|
||||||
"answer_count": answer_count,
|
"answer_count": answer_count,
|
||||||
"exam_length": len(questions),
|
"exam_length": len(questions),
|
||||||
"passcode": passcode,
|
"passcode": passcode,
|
||||||
"cid_user_exam": cid_user_exam,
|
"cid_user_exam": cid_user_exam,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def exam_take(request, pk, sk, cid, passcode):
|
def exam_take(request, pk, sk, cid, passcode):
|
||||||
|
|
||||||
exam = get_object_or_404(Exam, pk=pk)
|
exam = get_object_or_404(Exam, pk=pk)
|
||||||
|
|
||||||
if not exam.active:
|
if not exam.active:
|
||||||
raise Http404("Exam not found")
|
raise Http404("Exam not found")
|
||||||
|
|
||||||
if not exam.check_cid_user(cid, passcode):
|
if not exam.check_cid_user(cid, passcode):
|
||||||
raise Http404("Error accessing exam")
|
raise Http404("Error accessing exam")
|
||||||
|
|
||||||
c = CidUser.objects.filter(cid=cid).first()
|
c = CidUser.objects.filter(cid=cid).first()
|
||||||
cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c)
|
cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c)
|
||||||
|
|
||||||
question = exam.exam_questions.all()[sk]
|
question = exam.exam_questions.all()[sk]
|
||||||
|
|
||||||
exam_length = len(exam.exam_questions.all())
|
exam_length = len(exam.exam_questions.all())
|
||||||
|
|
||||||
pos = exam.get_question_index(question)
|
pos = exam.get_question_index(question)
|
||||||
|
|
||||||
answer = question.cid_user_answers.filter(cid=cid, exam=exam).first()
|
answer = question.cid_user_answers.filter(cid=cid, exam=exam).first()
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if answer:
|
if answer:
|
||||||
form = CidUserAnswerForm(request.POST, instance=answer)
|
form = CidUserAnswerForm(request.POST, instance=answer)
|
||||||
else:
|
else:
|
||||||
form = CidUserAnswerForm(request.POST)
|
form = CidUserAnswerForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
answer = form.save(commit=False)
|
answer = form.save(commit=False)
|
||||||
answer.cid = cid
|
answer.cid = cid
|
||||||
answer.question = question
|
answer.question = question
|
||||||
answer.exam = exam
|
answer.exam = exam
|
||||||
# answer.published_date = timezone.now()
|
# answer.published_date = timezone.now()
|
||||||
answer.save()
|
answer.save()
|
||||||
|
|
||||||
cid_user_exam.end_time = timezone.now()
|
cid_user_exam.end_time = timezone.now()
|
||||||
cid_user_exam.save()
|
cid_user_exam.save()
|
||||||
|
|
||||||
kwargs = {"pk": pk, "cid": cid, "passcode": passcode}
|
kwargs = {"pk": pk, "cid": cid, "passcode": passcode}
|
||||||
|
|
||||||
if "next" in request.POST:
|
if "next" in request.POST:
|
||||||
return redirect("sbas:exam_take", sk=pos + 1, **kwargs)
|
return redirect("sbas:exam_take", sk=pos + 1, **kwargs)
|
||||||
elif "previous" in request.POST:
|
elif "previous" in request.POST:
|
||||||
return redirect("sbas:exam_take", sk=pos - 1, **kwargs)
|
return redirect("sbas:exam_take", sk=pos - 1, **kwargs)
|
||||||
elif "finish" in request.POST:
|
elif "finish" in request.POST:
|
||||||
return redirect("sbas:exam_take_overview", **kwargs)
|
return redirect("sbas:exam_take_overview", **kwargs)
|
||||||
elif "goto" in request.POST:
|
elif "goto" in request.POST:
|
||||||
return redirect(
|
return redirect(
|
||||||
"sbas:exam_take", sk=int(request.POST.get("goto")), **kwargs
|
"sbas:exam_take", sk=int(request.POST.get("goto")), **kwargs
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
form = CidUserAnswerForm(instance=answer)
|
form = CidUserAnswerForm(instance=answer)
|
||||||
|
|
||||||
previous = -1
|
previous = -1
|
||||||
if sk > 0:
|
if sk > 0:
|
||||||
previous = sk - 1
|
previous = sk - 1
|
||||||
next = sk + 1
|
next = sk + 1
|
||||||
if sk == exam_length - 1:
|
if sk == exam_length - 1:
|
||||||
next = False
|
next = False
|
||||||
|
|
||||||
saved_answer = False
|
saved_answer = False
|
||||||
if answer is not None:
|
if answer is not None:
|
||||||
saved_answer = answer.answer
|
saved_answer = answer.answer
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"sbas/exam_take.html",
|
"sbas/exam_take.html",
|
||||||
{
|
{
|
||||||
"form": form,
|
"form": form,
|
||||||
"cid": cid,
|
"cid": cid,
|
||||||
"exam": exam,
|
"exam": exam,
|
||||||
"question": question,
|
"question": question,
|
||||||
"next": next,
|
"next": next,
|
||||||
"previous": previous,
|
"previous": previous,
|
||||||
"exam_length": exam_length,
|
"exam_length": exam_length,
|
||||||
"pos": pos,
|
"pos": pos,
|
||||||
"saved_answer": saved_answer,
|
"saved_answer": saved_answer,
|
||||||
"passcode": passcode,
|
"passcode": passcode,
|
||||||
"cid_user_exam": cid_user_exam,
|
"cid_user_exam": cid_user_exam,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def loadJsonAnswer(answer):
|
def loadJsonAnswer(answer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
GenericExamViews = ExamViews(
|
GenericExamViews = ExamViews(
|
||||||
Exam, Question, None, CidUserAnswer, "sbas", "sbas", loadJsonAnswer
|
Exam, Question, None, CidUserAnswer, "sbas", "sbas", loadJsonAnswer
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class QuestionView(LoginRequiredMixin, SingleTableMixin, FilterView):
|
class QuestionView(LoginRequiredMixin, SingleTableMixin, FilterView):
|
||||||
model = Question
|
model = Question
|
||||||
table_class = QuestionTable
|
table_class = QuestionTable
|
||||||
template_name = "sbas/question_view.html"
|
template_name = "sbas/question_view.html"
|
||||||
|
|
||||||
filterset_class = QuestionFilter
|
filterset_class = QuestionFilter
|
||||||
|
|
||||||
|
|
||||||
class UserAnswerView(LoginRequiredMixin, DetailView):
|
class UserAnswerView(LoginRequiredMixin, DetailView):
|
||||||
model = CidUserAnswer
|
model = CidUserAnswer
|
||||||
|
|
||||||
|
|
||||||
class UserAnswerTableView(LoginRequiredMixin, SingleTableMixin, FilterView):
|
class UserAnswerTableView(LoginRequiredMixin, SingleTableMixin, FilterView):
|
||||||
model = CidUserAnswer
|
model = CidUserAnswer
|
||||||
table_class = UserAnswerTable
|
table_class = UserAnswerTable
|
||||||
template_name = "sbas/user_answer_question_view.html"
|
template_name = "sbas/user_answer_question_view.html"
|
||||||
|
|
||||||
filterset_class = UserAnswerFilter
|
filterset_class = UserAnswerFilter
|
||||||
|
|
||||||
|
|
||||||
class UserAnswerDelete(SuperuserRequiredMixin, DeleteView):
|
class UserAnswerDelete(SuperuserRequiredMixin, DeleteView):
|
||||||
model = CidUserAnswer
|
model = CidUserAnswer
|
||||||
template_name = "user_answer_delete.html"
|
template_name = "user_answer_delete.html"
|
||||||
success_url = reverse_lazy("sbas:user_answer_table_view")
|
success_url = reverse_lazy("sbas:user_answer_table_view")
|
||||||
|
|
||||||
|
|
||||||
# @user_passes_test(lambda u: u.is_superuser)
|
# @user_passes_test(lambda u: u.is_superuser)
|
||||||
# def user_answer_delete_multiple(request):
|
# def user_answer_delete_multiple(request):
|
||||||
# if request.is_ajax():
|
# if request.is_ajax():
|
||||||
# print(request.POST["answer_ids"])
|
# print(request.POST["answer_ids"])
|
||||||
# answer_ids = json.loads(request.POST["answer_ids"])
|
# answer_ids = json.loads(request.POST["answer_ids"])
|
||||||
#
|
#
|
||||||
# # We could probably delete them all at once....
|
# # We could probably delete them all at once....
|
||||||
# for id in answer_ids:
|
# for id in answer_ids:
|
||||||
# CidUserAnswer.objects.get(pk=id).delete()
|
# CidUserAnswer.objects.get(pk=id).delete()
|
||||||
# return JsonResponse({"status": "success"})
|
# return JsonResponse({"status": "success"})
|
||||||
# return JsonResponse({"status": "error"})
|
# return JsonResponse({"status": "error"})
|
||||||
|
|
||||||
|
|
||||||
GenericViews = GenericViewBase("sbas", Question, CidUserAnswer, Exam)
|
GenericViews = GenericViewBase("sbas", Question, CidUserAnswer, Exam)
|
||||||
|
|
||||||
|
|
||||||
class QuestionDelete(AuthorOrCheckerRequiredMixin, DeleteView):
|
class QuestionDelete(AuthorOrCheckerRequiredMixin, DeleteView):
|
||||||
model = Question
|
model = Question
|
||||||
success_url = reverse_lazy("sbas:question_list")
|
success_url = reverse_lazy("sbas:question_list")
|
||||||
|
|
||||||
|
|
||||||
class ExamCreate(RevisionMixin, LoginRequiredMixin, CreateView):
|
class ExamCreate(RevisionMixin, LoginRequiredMixin, CreateView):
|
||||||
model = Exam
|
model = Exam
|
||||||
form_class = ExamForm
|
form_class = ExamForm
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
self.object = form.save(commit=False)
|
self.object = form.save(commit=False)
|
||||||
self.object.save()
|
self.object.save()
|
||||||
|
|
||||||
form.instance.author.add(self.request.user.id)
|
form.instance.author.add(self.request.user.id)
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
class ExamUpdate(
|
class ExamUpdate(
|
||||||
RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView
|
RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView
|
||||||
):
|
):
|
||||||
model = Exam
|
model = Exam
|
||||||
form_class = ExamForm
|
form_class = ExamForm
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
self.object = form.save(commit=False)
|
self.object = form.save(commit=False)
|
||||||
self.object.save()
|
self.object.save()
|
||||||
|
|
||||||
form.instance.author.add(self.request.user.id)
|
form.instance.author.add(self.request.user.id)
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
# class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
|
# class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
|
||||||
# # queryset = Exam.objects.all().order_by('name')
|
# # queryset = Exam.objects.all().order_by('name')
|
||||||
# serializer_class = ExamSerializer
|
# serializer_class = ExamSerializer
|
||||||
# permission_classes = [permissions.IsAuthenticated]
|
# permission_classes = [permissions.IsAuthenticated]
|
||||||
#
|
#
|
||||||
# def get_queryset(self):
|
# def get_queryset(self):
|
||||||
# """
|
# """
|
||||||
# This view should return a list exams available to a user.
|
# This view should return a list exams available to a user.
|
||||||
# """
|
# """
|
||||||
# user = self.request.user
|
# user = self.request.user
|
||||||
# if user.groups.filter(name="sbas_checker").exists():
|
# if user.groups.filter(name="sbas_checker").exists():
|
||||||
# return Exam.objects.all()
|
# return Exam.objects.all()
|
||||||
#
|
#
|
||||||
# return Exam.objects.filter(author__id=user.id)
|
# return Exam.objects.filter(author__id=user.id)
|
||||||
|
|
||||||
|
|
||||||
class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
||||||
model = Exam
|
model = Exam
|
||||||
template_name = "exam_confirm_delete.html"
|
template_name = "exam_confirm_delete.html"
|
||||||
success_url = reverse_lazy("sbas:index")
|
success_url = reverse_lazy("sbas:index")
|
||||||
|
|
||||||
|
|
||||||
class ExamClone(ExamCloneMixin, ExamCreate):
|
class ExamClone(ExamCloneMixin, ExamCreate):
|
||||||
"""Clone exam view"""
|
"""Clone exam view"""
|
||||||
|
|
||||||
class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
||||||
model = Exam
|
model = Exam
|
||||||
template_name = "exam_confirm_delete.html"
|
template_name = "exam_confirm_delete.html"
|
||||||
success_url = reverse_lazy("sbas:index")
|
success_url = reverse_lazy("sbas:index")
|
||||||
|
|
||||||
|
|
||||||
class ExamUpdate(
|
class ExamUpdate(
|
||||||
RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView
|
RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView
|
||||||
):
|
):
|
||||||
model = Exam
|
model = Exam
|
||||||
form_class = ExamForm
|
form_class = ExamForm
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
self.object = form.save(commit=False)
|
self.object = form.save(commit=False)
|
||||||
self.object.save()
|
self.object.save()
|
||||||
|
|
||||||
form.instance.author.add(self.request.user.id)
|
form.instance.author.add(self.request.user.id)
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
+830
-830
File diff suppressed because it is too large
Load Diff
+161
-161
@@ -1,162 +1,162 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
// send request to change the is_private state on customSwitches toggle
|
// send request to change the is_private state on customSwitches toggle
|
||||||
$("#exam-active-switch, .exam-active-switch").on("change", function (evt) {
|
$("#exam-active-switch, .exam-active-switch").on("change", function (evt) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: evt.currentTarget.dataset.posturl,
|
url: evt.currentTarget.dataset.posturl,
|
||||||
data: {
|
data: {
|
||||||
csrfmiddlewaretoken: "{{ csrf_token }}",
|
csrfmiddlewaretoken: "{{ csrf_token }}",
|
||||||
active: this.checked // true if checked else false
|
active: this.checked // true if checked else false
|
||||||
},
|
},
|
||||||
type: "POST",
|
type: "POST",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
})
|
})
|
||||||
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
||||||
.done(function (data) {
|
.done(function (data) {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
if (data.status == "success") {
|
if (data.status == "success") {
|
||||||
toastr.info(`Exam: ${data.name} [${data.id}]
|
toastr.info(`Exam: ${data.name} [${data.id}]
|
||||||
Active state changed to: ${data.active}.`)
|
Active state changed to: ${data.active}.`)
|
||||||
}
|
}
|
||||||
// show some message according to the response.
|
// show some message according to the response.
|
||||||
// For eg. A message box showing that the status has been changed
|
// For eg. A message box showing that the status has been changed
|
||||||
})
|
})
|
||||||
.fail(function (data) {
|
.fail(function (data) {
|
||||||
console.log(data)
|
console.log(data)
|
||||||
toastr.error(`Failed to change state<br/>${data.responseJSON.status}`);
|
toastr.error(`Failed to change state<br/>${data.responseJSON.status}`);
|
||||||
})
|
})
|
||||||
.always(function () {
|
.always(function () {
|
||||||
console.log('[Done]');
|
console.log('[Done]');
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
$("#exam-publish-results-switch, .exam-publish-results-switch").on("change", function (evt) {
|
$("#exam-publish-results-switch, .exam-publish-results-switch").on("change", function (evt) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: evt.currentTarget.dataset.posturl,
|
url: evt.currentTarget.dataset.posturl,
|
||||||
data: {
|
data: {
|
||||||
csrfmiddlewaretoken: "{{ csrf_token }}",
|
csrfmiddlewaretoken: "{{ csrf_token }}",
|
||||||
publish_results: this.checked // true if checked else false
|
publish_results: this.checked // true if checked else false
|
||||||
},
|
},
|
||||||
type: "POST",
|
type: "POST",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
})
|
})
|
||||||
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
||||||
.done(function (data) {
|
.done(function (data) {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
if (data.status == "success") {
|
if (data.status == "success") {
|
||||||
toastr.info(`Exam: ${data.name} [${data.id}]
|
toastr.info(`Exam: ${data.name} [${data.id}]
|
||||||
Published state changed to: ${data.publish_results}.`)
|
Published state changed to: ${data.publish_results}.`)
|
||||||
}
|
}
|
||||||
// show some message according to the response.
|
// show some message according to the response.
|
||||||
// For eg. A message box showing that the status has been changed
|
// For eg. A message box showing that the status has been changed
|
||||||
})
|
})
|
||||||
.fail(function (data) {
|
.fail(function (data) {
|
||||||
toastr.error(`Failed to change state`);
|
toastr.error(`Failed to change state`);
|
||||||
})
|
})
|
||||||
.always(function () {
|
.always(function () {
|
||||||
console.log('[Done]');
|
console.log('[Done]');
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
$("#button-open-access").click(function (evt) {
|
$("#button-open-access").click(function (evt) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: evt.currentTarget.dataset.posturl,
|
url: evt.currentTarget.dataset.posturl,
|
||||||
data: {
|
data: {
|
||||||
csrfmiddlewaretoken: "{{ csrf_token }}",
|
csrfmiddlewaretoken: "{{ csrf_token }}",
|
||||||
set_open_access: true,
|
set_open_access: true,
|
||||||
},
|
},
|
||||||
type: "POST",
|
type: "POST",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
})
|
})
|
||||||
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
||||||
.done(function (data) {
|
.done(function (data) {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
if (data.status == "success") {
|
if (data.status == "success") {
|
||||||
toastr.info('Question access state changed.');
|
toastr.info('Question access state changed.');
|
||||||
} else {
|
} else {
|
||||||
toastr.warning("Error");
|
toastr.warning("Error");
|
||||||
toastr.info(data.status);
|
toastr.info(data.status);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.fail((e) => {
|
.fail((e) => {
|
||||||
toastr.warning(`Error, ${e}`);
|
toastr.warning(`Error, ${e}`);
|
||||||
})
|
})
|
||||||
.always(function () {
|
.always(function () {
|
||||||
console.log('[Done]');
|
console.log('[Done]');
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
$("#button-closed-access").click(function (evt) {
|
$("#button-closed-access").click(function (evt) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: evt.currentTarget.dataset.posturl,
|
url: evt.currentTarget.dataset.posturl,
|
||||||
data: {
|
data: {
|
||||||
csrfmiddlewaretoken: "{{ csrf_token }}",
|
csrfmiddlewaretoken: "{{ csrf_token }}",
|
||||||
set_open_access: false,
|
set_open_access: false,
|
||||||
},
|
},
|
||||||
type: "POST",
|
type: "POST",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
})
|
})
|
||||||
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
||||||
.done(function (data) {
|
.done(function (data) {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
if (data.status == "success") {
|
if (data.status == "success") {
|
||||||
toastr.info('Question access state changed.');
|
toastr.info('Question access state changed.');
|
||||||
} else {
|
} else {
|
||||||
toastr.warning("Error");
|
toastr.warning("Error");
|
||||||
toastr.info(data.status);
|
toastr.info(data.status);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.fail((e) => {
|
.fail((e) => {
|
||||||
toastr.warning(`Error, ${e}`);
|
toastr.warning(`Error, ${e}`);
|
||||||
})
|
})
|
||||||
.always(function () {
|
.always(function () {
|
||||||
console.log('[Done]');
|
console.log('[Done]');
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
$("#button-edit-order").click(function (evt) {
|
$("#button-edit-order").click(function (evt) {
|
||||||
$(this).remove();
|
$(this).remove();
|
||||||
sortable('.sortable');
|
sortable('.sortable');
|
||||||
|
|
||||||
$("#full-question-list li").each((n, el) => {
|
$("#full-question-list li").each((n, el) => {
|
||||||
$(el).append($(
|
$(el).append($(
|
||||||
"<span class='exam-question-delete flex-col'><button>DELETE</button></span>"
|
"<span class='exam-question-delete flex-col'><button>DELETE</button></span>"
|
||||||
).click(() => {
|
).click(() => {
|
||||||
el.remove();
|
el.remove();
|
||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
$("#full-question-list").append($(
|
$("#full-question-list").append($(
|
||||||
"<button title='click and drag questions to change order'>Save exam order</button>"
|
"<button title='click and drag questions to change order'>Save exam order</button>"
|
||||||
).click(() => {
|
).click(() => {
|
||||||
new_order = [];
|
new_order = [];
|
||||||
$("#full-question-list li").each((n, el) => {
|
$("#full-question-list li").each((n, el) => {
|
||||||
new_order.push(el.dataset.question_pk)
|
new_order.push(el.dataset.question_pk)
|
||||||
})
|
})
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: evt.currentTarget.dataset.posturl,
|
url: evt.currentTarget.dataset.posturl,
|
||||||
data: {
|
data: {
|
||||||
csrfmiddlewaretoken: "{{ csrf_token }}",
|
csrfmiddlewaretoken: "{{ csrf_token }}",
|
||||||
set_exam_order: JSON.stringify(new_order),
|
set_exam_order: JSON.stringify(new_order),
|
||||||
},
|
},
|
||||||
type: "POST",
|
type: "POST",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
})
|
})
|
||||||
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
||||||
.done(function (data) {
|
.done(function (data) {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
if (data.status == "success") {
|
if (data.status == "success") {
|
||||||
toastr.info('Exam order changed.')
|
toastr.info('Exam order changed.')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.always(function () {
|
.always(function () {
|
||||||
console.log('[Done]');
|
console.log('[Done]');
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
Reference in New Issue
Block a user