This commit is contained in:
Ross
2020-10-15 22:54:10 +01:00
commit c923d52382
35 changed files with 28699 additions and 0 deletions
View File
+56
View File
@@ -0,0 +1,56 @@
from django.contrib import admin
from .models import (
AnatomyQuestion,
Examination,
QuestionType,
Answers,
HalfMarkAnswers,
IncorrectAnswers,
UserAnswer,
Exam,
Modality,
BodyPart,
Structure,
)
from reversion.admin import VersionAdmin
# Register your models here.
class AnatomyAnswersInline(admin.TabularInline):
model = Answers
extra = 1
class HalfMarkAnatomyAnswersInline(admin.TabularInline):
model = HalfMarkAnswers
extra = 1
class IncorrectAnatomyAnswersInline(admin.TabularInline):
model = IncorrectAnswers
extra = 1
class ExamInline(admin.TabularInline):
model = AnatomyQuestion.exams.through
extra = 1
class AnatomyAdmin(VersionAdmin):
inlines = [
AnatomyAnswersInline,
HalfMarkAnatomyAnswersInline,
IncorrectAnatomyAnswersInline,
ExamInline,
]
admin.site.register(AnatomyQuestion, AnatomyAdmin)
admin.site.register(Examination)
admin.site.register(QuestionType)
admin.site.register(UserAnswer)
admin.site.register(Exam)
admin.site.register(Modality)
admin.site.register(BodyPart)
admin.site.register(Structure)
+5
View File
@@ -0,0 +1,5 @@
from django.apps import AppConfig
class AnatomyConfig(AppConfig):
name = 'anatomy'
+16
View File
@@ -0,0 +1,16 @@
from django import forms
from .models import UserAnswer, AnatomyQuestion
class AnatomyAnswerForm(forms.ModelForm):
class Meta:
model = UserAnswer
fields = ("answer",)
class MarkAnatomyQuestionForm(forms.Form):
# correct = forms.CharField(required=False)
# half_correct = forms.CharField(required=False)
# incorrect = forms.CharField(required=False)
marked_answers = forms.CharField(required=False)
+242
View File
@@ -0,0 +1,242 @@
from django.db import models
from django.utils import timezone
from django.core.files.storage import FileSystemStorage
from django.conf import settings
image_storage = FileSystemStorage(
# Physical file location ROOT
location=u"{0}/anatomy/".format(settings.MEDIA_ROOT),
# Url for file
base_url=u"{0}anatomy/".format(settings.MEDIA_URL),
)
def image_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/anatomy/picture/<filename>
return u"picture/{0}".format(filename)
class Examination(models.Model):
examination = models.CharField(max_length=200)
def __str__(self):
return self.examination
class BodyPart(models.Model):
bodypart = models.CharField(max_length=200)
def __str__(self):
return self.bodypart
class Structure(models.Model):
bodypart = models.CharField(max_length=200)
def __str__(self):
return self.bodypart
class Modality(models.Model):
modality = models.CharField(max_length=200)
def __str__(self):
return self.modality
class QuestionType(models.Model):
text = models.CharField(max_length=400)
def __str__(self):
return self.text
class AnatomyQuestion(models.Model):
# author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
# image = models.ImageField()
# feedback = models.TextField(null=True)
question_type = models.ForeignKey(QuestionType,
on_delete=models.SET_NULL,
null=True)
image = models.ImageField(upload_to=image_directory_path,
storage=image_storage)
description = models.CharField(
max_length=400,
help_text=
"Short description of the image e.g. 'Sagittal CT Chest, Abdomen & Pelvis', will be displayed as the title."
)
examination = models.ForeignKey(Examination,
on_delete=models.SET_NULL,
null=True, blank=True)
modality = models.ForeignKey(Modality,
on_delete=models.SET_NULL,
null=True)
body_part = models.ForeignKey(BodyPart,
on_delete=models.SET_NULL,
null=True,
blank=True)
structure = models.ForeignKey(Structure,
on_delete=models.SET_NULL,
null=True,
blank=True)
created_date = models.DateTimeField(default=timezone.now)
def __str__(self):
# Get first answer
a = self.answers.all().first()
# Get associated exams
e = self.exams.all().values_list("name", flat=True)
exams = ", ".join(e)
if a is None:
return "({})".format(exams)
else:
return "{} ({}) [{}, {}]".format(a.answer, exams, self.modality,
self.structure)
def GetUnmarkedAnswersString(self):
unmarked_answers = self.GetUnmarkedAnswers()
if not unmarked_answers:
return "No answers to mark"
return "{} answer unmarked: {}".format(len(unmarked_answers),
", ".join(unmarked_answers))
def GetUnmarkedAnswers(self):
user_answers = set([i.answer for i in self.user_answers.all()])
unmarked_answers = user_answers - self.GetMarkedAnswers()
return unmarked_answers
def GetMarkedAnswers(self):
correct_answers = set([i.answer for i in self.answers.all()])
half_mark_answers = set(
[i.answer for i in self.half_mark_answers.all()])
incorrect_answers = set(
[i.answer for i in self.incorrect_answers.all()])
marked_answers = correct_answers | half_mark_answers | incorrect_answers
return marked_answers
class Answers(models.Model):
question = models.ForeignKey(AnatomyQuestion,
related_name="answers",
on_delete=models.CASCADE)
answer = models.CharField(max_length=500)
def __str__(self):
return self.answer
def clean(self):
if self.answer:
self.answer.strip()
class HalfMarkAnswers(models.Model):
question = models.ForeignKey(AnatomyQuestion,
related_name="half_mark_answers",
on_delete=models.CASCADE)
answer = models.CharField(max_length=500)
def __str__(self):
return self.answer
def clean(self):
if self.answer:
self.answer.strip()
class IncorrectAnswers(models.Model):
question = models.ForeignKey(AnatomyQuestion,
related_name="incorrect_answers",
on_delete=models.CASCADE)
answer = models.CharField(max_length=500)
def __str__(self):
return self.answer
def clean(self):
if self.answer:
self.answer.strip()
class Exam(models.Model):
name = models.CharField(max_length=200)
exam_questions = models.ManyToManyField(AnatomyQuestion,
related_name="exams")
def __str__(self):
return self.name
class UserAnswer(models.Model):
question = models.ForeignKey(AnatomyQuestion,
related_name="user_answers",
on_delete=models.CASCADE)
answer = models.CharField(max_length=500, blank=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL,
null=True,
blank=True,
on_delete=models.SET_NULL)
cid = models.IntegerField(blank=True, null=True)
# Each user answer is associated with a particular exam
exam = models.ForeignKey(Exam,
related_name="exam",
on_delete=models.CASCADE,
null=True)
flagged = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
try:
exam = self.exam
except (Exam.DoesNotExist, KeyError) as e:
exam = "None"
return "{}/{}/{}: {}".format(exam, self.cid, self.question.id,
self.answer)
def clean(self):
if self.answer:
self.answer.strip()
class CidUserAnswer(models.Model):
"""User answers by candidate"""
question = models.ForeignKey(AnatomyQuestion,
related_name="cid_user_answers",
on_delete=models.CASCADE)
answer = models.CharField(max_length=500, blank=True)
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):
try:
exam = self.exam
except (Exam.DoesNotExist, KeyError) as e:
exam = "None"
return "{}/{}/{}: {}".format(exam, self.cid, self.question.id,
self.answer)
def clean(self):
if self.answer:
self.answer.strip()
+96
View File
@@ -0,0 +1,96 @@
body {
color: gray;
background-color: black;
}
.answer-list {
font-size: 30px;
}
.answer-list .correct {
color: green;
}
.answer-list .half-correct {
color: yellow;
}
.answer-list .incorrect {
color: red;
}
.answer-list .not-marked {
color: lightblue;
}
.key {
font-size: 20;
}
.hide {
display: none;
}
.answered {
color: green;
}
.unanswered {
color: gray;
}
#question-list {
list-style: none;
float: right;
padding: 1px;
border: 1px solid gray;
/*max-height: 80%;*/
height: 100%;
overflow-y: scroll;
}
.flagged {
color: red;
}
.not-flagged {
}
button a {
text-decoration: none;
color: green;
}
#admin-link {
float: right;
}
.marking-list {
float: right;
}
.save {
display: inline-block;
padding: 0.35em 1.2em;
border: 0.1em solid #ffffff;
margin: 0 0.3em 0.3em 0;
border-radius: 0.12em;
box-sizing: border-box;
text-decoration: none;
font-family: "Roboto", sans-serif;
font-weight: 300;
color: #ffffff;
text-align: center;
transition: all 0.2s;
background-color: transparent;
}
.save:hover {
color: #000000;
background-color: #ffffff;
}
#stats-plot {
width: 50%;
float: right;
}
+126
View File
@@ -0,0 +1,126 @@
var dwvapp = [];
var marked_answers = {
"correct": [],
"half-correct": [],
"incorrect": [],
}
$(document).ready(function () {
$(".answer-list li").each(function (index, element) {
console.log(element);
$(element).click(function (e) {
var classes = ['correct', 'half-correct', 'incorrect'];
$(element).each(function () {
this.className = classes[($.inArray(this.className, classes) + 1) % classes.length];
});
prepAnswerData();
});
});
prepAnswerData();
if($(".post-form").length > 0) {
$(".post-form").get(0).addEventListener("submit", function (e) {
if($(".not-marked").length > 0) {
e.preventDefault(); // before the code
alert("Ensure all answers are marked first");
}
});
}
if($(".dwv-container").length) {
$(".dwv-container").append($('<!-- DWV --> <div id="dwv"> <!-- Toolbar --> <div class="toolbar"></div> <input type="range" id="sliceRange" value="0"><!-- Layer Container --> <div class="layerContainer"> <div class="dropBox"></div> <canvas class="imageLayer">Only for HTML5 compatible browsers...</canvas> <div class="infoLayer"> <div class="infotl"></div> <div class="infotc"></div> <div class="infotr"></div> <div class="infocl"></div> <div class="infocr"></div> <div class="infobl"></div> <div class="infobc"></div> <div class="infobr" style="bottom: 64px;"></div></div></div><!-- /layerContainer --> <!-- /dwv -->'));
var app = new dwv.App();
dwvapp = app;
//DEBUG
var listenerWL = function (event) {
console.log("event: " + event.type);
console.log(event);
$(".infotc").text(event.wc);
};
app.addEventListener("wl-width-change", listenerWL);
//app.addEventListener("wl-center-change", listener);
app.init({
"containerDivId": "dwv",
"fitToWindow": true,
"isMobile": true,
"gui": ["tool"],
"filters": ["Threshold", "Sharpen", "Sobel"],
"tools": ["Scroll", "WindowLevel", "ZoomAndPan"], // or try "ZoomAndPan"
});
var range = document.getElementById("sliceRange");
range.min = 0;
app.addEventListener("load-end", function () {
range.max = app.getImage().getGeometry().getSize().getNumberOfSlices() - 1;
if(range.max == 0) {
$(range).hide();
}
});
app.addEventListener("slice-change", function () {
range.value = app.getViewController().getCurrentPosition().k;
});
range.oninput = function () {
var pos = app.getViewController().getCurrentPosition();
pos.k = this.value;
app.getViewController().setCurrentPosition(pos);
}
try {
app.loadURLs([$(".dwv-container").data("url")]);
} catch(error) {
toastr.error(error.message);
}
dwv.gui.appendResetHtml(app);
}
});
function prepAnswerData() {
//$("#id_correct").val($("li.correct").map(function() {
// ans = $(this).text();
// window.marked_answers["correct"].push(ans);
// return ans
//}).get().join('--//--'));
//$("#id_half_correct").val($("li.half-correct").map(function() {
// ans = $(this).text();
// window.marked_answers["half-correct"].push(ans);
// return ans;
//}).get().join('--//--'));
//$("#id_incorrect").val($("li.incorrect").map(function() {
// ans = $(this).text();
// window.marked_answers["incorrect"].push(ans);
// return ans;
//}).get().join('--//--'));
window.marked_answers["correct"] = [];
window.marked_answers["half-correct"] = [];
window.marked_answers["incorrect"] = [];
$("li.correct").map(function () {
ans = $(this).text();
window.marked_answers["correct"].push(ans);
})
$("li.half-correct").map(function () {
ans = $(this).text();
window.marked_answers["half-correct"].push(ans);
})
$("li.incorrect").map(function () {
ans = $(this).text();
window.marked_answers["incorrect"].push(ans);
})
$("#id_marked_answers").val(JSON.stringify(window.marked_answers));
}
+174
View File
@@ -0,0 +1,174 @@
/**
* Application GUI.
*/
// Default window level presets.
dwv.tool.defaultpresets = {
"default (127+/-255)": {"center": 127, "width": 255},
//"default": {"center": 127, "width": 100},
};
dwv.tool.defaultpresets.SC = {
"default (127+/-255)": {"center": 127, "width": 255},
};
// Default window level presets for CT.
dwv.tool.defaultpresets.CT = {
"mediastinum": {"center": 40, "width": 400},
"lung": {"center": -500, "width": 1500},
"bone": {"center": 500, "width": 2000},
};
// decode query
dwv.utils.decodeQuery = dwv.utils.base.decodeQuery;
// Window
dwv.gui.getWindowSize = function() {
d = dwv.gui.base.getWindowSize();
//return {"width": d["width"], "height": d["height"]+100}
h = $(window).height() - ($(document).height()-d["height"]) - 100;
return {"width": d["width"], "height": h}
}
// Progress
dwv.gui.displayProgress = function (/*percent*/) { /*does nothing*/ };
// get element
dwv.gui.getElement = dwv.gui.base.getElement;
// refresh
dwv.gui.refreshElement = dwv.gui.base.refreshElement;
// Slider
dwv.gui.Slider = null;
// Tags table
dwv.gui.DicomTags = null;
// Toolbox
dwv.gui.Toolbox = function (app)
{
this.setup = function (/*list*/)
{
// does nothing
};
this.display = function (/*bool*/)
{
// does nothing
};
this.initialise = function (list)
{
// not wonderful: first one should be scroll is more than one slice
if ( list[0] === false ) {
var inputScroll = app.getElement("scroll-button");
inputScroll.style.display = "none";
var inputZoom = app.getElement("zoom-button");
inputZoom.checked = true;
}
};
};
// Window/level
dwv.gui.WindowLevel = function (app)
{
this.setup = function ()
{
var button = document.createElement("button");
button.className = "wl-button";
button.value = "WindowLevel";
button.onclick = app.onChangeTool;
button.appendChild(document.createTextNode("Window/Level"));
var node = app.getElement("toolbar");
node.appendChild(button);
};
this.display = function (bool)
{
var button = app.getElement("wl-button");
button.disabled = bool;
};
this.initialise = function ()
{
// clear previous
dwv.html.removeNode(app.getElement("presetSelect"));
dwv.html.removeNode(app.getElement("presetLabel"));
// create preset select
var select = dwv.html.createHtmlSelect("presetSelect",
app.getViewController().getWindowLevelPresetsNames(), "wl.presets", true);
select.className = "presetSelect";
select.onchange = app.onChangeWindowLevelPreset;
select.title = "Select w/l preset.";
select.setAttribute("data-inline","true");
var label = document.createElement("label");
label.className = "presetLabel";
label.setAttribute("for", "presetSelect");
label.appendChild(document.createTextNode("Select Preset"));
var node = app.getElement("toolbar");
node.appendChild(label);
node.appendChild(select);
};
};
// Zoom
dwv.gui.ZoomAndPan = function (app)
{
this.setup = function ()
{
var button = document.createElement("button");
button.className = "zoom-button";
button.value = "ZoomAndPan";
button.onclick = app.onChangeTool;
button.appendChild(document.createTextNode("ZoomAndPan"));
var node = app.getElement("toolbar");
node.appendChild(button);
};
this.display = function (bool)
{
var button = app.getElement("zoom-button");
button.disabled = bool;
};
};
// Scroll
dwv.gui.Scroll = function (app)
{
this.setup = function ()
{
var button = document.createElement("button");
button.className = "scroll-button";
button.value = "Scroll";
button.onclick = app.onChangeTool;
button.appendChild(document.createTextNode("Scroll"));
var node = app.getElement("toolbar");
node.appendChild(button);
};
this.display = function (bool)
{
var button = app.getElement("scroll-button");
button.disabled = bool;
};
};
dwv.gui.Slider = dwv.gui.base.Slider;
// NEEDS UI
dwv.gui.Filter = dwv.gui.base.Filter;
// Filter: threshold
dwv.gui.Threshold = dwv.gui.base.Threshold;
// Filter: sharpen
dwv.gui.Sharpen = dwv.gui.base.Sharpen;
// Filter: sobel
dwv.gui.Sobel = dwv.gui.base.Sobel;
//Reset
dwv.gui.appendResetHtml = function (app)
{
var button = document.createElement("button");
button.className = "reset-button";
button.value = "reset";
button.onclick = app.onDisplayReset;
button.appendChild(document.createTextNode("Reset"));
var node = app.getElement("toolbar");
node.appendChild(button);
};
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+9
View File
@@ -0,0 +1,9 @@
{% extends 'blog/base.html' %}
{% block content %}
<h1>Answer</h1>
<form method="POST" class="post-form">{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Save</button>
</form>
{% endblock %}
@@ -0,0 +1,13 @@
{% extends 'anatomy/base.html' %}
{% block content %}
<h1>Question</h1>
<p>{{ question.question_type.first }}</p>
<img src="/media/anatomy/{{ question.image|linebreaksbr }}" />
<form method="POST" class="post-form">{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default" formaction="/test">Previous</button>
<button type="submit" class="save btn btn-default" formaction="/previous">Next</button>
</form>
{% endblock %}
+32
View File
@@ -0,0 +1,32 @@
{% load static %}
<html>
<head>
<title>Anatomy Quiz</title>
<link rel="stylesheet" href="{% static 'css/anatomy.css' %}">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="{% static 'js/anatomy.js' %}"></script>
<script type="text/javascript" src="{% static 'js/dwv/i18next.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/dwv/dwv.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/dwv/appgui.js' %}"></script>
</head>
<body>
<div class="page-header">
{% if request.user.is_staff %}<span id="admin-link"><a
href="{% url 'admin:index' %}">Admin</a></span>{% endif %}
</div>
<div class="content container">
{% block navigation %}
{% endblock %}
<div class="row">
<div class="col-md-8">
{% block content %}
{% endblock %}
</div>
</div>
</div>
</body>
</html>
+79
View File
@@ -0,0 +1,79 @@
{% extends 'anatomy/base.html' %}
{% block content %}
<script>
$(document).ready(function () {
$("#flagged-button").click(function () {
$.ajax({
url: "{% url 'anatomy:flag_question' %}",
data: {
'pk': {{ question.pk }}
},
dataType: 'json',
success: function (data) {
console.log(data.flagged);
if (data.flagged) {
$("#flagged-button").text("Flagged");
$("#flagged-button").attr("class", "flagged");
} else {
$("#flagged-button").text("Not Flagged");
$("#flagged-button").attr("class", "not-flagged");
}
}
});
});
$("#id_answer").keyup(function (event) {
if (event.keyCode == 13) {
$(".btn-default").click();
}
});
});
</script>
<h1>Question {{question_details.current}} of {{question_details.total}}</h1>
<p>{{ question.question_type.first }}</p>
<table>
<tr>
<td>
<div class=dwv-container data-url="{{ question.image.url}}" />
</div>
<form method="POST" class="post-form">{% csrf_token %}
{{ form.as_p }}
{% if question_details.current > 1 %}
<button type="submit" name="previous" class="btn btn-previous">Previous</button>
{% endif %}
{% if question_details.current >= question_details.total %}
<button type="submit" name="save" class="save btn btn-default">Save</button>
{% else %}
<button type="submit" name="next" class="save btn btn-default">Next</button>
{% endif %}
<button id=flagged-button type="button"
class={% if answer.flagged == True %}flagged{% else %}not-flagged{% endif %}>{% if answer.flagged == True %}Flagged{% else %}Not
Flagged{% endif %}</button>
</form>
</td>
<td>
<div class=exam-navigation />
<ul id=question-list>
{% for question in questions %}
<li><a class={% if question.pk in answered_questions %}answered{% else %}unanswered{% endif %}
href="{% url 'exam' pk=exam.pk sk=forloop.counter0 %}">{{ forloop.counter }}{% if question.pk in flagged_questions %}!{% endif %}</a>
</li>
{% endfor %}
</ul>
</div>
</td>
</tr>
</table>
{% endblock %}
+9
View File
@@ -0,0 +1,9 @@
{% extends 'anatomy/base.html' %}
{% block content %}
{% for exam in exams %}
<div class="anatomy">
<h1><a href="{% url 'anatomy:exam_overview' pk=exam.pk %}">{{exam.name}}</a></h1>
</div>
{% endfor %}
{% endblock %}
@@ -0,0 +1,9 @@
{% extends 'anatomy/base.html' %}
{% block content %}
<div class="anatomy">
<h1>{{ exam.name }}</h1>
This exam has {{question_number}} questions.
<p><button><a href="{% url 'anatomy:exam' pk=exam.pk sk=0 %}">Click here to start</a></button></p>
</div>
{% endblock %}
@@ -0,0 +1,40 @@
{% extends 'anatomy/base.html' %}
{% block content %}
<div class="anatomy">
<h2>{{ exam.name }}</h2>
{% for user, value in user_answers_marks.items %}
<h3>Candidate: {{ user_names|get_item:user }}</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 %}
{% block navigation %}
<a href="{% url 'anatomy:exam_list' %}">Exams</a> <a href="{% url 'anatomy:mark_overview' pk=exam.pk %}">Marking
overview</a> <a href="{% url 'anatomy:exam_scores_cid' pk=exam.pk %}">Scores</a>
{% endblock %}
+11
View File
@@ -0,0 +1,11 @@
{% extends 'anatomy/base.html' %}
{% block content %}
{% for exam in exams %}
<div class="anatomy">
<h1><a href="{% url 'anatomy:exam_overview' pk=exam.pk %}">Exam {{ forloop.counter }} </a></h1>
{% if request.user.is_staff %}<a href="{% url 'anatomy:mark' pk=exam.pk sk=0 %}">Mark answers</a>{% endif %}
{% if request.user.is_staff %}<a href="{% url 'anatomy:exam_scores_cid' pk=exam.pk %}">Scores</a>{% endif %}
</div>
{% endfor %}
{% endblock %}
+47
View File
@@ -0,0 +1,47 @@
{% extends 'anatomy/base.html' %}
{% block content %}
<h2>Marking question {{question_details.current}} of {{question_details.total}}</h2>
<p>{{ question.question_type }}</p>
<form method="POST" class="post-form">{% csrf_token %}
<div class="marking-list">Marked:
<ul id="marked-answer-list" class="answer-list">
{% for answer in question.answers.all %}
<li class="correct">{{ answer }} </li>
{% endfor %}
{% for answer in question.half_mark_answers.all %}
<li class="half-correct">{{ answer }} </li>
{% endfor %}
{% for answer in question.incorrect_answers.all %}
<li class="incorrect">{{ answer }} </li>
{% endfor %}
</ul>
Unmarked:
<ul id="new-answer-list" class="answer-list">
{% for answer in user_answers %}
<li class="not-marked">{{ answer }} </li>
{% endfor %}
</ul>
<div class="answer-list key">Key: <span class="correct">2 Marks</span>, <span class="half-correct">1
Mark</span>, <span class="incorrect">0 Marks</span></div>
{% if question_details.current > 1 %}
<button type="submit" name="previous" class="save btn btn-default">Previous</button>
{% endif %}
<button type="submit" name="save" class="save btn btn-default">Save</button>
{% if question_details.current >= question_details.total %}
{% else %}
<button type="submit" name="next" class="save btn btn-default">Next</button>
{% endif %}
</div>
<span class=hide>
{{ form.as_p }}
</span>
</form>
<div class=dwv-container data-url="/media/anatomy/{{ question.image}}" />
</div>
{% endblock %}
{% block navigation %}
<a href="{% url 'anatomy:exam_list' %}">Exams</a> <a href="{% url 'anatomy:mark_overview' pk=exam.pk %}">Marking
overview</a> <a href="{% url 'anatomy:exam_scores_cid' pk=exam.pk %}">Scores</a>
{% endblock %}
@@ -0,0 +1,20 @@
{% extends 'anatomy/base.html' %}
{% block content %}
<div class="anatomy">
<h2>{{ exam.name }}</h2>
<ul>
{% for question in questions.all %}
<li><a href="{% url 'anatomy:mark' pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter }}:
{{ question }}</a><br /> {{ question.GetUnmarkedAnswersString }}</li>
{% endfor %}
</ul>
<p><button><a href="{% url 'anatomy:mark' pk=exam.pk sk=0 %}">Click here to start marking</a></button></p>
</div>
{% endblock %}
{% block navigation %}
<a href="{% url 'anatomy:exam_list' %}">Exams</a> <a href="{% url 'anatomy:mark_overview' pk=exam.pk %}">Marking
overview</a> <a href="{% url 'anatomy:exam_scores_cid' pk=exam.pk %}">Scores</a>
{% endblock %}
@@ -0,0 +1,14 @@
{% extends 'anatomy/base.html' %}
{% block content %}
<div class="question">
<div class="date">
{{ question.created_date }}
</div>
<h1>{% for answer in question.answers.all %}
{{ answer }},
{% endfor %}</h1>
<p>{{ question.question_type.first|linebreaksbr }}</p>
<img src="/media/anatomy/{{ question.image|linebreaksbr }}" />
</div>
{% 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 %}
+12
View File
@@ -0,0 +1,12 @@
{% extends '../anatomy/base.html' %}
{% block title %}Login{% endblock %}
{% block content %}
<h2>Login</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Login</button>
</form>
{% endblock %}
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+20
View File
@@ -0,0 +1,20 @@
from django.urls import path, include
from . import views
app_name = "anatomy"
urlpatterns = [
# path('', views.question_list, name='question_list'),
path("", views.index, name="index"),
path("question/<int:pk>/", views.question_detail, name="question_detail"),
path("question/<int:pk>/answer/", views.answer_question, name="answer_question"),
path("exam/<int:pk>/<int:sk>/mark", views.mark, name="mark"),
path("exam/<int:pk>/mark", views.mark_overview, name="mark_overview"),
path("exam/<int:pk>/<int:sk>/", views.exam, name="exam"),
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>/json", views.exam_json, name="exam_json"),
path("submit_answers", views.postExamAnswers, name="exam_answers_submit"),
path("exam/", views.exam_list, name="exam_list"),
path("ajax/exam/flag/", views.flag_question, name="flag_question"),
]
+530
View File
@@ -0,0 +1,530 @@
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.http import Http404, JsonResponse
from .forms import AnatomyAnswerForm, MarkAnatomyQuestionForm
from .models import (
AnatomyQuestion,
CidUserAnswer,
Exam,
Answers,
HalfMarkAnswers,
IncorrectAnswers,
)
from collections import defaultdict
import os
import base64
import mimetypes
import json
import statistics
import plotly.express as px
def image_as_base64(image_file):
"""
:param `image_file` for the complete path of image.
:param `format` is format for image, eg: `png` or `jpg`.
"""
# if not os.path.isfile(image_file):
# return None
encoded_string = ""
# with open(image_file, 'rb') as img_f:
# encoded_string = base64.b64encode(img_f.read())
encoded_string = base64.b64encode(image_file.file.read())
mimetype, enc = mimetypes.guess_type(image_file.path)
return "data:image/{};base64,{}".format(mimetype, encoded_string.decode("utf-8"))
from django.template.defaulttags import register
@register.filter
def get_item(dictionary, key):
return dictionary.get(key)
def user_is_admin(user):
if user:
if user.pk == 1:
return True
return False
def question_list(request):
questions = AnatomyQuestion.objects.all()
return render(request, "anatomy/question_list.html", {"questions": questions})
@login_required
def index(request):
exams = Exam.objects.all()
return render(request, "anatomy/index.html", {"exams": exams})
@login_required
def question_detail(request, pk):
question = get_object_or_404(AnatomyQuestion, pk=pk)
return render(request, "anatomy/question_detail.html", {"question": question})
@login_required
def answer_question(request, pk):
question = get_object_or_404(AnatomyQuestion, pk=pk)
answer = question.user_answers.filter(
user=request.user
).first() # .filter(user=User)
if request.method == "POST":
if answer:
form = AnatomyAnswerForm(request.POST, instance=answer)
else:
form = AnatomyAnswerForm(request.POST)
if form.is_valid():
answer = form.save(commit=False)
answer.user = request.user
answer.question = question
# answer.published_date = timezone.now()
answer.save()
return redirect("question_detail", pk=pk)
else:
form = AnatomyAnswerForm(instance=answer)
return render(
request, "anatomy/answer_question.html", {"form": form, "question": question}
)
@login_required
def exam_list(request):
exams = Exam.objects.all()
return render(request, "anatomy/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,
"anatomy/exam_overview.html",
{"exam": exam, "questions": questions, "question_number": question_number},
)
@login_required
def exam(request, pk, sk):
exam = get_object_or_404(Exam, pk=pk)
questions = exam.exam_questions.all()
try:
question = questions[sk]
except IndexError:
raise Http404("Exam question does not exist")
n = sk
question_details = {
"total": len(questions),
"current": n + 1,
}
# Get data for flagged
# answered_questions = UserAnswer.objects.filter(user=request.user).values_list("question__pk", flat=True)
user_answer_data = UserAnswer.objects.filter(user=request.user).values_list(
"question__pk", "answer", "flagged"
)
# flagged_questions = UserAnswer.objects.filter(user=request.user, flagged=True).values_list("question__pk", flat=True)
# flagged_questions I= UserAnswer.filter(user=request.user, ).values_list("question__pk", flat=True)
answered_questions = set()
flagged_questions = set()
for ans_pk, answer, flagged in user_answer_data:
if len(answer.strip()) > 0:
answered_questions.add(ans_pk)
if flagged:
flagged_questions.add(ans_pk)
# u = question.user_answers
answer = question.user_answers.filter(
user=request.user
).first() # .filter(user=User)
if request.method == "POST":
if answer:
form = AnatomyAnswerForm(request.POST, instance=answer)
else:
form = AnatomyAnswerForm(request.POST)
if form.is_valid():
answer = form.save(commit=False)
answer.user = request.user
answer.question = question
# answer.published_date = timezone.now()
answer.save()
if "next" in request.POST:
return redirect("exam", pk=pk, sk=n + 1)
elif "previous" in request.POST:
return redirect("exam", pk=pk, sk=n - 1)
else:
form = AnatomyAnswerForm(instance=answer)
return render(
request,
"anatomy/exam.html",
{
"exam": exam,
"form": form,
"question": question,
"question_details": question_details,
"questions": questions,
"flagged_questions": flagged_questions,
"answered_questions": answered_questions,
"answer": answer,
},
)
def flag_question(request):
pk = request.GET.get("pk", None)
ans = UserAnswer.objects.filter(user=request.user, question__pk=pk).first()
ans.flagged = not ans.flagged
ans.save()
data = {"flagged": ans.flagged}
return JsonResponse(data)
@csrf_exempt
def postExamAnswers(request):
if request.is_ajax and request.method == "POST":
# horrible but it works
for k in request.POST.dict():
for q in json.loads(k):
# As access is not restricted make sure the data appears valid
if (not isinstance(q["cid"], int)) or (
not isinstance(q["eid"], int) or (not isinstance(q["ans"], str))
):
return JsonResponse({"success": False})
# The model should catch invalid data but this should be less intensive
max_int = 10000000
if q["cid"] > max_int or q["eid"] > max_int:
return JsonResponse({"success": False})
exiting_answers = CidUserAnswer.objects.filter(
question__id=q["qid"], exam__id=q["eid"], cid=q["cid"]
)
if not exiting_answers:
ans = UserAnswer(answer=q["ans"], cid=q["cid"])
ans.question_id = q["qid"]
ans.exam_id = q["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.answer = q["ans"]
ans.save()
# print(UserAnswer.objects.filter(exam__id=q["eid"]))
# print(request.urlencode())
return JsonResponse({"success": True})
# return render(request, "anatomy/exam.html", {"exam" : exam, "question" : question})
@login_required
@user_passes_test(user_is_admin, login_url="/accounts/login")
def mark_overview(request, pk):
exam = get_object_or_404(Exam, pk=pk)
questions = exam.exam_questions.all()
return render(
request, "anatomy/mark_overview.html", {"exam": exam, "questions": questions}
)
@login_required
@user_passes_test(user_is_admin, login_url="/accounts/login")
def mark(request, pk, sk):
exam = get_object_or_404(Exam, pk=pk)
questions = exam.exam_questions.all()
n = sk
question_details = {
"total": len(questions),
"current": n + 1,
}
try:
question = questions[sk]
except IndexError:
raise Http404("Exam question does not exist")
correct_answers = [i.answer for i in question.answers.all()]
half_correct_answers = [i.answer for i in question.half_mark_answers.all()]
incorrect_answers = [i.answer for i in question.incorrect_answers.all()]
user_answers = (
set([i.answer for i in question.user_answers.all()])
- set(correct_answers)
- set(half_correct_answers)
- set(incorrect_answers)
) # .filter(user=User)
user_answers = set([i for i in user_answers if i.strip() != ""])
if request.method == "POST":
form = MarkAnatomyQuestionForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
# correct = cd.get("correct")
# half_correct = cd.get("half_correct")
# incorrect = cd.get("incorrect")
marked_answers = json.loads(cd.get("marked_answers"))
# This is mildly dangerous as we delete all answers
question.answers.all().delete()
question.half_mark_answers.all().delete()
question.incorrect_answers.all().delete()
# This should probably use json (or something else...)
# ajax.....
for ans in marked_answers["correct"]:
if ans.strip() == "":
continue
print("correct", ans)
print(question.pk)
a = Answers()
a.question_id = question.pk
a.answer = ans.strip()
a.save()
# for ans in half_correct.split("--//--"):
for ans in marked_answers["half-correct"]:
if ans.strip() == "":
continue
print("halfcorrect", ans)
a = HalfMarkAnswers()
a.question_id = question.pk
a.answer = ans.strip()
a.save()
for ans in marked_answers["incorrect"]:
if ans.strip() == "":
continue
print("incorrect", ans)
a = IncorrectAnswers()
a.question_id = question.pk
a.answer = ans.strip()
a.save()
# answer = form.save(commit=False)
# answer.user = request.user
# answer.question = question
# answer.published_date = timezone.now()
# answer.save()
if "next" in request.POST:
return redirect("mark", pk=pk, sk=n + 1)
elif "previous" in request.POST:
return redirect("mark", pk=pk, sk=n - 1)
else:
form = MarkAnatomyQuestionForm()
return render(
request,
"anatomy/mark.html",
{
"exam": exam,
"form": form,
"question": question,
"question_details": question_details,
"user_answers": user_answers,
},
)
def exam_json(request, pk):
exam = get_object_or_404(Exam, pk=pk)
questions = exam.exam_questions.all()
exam_questions = defaultdict(dict)
for q in questions:
exam_questions[q.id] = {
"title": "{}".format(q.examination),
"question": str(q.question_type),
"images": [image_as_base64(q.image)],
"type": "anatomy",
}
exam_json = {
"eid": exam.id,
"exam_type": "anatomy",
"exam_name": exam.name,
"exam_mode": True,
"questions": exam_questions,
}
return JsonResponse(exam_json)
@login_required
def exam_scores_cid(request, pk):
exam = get_object_or_404(Exam, pk=pk)
questions = exam.exam_questions.all()
cids = (
UserAnswer.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 = {}
for cid in cids:
# Convoluted (probably...)
user_names[cid] = cid
for q in questions:
s = q.user_answers.filter(cid=cid)
if not s:
user_answers_marks[cid].append(0)
user_answers[cid].append("")
continue
ans = s[0].answer
if ans in q.answers.all().values_list("answer", flat=True):
a = 2
elif ans in q.half_mark_answers.all().values_list("answer", flat=True):
a = 1
else:
a = 0
user_answers[cid].append(ans)
user_answers_marks[cid].append(a)
user_answers_and_marks[cid].append((ans, a))
user_scores = {}
for user in user_answers_marks:
user_scores[user] = sum(user_answers_marks[user])
user_scores_list = list(user_scores.values())
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,
"anatomy/exam_scores.html",
{
"exam": exam,
"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,
},
)
@login_required
def exam_scores(request, pk):
exam = get_object_or_404(Exam, pk=pk)
questions = exam.exam_questions.all()
users = (
UserAnswer.objects.filter(question__in=questions).values_list("user").distinct()
)
user_answers_and_marks = defaultdict(list)
user_answers_marks = defaultdict(list)
user_answers = defaultdict(list)
user_names = {}
for u in users:
# Convoluted (probably...)
user_names[u] = User.objects.filter(pk=u[0]).first().get_username()
for q in questions:
s = q.user_answers.filter(user__in=u)
if not s:
user_answers_marks[u].append(0)
user_answers[u].append("")
continue
ans = s[0].answer
if ans in q.answers.all().values_list("answer", flat=True):
a = 2
elif ans in q.half_mark_answers.all().values_list("answer", flat=True):
a = 1
else:
a = 0
user_answers[u].append(ans)
user_answers_marks[u].append(a)
user_answers_and_marks[u].append((ans, a))
user_scores = {}
for user in user_answers_marks:
user_scores[user] = sum(user_answers_marks[user])
# Users with answers
#
# for q in questions:
# answers = q.user_answers.all()
#
# for ans in answers:
# user = ans.user
# if ans.answer in q.answers.all().values_list("answer", flat=True):
# user_answers[user].append(2)
#
total = len(questions)
return render(
request,
"anatomy/exam_scores.html",
{
"exam": exam,
"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,
},
)
Executable
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "rad.settings")
try:
from django.core.management import execute_from_command_line
except ImportError:
# The above import may fail for some other reason. Ensure that the
# issue is really that Django is missing to avoid masking other
# exceptions on Python 2.
try:
import django
except ImportError:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
)
raise
execute_from_command_line(sys.argv)
View File
+170
View File
@@ -0,0 +1,170 @@
"""
Django settings for rad project.
Generated by 'django-admin startproject' using Django 1.11.14.
For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
with open('/etc/secret_key') as f:
SECRET_KEY = f.read().strip()
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ["localhost", "127.0.0.1", "161.35.163.87"]
# Application definition
INSTALLED_APPS = [
'anatomy',
#"tagulous",
"reversion",
"django_tables2",
"django_filters",
"easy_thumbnails",
"filer",
"mptt",
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"reversion.middleware.RevisionMiddleware",
]
ROOT_URLCONF = 'rad.urls'
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(BASE_DIR, "templates")],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"django.template.context_processors.static",
],
},
},
]
WSGI_APPLICATION = 'rad.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
DATABASES = {
'default': {
"ENGINE": "django.db.backends.postgresql",
"NAME": "rad",
"USER": "django",
"PASSWORD": "f7bf31dc9bda1256ea827953480d1917",
#"HOST": "xkjq.uk",
"HOST": "161.35.163.87",
"PORT": "5432",
}
}
# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
SERIALIZATION_MODULES = {
"xml": "tagulous.serializers.xml_serializer",
"json": "tagulous.serializers.json",
"python": "tagulous.serializers.python",
"yaml": "tagulous.serializers.pyyaml",
}
# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/
STATIC_URL = '/static/'
# Redirect to home URL after login (Default redirects to /accounts/profile/)
LOGIN_REDIRECT_URL = "/"
TAGULOUS_AUTOCOMPLETE_JS = (
"tagulous/lib/jquery.js",
"tagulous/lib/select2-3/select2.min.js",
"tagulous/tagulous.js",
"tagulous/adaptor/select2-3.js",
)
TAGULOUS_AUTOCOMPLETE_CSS = {"all": ["tagulous/lib/select2-3/select2.css"]}
INTERNAL_IPS = ["localhost", "127.0.0.1"]
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"incremental": True,
"root": {
"level": "DEBUG",
},
}
CORS_ORIGIN_ALLOW_ALL = True
+45
View File
@@ -0,0 +1,45 @@
"""mysite URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from django.views.generic import TemplateView
urlpatterns = [
path("admin/", admin.site.urls, name="admin"),
path("anatomy/", include("anatomy.urls"), name="anatomy"),
#path("sbas/", include("sbas.urls"), name="sbas"),
#path("rapids/", include("rapids.urls"), name="rapids"),
path("accounts/", include("django.contrib.auth.urls")),
path("", TemplateView.as_view(template_name="index.html"), name="home"),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL,
document_root=settings.STATIC_ROOT)
import debug_toolbar
urlpatterns = [
path("__debug__/", include(debug_toolbar.urls)),
# For django versions before 2.0:
# url(r'^__debug__/', include(debug_toolbar.urls)),
] + urlpatterns
+16
View File
@@ -0,0 +1,16 @@
"""
WSGI config for rad project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "rad.settings")
application = get_wsgi_application()
+13
View File
@@ -0,0 +1,13 @@
Django
django_debug_toolbar
django_jquery
django_reversion
django_tagulous
Pillow
django-cors-headers
plotly
pandas
django-tables2
django-filter
django-filer
psycopg2-binary
+28
View File
@@ -0,0 +1,28 @@
{% load static %}
<html>
<head>
<title>Radiology Stuff</title>
<link rel="stylesheet" href="{% static 'css/anatomy.css' %}">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<div class="page-header">
{% if request.user.is_staff %}<span id="admin-link"><a
href="{% url 'admin:index' %}">Admin</a></span>{% endif %}
</div>
<div class="content container">
{% block navigation %}
{% endblock %}
<div class="row">
<div class="col-md-8">
{% block content %}
{% endblock %}
</div>
</div>
</div>
</body>
</html>
+11
View File
@@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% block content %}
<div class="anatomy">
<h1>Radiology Stuff</h1>
<a href="{% url 'anatomy:index'%}">Anatomy</a>
<a href="{% url 'login'%}">Log in</a>
{% if request.user.is_staff %}<a href="{% url 'admin:index'%}">Admin</a>{% endif %}
<a href="{% url 'password_reset'%}">Reset password</a>
</div>
{% endblock %}