merge rapids commit

This commit is contained in:
Ross
2021-01-18 11:18:07 +00:00
parent 80059d375e
commit 508934f94c
24 changed files with 1533 additions and 4 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ urlpatterns = [
path("anatomy/", include("anatomy.urls"), name="anatomy"),
path("physics/", include("physics.urls"), name="physics"),
#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/profile", views.profile, name="profile"),
path("", TemplateView.as_view(template_name="index.html"), name="home"),
Regular → Executable
View File
Regular → Executable
+57
View File
@@ -1,3 +1,60 @@
from django.contrib import admin
from .models import Rapid, RapidImage, Examination, Site, Abnormality, Region, Note, RapidCreationDefault
import tagulous.admin
from django.forms import ModelForm
from reversion.admin import VersionAdmin
from django.forms.widgets import RadioSelect
# Register your models here.
admin.site.register(Examination)
admin.site.register(Site)
admin.site.register(Abnormality)
admin.site.register(Region)
admin.site.register(Note)
admin.site.register(RapidCreationDefault)
class RapidImageInline(admin.TabularInline):
model = RapidImage
extra = 1
readonly_fields = [
"image_tag",
]
class RapidAdminForm(ModelForm):
class Meta:
model = Rapid
fields = [
"normal", "abnormality", "region", "laterality", "examination",
"condition", "sign", "site", "feedback", "author"
]
widgets = {
"laterality": RadioSelect(attrs={'style': 'display: inline-flex'}),
}
class RapidAdmin(VersionAdmin):
form = RapidAdminForm
filter_horizontal = (
"abnormality",
"region",
"examination",
)
save_on_top = True
save_as = True
view_on_site = True
inlines = [
RapidImageInline,
]
admin.site.register(Rapid, RapidAdmin)
#tagulous.admin.register(Rapid.condition)
Regular → Executable
View File
+13
View File
@@ -0,0 +1,13 @@
from django.core.exceptions import PermissionDenied
from .models import Rapid
def user_is_author_or_rapid_checker(function):
def wrap(request, *args, **kwargs):
rapid = Rapid.objects.get(pk=kwargs['pk'])
if request.user in rapid.author.all() or request.user.groups.filter(name='rapid_checker').exists():
return function(request, *args, **kwargs)
else:
raise PermissionDenied
wrap.__doc__ = function.__doc__
wrap.__name__ = function.__name__
return wrap
+10
View File
@@ -0,0 +1,10 @@
import django_filters
from .models import Rapid
class RapidFilter(django_filters.FilterSet):
class Meta:
model = Rapid
fields = ("normal", "abnormality", "region", "examination",
"laterality", "site", "created_date", "author")
+102
View File
@@ -0,0 +1,102 @@
from django.forms import ModelForm, ModelMultipleChoiceField, ModelChoiceField, ChoiceField
from django.forms import inlineformset_factory
from rapids.models import Abnormality, Examination, Note, Rapid, RapidCreationDefault, RapidImage, Region
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.forms.widgets import RadioSelect
class ExaminationForm(ModelForm):
class Meta:
model = Examination
fields = ['examination']
class NoteForm(ModelForm):
class Meta:
model = Note
fields = ['note']
class RegionForm(ModelForm):
class Meta:
model = Region
fields = ['name']
class AbnormalityForm(ModelForm):
class Meta:
model = Abnormality
fields = ['name']
class RapidCreationDefaultForm(ModelForm):
class Meta:
model = RapidCreationDefault
fields = ['site']
exclude = ["author"]
class RapidForm(ModelForm):
class Media:
# Django also includes a few javascript files necessary
# for the operation of this form element. You need to
# include <script src="/admin/jsi18n"></script>
# in the template.
css = {
'all': ['css/widgets.css'],
}
# Adding this javascript is crucial
js = ['jsi18n.js', "tesseract.min.js"]
def __init__(self, *args, **kwargs):
super(RapidForm, self).__init__(*args, **kwargs)
#self.fields['question'].widget.attrs = {'class': 'question-form', 'rows': 10, 'cols': 100}
#self.fields['feedback'].widget.attrs = {'class': 'feedback-form', 'rows': 10, 'cols': 100}
self.fields['abnormality'] = ModelMultipleChoiceField(
required=False,
queryset=Abnormality.objects.all(),
widget=FilteredSelectMultiple(verbose_name="Abnormality",
is_stacked=False))
self.fields['region'] = ModelMultipleChoiceField(
required=False,
queryset=Region.objects.all(),
widget=FilteredSelectMultiple(verbose_name="Region",
is_stacked=False))
self.fields['examination'] = ModelMultipleChoiceField(
queryset=Examination.objects.all(),
widget=FilteredSelectMultiple(verbose_name="Examination",
is_stacked=False))
self.fields['laterality'] = ChoiceField(
choices=Rapid.LATERALITY_CHOICES,
required=True,
widget=RadioSelect())
class Meta:
model = Rapid
#fields = ['due_back']
#fields = '__all__'
fields = [
"normal", "abnormality", "region", "laterality", "examination",
"condition", "sign", "site", "feedback"
]
#fields = ['question', 'feedback', 'subspecialty', 'references']
widgets = {
# "normal": RadioSelect(
# choices=[(True, 'Yes'),
# (False, 'No')])
}
ImageFormSet = inlineformset_factory(Rapid,
RapidImage,
fields=['image', 'feedback_image'],
exclude=[],
can_delete=True,
extra=4,
max_num=10,
field_classes="testing")
+387 -1
View File
@@ -1,3 +1,389 @@
from django.db import models
from django.utils import timezone
# Create your models here.
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
import string
image_storage = FileSystemStorage(
# Physical file location ROOT
location=u"{0}/rapids/".format(settings.MEDIA_ROOT),
# Url for file
base_url=u"{0}rapids/".format(settings.MEDIA_URL),
)
def image_directory_path(instance, filename):
return u"picture/{0}".format(filename)
class AnswerStrings(models.Model):
text = models.CharField(max_length=255, unique=True)
def __str__(self):
return self.text
class Abnormality(models.Model):
name = models.CharField(max_length=200,
unique=True,
help_text="Primary abnormality on the film(s)")
def __str__(self):
return self.name
class Meta:
ordering = ('name', )
class Region(models.Model):
name = models.CharField(
max_length=200,
unique=True,
help_text="Region of the abnormality (not including lateralitiy)",
blank=True)
def __str__(self):
return self.name
class Meta:
ordering = ('name', )
class Examination(models.Model):
examination = models.CharField(max_length=200)
def __str__(self):
return self.examination
class Meta:
ordering = ('examination', )
class Site(models.Model):
site = models.CharField(max_length=200)
initials = models.CharField(max_length=200)
def __str__(self):
return self.site
#class Condition(tagulous.models.TagModel):
# pass
class Answer(models.Model):
question = models.ForeignKey(
Rapid, related_name="answers", on_delete=models.CASCADE
)
answer = models.TextField(max_length=500)
answer_compare = models.TextField(max_length=500)
class MarkOptions(models.TextChoices):
UNMARKED = "", _("Unmarked")
INCORRECT = "0", _("Incorrect")
HALF_MARK = "1", _("Half mark")
CORRECT = "2", _("Correct")
status = models.CharField(
max_length=1, choices=MarkOptions.choices, default=MarkOptions.UNMARKED
)
def __str__(self):
return self.answer
def save(self, *args, **kwargs):
self.clean()
return super(Answer, self).save(*args, **kwargs)
def clean(self):
if self.answer:
self.answer = self.answer.strip()
s = self.answer.lower()
s = s.translate(str.maketrans('', '', string.punctuation))
self.answer_compare = s
class Rapid(models.Model):
#author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
#image = models.ImageField()
question = models.TextField(null=True, blank=True)
feedback = models.TextField(null=True, blank=True)
normal = models.BooleanField(default=False, help_text="Tick if true")
NONE = "NONE"
LEFT = "LEFT"
RIGHT = "RIGHT"
BILATERAL = "BILAT"
LATERALITY_CHOICES = (
(NONE, "None"),
(LEFT, "Left"),
(RIGHT, "Right"),
(BILATERAL, "Bilateral"),
)
abnormality = models.ManyToManyField(
Abnormality,
blank=True,
help_text="The abnormality (laterality and region independent)",
)
region = models.ManyToManyField(
Region,
blank=True,
help_text="Region of the abnormality (laterality independent)")
examination = models.ManyToManyField(
Examination, help_text="Name of the (primary) examination")
laterality = models.CharField(
max_length=20,
choices=LATERALITY_CHOICES,
default=NONE,
help_text="Applies to the answer, not the examination")
condition = tagulous.models.TagField(
to=Condition,
blank=True,
help_text=
"Associated condition. Will allow searching / filtering and tips / hints to be displayed. Conditions with spaces must be enclosed in quotes \"...\""
)
sign = tagulous.models.TagField(
to=Sign, blank=True, help_text='Radiological signs in the question')
DEFAULT_SITE_ID = 1
site = models.ManyToManyField(
Site,
blank=True,
default=DEFAULT_SITE_ID,
help_text=
"If we know the source of the image (should possibly be removed?)")
verified = models.BooleanField(default=False)
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
author = models.ManyToManyField(settings.AUTH_USER_MODEL,
blank=True,
help_text='Author of question',
related_name="rapid_authored_questions")
scrapped = models.BooleanField(
default=False,
help_text='Question has been scrapped and will not be shown')
def get_absolute_url(self):
return reverse('rapids:rapid_detail', kwargs={'pk': self.pk})
def get_authors(self):
"""Returns a comma seperated text list of authors"""
authors = ", ".join([i.username for i in self.author.all()])
return authors
def get_regions(self):
"""Returns a comma seperated text list of regions"""
regions = ", ".join([i.name for i in self.region.all()])
return regions
def get_abnormalities(self):
"""Returns a comma seperated text list of regions"""
abnormalities = ", ".join([i.name for i in self.abnormality.all()])
return abnormalities
def __str__(self):
n = "Normal"
if not self.normal:
#n = self.answers.first()
n = "{} / {}".format(self.abnormality.first(), self.region.first())
exams = ", ".join([str(i) for i in self.examination.all()])
lat = ""
if self.laterality != "NONE":
lat = self.laterality
return "{} / {} {}".format(exams, lat, n)
class RapidImage(models.Model):
rapid = models.ForeignKey(Rapid,
related_name="images",
on_delete=models.CASCADE)
image = models.ImageField(upload_to=image_directory_path, storage=image_storage)
feedback_image = models.BooleanField(default=False)
def image_tag(self):
if self.image:
return mark_safe(
'<img src="{}{}" class="admin-rapid-image" /><span class="admin-rapid-image-info">Click and hold to zoom<span>'
.format(settings.MEDIA_URL, self.image))
else:
return ""
image_tag.short_description = 'Image'
class Note(models.Model):
rapid = models.ForeignKey(Rapid,
related_name="rapid_notes",
on_delete=models.CASCADE,
null=True)
author = models.ForeignKey(User,
related_name="rapid_notes",
on_delete=models.CASCADE)
note = models.TextField()
created_on = models.DateTimeField(auto_now_add=True)
def get_absolute_url(self):
self.pk = self.rapid_id
return reverse('rapids:rapid_detail', kwargs={'pk': self.pk})
def __str__(self):
return "{} [{}] {}".format(self.rapid, self.author, self.created_on)
class RapidCreationDefault(models.Model):
author = models.OneToOneField(User,
related_name="rapid_default",
on_delete=models.CASCADE)
site = models.ManyToManyField(
Site,
blank=True,
default=1,
help_text="Default site to use when creating a new rapid")
def get_absolute_url(self):
return reverse('rapids:rapid_create')
class 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)
image_annotations = models.TextField(
blank=True,
help_text="Stores a JSON representation of annotations to be applied by cornerstonetools",
)
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,
help_text="Modality of the image",
)
region = models.ForeignKey(
Region,
on_delete=models.SET_NULL,
null=True,
blank=True,
help_text="Region the image covers",
)
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)
open_access = models.BooleanField(
help_text="If an question should be freely available to browse", default=True
)
author = models.ManyToManyField(
settings.AUTH_USER_MODEL,
blank=True,
help_text="Author(s) of question",
related_name="anatomy_authored_questions",
)
def __str__(self):
# Get first answer
return "{} [{}, {}]".format(
self.GetPrimaryAnswer(), self.modality, self.structure
)
# 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 get_absolute_url(self):
return reverse("anatomy:question_detail", kwargs={"pk": self.pk})
def GetPrimaryAnswer(self):
if len(self.answers.all()) > 0:
return self.answers.all().first().answer
else:
return "None yet..."
def GetExams(self):
e = self.exams.all().values_list("name", flat=True)
exams = ", ".join(e)
return exams
def GetUnmarkedAnswersString(self):
unmarked_answers = self.GetUnmarkedAnswers()
if not unmarked_answers:
return "No answers to mark"
return format_html(
"<span class='warn'>{} answer unmarked:</span> {}".format(
len(unmarked_answers), ", ".join(unmarked_answers)
)
)
def GetUnmarkedAnswers(self):
user_answers = set(
[i.answer_compare for i in self.cid_user_answers.all()]
)
unmarked_answers = user_answers - self.GetMarkedAnswers()
return unmarked_answers
def GetUnmarkedAnswerCount(self):
return len(self.GetUnmarkedAnswers())
def GetMarkedAnswers(self):
return set(
[
i.answer_compare
for i in self.answers.all()
if i.status != i.MarkOptions.UNMARKED
]
)
correct_answers = set(
[i.answer.get_compare_string() for i in self.answers.all()]
)
half_mark_answers = set(
[i.answer.get_compare_string() for i in self.half_mark_answers.all()]
)
incorrect_answers = set(
[i.answer.get_compare_string() for i in self.incorrect_answers.all()]
)
marked_answers = correct_answers | half_mark_answers | incorrect_answers
return marked_answers
+26
View File
@@ -0,0 +1,26 @@
import django_tables2 as tables
from django_tables2.utils import A
from .models import Rapid
class RapidTable(tables.Table):
edit = tables.LinkColumn('rapids:rapid_update',
text='Edit',
args=[A('pk')],
orderable=False)
view = tables.LinkColumn('rapids:rapid_detail',
text='View',
args=[A('pk')],
orderable=False)
clone = tables.LinkColumn('rapids:rapid_clone',
text='Clone',
args=[A('pk')],
orderable=False)
class Meta:
model = Rapid
template_name = "django_tables2/bootstrap4.html"
fields = ("normal", "abnormality", "region", "examination",
"laterality", "site", "created_date", "author")
sequence = ("view", )
+5
View File
@@ -0,0 +1,5 @@
{% extends "admin/base_site.html" %}
{% load static %}
{% block extrahead %}
<link rel="stylesheet" href="{% static 'css/admin.css' %}">
{% endblock %}
+8
View File
@@ -0,0 +1,8 @@
{% extends 'rapids/base.html' %}
{% block content %}
{% for author in authors %}
<p>Author: <a href="{% url 'rapids:author_detail' pk=author.pk %}">{{author.username}}</a>, </p>
{% endfor %}
{% endblock %}
+54
View File
@@ -0,0 +1,54 @@
{% load static %}
<html>
<head>
{% block css %}
{% endblock %}
<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/sbas.css' %}">
<script src="{% static 'tagulous/lib/jquery.js' %}"></script>
<script src="{% static 'tagulous/lib/select2-3/select2.min.js' %}"></script>
<script src="{% static 'tagulous/tagulous.js' %}"></script>
<script src="{% static 'tagulous/adaptor/select2-3.js' %}"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.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>
{% block js %}
{% endblock %}
</head>
<body>
<title>Rapids</title>
<div id=link-block>
{% if request.user.is_staff %}<span id="admin-link"><a
href="{% url 'admin:index' %}">Admin</a></span>{% endif %}
<span id="view-link"><a href="{% url 'rapids:rapid_view' %}">View questions</a></span>
<span id="create-link"><a href="{% url 'rapids:rapid_create' %}">Add new rapid</a></span>
<span id="password-change-link"><a href="{% url 'password_change' %}">Change password</a></span>
<span id="logout-link"><a href="{% url 'logout' %}">Logout</a></span>
</br>
Questions by:
<span id="authors-link"><a href="{% url 'rapids:author_list' %}">author</a></span>
</div>
<div class="page-header">
<h1>Rapids</h1>
</div>
<div class="content container">
{% 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 'rapids/base.html' %}
{% block content %}
{{category}}:
<ul>
{% for rapid in rapids %}
<li><a href="{% url 'rapids:rapid_detail' pk=rapid.pk %}">{{rapid}}
[{{rapid.get_authors}}]</a></li>
{% endfor %}
</ul>
{% endblock %}
+5
View File
@@ -0,0 +1,5 @@
<h2>Add new {{name}}</h2>
<form method="POST" class="post-form">{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Save</button>
</form>
+23
View File
@@ -0,0 +1,23 @@
{% extends 'rapids/base.html' %}
{% block content %}
My Questions:
<ul>
{% for rapid in user_rapids %}
<li{% if rapid.scrapped %} class='rapid-scrapped' {% endif %}><a
href="{% url 'rapids:rapid_detail' pk=rapid.pk %}">{{rapid}}
[{{rapid.get_authors}}]</a></li>
{% endfor %}
</ul>
Other Questions:
<ul>
{% for rapid in other_rapids %}
<li{% if rapid.scrapped %} class='rapid-scrapped' {% endif %}><a
href="{% url 'rapids:rapid_detail' pk=rapid.pk %}">{{rapid}} [{{rapid.get_authors}}]</a></li>
{% endfor %}
</ul>
{% endblock %}
+12
View File
@@ -0,0 +1,12 @@
{% extends "rapids/base.html" %}
{% block content %}
<h2>Add Note</h2>
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit">
</form>
{% endblock %}
+11
View File
@@ -0,0 +1,11 @@
{% extends 'rapids/base.html' %}
{% block content %}
<a href="{% url 'rapids:rapid_update' pk=rapid.pk %}" title="Edit the Rapid">Edit</a>
<a href="{% url 'rapids:rapid_clone' pk=rapid.pk %}" title="Clone the Rapid (duplicate everything but the images)">Clone</a>
<a href="{% url 'rapids:rapid_add_note' pk=rapid.pk %}"> Add Note</a>
{% if request.user.is_superuser %}
<a href="{% url 'admin:rapids_rapid_change' rapid.id %}" title="Edit the Rapid using the admin interface">Admin Edit</a>
{% endif %}
{% include 'rapids/rapid_display_block.html' %}
{% endblock %}
+32
View File
@@ -0,0 +1,32 @@
<div class="rapid {% if rapid.scrapped %}rapid-scrapped{% endif %}">
<div class="date">
{{ rapid.created_date }}
</div>
<p class="pre-whitespace"><b>Rapid:</b> {{ rapid }}</p>
<p class="pre-whitespace"><b>Region:</b> {{ rapid.get_regions }}</p>
<p class="pre-whitespace"><b>Abnormality:</b> {{ rapid.get_abnormalities }}</p>
<p class="pre-whitespace"><b>Images:</b>
{% for image in rapid.images.all %}
Image {{ forloop.counter }}{% if image.feedback_image %} [feedback image]{% endif %}:
<img class="rapid-img {% if image.feedback_image %}feedback-img{% endif %}" src="{{ image.image.url }}">
{% endfor %}</p>
<p class="pre-whitespace"><b>Feedback:</b> {{ rapid.feedback }}</p>
<p><b>Author(s):</b> {% for author in rapid.author.all %} <a
href="{% url 'rapids:author_detail' pk=author.pk %}">{{author}}</a>, {% endfor %}</p>
<p><b>Checked by:</b> {% for verified in rapid.verified.all %} <a
href="{% url 'rapids:verified_detail' pk=verified.pk %}">{{verified}}</a>, {% endfor %}</p>
<p><b>Scrapped:</b> {{ rapid.scrapped }} <a href="{% url 'rapids:rapid_scrap' pk=rapid.pk %}">(toggle)</a>
</p>
</div>
<div>
Notes:
<ul>
{% for note in rapid.rapid_notes.all %}
<li>
{{ note.created_on }} by {{ note.author }}: {{ note.note }}
</li>
{% endfor %}
</ul>
</div>
+251
View File
@@ -0,0 +1,251 @@
{% extends "rapids/base.html" %}
{% load static from staticfiles %}
{% block js %}
<!--<script type="text/javascript" src="/admin/jsi18n/"></script>-->
<script type="text/javascript">
function showEditPopup(url) {
var win = window.open(url, "Edit",
'height=500,width=800,resizable=yes,scrollbars=yes');
return false;
}
function showAddPopup(triggeringLink) {
var name = triggeringLink.id.replace(/^add_/, '');
href = triggeringLink.href;
var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
win.focus();
return false;
}
function closePopup(win, newID, newRepr, id) {
console.log(id)
$(id + "_to").append('<option value=' + newID + ' title=' + newRepr + ' >' + newRepr + '</option>')
win.close();
}
document.addEventListener('drop', function (e) { e.preventDefault(); }, false);
function add_input_form() {
var form_idx = $('#id_images-TOTAL_FORMS').val();
$('#form_set').append($('#empty_form').html().replace(/__prefix__/g, form_idx));
$('#id_images-TOTAL_FORMS').val(parseInt(form_idx) + 1);
}
$(document).ready(function () {
$("#add_abnormality").appendTo($("label[for='id_abnormality']"));
$("#add_examination").appendTo($("label[for='id_examination']"));
$("#add_region").appendTo($("label[for='id_region']"));
dropContainer = document.getElementById("drop-container");
dropContainer.ondragover = function (evt) {
evt.preventDefault();
evt.stopPropagation();
};
dropContainer.ondragenter = function (evt) {
console.log("ENTER")
console.log(evt)
$(evt.target).addClass("drop-target-active")
evt.preventDefault();
evt.stopPropagation();
};
dropContainer.ondragleave = function (evt) {
console.log("ENTER")
console.log(evt)
$(evt.target).removeClass("drop-target-active")
evt.preventDefault();
evt.stopPropagation();
};
dropContainer.ondrop = function (evt) {
console.log("SHIT", evt);
$(evt.target).removeClass("drop-target-active");
// Get all input elements
inputs = $("#rapid-form input[type=file][id^=id_images-]");
//fileInput = document.getElementById("id_images-0-image");
// Make sure we have enough input targets
input_diff = (evt.dataTransfer.files.length - inputs.length)
console.log("diff", input_diff)
if(input_diff > 0) {
for(let j = 0; j < input_diff; j++) {
add_input_form()
}
// Need to make sure we include the new ones...
inputs = $("#rapid-form input[type=file][id^=id_images-]");
}
// Loop through each dropped file and try to assign to an
// input element
[...evt.dataTransfer.files].forEach((f) => {
let dT = new DataTransfer();
dT.clearData();
dT.items.add(f);
for(let i = 0; i < inputs.length; i++) {
el = inputs.get(i)
if(el.files.length == 0) {
el.files = dT.files;
ocr(el)
if(evt.target.id == "feedback-drop-target") {
arr = el.name.split("-");
arr.splice(2, 1, "feedback_image");
feedback_el_name = arr.join("-");
$(`[name=${feedback_el_name}]`).prop("checked", true);
}
return false
}
}
})
// // If you want to use some of the dropped files
// const dT = new DataTransfer();
// dT.items.add(evt.dataTransfer.files[0]);
// dT.items.add(evt.dataTransfer.files[3]);
// fileInput.files = dT.files;
evt.preventDefault();
evt.stopPropagation();
updateFileList();
};
$('#add_more').click(() => { add_input_form() });
function updateFileList() {
$("#drop-filenames").empty()
$("#form_set input[type=file]").each((n, el) => {
console.log(el);
if(el.files.length > 0) {
extra_class = " image-ident-loading";
if($(el).hasClass("image-ident-warning")) {
extra_class = " image-ident-warning"
} else if($(el).hasClass("image-ident-ok")) {
extra_class = " image-ident-ok"
}
$("#drop-filenames").append(
`<span>${n}: ${el.files[0].name}</span><img class='uploading${extra_class}' data-input-id='${el.id}' src=${URL.createObjectURL(el.files[0])}>`
)
}
})
}
$("input[type=file]").on('change', function () {
$(this).removeClass("image-ident-warning");
$(this).removeClass("image-ident-ok");
updateFileList();
console.log("input change1");
console.log("input change", this);
ocr(this);
});
});
function ocr(input) {
// Tesseract.recognize(f, "eng",{ logger: m => console.log(m) }
// ).then(({ data: { text } }) => {
// console.log(text);
// l = text.toLowerCase();
// if (l.includes("accesion") || l.includes("number") || l.search(/(REF|RK9|RH8|RA9)\d+/g)) {
// console.log("SHIT", this);
// }
// })
console.log('{% static "worker.min.js" %}');
console.log('{{ STATIC_URL }}/tesseract-core.wasm.js %}');
const worker = Tesseract.createWorker({
workerPath: '{% static "worker.min.js" %}',
langPath: '{% static "" %}',
//langPath: '{{ STATIC_URL }}',
corePath: '{% static "tesseract-core.wasm.js" %}',
});
const url = URL.createObjectURL(input.files[0]);
let img = new Image;
img.src = url;
img.onload = function () {
width = img.width;
// const rectangle = { left: 0, top: 0, width: width, height: 10 }
(async () => {
await worker.load();
await worker.loadLanguage('eng');
await worker.initialize('eng');
// const { data: { text } } = await worker.recognize(input.files[0], { rectangle });
const { data: { text } } = await worker.recognize(input.files[0]);
//console.log(text);
l = text.toLowerCase();
$(`img[data-input-id='${input.id}'`).removeClass("image-ident-loading");
console.log(l);
if(l.includes("accesion") || l.includes("number") || l.search(/(ref|rk9|rh8|ra9|rbz)\d+/g) > -1) {
console.log("SHIT", input);
$(input).addClass("image-ident-warning");
$(`img[data-input-id='${input.id}'`).addClass("image-ident-warning");
} else {
$(input).addClass("image-ident-ok");
}
await worker.terminate();
})();
}
}
</script>
{{ form.media }}
{% endblock %}
{% block content %}
<h2>Submit Rapid</h2>
<a href="{% url 'rapids:rapid_create_defaults' %}">Edit defaults</a>
<form action="" method="post" enctype="multipart/form-data" id="rapid-form">
{% csrf_token %}
<a href="/rapids/abnormality/create" id="add_abnormality" class="add-popup"
onclick="return showAddPopup(this);"><img src="{% static '/img/icon-addlink.svg' %}"></a>
<a href="/rapids/examination/create" id="add_examination" class="add-popup"
onclick="return showAddPopup(this);"><img src="{% static '/img/icon-addlink.svg' %}"></a>
<a href="/rapids/region/create" id="add_region" class="add-popup" onclick="return showAddPopup(this);"><img
src="{% static '/img/icon-addlink.svg' %}"></a>
<table>
{{ form.as_table }}
</table>
<h3>Images:</h3>
<input type="button" value="Add More Images" id="add_more">
<div id="drop-container" class="drop-target">Drop images here (or use the buttons below)<div
id="feedback-drop-target">Feedback image?<br />drop those here</div>
<div id="drop-filenames"></div>
</div>
<div id="form_set">
{% for form in image_formset %}
<ul class="no-error image-formset">
{{form.non_field_errors}}
{{form.errors}}
{{ form.as_ul }}
</ul>
{% endfor %}
</div>
{{ image_formset.management_form }}
<input type="submit" class="submit-button" value="Submit" name="submit">
<input type="submit" class="submit-button" value="Submit and Clone" name="submit-clone">
</form>
<div id="empty_form" style="display:none">
<ul class='no_error image-formset'>
{{ image_formset.empty_form.as_ul }}
</ul>
</div>
{% endblock %}
+10
View File
@@ -0,0 +1,10 @@
{% extends "rapids/base.html" %}
{% block content %}
<h2>Defaults for creating new rapids</h2>
<form method="POST" class="post-form">{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-primary">Save</button>
</form>
{% endblock %}
+18
View File
@@ -0,0 +1,18 @@
{% extends 'rapids/base.html' %}
{% load render_table from django_tables2 %}
{% block css %}
{% endblock %}
{% block content %}
<div id="view-filter-options">
<h3>Filter Rapids</h3>
<form action="" method="get">
{{ filter.form }}
<input class="btn btn-primary btn-sm mt-1 mb-1" type="submit" />
</form>
</div>
{% render_table table %}
{% endblock %}
Regular → Executable
View File
Executable
+46
View File
@@ -0,0 +1,46 @@
from django.urls import path, include
from . import views
app_name = "rapids"
urlpatterns = [
# path('', views.question_list, name='question_list'),
path("", views.index, name="index"),
path("author/<int:pk>/", views.author_detail, name="author_detail"),
path("author/", views.author_list, name="author_list"),
#path("unchecked/", views.unchecked_list, name="unchecked_list"),
#path("verified/<int:pk>/", views.verified_detail, name="verified_detail"),
path("<int:pk>/", views.rapid_detail, name="rapid_detail"),
path("<int:pk>/split", views.rapid_split, name="rapid_split"),
path("<int:pk>/clone", views.RapidClone.as_view(), name="rapid_clone"),
#path("verified/", views.verified, name="verified"),
#path("all_questions/", views.all_questions, name="all_questions"),
path("<int:pk>/scrap", views.rapid_scrap, name="rapid_scrap"),
path("create/", views.RapidCreate.as_view(), name="rapid_create"),
path("create/defaults",
views.RapidCreationDefaultView.as_view(),
name="rapid_create_defaults"),
path("view/", views.RapidView.as_view(), name="rapid_view"),
path("region/create/", views.create_region, name="create_region"),
path("region/ajax/get_region_id",
views.get_region_id,
name="get_region_id"),
path("abnormality/create/",
views.create_abnormality,
name="create_abnormality"),
path("abnormality/ajax/get_abnormality_id",
views.get_abnormality_id,
name="get_abnormality_id"),
path("examination/create/",
views.create_examination,
name="create_examination"),
path("examination/ajax/get_examination_id",
views.get_examination_id,
name="get_examination_id"),
path(
"<int:pk>/update",
views.RapidUpdate.as_view(),
name="rapid_update",
),
path("<int:pk>/add_note", views.AddNote.as_view(), name="rapid_add_note"),
]
Regular → Executable
+449 -2
View File
@@ -1,3 +1,450 @@
from django.shortcuts import render
from django.shortcuts import render, get_object_or_404, redirect
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.core.exceptions import PermissionDenied
# Create your views here.
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic import ListView
from django.views.decorators.csrf import csrf_exempt
from django.urls import reverse_lazy, reverse
from django.http import Http404, JsonResponse
from django.http import HttpResponseRedirect, HttpResponse
from .forms import RapidForm, ImageFormSet, NoteForm, RegionForm, AbnormalityForm, ExaminationForm
from .models import Rapid, Note, Abnormality, Region, Examination
from .tables import RapidTable
from .filters import RapidFilter
from django_tables2 import SingleTableView, SingleTableMixin
from django_filters.views import FilterView
from .decorators import user_is_author_or_rapid_checker
from collections import defaultdict
import logging
from copy import deepcopy
from django.forms.models import model_to_dict
from rapids.forms import RapidCreationDefaultForm
from rapids.models import RapidCreationDefault
logger = logging.getLogger(__name__)
class AuthorOrCheckerRequiredMixin(object):
def get_object(self, *args, **kwargs):
obj = super(UpdateView, self).get_object(*args, **kwargs)
if self.request.user.groups.filter(name='rapid_checker').exists():
return obj
if self.request.user not in obj.author.all():
raise PermissionDenied() #or Http404
return obj
@login_required
def index(request):
other_rapids = Rapid.objects.exclude(author=request.user.pk)
user_rapids = Rapid.objects.filter(author=request.user.pk)
return render(request, "rapids/index.html", {
"other_rapids": other_rapids,
"user_rapids": user_rapids
})
@login_required
def rapid_detail(request, pk):
rapid = get_object_or_404(Rapid, pk=pk)
#if request.user not in rapid.author.all():
# raise PermissionDenied
#logging.debug(rapid.rapid_notes.first())
#logging.debug(rapid.subspecialty.first().name.all())
return render(request, 'rapids/rapid_detail.html', {'rapid': rapid})
@login_required
def rapid_split(request, pk):
rapid = get_object_or_404(Rapid, pk=pk)
images = rapid.images.all()
old_abnormality = rapid.abnormality.all()
old_region = rapid.region.all()
old_examination = rapid.examination.all()
old_site = rapid.site.all()
old_author = rapid.author.all()
if not images:
raise Http404
for n in range(len(images) - 1):
rapid.pk = None
rapid.save()
rapid.abnormality.set(old_abnormality)
rapid.region.set(old_region)
rapid.examination.set(old_examination)
rapid.site.set(old_site)
rapid.author.set(old_author)
images[n].rapid = rapid
images[n].save()
#images[-1].rapid
#if request.user not in rapid.author.all():
# raise PermissionDenied
#logging.debug(rapid.rapid_notes.first())
#logging.debug(rapid.subspecialty.first().name.all())
return render(request, 'rapids/rapid_detail.html', {'rapid': rapid})
@login_required
def author_detail(request, pk):
#logging.debug(Author.objects.all())
#author = get_object_or_404(Author, pk=pk)
author = User.objects.get(pk=pk)
rapids = Rapid.objects.filter(author=pk)
return render(request, "rapids/category_detail.html", {
"category": author,
"rapids": rapids
})
def author_list(request):
authors = User.objects.all()
return render(request, 'rapids/author_list.html', {'authors': authors})
class RapidCreationDefaultView(LoginRequiredMixin, UpdateView):
model = RapidCreationDefault
form_class = RapidCreationDefaultForm
# fields = '__all__'
# #fields = [ 'condition' ]
# #initial = {'date_of_death': '05/01/2018'}
# exclude = [ 'created_date', 'published_date' ]
# def dispatch(self, request, *args, **kwargs):
# """
# Overridden so we can make sure the `Rapid` instance exists
# before going any further.
# """
# self.pk = get_object_or_404(Rapid, pk=kwargs['pk'])
# return super().dispatch(request, *args, **kwargs)
def get_object(self, queryset=None):
obj, create = RapidCreationDefault.objects.get_or_create(
author=self.request.user)
return obj
def form_valid(self, form):
model = form.save(commit=False)
model.author = self.request.user
model.save()
response = super().form_valid(form)
return response
#form.instance.author.add(self.request.user.id)
class AddNote(LoginRequiredMixin, CreateView):
model = Note
form_class = NoteForm
# fields = '__all__'
# #fields = [ 'condition' ]
# #initial = {'date_of_death': '05/01/2018'}
# exclude = [ 'created_date', 'published_date' ]
def dispatch(self, request, *args, **kwargs):
"""
Overridden so we can make sure the `Rapid` instance exists
before going any further.
"""
self.pk = get_object_or_404(Rapid, pk=kwargs['pk'])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
note = form.save(commit=False)
note.rapid = self.pk
note.author = self.request.user
note.save()
response = super().form_valid(form)
return response
#form.instance.author.add(self.request.user.id)
@login_required
def rapid_clone(request, pk):
new_item = get_object_or_404(Rapid, pk=pk)
new_item.pk = None #autogen a new pk (item_id)
#new_item.name = "Copy of " + new_item.name #need to change uniques
form = RapidForm(request.POST or None, instance=new_item)
formset = ImageFormSet()
if form.is_valid():
form.instance.author.add(request.user.id)
#logger.debug(formset.is_valid())
if formset.is_valid():
response = super().form_valid(form)
formset.instance = obj
formset.save()
return response
else:
return super().form_invalid(form)
context = {
"form": form,
"image_formset": formset
#other context
}
return render(request, "rapids/rapid_form.html", context)
class RapidCreateBase(LoginRequiredMixin, CreateView):
model = Rapid
form_class = RapidForm
def get_context_data(self, **kwargs):
context = super(RapidCreateBase, self).get_context_data(**kwargs)
if self.request.POST:
context['image_formset'] = ImageFormSet(self.request.POST,
self.request.FILES)
else:
context['image_formset'] = ImageFormSet()
return context
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.save()
form.instance.author.add(self.request.user.id)
context = self.get_context_data(form=form)
formset = context['image_formset']
if formset.is_valid():
response = super().form_valid(form)
formset.instance = self.object
formset.save()
# If the normal submit button is pressed we save as normal
if "submit" in self.request.POST:
return response
# else we redirect to the clone url
else:
return redirect('rapids:rapid_clone', pk=self.object.pk)
else:
return super().form_invalid(form)
#@login_required
class RapidCreate(RapidCreateBase):
initial = {'laterality': Rapid.NONE}
def get_initial(self):
# There has to be a better way...
try:
s = (i.pk for i in self.request.user.rapid_default.site.all())
self.initial.update({'site': s})
except AttributeError:
pass
return self.initial
# fields = '__all__'
# #fields = [ 'condition' ]
# #initial = {'date_of_death': '05/01/2018'}
# exclude = [ 'created_date', 'published_date' ]
#self.object = form.save(commit=False)
#self.object.save()
#form.instance.author.add(self.request.user.id)
#return super().form_valid(form)
class RapidUpdate(LoginRequiredMixin, AuthorOrCheckerRequiredMixin,
UpdateView):
model = Rapid
form_class = RapidForm
# fields = '__all__'
# #fields = [ 'condition' ]
# #initial = {'date_of_death': '05/01/2018'}
# exclude = [ 'created_date', 'published_date' ]
def get_context_data(self, **kwargs):
context = super(RapidUpdate, self).get_context_data(**kwargs)
if self.request.POST:
context['image_formset'] = ImageFormSet(self.request.POST,
self.request.FILES,
instance=self.object)
context['image_formset'].full_clean()
else:
context['image_formset'] = ImageFormSet(instance=self.object)
return context
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.save()
form.instance.author.add(self.request.user.id)
context = self.get_context_data(form=form)
formset = context['image_formset']
#logger.debug(formset.is_valid())
if formset.is_valid():
response = super().form_valid(form)
formset.instance = self.object
formset.save()
return response
else:
return super().form_invalid(form)
class RapidClone(RapidCreateBase):
"""Clones a existing rapid"""
# fields = '__all__'
# #fields = [ 'condition' ]
# #initial = {'date_of_death': '05/01/2018'}
# exclude = [ 'created_date', 'published_date' ]
def get_initial(self):
#print(self.request)
old_object = get_object_or_404(Rapid, pk=self.kwargs['pk'])
initial_data = model_to_dict(old_object, exclude=['id'])
return initial_data
@login_required
@user_is_author_or_rapid_checker
def rapid_scrap(request, pk):
try:
rapid = Rapid.objects.get(pk=pk)
except Rapid.DoesNotExist:
raise Http404("Rapid does not exist")
rapid.scrapped = not rapid.scrapped
rapid.save()
return HttpResponseRedirect(reverse('rapids:rapid_detail', args=(pk, )))
# @login_required
# def edit_abnormality_popup(request):
# instance = get_object_or_404(Abnormality, pk = pk)
# form = AbnormalityForm(request.POST or None)
# if form.is_valid():
# instance = form.save()
# return HttpResponse('<script>opener.closePopup(window, "%s", "%s", "#id_abnormality");</script>' % (instance.pk, instance))
# return render(request, "rapids/create_simple.html", {'form': form})
@login_required
def create_abnormality(request):
form = AbnormalityForm(request.POST or None)
if form.is_valid():
instance = form.save()
return HttpResponse(
'<script>opener.closePopup(window, "%s", "%s", "#id_abnormality");</script>'
% (instance.pk, instance))
return render(request, "rapids/create_simple.html", {
'form': form,
'name': "Abnormality"
})
@csrf_exempt
def get_abnormality_id(request):
if request.is_ajax():
abnormality_name = request.GET['abnormality_name']
abnormality_id = Abnormality.objects.get(name=abnormality_name).id
data = {
'abnormality_id': abnormality_id,
}
return HttpResponse(json.dumps(data), content_type='application/json')
return HttpResponse("/")
@login_required
def create_examination(request):
form = ExaminationForm(request.POST or None)
if form.is_valid():
instance = form.save()
return HttpResponse(
'<script>opener.closePopup(window, "%s", "%s", "#id_examination");</script>'
% (instance.pk, instance))
return render(request, "rapids/create_simple.html", {
'form': form,
'name': "Examination"
})
@csrf_exempt
def get_examination_id(request):
if request.is_ajax():
examination_name = request.GET['examination_name']
examination_id = Examination.objects.get(name=examination_name).id
data = {
'examination_id': examination_id,
}
return HttpResponse(json.dumps(data), content_type='application/json')
return HttpResponse("/")
@login_required
def create_region(request):
form = RegionForm(request.POST or None)
if form.is_valid():
instance = form.save()
return HttpResponse(
'<script>opener.closePopup(window, "%s", "%s", "#id_region");</script>'
% (instance.pk, instance))
return render(request, "rapids/create_simple.html", {
'form': form,
'name': "Region"
})
@csrf_exempt
def get_region_id(request):
if request.is_ajax():
region_name = request.GET['region_name']
region_id = Region.objects.get(name=region_name).id
data = {
'region_id': region_id,
}
return HttpResponse(json.dumps(data), content_type='application/json')
return HttpResponse("/")
class RapidView(SingleTableMixin, FilterView):
model = Rapid
table_class = RapidTable
template_name = "rapids/view.html"
filterset_class = RapidFilter