start physics...

This commit is contained in:
Ross
2020-12-15 23:07:06 +00:00
parent fc098f681c
commit 90f69c2c14
26 changed files with 1301 additions and 1 deletions
+16
View File
@@ -322,4 +322,20 @@ img.uploading:hover {
.google-link { .google-link {
padding-left: 10px; padding-left: 10px;
font-size: smaller; font-size: smaller;
}
#full-question-list-physics .abcde li {
padding: 0px;
clear: right;
}
#full-question-list-physics li {
padding-bottom: 10px;
}
#full-question-list-physics {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
} }
+3
View File
@@ -25,6 +25,9 @@
<body> <body>
<div class="page-header"> <div class="page-header">
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<span id="physics-link">
<a href="{% url 'physics:index' %}">Physics</a>
</span>
<span id="logout-link"> <span id="logout-link">
<a href="{% url 'logout' %}">Logout</a> <a href="{% url 'logout' %}">Logout</a>
</span> </span>
+1 -1
View File
@@ -25,7 +25,7 @@
Unmarked: Unmarked:
<ul id="new-answer-list" class="answer-list"> <ul id="new-answer-list" class="answer-list">
{% for answer in user_answers %} {% for answer in user_answers %}
<li class="not-marked"><span class="answer">{{ answer }}</span></li> <li><span class="answer not-marked">{{ answer }}</span></li>
{% endfor %} {% endfor %}
</ul> </ul>
<div class="answer-list key">Key: <span class="correct">2 Marks</span>, <span class="half-correct">1 <div class="answer-list key">Key: <span class="correct">2 Marks</span>, <span class="half-correct">1
View File
+26
View File
@@ -0,0 +1,26 @@
from anatomy.models import CidUserAnswer
from physics.models import Category, Question, Exam, CidUserAnswer
from django.contrib import admin
# Register your models here.
from reversion.admin import VersionAdmin
class ExamInline(admin.TabularInline):
model = Question.exams.through
extra = 1
class QuestionAdmin(VersionAdmin):
inlines = [
#HalfMarkAnatomyAnswersInline,
#IncorrectAnatomyAnswersInline,
ExamInline,
]
admin.site.register(Category)
admin.site.register(Question, QuestionAdmin)
admin.site.register(Exam)
admin.site.register(CidUserAnswer)
+5
View File
@@ -0,0 +1,5 @@
from django.apps import AppConfig
class PhysicsConfig(AppConfig):
name = 'physics'
+72
View File
@@ -0,0 +1,72 @@
# Generated by Django 3.1.3 on 2020-12-15 18:36
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import sortedm2m.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('category', models.CharField(max_length=200)),
],
),
migrations.CreateModel(
name='Question',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('stem', models.TextField(help_text='Stem of the question')),
('a', models.TextField(help_text='The question text')),
('a_answer', models.BooleanField(default=True, help_text='Answer')),
('b', models.TextField(help_text='The question text')),
('b_answer', models.BooleanField(default=True, help_text='Answer')),
('c', models.TextField(help_text='The question text')),
('c_answer', models.BooleanField(default=True, help_text='Answer')),
('d', models.TextField(help_text='The question text')),
('d_answer', models.BooleanField(default=True, help_text='Answer')),
('e', models.TextField(help_text='The question text')),
('e_answer', models.BooleanField(default=True, help_text='Answer')),
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
('author', models.ManyToManyField(blank=True, help_text='Author(s) of question', related_name='physics_authored_questions', to=settings.AUTH_USER_MODEL)),
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='physics.category')),
],
),
migrations.CreateModel(
name='Exam',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('active', models.BooleanField(default=True, help_text='If an exam should be available')),
('publish_results', models.BooleanField(default=True, help_text='If an exams results should be available')),
('exam_questions', sortedm2m.fields.SortedManyToManyField(blank='true', help_text=None, related_name='exams', to='physics.Question')),
],
),
migrations.CreateModel(
name='CidUserAnswer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('a', models.BooleanField()),
('b', models.BooleanField()),
('c', models.BooleanField()),
('d', models.BooleanField()),
('e', models.BooleanField()),
('cid', models.IntegerField(blank=True, null=True)),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('exam', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cid_user_answers', to='physics.exam')),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cid_user_answers', to='physics.question')),
],
),
]
View File
+145
View File
@@ -0,0 +1,145 @@
from django.db import models
from django.utils import timezone
from django.core.files.storage import FileSystemStorage
from django.conf import settings
from django.utils.html import format_html
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from sortedm2m.fields import SortedManyToManyField
class Category(models.Model):
category = models.CharField(max_length=200)
def __str__(self):
return self.category
class Question(models.Model):
stem = models.TextField(
blank=False,
help_text="Stem of the question",
)
a = models.TextField(
blank=False,
help_text="The question text",
)
a_answer = models.BooleanField(
help_text="Answer (check for true)", default=True
)
b = models.TextField(
blank=False,
help_text="The question text",
)
b_answer = models.BooleanField(
help_text="Answer", default=True
)
c = models.TextField(
blank=False,
help_text="The question text",
)
c_answer = models.BooleanField(
help_text="Answer", default=True
)
d = models.TextField(
blank=False,
help_text="The question text",
)
d_answer = models.BooleanField(
help_text="Answer", default=True
)
e = models.TextField(
blank=False,
help_text="The question text",
)
e_answer = models.BooleanField(
help_text="Answer", default=True
)
created_date = models.DateTimeField(default=timezone.now)
author = models.ManyToManyField(
settings.AUTH_USER_MODEL,
blank=True,
help_text="Author(s) of question",
related_name="physics_authored_questions",
)
category = models.ForeignKey(
Category, on_delete=models.SET_NULL, null=True, blank=True
)
def __str__(self):
return self.stem
def clean(self):
if self.a:
self.a = self.a.strip()
if self.b:
self.b = self.b.strip()
if self.c:
self.c = self.c.strip()
if self.d:
self.d = self.d.strip()
if self.e:
self.e = self.e.strip()
class Exam(models.Model):
name = models.CharField(max_length=200)
exam_questions = SortedManyToManyField(
Question, related_name="exams", blank="true"
)
active = models.BooleanField(
help_text="If an exam should be available", default=True
)
publish_results = models.BooleanField(
help_text="If an exams results should be available", default=True
)
def __str__(self):
return self.name
def get_exam_name(self):
return self.name
def get_json_url(self):
return reverse("anatomy:exam_json", args=(self.pk,))
def get_question_index(self, question):
return list(self.exam_questions.all()).index(question)
class CidUserAnswer(models.Model):
"""User answers by candidate"""
question = models.ForeignKey(
Question, related_name="cid_user_answers", on_delete=models.CASCADE
)
a = models.BooleanField(blank=False)
b = models.BooleanField(blank=False)
c = models.BooleanField(blank=False)
d = models.BooleanField(blank=False)
e = models.BooleanField(blank=False)
cid = models.IntegerField(blank=True, null=True)
# Each user answer is associated with a particular exam
exam = models.ForeignKey(
Exam, related_name="cid_user_answers", on_delete=models.CASCADE, null=True
)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return "{}/{}/{}".format(self.cid, self.exam, self.question.pk)
+59
View File
@@ -0,0 +1,59 @@
{% load static %}
<html>
<head>
<title>Anatomy Quiz</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" />
<link rel="stylesheet" href="{% static 'tagulous/lib/select2-3/select2.css' %}">
<link rel="stylesheet" href="{% static 'css/anatomy.css' %}">
<link rel="stylesheet" href="{% static 'css/toastr.min.css' %}">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="{% static 'js/toastr.min.js' %}"></script>
<script src="{% static 'js/cornerstone/hammer.js' %}"></script>
<script src="{% static 'js/cornerstone/cornerstone.min.js' %}"></script>
<script src="{% static 'js/cornerstone/dicomParser.min.js' %}"></script>
<script src="{% static 'js/cornerstone/cornerstoneMath.min.js' %}"></script>
<script src="{% static 'js/cornerstone/cornerstoneTools.min.js' %}"></script>
<script src="{% static 'js/cornerstone/cornerstoneWebImageLoader.min.js' %}"></script>
<script src="{% static 'js/cornerstone/cornerstoneWADOImageLoader.js' %}"></script>
<script src="{% static 'js/cornerstone/cornerstone-base64-image-loader.umd.js' %}"></script>
<script src="{% static 'js/anatomy.js' %}" defer="defer"></script>
{% block js %}
{% endblock %}
</head>
<body>
<div class="page-header">
{% if request.user.is_authenticated %}
<span id="anatomy-link">
<a href="{% url 'anatomy:index' %}">Anatomy</a>
</span>
<span id="logout-link">
<a href="{% url 'logout' %}">Logout</a>
</span>
<span id="profile-link">
<a href="{% url 'profile' %}">Profile</a>
</span>
{% endif %}
{% if request.user.is_staff %}
<span id="admin-link">
<a href="{% url 'admin:index' %}">Admin</a>
</span>
{% endif %}
</div>
<div class="content container">
{% if request.user.is_authenticated %}
<a href="{% url 'physics:exam_list' %}">Exams</a> /
{% endif %}
{% block navigation %}
{% endblock %}
<div class="row">
<div class="col-md-8">
{% block content %}
{% endblock %}
</div>
</div>
</div>
</body>
</html>
+13
View File
@@ -0,0 +1,13 @@
{% extends 'physics/base.html' %}
{% block content %}
<div class="physics">
<h2>CID: {{ cid }}</h2>
The following exams have been found (click to view answers and scores):
<ul>
{% for exam in exams %}
<li><a href="{% url 'physics:exam_scores_cid_user' pk=exam.pk sk=cid %}">{{exam.name}}</a></li>
{% endfor %}
</ul>
</div>
{% endblock %}
@@ -0,0 +1,18 @@
{% extends 'physics/base.html' %}
{% block content %}
<div class="physics">
<h2>Please enter your CID</h2>
<p>CID: <input type="text" name="cid" id="cid-input"></p>
<button id="cid-selector-go-button">Go...</button>
</div>
<script>
$(document).ready(function () {
$("#cid-selector-go-button").click(() => {
cid = document.getElementById("cid-input").value;
window.location = window.location + cid;
})
})
</script>
{% endblock %}
+30
View File
@@ -0,0 +1,30 @@
{% extends 'physics/base.html' %}
{% block content %}
<h1>Examinations</h1>
<div class="physics">
Active exams:<br/>
<ul>
{% for exam in exams %}
{% if exam.active %}
<li>
<a href="{% url 'physics:exam_overview' pk=exam.pk %}">{{exam.name}}</a>
</li>
{% endif %}
{% endfor %}
</ul>
Inactive exams:<br/>
<ul>
{% for exam in exams %}
{% if not exam.active %}
<li>
<a href="{% url 'physics:exam_overview' pk=exam.pk %}">{{exam.name}}</a>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
{% endblock %}
@@ -0,0 +1,99 @@
{% extends 'physics/exams.html' %}
{% block content %}
{% load thumbnail %}
<div class="physics">
<h1>Exam: {{ exam.name }}</h1>
This exam has {{question_number}} questions.
<div class="parent-help" title="Click to enable / disable the exam">
Exam active: <input type="checkbox" id="exam-active-switch" {% if exam.active %}checked{% endif %}> <span class="help-text">[When checked the exam will be available to take in the test system]</span>
</div>
<div class="parent-help" title="Click to enable / disable the exam results">
Publish results: <input type="checkbox" id="exam-publish-results-switch" {% if exam.publish_results %}checked{% endif %}> <span class="help-text">[When checked the exam results will be available on this site]</span>
</div>
<!--<p><button><a href="{% url 'anatomy:exam_take' pk=exam.pk sk=0 %}">Click here to start</a></button></p>-->
<ol id="full-question-list">
{% for question in questions.all %}
<li>
{{ question.stem }}
<ol type="a" class="abcde">
<li>
{{ question.a }}: {{ question.a_answer }}
</li>
<li>
{{ question.b }}: {{ question.b_answer }}
</li>
<li>
{{ question.c }}: {{ question.c_answer }}
</li>
<li>
{{ question.d }}: {{ question.d_answer }}
</li>
<li>
{{ question.e }}: {{ question.e_answer }}
</li>
</ol>
Category: {{ question.category }}, <a href="{% url 'physics:question_detail' pk=question.pk %}">View</a>
</li>
{% endfor %}
</ol>
</div>
<script type="text/javascript">
$(document).ready(function () {
// send request to change the is_private state on customSwitches toggle
$("#exam-active-switch").on("change", function () {
$.ajax({
url: "{% url 'anatomy:exam_toggle_active' pk=exam.pk %}",
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
active: this.checked // true if checked else false
},
type: "POST",
dataType: "json",
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
if (data.status == "success") {
toastr.info('Exam state changed.')
}
// show some message according to the response.
// For eg. A message box showing that the status has been changed
})
.always(function () {
console.log('[Done]');
})
})
$("#exam-publish-results-switch").on("change", function () {
$.ajax({
url: "{% url 'physics:exam_toggle_results_published' pk=exam.pk %}",
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
publish_results: this.checked // true if checked else false
},
type: "POST",
dataType: "json",
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
if (data.status == "success") {
toastr.info('Publish results state changed.')
}
// show some message according to the response.
// For eg. A message box showing that the status has been changed
})
.always(function () {
console.log('[Done]');
})
})
});
</script>
{% endblock %}
@@ -0,0 +1,36 @@
{% extends 'physics/exams.html' %}
{% block content %}
<div class="physics">
<h2>{{ exam.name }}</h2>
{% for user, value in user_answers_marks.items %}
<h3>Candidate: <a href="{% url 'physics:cid_scores' user %}">{{ user_names|get_item:user }}</a></h3>
<!-- Answers: {{ user_answers_and_marks|get_item:user }}-->
Answers: {% for ans, score in user_answers_and_marks|get_item:user %}
{{ans}} ({{score}}),
{% endfor %}
<br /> Total mark: {{ user_scores|get_item:user }}
{% endfor %}
</div>
<div id="stats-block">
<h2>Stats</h2>
Mean: {{mean}}, Median {{median}}, Mode {{mode}}
<h3>Distribution of results</h3>
<div id="stats-plot">{{plot|safe}}</div>
</div>
<div>
<table>
<tr>
<th>Candidate ID</th>
<th>Score</th>
</tr>
{% for user, value in user_answers_marks.items %}
<tr>
<td>{{user}}</td>
<td>{{user_scores|get_item:user}}</td>
</tr>
{% endfor %}
</table>
</div>
{% endblock %}
+222
View File
@@ -0,0 +1,222 @@
{% extends 'physics/base.html' %}
{% block content %}
<div class="physics">
<h1>Exam: {{ exam.name }}</h1>
<div>
Candidate number: <input type="number" id="cid" name="cid" title="please enter your candidate number">
</div>
<p>
Please make sure you submit answers before closing or navigating from this page (as otherwise they will not be
saved)
</p>
<ol id="full-question-list-physics">
{% for question in questions.all %}
<li>
{{ question.stem }}
<ol type="a" class="abcde">
<li>
<span class="question-text">{{ question.a }}:</span> <label class="truefalse-switch"> <input
type="checkbox" class="question" id="{{question.pk}}-a-checkbox" data-q="{{question.pk}}" data-a="a">
<div class="slider round"></div>
</label>
</li>
<li>
<span class="question-text">{{ question.b }}:</span> <label class="truefalse-switch"> <input
type="checkbox" class="question" id="{{question.pk}}-b-checkbox" data-q="{{question.pk}}" data-a="b">
<div class="slider round"></div>
</label>
</li>
<li>
<span class="question-text">{{ question.c }}:</span> <label class="truefalse-switch"> <input
type="checkbox" class="question" id="{{question.pk}}-c-checkbox" data-q="{{question.pk}}" data-a="c">
<div class="slider round"></div>
</label>
</li>
<li>
<span class="question-text">{{ question.d }}:</span> <label class="truefalse-switch"> <input
type="checkbox" class="question" id="{{question.pk}}-d-checkbox" data-q="{{question.pk}}" data-a="d">
<div class="slider round"></div>
</label>
</li>
<li>
<span class="question-text">{{ question.e }}:</span> <label class="truefalse-switch"> <input
type="checkbox" class="question" id="{{question.pk}}-e-checkbox" data-q="{{question.pk}}" data-a="e">
<div class="slider round"></div>
</label>
</li>
</ol>
</li>
{% endfor %}
</ol>
<button id="submit">Submit answers</button>
</div>
<script type="text/javascript">
window.answers_submitted = true;
$(document).ready(function () {
console.log($(".question-text"));
$(".question-text").click(function (evt) {
$(evt.target).parent().find("input").click();
});
$("#submit").click(() =>{
let cid = $("#cid").val()
if (cid == "") {
alert("You need to enter a valid candidate number.");
$("#cid").focus();
return
}
answers = [];
$("input.question").each((n, el) => {
answers.push([el.dataset.q, el.dataset.a, el.checked]);
})
ans = {
cid: parseInt(cid),
eid: {{exam.pk}},
ans: answers
}
postAnswers(ans);
})
});
window.onbeforeunload = confirmExit;
function confirmExit() {
if (!window.answers_submitted) {
return "You have attempted to leave this page. Are you sure?";
}
}
function postAnswers(ans) {
console.log(ans);
$.post("{% url 'physics:exam_answers_submit' %}", JSON.stringify(ans)).done((data) => {
console.log(data);
if (data.success) {
if (data.question_count == window.number_of_questions) {
let ret = confirm(
`${data.question_count} answers sucessfully submitted. Click OK to finish the exam.`
window.answers_submitted = true;
);
if (ret) {
window.saveSession();
if (config.exam_results_url != "") {
let url = config.exam_results_url;
if (window.cid != "") {
url = url + window.cid;
}
$("#options-link")
.empty()
.append(
`<a href="${url}" target="_blank"><div class="packet-button">Results and answers</div></a>`
);
}
$("#options-panel").show();
} else {
}
} else {
alert(`Answers sucessfully submitted.`);
}
} else {
alert(`Error submitting answers: ${data.error}`);
}
});
// $.post( "http://localhost:8000/submit_answers", JSON.stringify(ans));
}
</script>
<style>
.truefalse-switch {
position: relative;
display: inline-block;
width: 90px;
height: 20px;
margin: 0px;
margin-bottom: -5px;
}
.truefalse-switch input {
display: none;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 0, 0, 0.5);
-webkit-transition: .4s;
transition: .4s;
border-radius: 20px;
}
.slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 2px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
border-radius: 50%;
}
input:checked+.slider {
background-color: rgba(0, 255, 0, 0.5);
}
input:focus+.slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked+.slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(68px);
}
/*------ ADDED CSS ---------*/
.slider:after {
content: 'FALSE';
color: white;
display: block;
position: absolute;
transform: translate(-50%, -50%);
top: 50%;
left: 50%;
font-size: 10px;
font-family: Verdana, sans-serif;
}
input:checked+.slider:after {
content: 'TRUE';
}
.question-text {
display: inline-block;
}
</style>
{% endblock %}
+6
View File
@@ -0,0 +1,6 @@
{% extends 'physics/base.html' %}
{% block navigation %}
<br/>
{{exam.name}}-> <a href="{% url 'physics:exam_overview' pk=exam.pk %}">Overview</a> / <a href="{% url 'physics:exam_scores_cid' pk=exam.pk %}">Scores</a>
{% endblock %}
+10
View File
@@ -0,0 +1,10 @@
{% extends 'physics/base.html' %}
{% block content %}
{% for exam in exams %}
<div class="physics">
<h1><a href="{% url 'physics:exam_overview' pk=exam.pk %}">Exam: {{ exam.name }} </a></h1>
{% if request.user.is_staff %}<a href="{% url 'physics:exam_scores_cid' pk=exam.pk %}">Scores</a>{% endif %}
</div>
{% endfor %}
{% endblock %}
@@ -0,0 +1,69 @@
{% extends 'physics/base.html' %}
{% block content %}
{% load static %}
<div class="question">
<a href="{% url 'admin:physics_question_change' question.id %}" title="Edit the Question using the admin interface">Admin Edit</a>
<div class="date">
Created: {{ question.created_date }}
</div>
<h2>{{question.stem}}</h2>
<div>
<ol>
<li>{{ question.a }}: {{ question.a_answer }}</li>
<li>{{ question.b }}: {{ question.b_answer }}</li>
<li>{{ question.c }}: {{ question.c_answer }}</li>
<li>{{ question.d }}: {{ question.d_answer }}</li>
<li>{{ question.e }}: {{ question.e_answer }}</li>
</ol>
</div>
<div>
Examinations: {% for exam in question.exams.all %}
<a href="{% url 'physics:exam_overview' pk=exam.pk %}">{{ exam.name }}</a>
{% endfor %}
</div>
<div>
Category: {{ question.category }}
</div>
<div>
Author: {% for user in question.author.all %}
{{ author }},
{% endfor %}
</div>
</div>
<script type="text/javascript">
$(document).ready(function () {
$("#annotation-json-toggle").click(() => { $("#annotation-json-content").toggle() });
// send request to change the is_private state on customSwitches toggle
$("#save-annotations").click(function () {
json = getJsonToolStateNoId();
$.ajax({
url: "{% url 'anatomy:question_save_annotation' pk=question.pk %}",
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
annotation: json,
},
type: "POST",
dataType: "json",
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
// show some message according to the response.
// For eg. A message box showing that the status has been changed
if (data.status == "success") {
toastr.info('Annotations saved')
} else {
toastr.warning('Error saving annotations')
}
})
.always(function () {
console.log('[Done]');
})
})
});
</script>
{% endblock %}
@@ -0,0 +1,20 @@
{% extends 'anatomy/base.html' %}
{% block content %}
{% for question in questions %}
<div class="anatomy">
<div class="date">
{{ question.created_date }}
</div>
<h1><a href="{% url 'anatomy:question_detail' pk=question.pk %}">{% for answer in question.answers.all %}
{{ answer }},
{% endfor %}
</a></h1>
<p>{{ question.question_type.first|linebreaksbr }}</p>
<img src="/media/anatomy/{{ question.image|linebreaksbr }}" />
<a href="{% url 'anatomy:answer_question' pk=question.pk %}">ANSWER</a>
</div>
{% endfor %}
{% endblock %}
{$ black navigation %}
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+30
View File
@@ -0,0 +1,30 @@
from django.urls import path, include
from . import views
app_name = "physics"
urlpatterns = [
# path('', views.question_list, name='question_list'),
path("", views.index, name="index"),
#path("question/", views.QuestionView.as_view(), name="question_view"),
path("question/<int:pk>/", views.question_detail, name="question_detail"),
#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("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>/take", views.exam_take, name="exam_take"),
path("exam/<int:pk>/", views.exam_overview, name="exam_overview"),
path("exam/<int:pk>/scores", views.exam_scores_cid,
name="exam_scores_cid"),
path("exam/<int:pk>/scores/<int:sk>/", views.exam_scores_cid_user,
name="exam_scores_cid_user"),
path("exam/<int:pk>/toggle_active", views.exam_toggle_active, name="exam_toggle_active"),
path("exam/<int:pk>/toggle_results_published", views.exam_toggle_results_published, name="exam_toggle_results_published"),
path("exam/submit", views.postExamAnswers, name="exam_answers_submit"),
path("exam/", views.exam_list, name="exam_list"),
#path("exam/json/", views.active_exams, name="active_exams"),
#path("exam/json/<int:pk>", views.exam_json, name="exam_json"),
#path("exam/json/<int:pk>/recreate", views.exam_json_recreate, name="exam_json_recreate"),
path("cid/<int:pk>", views.cid_scores, name="cid_scores"),
path("cid/", views.cid_selector, name="cid_selector"),
]
+415
View File
@@ -0,0 +1,415 @@
from django.shortcuts import render, get_object_or_404, redirect
from django.views.decorators.csrf import csrf_exempt
from django import forms
# from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.models import User
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic import ListView
from django.db.models.functions import Lower
from django.core.cache import cache
from django.urls import reverse_lazy, reverse
from django.http import Http404, JsonResponse
from django.http import HttpResponseRedirect, HttpResponse
from .models import Question, Category, Exam, CidUserAnswer
from collections import defaultdict
import os
import base64
import mimetypes
import json
import statistics
import plotly.express as px
def question_list(request):
questions = Question.objects.all()
return render(request, "physics/question_list.html",
{"questions": questions})
@login_required
def index(request):
exams = Exam.objects.all()
return render(request, "physics/index.html", {"exams": exams})
@login_required
def question_detail(request, pk):
question = get_object_or_404(Question, pk=pk)
return render(request, "physics/question_detail.html",
{"question": question})
@login_required
def exam_list(request):
exams = Exam.objects.all()
return render(request, "physics/exam_list.html", {"exams": exams})
@login_required
def exam_overview(request, pk):
print(type(pk))
print(Exam.objects.all())
exams = Exam.objects.all()
print("test", Exam.objects.all().get(id=pk))
exam = get_object_or_404(Exam, pk=pk)
print("exam", exam)
questions = exam.exam_questions.all()
question_number = len(questions)
return render(
request,
"physics/exam_overview.html",
{
"exam": exam,
"questions": questions,
"question_number": question_number
},
)
def exam_toggle_results_published(request, pk):
if request.is_ajax() and request.method=='POST':
exam = get_object_or_404(Exam, pk=pk)
exam.publish_results = True if request.POST.get('publish_results') == 'true' else False
exam.save()
data = {'status':'success', 'publish_results':exam.publish_results}
return JsonResponse(data, status=200)
else:
data = {'status':'error'}
return JsonResponse(data, status=400)
def exam_toggle_active(request, pk):
if request.is_ajax() and request.method=='POST':
exam = get_object_or_404(Exam, pk=pk)
exam.active = True if request.POST.get('active') == 'true' else False
exam.save()
data = {'status':'success', 'active':exam.active}
return JsonResponse(data, status=200)
else:
data = {'status':'error'}
return JsonResponse(data, status=400)
def active_exams(request):
exams = Exam.objects.all()
print(exams)
active_exams = {"exams": []}
for exam in exams:
if exam.active:
active_exams["exams"].append({
"name":
exam.get_exam_name(),
"url":
request.build_absolute_uri(exam.get_json_url()),
})
return JsonResponse(active_exams)
@login_required
def exam_scores_cid(request, pk):
exam = get_object_or_404(Exam, pk=pk)
questions = exam.exam_questions.all()
cids = (CidUserAnswer.objects.filter(question__in=questions).values_list(
"cid", flat=True).distinct())
user_answers_and_marks = defaultdict(list)
user_answers_marks = defaultdict(list)
user_answers = defaultdict(list)
user_names = {}
unmarked = set()
# Loop through all candidates
for cid in cids:
# Convoluted (probably...)
user_names[cid] = cid
for q in questions:
# Get user answer
s = q.cid_user_answers.filter(cid=cid).first()
if not s:
# skip if no answer
user_answers_marks[cid].append(0)
user_answers[cid].append("")
continue
ans = s.answer
answer_score = s.get_answer_score()
if answer_score == "unmarked":
index = exam.get_question_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))
user_scores = {}
for user in user_answers_marks:
user_scores[user] = sum([i for i in user_answers_marks[user] if i != "unmarked"])
user_scores_list = list(user_scores.values())
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)
fig_html = fig.to_html()
total = len(questions)
return render(
request,
"physics/exam_scores.html",
{
"exam": exam,
"unmarked": unmarked,
"questions": questions,
"user_answers": dict(user_answers),
"user_answers_marks": dict(user_answers_marks),
"user_scores": user_scores,
"user_names": user_names,
"user_answers_and_marks": user_answers_and_marks,
"mean": mean,
"median": median,
"mode": mode,
"plot": fig_html,
},
)
def exam_scores_cid_user(request, pk, sk):
exam = get_object_or_404(Exam, pk=pk)
# TODO:Need some kind of test for cid
cid = sk
questions = exam.exam_questions.all()
answers_and_marks = []
answers_marks = []
answers = []
for q in questions:
# Get user answer
user_answer = q.cid_user_answers.filter(cid=cid).first()
if not user_answer:
# skip if no answer
answers_marks.append(0)
answers.append("")
answer_score = 0
ans = "Not answered"
else:
ans = user_answer.answer
answer_score = user_answer.get_answer_score()
correct_answer = q.GetPrimaryAnswer()
if not exam.publish_results:
correct_answer = "*****"
answer_score = 0
answers.append(ans)
answers_marks.append(answer_score)
answers_and_marks.append((ans, answer_score, correct_answer))
if "unmarked" in answers_marks:
answered = [i for i in answers_marks if type(i) == int]
total_score = sum(answered)
unmarked_number = len(answers_marks) - len(answered)
total_score = "{} ({} unmarked)".format(total_score, unmarked_number)
else:
total_score = sum(answers_marks)
max_score = len(questions) * 2
return render(
request,
"physics/exam_scores_user.html",
{
"exam": exam,
"cid": cid,
"questions": questions,
"answers": answers,
"answers_marks": answers_marks,
"total_score": total_score,
"max_score": max_score,
"answers_and_marks": answers_and_marks,
},
)
def cid_selector(request):
return render(
request,
"physics/cid_selector.html",
)
def cid_scores(request, pk):
#exam = get_object_or_404(Exam, pk=pk)
# TODO:Need some kind of test for cid
cid = pk
#questions = exam.exam_questions.all()
answers = CidUserAnswer.objects.filter(
cid=cid)
if not answers:
raise Http404("cid not found")
exam_ids = answers.values_list("exam").distinct()
exams = Exam.objects.filter(id__in=exam_ids)
return render(
request,
"physics/cid_scores.html",
{
"exams": exams,
"cid": cid,
},
)
def exam_take(request, pk):
exam = get_object_or_404(Exam, pk=pk)
if not exam.active:
raise Http404("Exam not found")
questions = exam.exam_questions.all()
return render(
request,
"physics/exam_take.html",
{
"exam": exam,
"questions": questions,
},
)
@csrf_exempt
def postExamAnswers(request):
if request.is_ajax and request.method == "POST":
n = 0
# horrible but it works
for k in request.POST.dict():
print(k)
ret = loadJsonAnswer(json.loads(k))
if ret is not True:
return ret
# print(UserAnswer.objects.filter(exam__id=q["eid"]))
# print(request.urlencode())
return JsonResponse({"success": True})
return JsonResponse({"success": False, "error": "Invalid data"})
def loadJsonAnswer(answer):
# As access is not restricted make sure the data appears valid
if (not isinstance(answer["cid"],
int)) or (not isinstance(answer["eid"], int)
):
return JsonResponse({"success": False, "error": "invalid"})
# The model should catch invalid data but this should be less intensive
max_int = 10000000
if answer["cid"] > max_int or answer["eid"] > max_int:
return JsonResponse({"success": False})
exam = get_object_or_404(Exam, pk=answer["eid"])
if not exam.active:
return JsonResponse({
"success": False,
"error": "No active exam: {}".format(answer["eid"])
})
questions = defaultdict(dict)
for q, n, a in answer["ans"]:
questions[q][n] = a
pass
for qid in questions:
exiting_answers = CidUserAnswer.objects.filter(question__id=qid,
exam__id=answer["eid"],
cid=answer["cid"])
a = questions[qid]["a"]
b = questions[qid]["b"]
c = questions[qid]["c"]
d = questions[qid]["d"]
e = questions[qid]["e"]
if not exiting_answers:
ans = CidUserAnswer(a=a, b=b, c=c, d=d, e=e, cid=answer["cid"])
ans.question_id = qid
ans.exam_id = answer["eid"]
ans.full_clean()
ans.save()
else:
# Update an existing answer
# should never be more than one (famous last words)
ans = exiting_answers[0]
ans.a = a
ans.b = b
ans.c = c
ans.d = d
ans.e = e
ans.full_clean()
ans.save()
return True
+1
View File
@@ -31,6 +31,7 @@ ALLOWED_HOSTS = ["localhost", "127.0.0.1", "161.35.163.87"]
INSTALLED_APPS = [ INSTALLED_APPS = [
'anatomy', 'anatomy',
'physics',
"reversion", "reversion",
"django_tables2", "django_tables2",
"django_filters", "django_filters",
+1
View File
@@ -25,6 +25,7 @@ from . import views
urlpatterns = [ urlpatterns = [
path("admin/", admin.site.urls, name="admin"), path("admin/", admin.site.urls, name="admin"),
path("anatomy/", include("anatomy.urls"), name="anatomy"), path("anatomy/", include("anatomy.urls"), name="anatomy"),
path("physics/", include("physics.urls"), name="physics"),
#path("sbas/", include("sbas.urls"), name="sbas"), #path("sbas/", include("sbas.urls"), name="sbas"),
#path("rapids/", include("rapids.urls"), name="rapids"), #path("rapids/", include("rapids.urls"), name="rapids"),
path("accounts/", include("django.contrib.auth.urls")), path("accounts/", include("django.contrib.auth.urls")),
+1
View File
@@ -4,6 +4,7 @@
<div class="anatomy"> <div class="anatomy">
<h1>Radiology Stuff</h1> <h1>Radiology Stuff</h1>
<a href="{% url 'anatomy:index'%}">Anatomy</a> <a href="{% url 'anatomy:index'%}">Anatomy</a>
<a href="{% url 'physics:index'%}">Physics</a>
<a href="{% url 'login'%}">Log in</a> <a href="{% url 'login'%}">Log in</a>
{% if request.user.is_staff %}<a href="{% url 'admin:index'%}">Admin</a>{% endif %} {% if request.user.is_staff %}<a href="{% url 'admin:index'%}">Admin</a>{% endif %}
<a href="{% url 'password_reset'%}">Reset password</a> <a href="{% url 'password_reset'%}">Reset password</a>