Merge ssh://penracourses.org.uk:/home/django/rad
This commit is contained in:
+6
-12
@@ -337,15 +337,6 @@ CaseDifferentialFormSet = inlineformset_factory(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BaseSeriesFormSet(BaseInlineFormSet):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
|
|
||||||
super(BaseSeriesFormSet, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
self.queryset = Series.case.through.objects.filter(series__author__id=99)
|
|
||||||
|
|
||||||
# for form in self.forms:
|
|
||||||
# form.fields['series'].queryset = series_queryset
|
|
||||||
|
|
||||||
|
|
||||||
class CaseSeriesForm(ModelForm):
|
class CaseSeriesForm(ModelForm):
|
||||||
@@ -362,9 +353,14 @@ class CaseCollectionCaseForm(ModelForm):
|
|||||||
def __init__(self, *args, user, **kwargs):
|
def __init__(self, *args, user, **kwargs):
|
||||||
super(CaseCollectionCaseForm, self).__init__(*args, **kwargs)
|
super(CaseCollectionCaseForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if not user.groups.filter(name="atlas_editor").exists():
|
||||||
|
queryset = Case.objects.filter(author__id=user.id)
|
||||||
|
else:
|
||||||
|
queryset = Case.objects.all()
|
||||||
|
|
||||||
self.fields["case"] = ModelChoiceField(
|
self.fields["case"] = ModelChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=Case.objects.filter(author__id=user.id),
|
queryset=queryset,
|
||||||
# widget=Select(verbose_name="Series", is_stacked=False),
|
# widget=Select(verbose_name="Series", is_stacked=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -372,7 +368,6 @@ SeriesFormSet = inlineformset_factory(
|
|||||||
Case,
|
Case,
|
||||||
Series.case.through,
|
Series.case.through,
|
||||||
form=CaseSeriesForm,
|
form=CaseSeriesForm,
|
||||||
# formset=BaseSeriesFormSet,
|
|
||||||
exclude=[],
|
exclude=[],
|
||||||
can_delete=True,
|
can_delete=True,
|
||||||
extra=0,
|
extra=0,
|
||||||
@@ -383,7 +378,6 @@ CaseCollectionCaseFormSet = inlineformset_factory(
|
|||||||
CaseCollection,
|
CaseCollection,
|
||||||
Case.casecollection_set.through,
|
Case.casecollection_set.through,
|
||||||
form=CaseCollectionCaseForm,
|
form=CaseCollectionCaseForm,
|
||||||
# formset=BaseSeriesFormSet,
|
|
||||||
exclude=[],
|
exclude=[],
|
||||||
can_delete=True,
|
can_delete=True,
|
||||||
extra=0,
|
extra=0,
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Generated by Django 3.2.4 on 2022-04-08 22:55
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('atlas', '0037_cidreportanswer_feedback'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='casecollection',
|
||||||
|
old_name='show_description',
|
||||||
|
new_name='show_description_pre',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='casecollection',
|
||||||
|
old_name='show_discussion',
|
||||||
|
new_name='show_discussion_pre',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='casecollection',
|
||||||
|
old_name='show_history',
|
||||||
|
new_name='show_history_pre',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='casecollection',
|
||||||
|
old_name='show_report',
|
||||||
|
new_name='show_report_pre',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='casecollection',
|
||||||
|
old_name='show_title',
|
||||||
|
new_name='show_title_pre',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='publish_results',
|
||||||
|
field=models.BooleanField(default=False, help_text='If a collection should published'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# Generated by Django 3.2.4 on 2022-04-08 22:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('atlas', '0038_auto_20220408_2355'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='show_description_post',
|
||||||
|
field=models.BooleanField(default=False, help_text='Show the description of the cases (post exam)'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='show_discussion_post',
|
||||||
|
field=models.BooleanField(default=False, help_text='Show the case discussion (post exam)'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='show_history_post',
|
||||||
|
field=models.BooleanField(default=False, help_text='Show the history of the cases (post exam)'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='show_report_post',
|
||||||
|
field=models.BooleanField(default=False, help_text='Show the case report (post exam)'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='show_title_post',
|
||||||
|
field=models.BooleanField(default=False, help_text='Show the title of the cases (post exam)'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='show_description_pre',
|
||||||
|
field=models.BooleanField(default=False, help_text='Show the description of the cases (pre exam)'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='show_discussion_pre',
|
||||||
|
field=models.BooleanField(default=False, help_text='Show the case discussion (pre exam)'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='show_history_pre',
|
||||||
|
field=models.BooleanField(default=False, help_text='Show the history of the cases (pre exam)'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='show_report_pre',
|
||||||
|
field=models.BooleanField(default=False, help_text='Show the case report (pre exam)'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='show_title_pre',
|
||||||
|
field=models.BooleanField(default=False, help_text='Show the title of the cases (pre exam)'),
|
||||||
|
),
|
||||||
|
]
|
||||||
+32
-12
@@ -11,7 +11,6 @@ import tagulous
|
|||||||
import tagulous.models
|
import tagulous.models
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import pydicom
|
import pydicom
|
||||||
import dicognito.anonymizer
|
import dicognito.anonymizer
|
||||||
|
|
||||||
@@ -591,33 +590,50 @@ class Series(models.Model):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CaseCollection(models.Model):
|
class CaseCollection(models.Model):
|
||||||
name = models.CharField(max_length=255, unique=True)
|
name = models.CharField(max_length=255, unique=True)
|
||||||
|
|
||||||
cases = models.ManyToManyField(Case, through="CaseDetail")
|
cases = models.ManyToManyField(Case, through="CaseDetail")
|
||||||
|
|
||||||
publish_results = models.BooleanField(
|
publish_results = models.BooleanField(
|
||||||
help_text="If a collection should published", default=True
|
help_text="If a collection should published", default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
active = models.BooleanField(
|
active = models.BooleanField(
|
||||||
help_text="If a collection should be available", default=True
|
help_text="If a collection should be available", default=True
|
||||||
)
|
)
|
||||||
|
|
||||||
show_title = models.BooleanField(
|
show_title_pre = models.BooleanField(
|
||||||
default=False, help_text="Show the title of the cases"
|
default=False, help_text="Show the title of the cases (pre exam)"
|
||||||
)
|
)
|
||||||
show_history = models.BooleanField(
|
show_history_pre = models.BooleanField(
|
||||||
default=False, help_text="Show the history of the cases"
|
default=False, help_text="Show the history of the cases (pre exam)"
|
||||||
)
|
)
|
||||||
show_description = models.BooleanField(
|
show_description_pre = models.BooleanField(
|
||||||
default=False, help_text="Show the description of the cases"
|
default=False, help_text="Show the description of the cases (pre exam)"
|
||||||
)
|
)
|
||||||
show_discussion = models.BooleanField(
|
show_discussion_pre = models.BooleanField(
|
||||||
default=False, help_text="Show the case discussion"
|
default=False, help_text="Show the case discussion (pre exam)"
|
||||||
|
)
|
||||||
|
show_report_pre = models.BooleanField(
|
||||||
|
default=False, help_text="Show the case report (pre exam)"
|
||||||
|
)
|
||||||
|
|
||||||
|
show_title_post = models.BooleanField(
|
||||||
|
default=False, help_text="Show the title of the cases (post exam)"
|
||||||
|
)
|
||||||
|
show_history_post = models.BooleanField(
|
||||||
|
default=False, help_text="Show the history of the cases (post exam)"
|
||||||
|
)
|
||||||
|
show_description_post = models.BooleanField(
|
||||||
|
default=False, help_text="Show the description of the cases (post exam)"
|
||||||
|
)
|
||||||
|
show_discussion_post = models.BooleanField(
|
||||||
|
default=False, help_text="Show the case discussion (post exam)"
|
||||||
|
)
|
||||||
|
show_report_post = models.BooleanField(
|
||||||
|
default=False, help_text="Show the case report (post exam)"
|
||||||
)
|
)
|
||||||
show_report = models.BooleanField(default=False, help_text="Show the case report")
|
|
||||||
|
|
||||||
author = models.ManyToManyField(
|
author = models.ManyToManyField(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
@@ -709,6 +725,7 @@ class CaseCollection(models.Model):
|
|||||||
content_type=content_type, object_id=self.pk, cid_user=cid_user
|
content_type=content_type, object_id=self.pk, cid_user=cid_user
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CaseDetail(models.Model):
|
class CaseDetail(models.Model):
|
||||||
case = models.ForeignKey(Case, on_delete=models.CASCADE)
|
case = models.ForeignKey(Case, on_delete=models.CASCADE)
|
||||||
collection = models.ForeignKey(CaseCollection, on_delete=models.CASCADE)
|
collection = models.ForeignKey(CaseCollection, on_delete=models.CASCADE)
|
||||||
@@ -745,3 +762,6 @@ class CidReportAnswer(models.Model):
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
if self.answer:
|
if self.answer:
|
||||||
self.answer = self.answer.strip()
|
self.answer = self.answer.strip()
|
||||||
|
|
||||||
|
def get_answer_score(self):
|
||||||
|
return self.score
|
||||||
@@ -15,7 +15,22 @@
|
|||||||
{% if request.user.is_superuser %}
|
{% if request.user.is_superuser %}
|
||||||
<a href="{% url 'admin:atlas_case_change' case.id %}" title="Edit the Case using the admin interface">Admin Edit</a>
|
<a href="{% url 'admin:atlas_case_change' case.id %}" title="Edit the Case using the admin interface">Admin Edit</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if collection %}
|
||||||
|
<div>
|
||||||
|
|
||||||
|
{% if previous %}
|
||||||
|
<a href="{% url 'atlas:collection_case_view' collection.id case_number|add:-1 %}">Previous question</a>
|
||||||
|
{% endif %}
|
||||||
|
Viewing question as part of collection: {{collection.name}} [{{case_number|add:1}}/{{collection_length}}]
|
||||||
|
{% if next %}
|
||||||
|
<a href="{% url 'atlas:collection_case_view' collection.id case_number|add:1 %}">Next question</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% include 'atlas/case_display_block.html' %}
|
{% include 'atlas/case_display_block.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,12 @@
|
|||||||
<li>{{p.get_link}}</li>
|
<li>{{p.get_link}}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
<b>Collections:</b>
|
||||||
|
<ul>
|
||||||
|
{% for p in case.casecollection_set.all %}
|
||||||
|
<li>{{p.name}}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</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>
|
||||||
|
|||||||
+5
-19
@@ -3,18 +3,18 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Case {{case_number|add:1}}
|
<h2>Case {{case_number|add:1}}
|
||||||
|
|
||||||
{% if collection.show_title %}
|
{% if show_title %}
|
||||||
: {{case.title}}
|
: {{case.title}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{% if collection.show_description and case.description%}
|
{% if show_description and case.description %}
|
||||||
<div>
|
<div>
|
||||||
Description: {{case.description}}
|
Description: {{case.description}}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if collection.show_history and case.history%}
|
{% if collection.show_history_pre and case.history%}
|
||||||
<div>
|
<div>
|
||||||
History: {{case.history}}
|
History: {{case.history}}
|
||||||
</div>
|
</div>
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% if collection.show_discussion and case.discussion%}
|
{% if show_discussion and case.discussion%}
|
||||||
<details>
|
<details>
|
||||||
<summary>
|
<summary>
|
||||||
Discussion:
|
Discussion:
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if collection.show_report and case.report%}
|
{% if show_report and case.report%}
|
||||||
<details>
|
<details>
|
||||||
<summary>
|
<summary>
|
||||||
Report:
|
Report:
|
||||||
@@ -59,7 +59,6 @@
|
|||||||
</details>
|
</details>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if take %}
|
|
||||||
<form method="POST" class="post-form">{% csrf_token %}
|
<form method="POST" class="post-form">{% csrf_token %}
|
||||||
<div class="form-contents">
|
<div class="form-contents">
|
||||||
{% if collection.publish_results %}<fieldset disabled="disabled">{% endif %}
|
{% if collection.publish_results %}<fieldset disabled="disabled">{% endif %}
|
||||||
@@ -85,19 +84,6 @@
|
|||||||
<button type="submit" name="finish" class="save btn btn-default">Overview</button>
|
<button type="submit" name="finish" class="save btn btn-default">Overview</button>
|
||||||
<button type="submit" id="goto-button" value="test" name="goto" class="hide">goto</button>
|
<button type="submit" id="goto-button" value="test" name="goto" class="hide">goto</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
|
||||||
<div>
|
|
||||||
{% if previous %}
|
|
||||||
<a href="{% url 'atlas:collection_case_view' pk=collection.pk case_number=case_number|add:-1 %}">Previous</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if next %}
|
|
||||||
<a href="{% url 'atlas:collection_case_view' pk=collection.pk case_number=case_number|add:1 %}">Next</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
Return to <a href='{{collection.get_absolute_url}}'>collection</a>
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block js %}
|
{% block js %}
|
||||||
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'atlas/collection_headers.html' %}
|
{% include 'atlas/collection_headers.html' %}
|
||||||
|
<div>
|
||||||
|
Exam mode: {{collection.exam_mode}}<br />
|
||||||
|
Publish results: {{collection.publish_results}}<br />
|
||||||
|
Active: {{collection.active}}<br />
|
||||||
|
Collection Type: {{collection.collection_type}}<br />
|
||||||
|
</div>
|
||||||
|
<h3>Cases</h3>
|
||||||
<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><a href="{% url 'atlas:collection_case_view' pk=collection.pk case_number=forloop.counter0 %}">Case {{forloop.counter}}</a>
|
||||||
@@ -11,4 +18,6 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<p>This collection will be available to view/take <a href='{{collection.get_take_url}}'>here</a>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
{% extends 'atlas/base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="stats-plot">{{plot|safe}}</div>
|
||||||
|
<div class="atlas">
|
||||||
|
<h2>{{ exam.name }}</h2>
|
||||||
|
|
||||||
|
{% if unmarked %}
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
The following questions need marking
|
||||||
|
{% for exam_index in unmarked %}
|
||||||
|
<a href="{% url 'atlas:collection_mark_question' exam.pk exam_index %}">{{ exam_index|add:1 }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="stats-block">
|
||||||
|
<h3>Stats</h3>
|
||||||
|
Candidates: {{cids|length}}<br />
|
||||||
|
Max score: {{max_score}}<br />
|
||||||
|
Mean: {{mean}}, Median {{median}}, Mode {{mode}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<table class="table table-dark table-striped table-hover table-sm cid-score-table">
|
||||||
|
<tr>
|
||||||
|
<th>Candidate ID</th>
|
||||||
|
<th>Score</th>
|
||||||
|
{% comment %} <th>Normalised Score</th> {% endcomment %}
|
||||||
|
</tr>
|
||||||
|
{% comment %} {% for user, value in user_answers_marks.items %} {% endcomment %}
|
||||||
|
{% for cid in cids %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{% url 'cid_scores_admin' cid %}">{{cid}}</a></td>
|
||||||
|
<td>{{user_scores|get_item:cid}}</td>
|
||||||
|
{% comment %} <td>{{user_scores_normalised|get_item:user}}</td> {% endcomment %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>Answers as a table</h3>
|
||||||
|
<table class="table table-dark table-striped table-hover table-sm col-sm">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Candidate</th>
|
||||||
|
{% for cid in cids %}
|
||||||
|
<th>{{cid}}</th>
|
||||||
|
{% comment %} <th><a href="{% url 'atlas:exam_scores_cid_user' exam.pk cid 'None' %}">{{cid}}</a></th> {% endcomment %}
|
||||||
|
{% comment %} <th><a href="">{{cid}}</a></th> {% endcomment %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% for question in questions %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{% url 'atlas:collection_mark_question' exam.pk forloop.counter0 %}">Question {{forloop.counter}}</a>
|
||||||
|
</td>
|
||||||
|
{% comment %} {% for cid in cids %}
|
||||||
|
<td class="atlas-ans user-answer-score-{{score_by_question|get_item:question|get_item:cid}}" title="answer score: {{score_by_question|get_item:question|get_item:cid}}">{{ans_by_question|get_item:question|get_item:cid}}</td>
|
||||||
|
{% endfor %} {% endcomment %}
|
||||||
|
{% for cid in cids %}
|
||||||
|
{% with by_question|get_item:question|get_item:cid as ans_score %}
|
||||||
|
<td class="atlas-ans user-answer-score-{{ans_score.1}}" title="answer score: {{ans_score.1}}">{{ans_score.0}}</td>
|
||||||
|
{% endwith %}
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
<tr>
|
||||||
|
<td>Score:</td>
|
||||||
|
{% for cid in cids %}
|
||||||
|
<td>{{user_scores|get_item:cid}}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
+211
-37
@@ -873,6 +873,7 @@ def series_order_dicom(request, pk):
|
|||||||
|
|
||||||
return redirect("atlas:series_detail", pk=pk)
|
return redirect("atlas:series_detail", pk=pk)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@user_is_author_or_atlas_series_checker
|
@user_is_author_or_atlas_series_checker
|
||||||
def series_anonymise_dicom(request, pk):
|
def series_anonymise_dicom(request, pk):
|
||||||
@@ -1138,7 +1139,9 @@ def collection_mark_question(request, pk, case_number):
|
|||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
for answer in answers:
|
for answer in answers:
|
||||||
if answer:
|
if answer:
|
||||||
form = CidReportAnswerMarkForm(request.POST, instance=answer, prefix=answer.id)
|
form = CidReportAnswerMarkForm(
|
||||||
|
request.POST, instance=answer, prefix=answer.id
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
form = CidReportAnswerMarkForm(request.POST, prefix=answer.id)
|
form = CidReportAnswerMarkForm(request.POST, prefix=answer.id)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@@ -1152,27 +1155,21 @@ def collection_mark_question(request, pk, case_number):
|
|||||||
|
|
||||||
if "save" in request.POST:
|
if "save" in request.POST:
|
||||||
return redirect(
|
return redirect(
|
||||||
"atlas:collection_mark_question",
|
"atlas:collection_mark_question", case_number=case_number, **kwargs
|
||||||
case_number=case_number,
|
|
||||||
**kwargs
|
|
||||||
)
|
)
|
||||||
if "next" in request.POST:
|
if "next" in request.POST:
|
||||||
return redirect(
|
return redirect(
|
||||||
"atlas:collection_mark_question",
|
"atlas:collection_mark_question", case_number=case_number + 1, **kwargs
|
||||||
case_number=case_number + 1,
|
|
||||||
**kwargs
|
|
||||||
)
|
)
|
||||||
elif "previous" in request.POST:
|
elif "previous" in request.POST:
|
||||||
return redirect(
|
return redirect(
|
||||||
"atlas:collection_mark_question",
|
"atlas:collection_mark_question", case_number=case_number - 1, **kwargs
|
||||||
case_number=case_number - 1,
|
|
||||||
**kwargs
|
|
||||||
)
|
)
|
||||||
elif "goto" in request.POST:
|
elif "goto" in request.POST:
|
||||||
return redirect(
|
return redirect(
|
||||||
"atlas:collection_mark_question",
|
"atlas:collection_mark_question",
|
||||||
case_number=int(request.POST.get("goto")),
|
case_number=int(request.POST.get("goto")),
|
||||||
**kwargs
|
**kwargs,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
for answer in answers:
|
for answer in answers:
|
||||||
@@ -1193,18 +1190,11 @@ def collection_mark_question(request, pk, case_number):
|
|||||||
"case_number": case_number,
|
"case_number": case_number,
|
||||||
"answer_forms": answer_forms,
|
"answer_forms": answer_forms,
|
||||||
"previous": previous,
|
"previous": previous,
|
||||||
"next": next
|
"next": next,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@user_is_collection_author_or_atlas_editor
|
|
||||||
def collection_scores_cid(request, pk):
|
|
||||||
collection = get_object_or_404(CaseCollection, pk=pk)
|
|
||||||
|
|
||||||
return render(request, "atlas/collection_take.html", {"collection": collection})
|
|
||||||
|
|
||||||
|
|
||||||
@user_is_collection_author_or_atlas_editor
|
@user_is_collection_author_or_atlas_editor
|
||||||
def collection_candidates(request, pk):
|
def collection_candidates(request, pk):
|
||||||
collection = get_object_or_404(CaseCollection, pk=pk)
|
collection = get_object_or_404(CaseCollection, pk=pk)
|
||||||
@@ -1275,13 +1265,6 @@ def collection_take_overview(request, pk, cid, passcode):
|
|||||||
|
|
||||||
|
|
||||||
def collection_case_view_take(request, pk, case_number, cid, passcode):
|
def collection_case_view_take(request, pk, case_number, cid, passcode):
|
||||||
return collection_case_view_base(request, pk, case_number, True, cid, passcode)
|
|
||||||
|
|
||||||
@user_is_collection_author_or_atlas_editor
|
|
||||||
def collection_case_view(request, pk, case_number, take=False, cid=None, passcode=None):
|
|
||||||
return collection_case_view_base(request, pk, case_number, take=False, cid=None, passcode=None)
|
|
||||||
|
|
||||||
def collection_case_view_base(request, pk, case_number, take=False, cid=None, passcode=None):
|
|
||||||
collection = get_object_or_404(CaseCollection, pk=pk)
|
collection = get_object_or_404(CaseCollection, pk=pk)
|
||||||
|
|
||||||
form = None
|
form = None
|
||||||
@@ -1291,13 +1274,12 @@ def collection_case_view_base(request, pk, case_number, take=False, cid=None, pa
|
|||||||
if not collection.active:
|
if not collection.active:
|
||||||
raise Http404("Exam not found")
|
raise Http404("Exam not found")
|
||||||
|
|
||||||
if take and not collection.check_cid_user(cid, passcode, request):
|
if not collection.check_cid_user(cid, passcode, request):
|
||||||
raise Http404("Error accessing exam")
|
raise Http404("Error accessing exam")
|
||||||
|
|
||||||
cases = collection.cases.all().order_by("casedetail__sort_order").prefetch_related()
|
cases = collection.cases.all().order_by("casedetail__sort_order").prefetch_related()
|
||||||
case = cases[case_number]
|
case = cases[case_number]
|
||||||
|
|
||||||
if take:
|
|
||||||
c = CidUser.objects.filter(cid=cid).first()
|
c = CidUser.objects.filter(cid=cid).first()
|
||||||
cid_user_exam = collection.get_or_create_cid_user_exam(cid_user=c)
|
cid_user_exam = collection.get_or_create_cid_user_exam(cid_user=c)
|
||||||
case_detail = CaseDetail.objects.get(case=case, collection=collection)
|
case_detail = CaseDetail.objects.get(case=case, collection=collection)
|
||||||
@@ -1323,15 +1305,11 @@ def collection_case_view_base(request, pk, case_number, take=False, cid=None, pa
|
|||||||
|
|
||||||
if "next" in request.POST:
|
if "next" in request.POST:
|
||||||
return redirect(
|
return redirect(
|
||||||
"atlas:collection_case_view_take",
|
"atlas:collection_case_view_take", case_number=case_number + 1, **kwargs
|
||||||
case_number=case_number + 1,
|
|
||||||
**kwargs
|
|
||||||
)
|
)
|
||||||
elif "previous" in request.POST:
|
elif "previous" in request.POST:
|
||||||
return redirect(
|
return redirect(
|
||||||
"atlas:collection_case_view_take",
|
"atlas:collection_case_view_take", case_number=case_number - 1, **kwargs
|
||||||
case_number=case_number - 1,
|
|
||||||
**kwargs
|
|
||||||
)
|
)
|
||||||
elif "finish" in request.POST:
|
elif "finish" in request.POST:
|
||||||
return redirect("atlas:collection_take_overview", **kwargs)
|
return redirect("atlas:collection_take_overview", **kwargs)
|
||||||
@@ -1339,7 +1317,7 @@ def collection_case_view_base(request, pk, case_number, take=False, cid=None, pa
|
|||||||
return redirect(
|
return redirect(
|
||||||
"atlas:collection_case_view_take",
|
"atlas:collection_case_view_take",
|
||||||
case_number=int(request.POST.get("goto")),
|
case_number=int(request.POST.get("goto")),
|
||||||
**kwargs
|
**kwargs,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
form = CidReportAnswerForm(instance=answer)
|
form = CidReportAnswerForm(instance=answer)
|
||||||
@@ -1349,9 +1327,22 @@ def collection_case_view_base(request, pk, case_number, take=False, cid=None, pa
|
|||||||
previous = case_number > 0
|
previous = case_number > 0
|
||||||
next = case_number < (len(cases) - 1)
|
next = case_number < (len(cases) - 1)
|
||||||
|
|
||||||
|
if collection.publish_results:
|
||||||
|
show_title = collection.show_title_post
|
||||||
|
show_history = collection.show_history_post
|
||||||
|
show_description = collection.show_description_post
|
||||||
|
show_discussion = collection.show_discussion_post
|
||||||
|
show_report = collection.show_report_post
|
||||||
|
else:
|
||||||
|
show_title = collection.show_title_pre
|
||||||
|
show_history = collection.show_history_pre
|
||||||
|
show_description = collection.show_description_pre
|
||||||
|
show_discussion = collection.show_discussion_pre
|
||||||
|
show_report = collection.show_report_pre
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"atlas/collection_case_view.html",
|
"atlas/collection_case_view_take.html",
|
||||||
{
|
{
|
||||||
"form": form,
|
"form": form,
|
||||||
"collection": collection,
|
"collection": collection,
|
||||||
@@ -1360,9 +1351,192 @@ def collection_case_view_base(request, pk, case_number, take=False, cid=None, pa
|
|||||||
"case_number": case_number,
|
"case_number": case_number,
|
||||||
"previous": previous,
|
"previous": previous,
|
||||||
"next": next,
|
"next": next,
|
||||||
"take": take,
|
|
||||||
"cid": cid,
|
"cid": cid,
|
||||||
"passcode": passcode,
|
"passcode": passcode,
|
||||||
"answer": answer,
|
"answer": answer,
|
||||||
|
"show_title": show_title,
|
||||||
|
"show_history": show_history,
|
||||||
|
"show_description": show_description,
|
||||||
|
"show_discussion": show_discussion,
|
||||||
|
"show_report": show_report,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@user_is_collection_author_or_atlas_editor
|
||||||
|
def collection_case_view(request, pk, case_number):
|
||||||
|
collection = get_object_or_404(CaseCollection, pk=pk)
|
||||||
|
|
||||||
|
form = None
|
||||||
|
answer = None
|
||||||
|
|
||||||
|
if collection.collection_type != collection.COLLECTION_TYPE_CHOICES.REVIEW:
|
||||||
|
if not collection.active:
|
||||||
|
raise Http404("Exam not found")
|
||||||
|
|
||||||
|
cases = collection.cases.all().order_by("casedetail__sort_order").prefetch_related()
|
||||||
|
case = cases[case_number]
|
||||||
|
|
||||||
|
series_list = case.series.all().prefetch_related("images", "examination", "plane")
|
||||||
|
|
||||||
|
previous = case_number > 0
|
||||||
|
next = case_number < (len(cases) - 1)
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"atlas/case_detail.html",
|
||||||
|
{
|
||||||
|
"form": form,
|
||||||
|
"collection": collection,
|
||||||
|
"collection_length": len(cases),
|
||||||
|
"case": case,
|
||||||
|
"series_list": series_list,
|
||||||
|
"case_number": case_number,
|
||||||
|
"previous": previous,
|
||||||
|
"next": next,
|
||||||
|
"answer": answer,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@user_is_collection_author_or_atlas_editor
|
||||||
|
def collection_scores_cid(request, pk):
|
||||||
|
collection = get_object_or_404(CaseCollection, pk=pk)
|
||||||
|
|
||||||
|
if not collection.exam_mode:
|
||||||
|
raise Http404("Collection not in exam mode")
|
||||||
|
|
||||||
|
user_answers_and_marks = defaultdict(list)
|
||||||
|
user_answers_marks = defaultdict(list)
|
||||||
|
user_answers = defaultdict(list)
|
||||||
|
user_names = {}
|
||||||
|
# cid_passcodes = {}
|
||||||
|
|
||||||
|
by_question = defaultdict(dict)
|
||||||
|
unmarked = set()
|
||||||
|
|
||||||
|
cases = collection.cases.all().order_by("casedetail__sort_order").prefetch_related()
|
||||||
|
|
||||||
|
case_details = CaseDetail.objects.filter(
|
||||||
|
case__in=cases, collection=collection
|
||||||
|
).prefetch_related("case")
|
||||||
|
|
||||||
|
cid_user_answers = CidReportAnswer.objects.filter(question__in=case_details)
|
||||||
|
|
||||||
|
# questions = exam.exam_questions.all().prefetch_related("answers")
|
||||||
|
|
||||||
|
# cid_user_answers = CidReportAnswer.objects.select_related("question").filter(
|
||||||
|
# question__in=questions, exam__id=pk
|
||||||
|
# )
|
||||||
|
|
||||||
|
cids = set()
|
||||||
|
|
||||||
|
# Loop through all candidates
|
||||||
|
for cid_user_answer in cid_user_answers:
|
||||||
|
# Convoluted (probably...)
|
||||||
|
cid = cid_user_answer.cid
|
||||||
|
# cid_passcodes[cid] = cid_user_answer.passcode
|
||||||
|
cids.add(cid)
|
||||||
|
s = cid_user_answer
|
||||||
|
user_names[cid] = cid
|
||||||
|
|
||||||
|
q = cid_user_answer.question
|
||||||
|
|
||||||
|
# if not s:
|
||||||
|
# # skip if no answer
|
||||||
|
# user_answers_marks[cid].append(0)
|
||||||
|
# user_answers[cid].append("")
|
||||||
|
# by_question[q].append(("", 0))
|
||||||
|
# continue
|
||||||
|
|
||||||
|
ans = s.answer
|
||||||
|
|
||||||
|
answer_score = s.get_answer_score()
|
||||||
|
if answer_score == "unmarked":
|
||||||
|
index = cases.index(q)
|
||||||
|
unmarked.add(index)
|
||||||
|
user_answers[cid].append(ans)
|
||||||
|
user_answers_marks[cid].append(answer_score)
|
||||||
|
user_answers_and_marks[cid].append((ans, answer_score))
|
||||||
|
|
||||||
|
by_question[q][cid] = (ans, answer_score)
|
||||||
|
|
||||||
|
user_scores = {}
|
||||||
|
user_scores_normalised = {}
|
||||||
|
for user in user_answers_marks:
|
||||||
|
|
||||||
|
user_scores[user] = sum(
|
||||||
|
[i for i in user_answers_marks[user] if i != "unmarked"]
|
||||||
|
)
|
||||||
|
|
||||||
|
user_scores_normalised[user] = user_scores[user]
|
||||||
|
|
||||||
|
# ignore scores of 0 for stats
|
||||||
|
user_scores_list = [i for i in user_scores.values() if i > 0]
|
||||||
|
|
||||||
|
max_score = len(cases)
|
||||||
|
|
||||||
|
if len(user_scores_list) < 1:
|
||||||
|
mean = 0
|
||||||
|
median = 0
|
||||||
|
mode = 0
|
||||||
|
fig_html = ""
|
||||||
|
else:
|
||||||
|
mean = statistics.mean(user_scores_list)
|
||||||
|
median = statistics.median(user_scores_list)
|
||||||
|
try:
|
||||||
|
mode = statistics.mode(user_scores_list)
|
||||||
|
except statistics.StatisticsError:
|
||||||
|
mode = "No unique mode"
|
||||||
|
|
||||||
|
df = user_scores_list
|
||||||
|
fig = px.histogram(
|
||||||
|
df,
|
||||||
|
x=0,
|
||||||
|
title="{}: distribution of scores".format(collection),
|
||||||
|
labels={"0": "Score"},
|
||||||
|
height=400,
|
||||||
|
width=600,
|
||||||
|
)
|
||||||
|
fig_html = fig.to_html()
|
||||||
|
|
||||||
|
collection.stats_mean = mean
|
||||||
|
collection.stats_median = median
|
||||||
|
collection.stats_mode = mode
|
||||||
|
|
||||||
|
collection.stats_candidates = len(user_scores_list)
|
||||||
|
collection.stats_max_possible = max_score
|
||||||
|
|
||||||
|
collection.stats_min = min(user_scores_list)
|
||||||
|
collection.stats_max = max(user_scores_list)
|
||||||
|
|
||||||
|
collection.stats_graph = fig_html
|
||||||
|
|
||||||
|
collection.user_scores = user_scores
|
||||||
|
|
||||||
|
collection.save()
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
f"atlas/collection_scores.html",
|
||||||
|
{
|
||||||
|
"cids": sorted(cids),
|
||||||
|
# "cid_passcodes": cid_passcodes,
|
||||||
|
"exam": collection,
|
||||||
|
"unmarked": unmarked,
|
||||||
|
"questions": cases,
|
||||||
|
"by_question": by_question,
|
||||||
|
"user_answers": dict(user_answers),
|
||||||
|
"user_answers_marks": dict(user_answers_marks),
|
||||||
|
"user_scores": user_scores,
|
||||||
|
"user_scores_normalised": user_scores_normalised,
|
||||||
|
"user_scores_list": user_scores_list,
|
||||||
|
"user_names": user_names,
|
||||||
|
"user_answers_and_marks": user_answers_and_marks,
|
||||||
|
"max_score": max_score,
|
||||||
|
"mean": mean,
|
||||||
|
"median": median,
|
||||||
|
"mode": mode,
|
||||||
|
"plot": fig_html,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,3 +24,4 @@ pymemcache
|
|||||||
django-autocomplete-light
|
django-autocomplete-light
|
||||||
django-querysetsequence
|
django-querysetsequence
|
||||||
dicognito
|
dicognito
|
||||||
|
django_unused_media
|
||||||
Reference in New Issue
Block a user