Merge branch 'master' of ssh://git.xkjq.uk:30001/ross/rad

This commit is contained in:
Ross
2021-01-23 11:37:52 +00:00
45 changed files with 2298 additions and 98 deletions
-2
View File
@@ -156,7 +156,6 @@ AnswerFormSet = inlineformset_factory(
can_delete=True, can_delete=True,
extra=1, extra=1,
max_num=10, max_num=10,
field_classes="testing",
) )
AnswerUpdateFormSet = inlineformset_factory( AnswerUpdateFormSet = inlineformset_factory(
@@ -172,7 +171,6 @@ AnswerUpdateFormSet = inlineformset_factory(
can_delete=True, can_delete=True,
extra=0, extra=0,
max_num=10, max_num=10,
field_classes="testing",
) )
@@ -0,0 +1,20 @@
# Generated by Django 3.1.2 on 2021-01-18 13:39
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0018_auto_20210112_1726'),
]
operations = [
migrations.AlterField(
model_name='anatomyquestion',
name='image',
field=models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='/media/anatomy/', location='/home/ross/sites/rad/media//anatomy/'), upload_to=anatomy.models.image_directory_path),
),
]
@@ -0,0 +1,20 @@
# Generated by Django 3.1.2 on 2021-01-18 14:02
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0019_auto_20210118_1339'),
]
operations = [
migrations.AlterField(
model_name='anatomyquestion',
name='image',
field=models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='http://penracourses.org.uk//media/anatomy/', location='/home/ross/sites/rad/media//anatomy/'), upload_to=anatomy.models.image_directory_path),
),
]
@@ -0,0 +1,20 @@
# Generated by Django 3.1.3 on 2021-01-20 22:06
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0020_auto_20210118_1402'),
]
operations = [
migrations.AlterField(
model_name='anatomyquestion',
name='image',
field=models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='http://penracourses.org.uk//media/anatomy/', location='/home/ross/scripts/sites/rad/media//anatomy/'), upload_to=anatomy.models.image_directory_path),
),
]
@@ -0,0 +1,20 @@
# Generated by Django 3.1.3 on 2021-01-21 18:06
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0021_auto_20210120_2206'),
]
operations = [
migrations.AlterField(
model_name='anatomyquestion',
name='image',
field=models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='/media/anatomy/', location='/home/ross/scripts/sites/rad/media//anatomy/'), upload_to=anatomy.models.image_directory_path),
),
]
+40
View File
@@ -397,3 +397,43 @@ td.user-answer-score-2::after {
.answer-0 { .answer-0 {
color: red; color: red;
} }
.selector {
border-color: #05668d;
}
.selector input {
color: gray;
background-color: white;
font-size: small;
}
.selector .selector-available h2, .selector .selector-chosen h2 {
margin-bottom: 0px;
border: none;
/* border-color: #05668d; */
background-color: #ebf2fa;
color: rgb(28, 184, 65);
}
.selector .selector-available select, .selector .selector-chosen select {
border-color: #05668d;
}
#id_laterality {
margin-top: 30px;
}
#id_laterality li{
display: inline-block;
border-radius: 4px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
background: rgb(28, 184, 65);
padding-right: 10px;
color: white;
}
.add-popup {
display: block;
}
+87 -74
View File
@@ -39,82 +39,13 @@ $(document).ready(function () {
}); });
} }
if ($("#dicom-image").length) { dicom_images = document.getElementsByClassName("dicom-image");
if (dicom_images.length) {
console.log("Dicom element found ", $("#dicom-image")); for (let element of dicom_images) {
setUpDicom(element)
}
$('#dicom-image').bind('contextmenu', function (e) {
return false;
});
cornerstoneBase64ImageLoader.external.cornerstone = cornerstone;
cornerstoneWebImageLoader.external.cornerstone = cornerstone;
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
cornerstoneTools.init();
const PanTool = cornerstoneTools.PanTool;
const ZoomTool = cornerstoneTools.ZoomTool;
const ZoomMouseWheelTool = cornerstoneTools.ZoomMouseWheelTool;
const WwwcTool = cornerstoneTools.WwwcTool;
const WwwcRegionTool = cornerstoneTools.WwwcRegionTool;
const RotateTool = cornerstoneTools.RotateTool;
const StackScrollTool = cornerstoneTools.StackScrollTool;
const MagnifyTool = cornerstoneTools.MagnifyTool;
const ArrowAnnotateTool = cornerstoneTools.ArrowAnnotateTool;
const element = document.getElementById('dicom-image');
const imageId = element.dataset.url;
console.log("Dicom - load imageId: ", imageId);
cornerstone.enable(element);
cornerstone.loadAndCacheImage(imageId).then(function (image) {
cornerstone.displayImage(element, image);
cornerstoneTools.addTool(PanTool);
cornerstoneTools.addTool(ZoomTool);
cornerstoneTools.addTool(ZoomMouseWheelTool);
cornerstoneTools.addTool(WwwcTool);
cornerstoneTools.addTool(WwwcRegionTool);
cornerstoneTools.addTool(RotateTool);
cornerstoneTools.addTool(StackScrollTool);
cornerstoneTools.addTool(MagnifyTool);
cornerstoneTools.addTool(ArrowAnnotateTool, {
configuration: {
getTextCallback: () => { },
changeTextCallback: () => { },
allowEmptyLabel: true,
renderDashed: false,
drawHandles: false,
drawHandlesOnHover: true,
},
});
// Enable our tools
// Avoid incorrect aspect ratio
cornerstoneTools.setToolActive("Pan", { mouseButtonMask: 1 });
cornerstoneTools.setToolActive("Wwwc", { mouseButtonMask: 2 });
cornerstoneTools.setToolActive("ZoomMouseWheel", { mouseButtonMask: 3 });
cornerstoneTools.setToolActive("Zoom", { mouseButtonMask: 4 });
if (element.dataset.edit_annotation == "true") {
cornerstoneTools.setToolActive("ArrowAnnotate", { mouseButtonMask: 2 });
} else {
cornerstoneTools.setToolEnabled("ArrowAnnotate");
}
if (element.dataset.annotations) {
loadJsonToolStateOnCurrentImage(element.dataset.annotations)
}
cornerstone.resize(element);
});
} }
if ($("#question-mark-list").length) { if ($("#question-mark-list").length) {
@@ -208,3 +139,85 @@ function loadJsonToolStateOnCurrentImage(json) {
} }
function setUpDicom(element) {
console.log("Dicom element found ", $(element));
$(element).bind('contextmenu', function (e) {
return false;
});
$(element).dblclick((evt) => {
element.requestFullscreen();
})
cornerstoneBase64ImageLoader.external.cornerstone = cornerstone;
cornerstoneWebImageLoader.external.cornerstone = cornerstone;
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
cornerstoneTools.init();
const PanTool = cornerstoneTools.PanTool;
const ZoomTool = cornerstoneTools.ZoomTool;
const ZoomMouseWheelTool = cornerstoneTools.ZoomMouseWheelTool;
const WwwcTool = cornerstoneTools.WwwcTool;
const WwwcRegionTool = cornerstoneTools.WwwcRegionTool;
const RotateTool = cornerstoneTools.RotateTool;
const StackScrollTool = cornerstoneTools.StackScrollTool;
const MagnifyTool = cornerstoneTools.MagnifyTool;
const ArrowAnnotateTool = cornerstoneTools.ArrowAnnotateTool;
const imageId = element.dataset.url;
console.log("Dicom - load imageId: ", imageId);
cornerstone.enable(element);
cornerstone.loadAndCacheImage(imageId).then(function (image) {
cornerstone.displayImage(element, image);
cornerstoneTools.addTool(PanTool);
cornerstoneTools.addTool(ZoomTool);
cornerstoneTools.addTool(ZoomMouseWheelTool);
cornerstoneTools.addTool(WwwcTool);
cornerstoneTools.addTool(WwwcRegionTool);
cornerstoneTools.addTool(RotateTool);
cornerstoneTools.addTool(StackScrollTool);
cornerstoneTools.addTool(MagnifyTool);
cornerstoneTools.addTool(ArrowAnnotateTool, {
configuration: {
getTextCallback: () => { },
changeTextCallback: () => { },
allowEmptyLabel: true,
renderDashed: false,
drawHandles: false,
drawHandlesOnHover: true,
},
});
// Enable our tools
// Avoid incorrect aspect ratio
cornerstoneTools.setToolActive("Pan", { mouseButtonMask: 1 });
cornerstoneTools.setToolActive("Wwwc", { mouseButtonMask: 2 });
cornerstoneTools.setToolActive("ZoomMouseWheel", { mouseButtonMask: 3 });
cornerstoneTools.setToolActive("Zoom", { mouseButtonMask: 4 });
if (element.dataset.edit_annotation == "true") {
cornerstoneTools.setToolActive("ArrowAnnotate", { mouseButtonMask: 2 });
} else {
cornerstoneTools.setToolEnabled("ArrowAnnotate");
}
if (element.dataset.annotations) {
loadJsonToolStateOnCurrentImage(element.dataset.annotations)
}
cornerstone.resize(element);
});
}
+3
View File
@@ -28,6 +28,9 @@
<span id="physics-link"> <span id="physics-link">
<a href="{% url 'physics:index' %}">Physics</a> <a href="{% url 'physics:index' %}">Physics</a>
</span> </span>
<span id="rapids-link">
<a href="{% url 'rapids:index' %}">Rapids</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
@@ -41,7 +41,7 @@
<table> <table>
<tr> <tr>
<td> <td>
<div id="dicom-image" data-url="{{ question.image.url}}" data-annotations='{{question.image_annotations}}'> <div id="dicom-image" class="dicom-image" data-url="{{ question.image.url}}" data-annotations='{{question.image_annotations}}'>
</div> </div>
<form method="POST" class="post-form">{% csrf_token %} <form method="POST" class="post-form">{% csrf_token %}
+1 -1
View File
@@ -5,7 +5,7 @@
<a href="{% url 'anatomy:anatomy_question_update' question.id %}" title="Edit the Question">Edit</a> <a href="{% url 'admin:anatomy_anatomyquestion_change' question.id %}" title="Edit the Question using the admin interface">Admin Edit</a> <a href="{% url 'anatomy:anatomy_question_update' question.id %}" title="Edit the Question">Edit</a> <a href="{% url 'admin:anatomy_anatomyquestion_change' question.id %}" title="Edit the Question using the admin interface">Admin Edit</a>
<h3>{{ question.question_type }}</h3> <h3>{{ question.question_type }}</h3>
<div id="dicom-image" class="marking-dicom" data-url="http://penracourses.org.uk{{ question.image.url}}" data-annotations='{{question.image_annotations}}'> <div id="dicom-image" class="marking-dicom dicom-image" data-url="http://penracourses.org.uk{{ question.image.url}}" data-annotations='{{question.image_annotations}}'>
</div> </div>
<div class="marking"> <div class="marking">
<form method="POST" class="post-form">{% csrf_token %} <form method="POST" class="post-form">{% csrf_token %}
@@ -15,7 +15,7 @@
</div> </div>
{% endif %} {% endif %}
<button id="save-annotations">Save Annotations</button> <button id="save-annotations">Save Annotations</button>
<div id="dicom-image" data-url="http://penracourses.org.uk{{ question.image.url}}" data-annotations='{{question.image_annotations}}' data-edit_annotation=true></div> <div id="dicom-image" class="dicom-image" data-url="http://penracourses.org.uk{{ question.image.url}}" data-annotations='{{question.image_annotations}}' data-edit_annotation=true></div>
<!-- testing --> <!-- testing -->
<!--<div id="dicom-image" data-url="http://localhost:8000/static/abdoct.jpg" <!--<div id="dicom-image" data-url="http://localhost:8000/static/abdoct.jpg"
data-annotations='{{question.image_annotations}}' data-edit_annotation=true></div>--> data-annotations='{{question.image_annotations}}' data-edit_annotation=true></div>-->
+3
View File
@@ -29,6 +29,9 @@
<span id="anatomy-link"> <span id="anatomy-link">
<a href="{% url 'anatomy:index' %}">Anatomy</a> <a href="{% url 'anatomy:index' %}">Anatomy</a>
</span> </span>
<span id="rapids-link">
<a href="{% url 'rapids:index' %}">Rapids</a>
</span>
<span id="logout-link"> <span id="logout-link">
<a href="{% url 'logout' %}">Logout</a> <a href="{% url 'logout' %}">Logout</a>
</span> </span>
+18 -17
View File
@@ -23,7 +23,7 @@ with open('/etc/secret_key') as f:
SECRET_KEY = f.read().strip() SECRET_KEY = f.read().strip()
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False DEBUG = True
ALLOWED_HOSTS = ["localhost", "127.0.0.1", "161.35.163.87", "penracourses.org.uk"] ALLOWED_HOSTS = ["localhost", "127.0.0.1", "161.35.163.87", "penracourses.org.uk"]
@@ -32,6 +32,7 @@ ALLOWED_HOSTS = ["localhost", "127.0.0.1", "161.35.163.87", "penracourses.org.uk
INSTALLED_APPS = [ INSTALLED_APPS = [
'anatomy', 'anatomy',
'physics', 'physics',
'rapids',
"reversion", "reversion",
"django_tables2", "django_tables2",
"django_filters", "django_filters",
@@ -46,7 +47,7 @@ INSTALLED_APPS = [
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'debug_toolbar', 'debug_toolbar',
#"tagulous", "tagulous",
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@@ -120,12 +121,12 @@ AUTH_PASSWORD_VALIDATORS = [
}, },
] ]
#SERIALIZATION_MODULES = { SERIALIZATION_MODULES = {
# "xml": "tagulous.serializers.xml_serializer", "xml": "tagulous.serializers.xml_serializer",
# "json": "tagulous.serializers.json", "json": "tagulous.serializers.json",
# "python": "tagulous.serializers.python", "python": "tagulous.serializers.python",
# "yaml": "tagulous.serializers.pyyaml", "yaml": "tagulous.serializers.pyyaml",
#} }
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/ # https://docs.djangoproject.com/en/1.11/topics/i18n/
@@ -154,20 +155,20 @@ REMOTE_URL = "http://penracourses.org.uk"
MEDIA_URL = '/media/' MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/') MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
if DEBUG: #if DEBUG:
MEDIA_URL = REMOTE_URL + MEDIA_URL # MEDIA_URL = REMOTE_URL + MEDIA_URL
# Redirect to home URL after login (Default redirects to /accounts/profile/) # Redirect to home URL after login (Default redirects to /accounts/profile/)
LOGIN_REDIRECT_URL = "/" LOGIN_REDIRECT_URL = "/"
#TAGULOUS_AUTOCOMPLETE_JS = ( TAGULOUS_AUTOCOMPLETE_JS = (
#"tagulous/lib/jquery.js", "tagulous/lib/jquery.js",
#"tagulous/lib/select2-3/select2.min.js", "tagulous/lib/select2-3/select2.min.js",
#"tagulous/tagulous.js", "tagulous/tagulous.js",
#"tagulous/adaptor/select2-3.js", "tagulous/adaptor/select2-3.js",
#) )
#TAGULOUS_AUTOCOMPLETE_CSS = {"all": ["tagulous/lib/select2-3/select2.css"]} TAGULOUS_AUTOCOMPLETE_CSS = {"all": ["tagulous/lib/select2-3/select2.css"]}
INTERNAL_IPS = ["localhost", "127.0.0.1"] INTERNAL_IPS = ["localhost", "127.0.0.1"]
+1 -1
View File
@@ -27,7 +27,7 @@ urlpatterns = [
path("anatomy/", include("anatomy.urls"), name="anatomy"), path("anatomy/", include("anatomy.urls"), name="anatomy"),
path("physics/", include("physics.urls"), name="physics"), 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")),
path("accounts/profile", views.profile, name="profile"), path("accounts/profile", views.profile, name="profile"),
path("", TemplateView.as_view(template_name="index.html"), name="home"), path("", TemplateView.as_view(template_name="index.html"), name="home"),
View File
+73
View File
@@ -0,0 +1,73 @@
from django.contrib import admin
from .models import Rapid, RapidImage, Examination, Site, Abnormality, Region, Note, RapidCreationDefault, Answer, Exam
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(Exam)
admin.site.register(Abnormality)
admin.site.register(Region)
admin.site.register(Note)
admin.site.register(RapidCreationDefault)
admin.site.register(Answer)
class RapidAnswersInline(admin.TabularInline):
model = Answer
extra = 1
class RapidImageInline(admin.TabularInline):
model = RapidImage
extra = 1
readonly_fields = [
"image_tag",
]
class ExamInline(admin.TabularInline):
model = Rapid.exams.through
extra = 1
class RapidAdminForm(ModelForm):
class Meta:
model = Rapid
fields = [
"normal", "abnormality", "region", "laterality", "examination",
"site", "sign", "condition", "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,
RapidAnswersInline,
ExamInline,
]
admin.site.register(Rapid, RapidAdmin)
#tagulous.admin.register(Rapid.condition)
Executable
+5
View File
@@ -0,0 +1,5 @@
from django.apps import AppConfig
class RapidsConfig(AppConfig):
name = 'rapids'
+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")
+132
View File
@@ -0,0 +1,132 @@
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, Answer
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.forms.widgets import RadioSelect, TextInput, Textarea
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",
"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")
AnswerFormSet = inlineformset_factory(
Rapid,
Answer,
fields=["answer", "status"],
widgets={
"answer": Textarea(
attrs={"required": "true", "minlength": 2, "maxlength": 500, "rows": 3}
)
},
exclude=[],
can_delete=True,
extra=1,
max_num=10,
)
AnswerUpdateFormSet = inlineformset_factory(
Rapid,
Answer,
fields=["answer", "status"],
widgets={
"answer": Textarea(
attrs={"required": "true", "minlength": 2, "maxlength": 500, "rows": 3}
)
},
exclude=[],
can_delete=True,
extra=0,
max_num=10,
)
+122
View File
@@ -0,0 +1,122 @@
# Generated by Django 3.1.2 on 2021-01-18 13:39
from django.conf import settings
import django.core.files.storage
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import rapids.models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Abnormality',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Primary abnormality on the film(s)', max_length=200, unique=True)),
],
options={
'ordering': ('name',),
},
),
migrations.CreateModel(
name='Examination',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('examination', models.CharField(max_length=200)),
],
options={
'ordering': ('examination',),
},
),
migrations.CreateModel(
name='Rapid',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('question', models.TextField(blank=True, null=True)),
('feedback', models.TextField(blank=True, null=True)),
('normal', models.BooleanField(default=False, help_text='Tick if true')),
('laterality', models.CharField(choices=[('NONE', 'None'), ('LEFT', 'Left'), ('RIGHT', 'Right'), ('BILAT', 'Bilateral')], default='NONE', help_text='Applies to the answer, not the examination', max_length=20)),
('verified', models.BooleanField(default=False)),
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
('published_date', models.DateTimeField(blank=True, null=True)),
('scrapped', models.BooleanField(default=False, help_text='Question has been scrapped and will not be shown')),
('abnormality', models.ManyToManyField(blank=True, help_text='The abnormality (laterality and region independent)', to='rapids.Abnormality')),
('author', models.ManyToManyField(blank=True, help_text='Author of question', related_name='rapid_authored_questions', to=settings.AUTH_USER_MODEL)),
('examination', models.ManyToManyField(help_text='Name of the (primary) examination', to='rapids.Examination')),
],
),
migrations.CreateModel(
name='Region',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, help_text='Region of the abnormality (not including lateralitiy)', max_length=200, unique=True)),
],
options={
'ordering': ('name',),
},
),
migrations.CreateModel(
name='Site',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('site', models.CharField(max_length=200)),
('initials', models.CharField(max_length=200)),
],
),
migrations.CreateModel(
name='RapidImage',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='/media/rapids/', location='/home/ross/sites/rad/media//rapids/'), upload_to=rapids.models.image_directory_path)),
('feedback_image', models.BooleanField(default=False)),
('rapid', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='rapids.rapid')),
],
),
migrations.CreateModel(
name='RapidCreationDefault',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('author', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='rapid_default', to=settings.AUTH_USER_MODEL)),
('site', models.ManyToManyField(blank=True, default=1, help_text='Default site to use when creating a new rapid', to='rapids.Site')),
],
),
migrations.AddField(
model_name='rapid',
name='region',
field=models.ManyToManyField(blank=True, help_text='Region of the abnormality (laterality independent)', to='rapids.Region'),
),
migrations.AddField(
model_name='rapid',
name='site',
field=models.ManyToManyField(blank=True, default=1, help_text='If we know the source of the image (should possibly be removed?)', to='rapids.Site'),
),
migrations.CreateModel(
name='Note',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('note', models.TextField()),
('created_on', models.DateTimeField(auto_now_add=True)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rapid_notes', to=settings.AUTH_USER_MODEL)),
('rapid', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='rapid_notes', to='rapids.rapid')),
],
),
migrations.CreateModel(
name='Answer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('answer', models.TextField(max_length=500)),
('answer_compare', models.TextField(max_length=500)),
('status', models.CharField(choices=[('', 'Unmarked'), ('0', 'Incorrect'), ('1', 'Half mark'), ('2', 'Correct')], default='', max_length=1)),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='rapids.rapid')),
],
),
]
@@ -0,0 +1,52 @@
# Generated by Django 3.1.2 on 2021-01-18 14:02
import django.core.files.storage
from django.db import migrations, models
import django.db.models.deletion
import rapids.models
import sortedm2m.fields
class Migration(migrations.Migration):
dependencies = [
('rapids', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='rapid',
name='abnormality',
field=models.ManyToManyField(blank=True, help_text='The abnormality (laterality and region independent). Used for categorisation but does not affect the answer', to='rapids.Abnormality'),
),
migrations.AlterField(
model_name='rapidimage',
name='image',
field=models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='http://penracourses.org.uk//media/rapids/', location='/home/ross/sites/rad/media//rapids/'), upload_to=rapids.models.image_directory_path),
),
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')),
('recreate_json', models.BooleanField(default=False, help_text='If the json cache needs updating')),
('time_limit', models.IntegerField(default=5400, help_text='Exam time limit (in seconds)')),
('exam_questions', sortedm2m.fields.SortedManyToManyField(blank='true', help_text=None, related_name='exams', to='rapids.Rapid')),
],
),
migrations.CreateModel(
name='CidUserAnswer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('answer', models.TextField(blank=True, max_length=500)),
('answer_compare', models.TextField(blank=True, max_length=500)),
('cid', models.BigIntegerField(blank=True, help_text='Candidate ID (limitied by BigIntegerField size)', 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='rapids.exam')),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cid_user_answers', to='rapids.rapid')),
],
),
]
@@ -0,0 +1,69 @@
# Generated by Django 3.1.3 on 2021-01-20 22:06
import django.core.files.storage
from django.db import migrations, models
import rapids.models
import tagulous.models.fields
import tagulous.models.models
class Migration(migrations.Migration):
dependencies = [
('rapids', '0002_auto_20210118_1402'),
]
operations = [
migrations.AlterField(
model_name='rapid',
name='site',
field=models.ManyToManyField(blank=True, default=1, help_text='If we know the source of the image', to='rapids.Site'),
),
migrations.AlterField(
model_name='rapidimage',
name='image',
field=models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='http://penracourses.org.uk//media/rapids/', location='/home/ross/scripts/sites/rad/media//rapids/'), upload_to=rapids.models.image_directory_path),
),
migrations.CreateModel(
name='Sign',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('slug', models.SlugField()),
('count', models.IntegerField(default=0, help_text='Internal counter of how many times this tag is in use')),
('protected', models.BooleanField(default=False, help_text='Will not be deleted when the count reaches 0')),
],
options={
'ordering': ('name',),
'abstract': False,
'unique_together': {('slug',)},
},
bases=(tagulous.models.models.BaseTagModel, models.Model),
),
migrations.CreateModel(
name='Condition',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('slug', models.SlugField()),
('count', models.IntegerField(default=0, help_text='Internal counter of how many times this tag is in use')),
('protected', models.BooleanField(default=False, help_text='Will not be deleted when the count reaches 0')),
],
options={
'ordering': ('name',),
'abstract': False,
'unique_together': {('slug',)},
},
bases=(tagulous.models.models.BaseTagModel, models.Model),
),
migrations.AddField(
model_name='rapid',
name='condition',
field=tagulous.models.fields.TagField(_set_tag_meta=True, blank=True, help_text='Associated condition. Will allow searching / filtering and tips / hints to be displayed. Conditions with spaces must be enclosed in quotes "..."', to='rapids.Condition'),
),
migrations.AddField(
model_name='rapid',
name='sign',
field=tagulous.models.fields.TagField(_set_tag_meta=True, blank=True, help_text='Radiological signs in the question', to='rapids.Sign'),
),
]
@@ -0,0 +1,31 @@
# Generated by Django 3.1.3 on 2021-01-20 22:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rapids', '0003_auto_20210120_2206'),
]
operations = [
migrations.RemoveField(
model_name='rapid',
name='condition',
),
migrations.AddField(
model_name='rapid',
name='condition',
field=models.CharField(blank=True, max_length=255),
),
migrations.RemoveField(
model_name='rapid',
name='sign',
),
migrations.AddField(
model_name='rapid',
name='sign',
field=models.CharField(blank=True, max_length=255),
),
]
@@ -0,0 +1,30 @@
# Generated by Django 3.1.3 on 2021-01-21 18:06
import django.core.files.storage
from django.db import migrations, models
import django.db.models.deletion
import rapids.models
class Migration(migrations.Migration):
dependencies = [
('rapids', '0004_auto_20210120_2214'),
]
operations = [
migrations.RemoveField(
model_name='rapid',
name='examination',
),
migrations.AddField(
model_name='rapid',
name='examination',
field=models.ForeignKey(help_text='Name of the (primary) examination', null=True, on_delete=django.db.models.deletion.SET_NULL, to='rapids.examination'),
),
migrations.AlterField(
model_name='rapidimage',
name='image',
field=models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='/media/rapids/', location='/home/ross/scripts/sites/rad/media//rapids/'), upload_to=rapids.models.image_directory_path),
),
]
@@ -0,0 +1,22 @@
# Generated by Django 3.1.3 on 2021-01-21 18:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rapids', '0005_auto_20210121_1806'),
]
operations = [
migrations.RemoveField(
model_name='rapid',
name='examination',
),
migrations.AddField(
model_name='rapid',
name='examination',
field=models.ManyToManyField(help_text='Name of the (primary) examination', to='rapids.Examination'),
),
]
View File
+436
View File
@@ -0,0 +1,436 @@
from django.db import models
from django.utils import timezone
import tagulous
import tagulous.models
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 django.utils.html import mark_safe
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 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 Sign(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). Used for categorisation but does not affect the answer",
)
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 = models.CharField(max_length=255, blank=True)
#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 = models.CharField(max_length=255, blank=True)
#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")
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 get_examinations(self):
"""Returns a comma seperated text list of regions"""
examinations = ", ".join([i.examination for i in self.examination.all()])
return examinations
def __str__(self):
n = "Normal"
if not self.normal:
#n = self.answers.first()
n = "{} / {}".format(self.abnormality.first(), self.region.first())
exams = self.get_examinations()
lat = ""
if self.laterality != "NONE":
lat = self.laterality
return "{} / {} {}".format(exams, lat, n)
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
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(self.image.url))
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,
author = models.ForeignKey( settings.AUTH_USER_MODEL,
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,
author = models.OneToOneField(
settings.AUTH_USER_MODEL,
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 Exam(models.Model):
name = models.CharField(max_length=200)
exam_questions = SortedManyToManyField(
Rapid, 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
)
recreate_json = models.BooleanField(
help_text="If the json cache needs updating", default=False
)
time_limit = models.IntegerField(
help_text="Exam time limit (in seconds)", default=5400
)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("rapids:exam_overview", kwargs={"pk": self.pk})
def get_exam_name(self):
return self.name
def get_json_url(self):
return reverse("rapids: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(
Rapid, related_name="cid_user_answers", on_delete=models.CASCADE
)
answer = models.TextField(max_length=500, blank=True)
answer_compare = models.TextField(max_length=500, blank=True)
cid = models.BigIntegerField(blank=True, null=True, help_text="Candidate ID (limitied by BigIntegerField size)")
# 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.GetPrimaryAnswer(), self.answer
)
def save(self, *args, **kwargs):
self.clean()
return super(CidUserAnswer, 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
# def get_compare_string(self):
# # strip here should be unneccasry (providing clean is now working)
# s = self.answer.lower().strip()
# s = s.translate(str.maketrans('', '', string.punctuation))
# return s
def get_answer_score(self):
q = self.question
ans = self.answer_compare
if (
q.answers.filter(
answer_compare__iexact=ans, status=Answer.MarkOptions.CORRECT
).first()
is not None
):
mark = 2
elif (
q.answers.filter(
answer_compare__iexact=ans, status=Answer.MarkOptions.HALF_MARK
).first()
is not None
):
mark = 1
elif q.answers.filter(
answer_compare__iexact=ans, status=Answer.MarkOptions.INCORRECT
).first():
mark = 0
else:
mark = "unmarked"
return mark
+43
View File
@@ -0,0 +1,43 @@
import django_tables2 as tables
from django_tables2.utils import A
from .models import Rapid
from django.utils.html import format_html
from easy_thumbnails.files import get_thumbnailer
from easy_thumbnails.exceptions import InvalidImageFormatError
class ImageColumn(tables.Column):
def render(self, value):
image_object = value.first().image
try:
thumbnailer = get_thumbnailer(image_object)
return format_html('<img src="/media/{}" />', thumbnailer["exam-list"])
except InvalidImageFormatError:
return format_html('<span title="{}">Invalid image url<span>', image_object)
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)
images = ImageColumn("images", orderable=False)
class Meta:
model = Rapid
template_name = "django_tables2/bootstrap4.html"
fields = ("normal", "abnormality", "region", "examination",
"laterality", "site", "created_date", "author")
sequence = ("view", "images")
+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 %}
+74
View File
@@ -0,0 +1,74 @@
{% load static %}
<html>
<head>
<title>Rapids</title>
{% 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/anatomy.css' %}">
<link rel="stylesheet" href="{% static 'css/toastr.min.css' %}">
{% comment %} <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> {% endcomment %}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.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="physics-link">
<a href="{% url 'physics:index' %}">Physics</a>
</span>
<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_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>
</br>
Questions by:
<span id="authors-link"><a href="{% url 'rapids:author_list' %}">author</a></span>
{% 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 %}
+36
View File
@@ -0,0 +1,36 @@
<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>Examination:</b> {{ rapid.get_examinations }}</p>
<p class="pre-whitespace"><b>Laterality:</b> {{ rapid.laterality }}</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 }}"><br/> -->
<div class="dicom-image rapid-img {% if image.feedback_image %}feedback-img{% endif %}" data-url="http://penracourses.org.uk{{ image.image.url}}"></div>
{% 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 class="pre-whitespace"><b>Answers:</b> {{ rapid.GetMarkedAnswers }}</p>
</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>
+275
View File
@@ -0,0 +1,275 @@
{% extends "rapids/base.html" %}
{% load static from static %}
{% 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();
$('#image_form_set').append($('#empty_form').html().replace(/__prefix__/g, form_idx));
$('#id_images-TOTAL_FORMS').val(parseInt(form_idx) + 1);
}
function add_answers_input_form() {
var form_idx = $('#id_answers-TOTAL_FORMS').val();
$('#answer_form_set').append($('#answer_empty_form').html().replace(/__prefix__/g, form_idx));
$('#id_answers-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_images').click(() => { add_input_form() });
$('#add_more_answers').click(() => { add_answers_input_form() });
function updateFileList() {
$("#drop-filenames").empty()
$("#image_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>Answers:</h3>
<input type="button" value="Add More Answers" id="add_more_answers">
<div id="answer_form_set">
{% for form in answer_formset %}
<ul class="no-error answer-formset">
{{form.non_field_errors}}
{{form.errors}}
{{ form.as_ul }}
</ul>
{% endfor %}
</div>
{{ answer_formset.management_form }}
<h3>Images:</h3>
<input type="button" value="Add More Images" id="add_more_images">
<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="image_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>
<div id="answer_empty_form" style="display:none">
<ul class='no_error answer-formset'>
{{ answer_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 %}
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
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"),
]
+484
View File
@@ -0,0 +1,484 @@
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
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,
AnswerFormSet,
AnswerUpdateFormSet,
)
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)
image_formset = ImageFormSet()
answer_formset = AnswerFormSet()
if form.is_valid():
form.instance.author.add(request.user.id)
# logger.debug(formset.is_valid())
if image_formset.is_valid() and answer_formset.is_valid():
response = super().form_valid(form)
image_formset.instance = obj
image_formset.save()
answer_formset.instance = obj
answer_formset.save()
return response
else:
return super().form_invalid(form)
context = {
"form": form,
"image_formset": image_formset,
"answer_formset": answer_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
)
context["image_formset"].full_clean()
context["answer_formset"] = AnswerFormSet(
self.request.POST
)
context["answer_formset"].full_clean()
else:
context["image_formset"] = ImageFormSet()
context["answer_formset"] = AnswerFormSet()
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)
image_formset = context["image_formset"]
answer_formset = context["answer_formset"]
if image_formset.is_valid() and answer_formset.is_valid():
response = super().form_valid(form)
image_formset.instance = self.object
image_formset.save()
answer_formset.instance = self.object
answer_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()
context["answer_formset"] = AnswerFormSet(
self.request.POST, instance=self.object
)
context["answer_formset"].full_clean()
else:
context["image_formset"] = ImageFormSet(instance=self.object)
context["answer_formset"] = AnswerFormSet(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)
image_formset = context["image_formset"]
answer_formset = context["answer_formset"]
# logger.debug(formset.is_valid())
if image_formset.is_valid() and answer_formset.is_valid():
response = super().form_valid(form)
image_formset.instance = self.object
image_formset.save()
answer_formset.instance = self.object
answer_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
+1
View File
@@ -5,6 +5,7 @@
<h1>PENRA Courses</h1> <h1>PENRA Courses</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 'physics:index'%}">Physics</a>
<a href="{% url 'rapids:index'%}">Rapids</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>