more improvements
This commit is contained in:
+6
-1
@@ -6,6 +6,9 @@ import cv2
|
|||||||
import os
|
import os
|
||||||
import glob
|
import glob
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def ocr_file(filename):
|
def ocr_file(filename):
|
||||||
# load the example image and convert it to grayscale
|
# load the example image and convert it to grayscale
|
||||||
image = cv2.imread(filename)
|
image = cv2.imread(filename)
|
||||||
@@ -95,6 +98,8 @@ def search_image(file_name):
|
|||||||
|
|
||||||
edit_path = "{}/test/".format(path)
|
edit_path = "{}/test/".format(path)
|
||||||
|
|
||||||
|
Path(edit_path).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
print(edit_path)
|
print(edit_path)
|
||||||
fn1, fn2 = fn.rsplit(".", 1)
|
fn1, fn2 = fn.rsplit(".", 1)
|
||||||
cv2.imwrite("{}{}_new.{}".format(edit_path, fn1, fn2), img)
|
cv2.imwrite("{}{}_new.{}".format(edit_path, fn1, fn2), img)
|
||||||
@@ -126,7 +131,7 @@ image_list = []
|
|||||||
|
|
||||||
file_types = ("gif", "jpg", "jpeg", "png")
|
file_types = ("gif", "jpg", "jpeg", "png")
|
||||||
for file_type in file_types:
|
for file_type in file_types:
|
||||||
for filename in glob.glob('/Users/ross/media/test/*.{}'.format(file_type)): #assuming gif
|
for filename in glob.glob('/home/ross/scripts/sites/backups/New/media/rapids/*.{}'.format(file_type)): #assuming gif
|
||||||
image_list.append(filename)
|
image_list.append(filename)
|
||||||
|
|
||||||
for f in image_list:
|
for f in image_list:
|
||||||
|
|||||||
+8
-24
@@ -1,5 +1,5 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import Long, LongImage, Examination, Site, Abnormality, Region, Note, LongCreationDefault, Answer, Exam
|
from .models import Long, LongSeries, LongSeriesImage, Examination, Site, Note, LongCreationDefault, Exam
|
||||||
|
|
||||||
import tagulous.admin
|
import tagulous.admin
|
||||||
|
|
||||||
@@ -12,23 +12,18 @@ from django.forms.widgets import RadioSelect
|
|||||||
admin.site.register(Examination)
|
admin.site.register(Examination)
|
||||||
admin.site.register(Site)
|
admin.site.register(Site)
|
||||||
admin.site.register(Exam)
|
admin.site.register(Exam)
|
||||||
admin.site.register(Abnormality)
|
|
||||||
admin.site.register(Region)
|
|
||||||
admin.site.register(Note)
|
admin.site.register(Note)
|
||||||
admin.site.register(LongCreationDefault)
|
admin.site.register(LongCreationDefault)
|
||||||
admin.site.register(Answer)
|
admin.site.register(LongSeries)
|
||||||
|
admin.site.register(LongSeriesImage)
|
||||||
class LongAnswersInline(admin.TabularInline):
|
|
||||||
model = Answer
|
|
||||||
extra = 1
|
|
||||||
|
|
||||||
|
|
||||||
class LongImageInline(admin.TabularInline):
|
class LongImageInline(admin.TabularInline):
|
||||||
model = LongImage
|
model = LongSeries
|
||||||
extra = 1
|
extra = 1
|
||||||
readonly_fields = [
|
# readonly_fields = [
|
||||||
"image_tag",
|
# "image_tag",
|
||||||
]
|
# ]
|
||||||
|
|
||||||
class ExamInline(admin.TabularInline):
|
class ExamInline(admin.TabularInline):
|
||||||
model = Long.exams.through
|
model = Long.exams.through
|
||||||
@@ -40,30 +35,19 @@ class LongAdminForm(ModelForm):
|
|||||||
model = Long
|
model = Long
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
"normal", "abnormality", "region", "laterality", "examination",
|
"site", "sign", "feedback", "author"
|
||||||
"site", "sign", "condition", "feedback", "author"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
widgets = {
|
|
||||||
"laterality": RadioSelect(attrs={'style': 'display: inline-flex'}),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class LongAdmin(VersionAdmin):
|
class LongAdmin(VersionAdmin):
|
||||||
form = LongAdminForm
|
form = LongAdminForm
|
||||||
|
|
||||||
filter_horizontal = (
|
|
||||||
"abnormality",
|
|
||||||
"region",
|
|
||||||
#"examination",
|
|
||||||
)
|
|
||||||
save_on_top = True
|
save_on_top = True
|
||||||
save_as = True
|
save_as = True
|
||||||
view_on_site = True
|
view_on_site = True
|
||||||
|
|
||||||
inlines = [
|
inlines = [
|
||||||
LongImageInline,
|
LongImageInline,
|
||||||
LongAnswersInline,
|
|
||||||
ExamInline,
|
ExamInline,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -6,5 +6,4 @@ from .models import Long
|
|||||||
class LongFilter(django_filters.FilterSet):
|
class LongFilter(django_filters.FilterSet):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Long
|
model = Long
|
||||||
fields = ("normal", "abnormality", "region", "examination",
|
fields = ("site", "created_date", "author")
|
||||||
"laterality", "site", "created_date", "author")
|
|
||||||
|
|||||||
+10
-74
@@ -9,14 +9,12 @@ from django.forms import (
|
|||||||
from django.forms import inlineformset_factory
|
from django.forms import inlineformset_factory
|
||||||
|
|
||||||
from longs.models import (
|
from longs.models import (
|
||||||
Abnormality,
|
|
||||||
Examination,
|
Examination,
|
||||||
Note,
|
Note,
|
||||||
Long,
|
Long,
|
||||||
|
LongSeries,
|
||||||
|
LongSeriesImage,
|
||||||
LongCreationDefault,
|
LongCreationDefault,
|
||||||
LongImage,
|
|
||||||
Region,
|
|
||||||
Answer,
|
|
||||||
CidUserAnswer,
|
CidUserAnswer,
|
||||||
Exam,
|
Exam,
|
||||||
)
|
)
|
||||||
@@ -28,7 +26,7 @@ from django.forms.widgets import RadioSelect, TextInput, Textarea
|
|||||||
class LongAnswerForm(ModelForm):
|
class LongAnswerForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CidUserAnswer
|
model = CidUserAnswer
|
||||||
fields = ("answer",)
|
fields = ("answer_observations","answer_interpretation","answer_principle_diagnosis","answer_differential_diagnosis","answer_further_management",)
|
||||||
|
|
||||||
|
|
||||||
class MarkLongQuestionForm(Form):
|
class MarkLongQuestionForm(Form):
|
||||||
@@ -50,17 +48,6 @@ class NoteForm(ModelForm):
|
|||||||
fields = ["note"]
|
fields = ["note"]
|
||||||
|
|
||||||
|
|
||||||
class RegionForm(ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = Region
|
|
||||||
fields = ["name"]
|
|
||||||
|
|
||||||
|
|
||||||
class AbnormalityForm(ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = Abnormality
|
|
||||||
fields = ["name"]
|
|
||||||
|
|
||||||
|
|
||||||
class LongCreationDefaultForm(ModelForm):
|
class LongCreationDefaultForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -85,37 +72,17 @@ class LongForm(ModelForm):
|
|||||||
super(LongForm, self).__init__(*args, **kwargs)
|
super(LongForm, self).__init__(*args, **kwargs)
|
||||||
# self.fields['question'].widget.attrs = {'class': 'question-form', 'rows': 10, 'cols': 100}
|
# 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['feedback'].widget.attrs = {'class': 'feedback-form', 'rows': 10, 'cols': 100}
|
||||||
self.fields["abnormality"] = ModelMultipleChoiceField(
|
#self.fields["examination"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
# queryset=Examination.objects.all(),
|
||||||
queryset=Abnormality.objects.all(),
|
# widget=FilteredSelectMultiple(verbose_name="Examination", is_stacked=False),
|
||||||
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=Long.LATERALITY_CHOICES, required=True, widget=RadioSelect()
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Long
|
model = Long
|
||||||
# fields = ['due_back']
|
# fields = ['due_back']
|
||||||
# fields = '__all__'
|
# fields = '__all__'
|
||||||
fields = [
|
fields = [
|
||||||
"normal",
|
#"examination",
|
||||||
"abnormality",
|
|
||||||
"region",
|
|
||||||
"laterality",
|
|
||||||
"examination",
|
|
||||||
"site",
|
"site",
|
||||||
"feedback",
|
"feedback",
|
||||||
]
|
]
|
||||||
@@ -127,43 +94,12 @@ class LongForm(ModelForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ImageFormSet = inlineformset_factory(
|
SeriesFormSet = inlineformset_factory(
|
||||||
Long,
|
Long,
|
||||||
LongImage,
|
LongSeries,
|
||||||
fields=["image", "feedback_image"],
|
|
||||||
exclude=[],
|
exclude=[],
|
||||||
can_delete=True,
|
can_delete=True,
|
||||||
extra=4,
|
extra=4,
|
||||||
max_num=10,
|
max_num=10,
|
||||||
field_classes="testing",
|
field_classes="testing",
|
||||||
)
|
)
|
||||||
|
|
||||||
AnswerFormSet = inlineformset_factory(
|
|
||||||
Long,
|
|
||||||
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(
|
|
||||||
Long,
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
# Generated by Django 3.1.3 on 2021-02-02 19:46
|
||||||
|
|
||||||
|
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 longs.models
|
||||||
|
import sortedm2m.fields
|
||||||
|
import tagulous.models.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('anatomy', '0024_auto_20210130_1515'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
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='Long',
|
||||||
|
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)),
|
||||||
|
('sign', models.CharField(blank=True, max_length=255)),
|
||||||
|
('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')),
|
||||||
|
('open_access', models.BooleanField(default=True, help_text='If an question should be freely available to browse')),
|
||||||
|
('author', models.ManyToManyField(blank=True, help_text='Author of question', related_name='long_authored_questions', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='LongSeries',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('examination', models.ForeignKey(help_text='Name of the examination', null=True, on_delete=django.db.models.deletion.SET_NULL, to='longs.examination')),
|
||||||
|
('long', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='long', to='longs.long')),
|
||||||
|
('modality', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='series_modality', to='anatomy.modality')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
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='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='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='long_notes', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('long', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='long_notes', to='longs.long')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='LongSeriesImage',
|
||||||
|
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/longs/', location='/home/ross/scripts/sites/rad/media//longs/'), upload_to=longs.models.image_directory_path)),
|
||||||
|
('position', models.IntegerField(default=0)),
|
||||||
|
('series', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='series', to='longs.longseries')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='LongCreationDefault',
|
||||||
|
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='long_default', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('site', models.ManyToManyField(blank=True, default=1, help_text='Default site to use when creating a new long', to='longs.Site')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='long',
|
||||||
|
name='site',
|
||||||
|
field=models.ManyToManyField(blank=True, default=1, help_text='If we know the source of the image', to='longs.Site'),
|
||||||
|
),
|
||||||
|
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=2100, help_text='Exam time limit (in seconds). Default is 2100 secondse (35 minutes)')),
|
||||||
|
('exam_questions', sortedm2m.fields.SortedManyToManyField(blank='true', help_text=None, related_name='exams', to='longs.Long')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
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.CreateModel(
|
||||||
|
name='CidUserAnswer',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('normal', models.BooleanField(default=False)),
|
||||||
|
('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='longs.exam')),
|
||||||
|
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cid_user_answers', to='longs.long')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
+43
-177
@@ -1,3 +1,6 @@
|
|||||||
|
from django.db.models.fields.files import ImageField
|
||||||
|
from django.db.models.fields.related import ForeignKey
|
||||||
|
from anatomy.models import Modality
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
import tagulous
|
import tagulous
|
||||||
@@ -29,32 +32,6 @@ def image_directory_path(instance, 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):
|
class Examination(models.Model):
|
||||||
examination = models.CharField(max_length=200)
|
examination = models.CharField(max_length=200)
|
||||||
|
|
||||||
@@ -79,39 +56,6 @@ class Condition(tagulous.models.TagModel):
|
|||||||
class Sign(tagulous.models.TagModel):
|
class Sign(tagulous.models.TagModel):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class Answer(models.Model):
|
|
||||||
question = models.ForeignKey(
|
|
||||||
"Long", 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 Long(models.Model):
|
class Long(models.Model):
|
||||||
#author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
|
#author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
|
||||||
@@ -119,37 +63,6 @@ class Long(models.Model):
|
|||||||
question = models.TextField(null=True, blank=True)
|
question = models.TextField(null=True, blank=True)
|
||||||
feedback = 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(
|
#condition = tagulous.models.TagField(
|
||||||
# to=Condition,
|
# to=Condition,
|
||||||
# blank=True,
|
# blank=True,
|
||||||
@@ -161,6 +74,14 @@ class Long(models.Model):
|
|||||||
#sign = tagulous.models.TagField(
|
#sign = tagulous.models.TagField(
|
||||||
# to=Sign, blank=True, help_text='Radiological signs in the question')
|
# to=Sign, blank=True, help_text='Radiological signs in the question')
|
||||||
|
|
||||||
|
# Model answers
|
||||||
|
model_observations = models.TextField(null=True, blank=True)
|
||||||
|
model_interpretation = models.TextField(null=True, blank=True)
|
||||||
|
model_principle_diagnosis = models.TextField(null=True, blank=True)
|
||||||
|
model_differential_diagnosis = models.TextField(null=True, blank=True)
|
||||||
|
model_further_management = models.TextField(null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_SITE_ID = 1
|
DEFAULT_SITE_ID = 1
|
||||||
site = models.ManyToManyField(
|
site = models.ManyToManyField(
|
||||||
Site,
|
Site,
|
||||||
@@ -193,91 +114,23 @@ class Long(models.Model):
|
|||||||
authors = ", ".join([i.username for i in self.author.all()])
|
authors = ", ".join([i.username for i in self.author.all()])
|
||||||
return authors
|
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):
|
def get_examinations(self):
|
||||||
"""Returns a comma seperated text list of regions"""
|
"""Returns a comma seperated text list of regions"""
|
||||||
examinations = ", ".join([i.examination for i in self.examination.all()])
|
examinations = ", ".join([i.examination for i in self.examination.all()])
|
||||||
return examinations
|
return examinations
|
||||||
|
|
||||||
def __str__(self):
|
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()
|
exams = self.get_examinations()
|
||||||
lat = ""
|
lat = ""
|
||||||
if self.laterality != "NONE":
|
if self.laterality != "NONE":
|
||||||
lat = self.laterality
|
lat = self.laterality
|
||||||
return "{} / {} {}".format(exams, lat, n)
|
return "{} / {} {}".format(exams, lat, n)
|
||||||
|
|
||||||
def GetPrimaryAnswer(self):
|
|
||||||
if len(self.answers.all()) > 0:
|
|
||||||
return self.answers.all().first().answer
|
|
||||||
elif self.normal:
|
|
||||||
return "Normal"
|
|
||||||
else:
|
|
||||||
return "None yet..."
|
|
||||||
|
|
||||||
def GetExams(self):
|
def GetExams(self):
|
||||||
e = self.exams.all().values_list("name", flat=True)
|
e = self.exams.all().values_list("name", flat=True)
|
||||||
exams = ", ".join(e)
|
exams = ", ".join(e)
|
||||||
return exams
|
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):
|
|
||||||
# If normal no answers to mark
|
|
||||||
if self.normal:
|
|
||||||
return []
|
|
||||||
|
|
||||||
user_answers = set(
|
|
||||||
[i.answer_compare for i in self.cid_user_answers.all() if i.normal == False]
|
|
||||||
)
|
|
||||||
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
|
|
||||||
|
|
||||||
def GetImages(self, feedback=False):
|
def GetImages(self, feedback=False):
|
||||||
qs = self.images.all()
|
qs = self.images.all()
|
||||||
@@ -294,24 +147,34 @@ class Long(models.Model):
|
|||||||
#def GetNonFeedbackQuestionImages(self):
|
#def GetNonFeedbackQuestionImages(self):
|
||||||
#return self.GetImages()
|
#return self.GetImages()
|
||||||
|
|
||||||
|
class LongSeriesImage(models.Model):
|
||||||
class LongImage(models.Model):
|
|
||||||
long = models.ForeignKey(Long,
|
|
||||||
related_name="images",
|
|
||||||
on_delete=models.CASCADE)
|
|
||||||
image = models.ImageField(upload_to=image_directory_path, storage=image_storage)
|
image = models.ImageField(upload_to=image_directory_path, storage=image_storage)
|
||||||
|
position = models.IntegerField(default=0)
|
||||||
|
series = models.ForeignKey("LongSeries", related_name="series", on_delete=models.SET_NULL, null=True)
|
||||||
|
|
||||||
feedback_image = models.BooleanField(default=False)
|
class LongSeries(models.Model):
|
||||||
|
modality = models.ForeignKey(Modality, related_name="series_modality", on_delete=models.SET_NULL, null=True)
|
||||||
|
examination = models.ForeignKey(
|
||||||
|
Examination, help_text="Name of the examination", on_delete=models.SET_NULL, null=True)
|
||||||
|
long = models.ForeignKey("Long", related_name="long", on_delete=models.SET_NULL, null=True)
|
||||||
|
|
||||||
def image_tag(self):
|
#class LongImage(models.Model):
|
||||||
if self.image:
|
# long = models.ForeignKey(Long,
|
||||||
return mark_safe(
|
# related_name="images",
|
||||||
'<img src="{}" class="admin-long-image" /><span class="admin-long-image-info">Click and hold to zoom<span>'
|
# on_delete=models.CASCADE)
|
||||||
.format(self.image.url))
|
# image = models.ImageField(upload_to=image_directory_path, storage=image_storage)
|
||||||
else:
|
#
|
||||||
return ""
|
# feedback_image = models.BooleanField(default=False)
|
||||||
|
#
|
||||||
image_tag.short_description = 'Image'
|
# def image_tag(self):
|
||||||
|
# if self.image:
|
||||||
|
# return mark_safe(
|
||||||
|
# '<img src="{}" class="admin-long-image" /><span class="admin-long-image-info">Click and hold to zoom<span>'
|
||||||
|
# .format(self.image.url))
|
||||||
|
# else:
|
||||||
|
# return ""
|
||||||
|
#
|
||||||
|
# image_tag.short_description = 'Image'
|
||||||
|
|
||||||
|
|
||||||
class Note(models.Model):
|
class Note(models.Model):
|
||||||
@@ -395,11 +258,14 @@ class CidUserAnswer(models.Model):
|
|||||||
Long, related_name="cid_user_answers", on_delete=models.CASCADE
|
Long, related_name="cid_user_answers", on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
|
||||||
# For longs the answer can be normal in which case the below field is true
|
|
||||||
normal = models.BooleanField(default=False)
|
|
||||||
answer = models.TextField(max_length=500, blank=True)
|
|
||||||
|
|
||||||
answer_compare = models.TextField(max_length=500, blank=True)
|
# Answers
|
||||||
|
answer_observations = models.TextField(null=True, blank=True)
|
||||||
|
answer_interpretation = models.TextField(null=True, blank=True)
|
||||||
|
answer_principle_diagnosis = models.TextField(null=True, blank=True)
|
||||||
|
answer_differential_diagnosis = models.TextField(null=True, blank=True)
|
||||||
|
answer_further_management = models.TextField(null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
cid = models.BigIntegerField(blank=True, null=True, help_text="Candidate ID (limitied by BigIntegerField size)")
|
cid = models.BigIntegerField(blank=True, null=True, help_text="Candidate ID (limitied by BigIntegerField size)")
|
||||||
|
|
||||||
|
|||||||
+20
-46
@@ -21,22 +21,15 @@ from django.http import HttpResponseRedirect, HttpResponse
|
|||||||
from .forms import (
|
from .forms import (
|
||||||
LongForm,
|
LongForm,
|
||||||
MarkLongQuestionForm,
|
MarkLongQuestionForm,
|
||||||
ImageFormSet,
|
SeriesFormSet,
|
||||||
NoteForm,
|
NoteForm,
|
||||||
RegionForm,
|
|
||||||
AbnormalityForm,
|
|
||||||
ExaminationForm,
|
ExaminationForm,
|
||||||
AnswerFormSet,
|
|
||||||
AnswerUpdateFormSet,
|
|
||||||
)
|
)
|
||||||
from .models import (
|
from .models import (
|
||||||
Long,
|
Long,
|
||||||
Note,
|
Note,
|
||||||
Abnormality,
|
|
||||||
Region,
|
|
||||||
Examination,
|
Examination,
|
||||||
Exam,
|
Exam,
|
||||||
Answer,
|
|
||||||
CidUserAnswer,
|
CidUserAnswer,
|
||||||
)
|
)
|
||||||
from .tables import LongTable
|
from .tables import LongTable
|
||||||
@@ -236,28 +229,24 @@ def long_clone(request, pk):
|
|||||||
|
|
||||||
form = LongForm(request.POST or None, instance=new_item)
|
form = LongForm(request.POST or None, instance=new_item)
|
||||||
|
|
||||||
image_formset = ImageFormSet()
|
series_formset = SeriesFormSet()
|
||||||
answer_formset = AnswerFormSet()
|
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.instance.author.add(request.user.id)
|
form.instance.author.add(request.user.id)
|
||||||
|
|
||||||
# logger.debug(formset.is_valid())
|
# logger.debug(formset.is_valid())
|
||||||
if image_formset.is_valid() and answer_formset.is_valid():
|
if series_formset.is_valid():
|
||||||
response = super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
image_formset.instance = obj
|
series_formset.instance = obj
|
||||||
image_formset.save()
|
series_formset.save()
|
||||||
|
|
||||||
answer_formset.instance = obj
|
|
||||||
answer_formset.save()
|
|
||||||
return response
|
return response
|
||||||
else:
|
else:
|
||||||
return super().form_invalid(form)
|
return super().form_invalid(form)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"form": form,
|
"form": form,
|
||||||
"image_formset": image_formset,
|
"series_formset": series_formset,
|
||||||
"answer_formset": answer_formset
|
|
||||||
# other context
|
# other context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,15 +260,12 @@ class LongCreateBase(LoginRequiredMixin, CreateView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(LongCreateBase, self).get_context_data(**kwargs)
|
context = super(LongCreateBase, self).get_context_data(**kwargs)
|
||||||
if self.request.POST:
|
if self.request.POST:
|
||||||
context["image_formset"] = ImageFormSet(
|
context["series_formset"] = SeriesFormSet(
|
||||||
self.request.POST, self.request.FILES
|
self.request.POST, self.request.FILES
|
||||||
)
|
)
|
||||||
context["image_formset"].full_clean()
|
context["series_formset"].full_clean()
|
||||||
context["answer_formset"] = AnswerFormSet(self.request.POST)
|
|
||||||
context["answer_formset"].full_clean()
|
|
||||||
else:
|
else:
|
||||||
context["image_formset"] = ImageFormSet()
|
context["series_formset"] = SeriesFormSet()
|
||||||
context["answer_formset"] = AnswerFormSet()
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
@@ -290,14 +276,11 @@ class LongCreateBase(LoginRequiredMixin, CreateView):
|
|||||||
form.instance.author.add(self.request.user.id)
|
form.instance.author.add(self.request.user.id)
|
||||||
|
|
||||||
context = self.get_context_data(form=form)
|
context = self.get_context_data(form=form)
|
||||||
image_formset = context["image_formset"]
|
series_formset = context["series_formset"]
|
||||||
answer_formset = context["answer_formset"]
|
if series_formset.is_valid():
|
||||||
if image_formset.is_valid() and answer_formset.is_valid():
|
|
||||||
response = super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
image_formset.instance = self.object
|
series_formset.instance = self.object
|
||||||
image_formset.save()
|
series_formset.save()
|
||||||
answer_formset.instance = self.object
|
|
||||||
answer_formset.save()
|
|
||||||
# If the normal submit button is pressed we save as normal
|
# If the normal submit button is pressed we save as normal
|
||||||
if "submit" in self.request.POST:
|
if "submit" in self.request.POST:
|
||||||
return response
|
return response
|
||||||
@@ -312,7 +295,6 @@ class LongCreateBase(LoginRequiredMixin, CreateView):
|
|||||||
# @login_required
|
# @login_required
|
||||||
class LongCreate(LongCreateBase):
|
class LongCreate(LongCreateBase):
|
||||||
|
|
||||||
initial = {"laterality": Long.NONE}
|
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
# There has to be a better way...
|
# There has to be a better way...
|
||||||
@@ -346,17 +328,12 @@ class LongUpdate(LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(LongUpdate, self).get_context_data(**kwargs)
|
context = super(LongUpdate, self).get_context_data(**kwargs)
|
||||||
if self.request.POST:
|
if self.request.POST:
|
||||||
context["image_formset"] = ImageFormSet(
|
context["series_formset"] = SeriesFormSet(
|
||||||
self.request.POST, self.request.FILES, instance=self.object
|
self.request.POST, self.request.FILES, instance=self.object
|
||||||
)
|
)
|
||||||
context["image_formset"].full_clean()
|
context["series_formset"].full_clean()
|
||||||
context["answer_formset"] = AnswerFormSet(
|
|
||||||
self.request.POST, instance=self.object
|
|
||||||
)
|
|
||||||
context["answer_formset"].full_clean()
|
|
||||||
else:
|
else:
|
||||||
context["image_formset"] = ImageFormSet(instance=self.object)
|
context["series_formset"] = SeriesFormSet(instance=self.object)
|
||||||
context["answer_formset"] = AnswerFormSet(instance=self.object)
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
@@ -367,15 +344,12 @@ class LongUpdate(LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView):
|
|||||||
form.instance.author.add(self.request.user.id)
|
form.instance.author.add(self.request.user.id)
|
||||||
|
|
||||||
context = self.get_context_data(form=form)
|
context = self.get_context_data(form=form)
|
||||||
image_formset = context["image_formset"]
|
series_formset = context["series_formset"]
|
||||||
answer_formset = context["answer_formset"]
|
|
||||||
# logger.debug(formset.is_valid())
|
# logger.debug(formset.is_valid())
|
||||||
if image_formset.is_valid() and answer_formset.is_valid():
|
if series_formset.is_valid():
|
||||||
response = super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
image_formset.instance = self.object
|
series_formset.instance = self.object
|
||||||
image_formset.save()
|
series_formset.save()
|
||||||
answer_formset.instance = self.object
|
|
||||||
answer_formset.save()
|
|
||||||
return response
|
return response
|
||||||
else:
|
else:
|
||||||
return super().form_invalid(form)
|
return super().form_invalid(form)
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ INSTALLED_APPS = [
|
|||||||
'anatomy',
|
'anatomy',
|
||||||
'physics',
|
'physics',
|
||||||
'rapids',
|
'rapids',
|
||||||
|
'longs',
|
||||||
"reversion",
|
"reversion",
|
||||||
"django_tables2",
|
"django_tables2",
|
||||||
"django_filters",
|
"django_filters",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ urlpatterns = [
|
|||||||
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("longs/", include("longs.urls"), name="longs"),
|
||||||
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"),
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ image_storage = FileSystemStorage(
|
|||||||
)
|
)
|
||||||
|
|
||||||
def image_directory_path(instance, filename):
|
def image_directory_path(instance, filename):
|
||||||
|
return u"{0}".format(filename)
|
||||||
return u"picture/{0}".format(filename)
|
return u"picture/{0}".format(filename)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user