Compare commits

..
77 Commits
Author SHA1 Message Date
Ross 297f8619b4 a number of updates 2023-09-08 18:10:42 +01:00
Ross 98a874f805 fix user scores for sba 2023-09-05 20:42:36 +01:00
Ross 5759271751 . 2023-09-04 09:43:28 +01:00
Ross a0b38f541a add user to sba / physics answer tables 2023-09-04 09:36:31 +01:00
Ross f6d7bd594a fix sba 2023-09-02 10:24:17 +01:00
Ross 06f1e7852d allow adding groups to users in bulk 2023-09-02 08:25:12 +01:00
Ross 48cba9353e fix image hashes (possible) 2023-08-21 14:05:54 +01:00
Ross fcd77514eb return which files have been uploaded successfully 2023-08-21 12:44:40 +01:00
Ross 7a9b3177d7 . 2023-08-14 14:36:18 +01:00
Ross a4e66696bd further improvements 2023-08-14 14:30:01 +01:00
Ross cc2cbde691 some more ui for bulk uploads 2023-08-14 13:24:10 +01:00
Ross d4eda9fdfb basic support for bulk series uploading 2023-08-14 12:39:26 +01:00
Ross 9ae1874073 start adding uncat dicom 2023-08-14 10:15:56 +01:00
Ross c3a735fa52 add a field to store dicom tags for the ohif viewer 2023-08-14 09:01:06 +01:00
Ross 4aa4db5d01 don't display contrast if none 2023-08-08 08:55:55 +01:00
Ross f3c8fb5847 . 2023-08-07 22:43:28 +01:00
Ross 32b861f213 fake SOPInsanceUID 2023-08-07 22:31:52 +01:00
Ross 5e33d5f869 . 2023-08-07 22:12:47 +01:00
Ross a435d82f52 try and allow loading jpg etc in new viewer 2023-08-07 22:09:23 +01:00
Ross 671c6f9ca3 . 2023-08-07 22:02:25 +01:00
Ross 6ab1355919 . 2023-08-07 21:56:19 +01:00
Ross 522fa6bcb0 improve dicom json support 2023-08-07 21:46:34 +01:00
Ross 8ec7662015 . 2023-08-07 20:46:36 +01:00
Ross 2b679b6149 . 2023-08-07 15:51:10 +01:00
Ross 9b334085cd . 2023-08-07 15:50:30 +01:00
Ross 33414e9575 . 2023-08-07 15:45:05 +01:00
Ross ce531e7030 . 2023-08-07 13:51:46 +01:00
Ross 2520fa7879 . 2023-08-07 13:50:37 +01:00
Ross 15ab98b682 . 2023-08-07 13:48:27 +01:00
Ross c3e36bfb12 fix a number of things 2023-08-07 13:42:22 +01:00
Ross b36fa370f1 fix case rodering 2023-07-31 12:02:23 +01:00
Ross fdc0de29d3 allow ordering of case collections 2023-07-31 11:39:36 +01:00
Ross bce3469676 allow adding users to case collections 2023-07-31 10:40:53 +01:00
Ross f268f040e2 continue working on case collections 2023-07-31 10:25:23 +01:00
Ross dd300afd63 some refactoring and atlas improvements 2023-07-24 11:56:12 +01:00
Ross e6469e70f0 reoorganise atlas modules 2023-07-24 09:19:08 +01:00
Ross 9a5088c03a start adding self review to collections 2023-07-10 09:06:46 +01:00
Ross c799f106d7 add user_user support to atlas collections 2023-07-03 13:06:08 +01:00
Ross 15b6233802 update get_or_create_cid_user_exam 2023-07-03 12:51:37 +01:00
Ross f458dc1306 . 2023-07-03 11:16:32 +01:00
Ross 6b5c3f8914 allow merging of examinations 2023-07-03 11:10:50 +01:00
Ross 1ade42d7ba improve test 2023-07-03 09:00:45 +01:00
Ross 100945478b collections user testing 2023-07-03 09:00:28 +01:00
Ross abdba22cdf format with black 2023-07-03 08:59:58 +01:00
Ross 656f1658f8 add (correct) support for logged in users to sba and physics exams 2023-06-26 11:52:23 +01:00
Ross 09951286e2 update check_cid_user 2023-06-26 11:51:16 +01:00
Ross dc195effc6 start physics and sbas api 2023-06-26 11:49:16 +01:00
Ross 4e5c1c213d allow logged in users to assign user groups 2023-06-19 13:31:45 +01:00
Ross c9bbb5de50 . 2023-06-19 12:43:51 +01:00
Ross be8f00566a fix some candidate dilsplay 2023-06-19 12:43:00 +01:00
Ross 6c1fd3ae2f migrate examinations to generic model (and start adding mangament) 2023-06-19 12:37:55 +01:00
Ross 154494e4b0 fix missing answer alert 2023-06-19 10:59:49 +01:00
Ross 5ca1d5b44e Improve candidate display in exams 2023-06-19 10:55:08 +01:00
Ross c99fb5a3d6 Start improving atlas forms 2023-06-12 16:31:08 +01:00
Ross 5fbd4cd35e fix a few things 2023-06-12 16:16:45 +01:00
Ross d1ba8c87dc tidy up table image dlocks 2023-06-12 12:34:38 +01:00
Ross e0f0c7619a further abstraction 2023-06-12 11:35:15 +01:00
Ross cbe7257fe8 extract a little more shared case /longs 2023-06-12 11:27:07 +01:00
Ross 56cc23a8f2 tidy up block display a little more 2023-06-12 11:20:31 +01:00
Ross 4eb421bc33 hacky reset of migrations 2023-06-12 11:14:19 +01:00
Ross 796ec1a072 . 2023-06-12 10:59:36 +01:00
Ross 43c984d5aa tidy up series block in collections a little 2023-06-12 10:53:21 +01:00
Ross d8c36b81d7 test updating css 2023-06-12 09:36:04 +01:00
Ross ff00047110 Allow browsing user scores as admin 2023-06-05 17:44:32 +01:00
Ross fef378a2ac . 2023-06-05 17:10:22 +01:00
Ross afdc8e95c7 fix longs tests 2023-06-05 16:44:35 +01:00
Ross e0723d4d3a generalise a QuestionBase 2023-06-05 09:10:54 +01:00
Ross ac558b9ea2 try to fix migartions 2023-06-05 09:08:56 +01:00
Ross ae670c738b more Long improvements 2023-05-22 13:29:07 +01:00
Ross 4345f46615 many Longs fixes 2023-05-22 11:21:56 +01:00
Ross 56eb5f8230 add link to edit user in admin 2023-05-22 11:21:32 +01:00
Ross 4dc9fdcb8b bump python version 2023-05-22 11:21:06 +01:00
Ross 8cd8e9226c hopefulyl remove tagulous properly 2023-05-22 11:20:42 +01:00
Ross 664e29937d move a user more UserAnswer features to base class 2023-05-15 11:23:06 +01:00
Ross d05752b2dc rename CidUserAnswer to UserAnswer 2023-05-15 11:15:17 +01:00
Ross 7aa78c2807 create base useranswer class 2023-05-15 11:12:52 +01:00
Ross 525373f393 start allowing user candidates in longs 2023-05-15 10:54:09 +01:00
481 changed files with 7081 additions and 10171 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
# pull official base image # pull official base image
FROM python:3.11.1-slim FROM python:3.11.3-slim
# set work directory # set work directory
WORKDIR /usr/src/rad WORKDIR /usr/src/rad
@@ -13,7 +13,7 @@ ENV PYTHONUNBUFFERED 1
# python3 python3-dev gcc \ # python3 python3-dev gcc \
# gfortran musl-dev \ # gfortran musl-dev \
# libffi-dev openssl-dev # libffi-dev openssl-dev
RUN apt-get update && apt-get -y install postgresql postgresql-contrib git RUN apt-get update && apt-get -y install postgresql postgresql-contrib git libcairo2-dev pkg-config gcc
# install dependencies # install dependencies
+6 -1
View File
@@ -1,5 +1,6 @@
# Current target # Current target
- User management - User management
- SQUASH MIGRATIONS
# Ongoing # Ongoing
@@ -13,4 +14,8 @@
# Future # Future
- Clean up user results / score pages - Clean up user results / score pages
- Remove external javascript dependencies - Remove external javascript dependencies
- Viva cases
- Update rapids for new style
+3 -4
View File
@@ -4,9 +4,9 @@ from .models import (
#Examination, #Examination,
QuestionType, QuestionType,
Answer, Answer,
CidUserAnswer, UserAnswer,
Exam, Exam,
Modality, #Modality,
Region, Region,
BodyPart, BodyPart,
Structure, Structure,
@@ -54,9 +54,8 @@ class ExamAdmin(VersionAdmin, admin.ModelAdmin):
admin.site.register(AnatomyQuestion, AnatomyAdmin) admin.site.register(AnatomyQuestion, AnatomyAdmin)
#admin.site.register(Examination) #admin.site.register(Examination)
admin.site.register(QuestionType) admin.site.register(QuestionType)
admin.site.register(CidUserAnswer) admin.site.register(UserAnswer)
admin.site.register(Exam, ExamAdmin) admin.site.register(Exam, ExamAdmin)
admin.site.register(Modality)
admin.site.register(Region) admin.site.register(Region)
admin.site.register(BodyPart) admin.site.register(BodyPart)
admin.site.register(Structure) admin.site.register(Structure)
+2 -2
View File
@@ -1,6 +1,6 @@
import django_filters import django_filters
from .models import AnatomyQuestion, CidUserAnswer, Exam from .models import AnatomyQuestion, UserAnswer, Exam
#from generic.filters import UserAnswerFilter #from generic.filters import UserAnswerFilter
from django.contrib.auth.models import User from django.contrib.auth.models import User
@@ -48,7 +48,7 @@ class AnatomyQuestionFilter(django_filters.FilterSet):
class AnatomyUserAnswerFilter(django_filters.FilterSet): class AnatomyUserAnswerFilter(django_filters.FilterSet):
class Meta: class Meta:
model= CidUserAnswer model= UserAnswer
fields = ( fields = (
"cid", "cid",
"user", "user",
+2 -2
View File
@@ -12,7 +12,7 @@ from generic.forms import ExamAuthorFormMixin, ExamFormMixin
from .models import ( from .models import (
Answer, Answer,
CidUserAnswer, UserAnswer,
AnatomyQuestion, AnatomyQuestion,
# Examination, # Examination,
BodyPart, BodyPart,
@@ -32,7 +32,7 @@ from dal import autocomplete
class AnatomyAnswerForm(ModelForm): class AnatomyAnswerForm(ModelForm):
class Meta: class Meta:
model = CidUserAnswer model = UserAnswer
fields = ("answer",) fields = ("answer",)
+62 -79
View File
@@ -1,8 +1,7 @@
# Generated by Django 3.1.2 on 2020-10-16 19:07 # Generated by Django 4.1.4 on 2023-06-12 10:07
import anatomy.models import anatomy.models
from django.conf import settings from django.conf import settings
import django.core.files.storage
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
@@ -21,9 +20,26 @@ class Migration(migrations.Migration):
name='AnatomyQuestion', name='AnatomyQuestion',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('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='http://161.35.163.87//media/anatomy/', location='/home/ross/scripts/sites/rad/media//anatomy/'), upload_to=anatomy.models.image_directory_path)), ('authors_only', models.BooleanField(default=False, help_text='If true only question authors will be able to view.')),
('description', models.CharField(help_text="Short description of the image e.g. 'Sagittal CT Chest, Abdomen & Pelvis', will be displayed as the title.", max_length=400)),
('created_date', models.DateTimeField(default=django.utils.timezone.now)), ('created_date', models.DateTimeField(default=django.utils.timezone.now)),
('open_access', models.BooleanField(default=True, help_text='If a question should be freely available to browse')),
('feedback', models.TextField(blank=True, help_text='Question Feedback', null=True)),
('image', models.ImageField(help_text="The image to use for the question. Ideally use use unmarked images and annotate (arrow) them on the test system. If you wish to reuse an image that is already uploaded 'clone' the question that contains it.", upload_to=anatomy.models.image_directory_path)),
('image_annotations', models.TextField(blank=True, help_text='Stores a JSON representation of annotations to be applied by cornerstonetools')),
('description', models.CharField(help_text="Short description of the image e.g. 'Sagittal CT Chest, Abdomen & Pelvis', will be displayed as the title.", max_length=400)),
],
options={
'permissions': (),
},
),
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)),
('proposed', models.BooleanField(default=False)),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
@@ -37,16 +53,32 @@ class Migration(migrations.Migration):
name='Exam', name='Exam',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)), ('name', models.CharField(help_text='Name of the exam', max_length=200)),
('exam_questions', models.ManyToManyField(related_name='exams', to='anatomy.AnatomyQuestion')), ('active', models.BooleanField(default=False, help_text='If an exam should be available to take')),
], ('exam_mode', models.BooleanField(default=False, help_text='If an exam should be taken in exam mode (users results will be submited to the server for marking)')),
), ('include_history', models.BooleanField(default=False, help_text='If an exam should include history when taking')),
migrations.CreateModel( ('publish_results', models.BooleanField(default=False, help_text='If an exams results should be available')),
name='Examination', ('recreate_json', models.BooleanField(default=False, help_text='If the json cache needs updating')),
fields=[ ('json_creation_time', models.DateTimeField(blank=True, default=None, null=True)),
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('exam_json_id', models.IntegerField(default=1, help_text='auto incrementing field when json recreated')),
('examination', models.CharField(max_length=200)), ('archive', models.BooleanField(default=False, help_text='Archived exams will remain on the test system but will not be displayed by default')),
('open_access', models.BooleanField(default=False, help_text='If the exam is freely accessible (to view and edit on the test system)')),
('authors_only', models.BooleanField(default=False, help_text='If true only exam authors will be able to view.')),
('stats_mean', models.FloatField(default=0)),
('stats_mode', models.CharField(default=0, max_length=25)),
('stats_median', models.FloatField(default=0)),
('stats_candidates', models.FloatField(default=0)),
('stats_min', models.FloatField(default=0)),
('stats_max', models.FloatField(default=0)),
('stats_max_possible', models.FloatField(default=0)),
('user_scores', models.JSONField(default=dict)),
('exam_results_emailed', models.DateTimeField(default=None, null=True)),
('time_limit', models.IntegerField(default=5400, help_text='Exam time limit (in seconds)')),
('author', models.ManyToManyField(blank=True, help_text='Author of exam', related_name='anatomy_exam_author', to=settings.AUTH_USER_MODEL)),
], ],
options={
'abstract': False,
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='Modality', name='Modality',
@@ -62,86 +94,37 @@ class Migration(migrations.Migration):
('text', models.CharField(max_length=400)), ('text', models.CharField(max_length=400)),
], ],
), ),
migrations.CreateModel(
name='Region',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('region', models.CharField(max_length=200)),
],
),
migrations.CreateModel( migrations.CreateModel(
name='Structure', name='Structure',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('bodypart', models.CharField(max_length=200)), ('structure', models.CharField(max_length=200, unique=True)),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='UserAnswer', name='UserAnswer',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('answer', models.CharField(blank=True, max_length=500)), ('cid', models.BigIntegerField(blank=True, help_text='Candidate ID (limitied by BigIntegerField size)', null=True)),
('cid', models.IntegerField(blank=True, null=True)),
('flagged', models.BooleanField(default=False)),
('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='exam', to='anatomy.exam')),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_answers', to='anatomy.anatomyquestion')),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='IncorrectAnswers',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('answer', models.CharField(max_length=500)),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='incorrect_answers', to='anatomy.anatomyquestion')),
],
),
migrations.CreateModel(
name='HalfMarkAnswers',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('answer', models.CharField(max_length=500)),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='half_mark_answers', to='anatomy.anatomyquestion')),
],
),
migrations.CreateModel(
name='CidUserAnswer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('answer', models.CharField(blank=True, max_length=500)),
('cid', models.IntegerField(blank=True, null=True)),
('created', models.DateTimeField(auto_now_add=True)), ('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)), ('updated', models.DateTimeField(auto_now=True)),
('answer', models.TextField(blank=True, max_length=500)),
('answer_compare', models.TextField(blank=True, max_length=500)),
('score', models.CharField(blank=True, choices=[('', 'Unmarked'), ('0', 'Incorrect'), ('1', 'Half mark'), ('2', 'Correct')], default='', max_length=1)),
('exam', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cid_user_answers', to='anatomy.exam')), ('exam', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cid_user_answers', to='anatomy.exam')),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cid_user_answers', to='anatomy.anatomyquestion')), ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cid_user_answers', to='anatomy.anatomyquestion')),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user_anatomy_user_answers', to=settings.AUTH_USER_MODEL)),
], ],
), options={
migrations.CreateModel( 'ordering': ['cid'],
name='Answers', 'abstract': False,
fields=[ },
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('answer', models.CharField(max_length=500)),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='anatomy.anatomyquestion')),
],
),
migrations.AddField(
model_name='anatomyquestion',
name='body_part',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='anatomy.bodypart'),
),
migrations.AddField(
model_name='anatomyquestion',
name='examination',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='anatomy.examination'),
),
migrations.AddField(
model_name='anatomyquestion',
name='modality',
field=models.ForeignKey(help_text='Modality of the image', null=True, on_delete=django.db.models.deletion.SET_NULL, to='anatomy.modality'),
),
migrations.AddField(
model_name='anatomyquestion',
name='question_type',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='anatomy.questiontype'),
),
migrations.AddField(
model_name='anatomyquestion',
name='structure',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='anatomy.structure'),
), ),
] ]
-18
View File
@@ -1,18 +0,0 @@
# Generated by Django 3.1.2 on 2020-10-16 19:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='exam',
name='active',
field=models.BooleanField(default=True, help_text='If an exam should be available'),
),
]
+85
View File
@@ -0,0 +1,85 @@
# Generated by Django 4.1.4 on 2023-06-12 10:07
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import sortedm2m.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
('generic', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('anatomy', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='exam',
name='cid_user_groups',
field=models.ManyToManyField(blank=True, help_text='These groups define which candidates are able to be added to the exams/collection.', related_name='anatomy_cid_user_groups', to='generic.cidusergroup'),
),
migrations.AddField(
model_name='exam',
name='exam_questions',
field=sortedm2m.fields.SortedManyToManyField(blank='true', help_text=None, related_name='exams', to='anatomy.anatomyquestion'),
),
migrations.AddField(
model_name='exam',
name='user_user_groups',
field=models.ManyToManyField(blank=True, help_text='These groups define which candidates are able to be added to the exams/collection.', related_name='anatomy_user_user_groups', to='generic.userusergroup'),
),
migrations.AddField(
model_name='exam',
name='valid_cid_users',
field=models.ManyToManyField(blank=True, related_name='anatomy_exams', to='generic.ciduser'),
),
migrations.AddField(
model_name='exam',
name='valid_user_users',
field=models.ManyToManyField(blank=True, related_name='user_anatomy_exams', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='answer',
name='question',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='anatomy.anatomyquestion'),
),
migrations.AddField(
model_name='anatomyquestion',
name='author',
field=models.ManyToManyField(blank=True, help_text='Author(s) of question', related_name='anatomy_authored_questions', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='anatomyquestion',
name='body_part',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='anatomy.bodypart'),
),
migrations.AddField(
model_name='anatomyquestion',
name='examination',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='generic.examination'),
),
migrations.AddField(
model_name='anatomyquestion',
name='modality',
field=models.ForeignKey(help_text='Modality of the image', null=True, on_delete=django.db.models.deletion.SET_NULL, to='anatomy.modality'),
),
migrations.AddField(
model_name='anatomyquestion',
name='question_type',
field=models.ForeignKey(default=1, null=True, on_delete=django.db.models.deletion.SET_NULL, to='anatomy.questiontype'),
),
migrations.AddField(
model_name='anatomyquestion',
name='region',
field=models.ForeignKey(blank=True, help_text='Region the image covers', null=True, on_delete=django.db.models.deletion.SET_NULL, to='anatomy.region'),
),
migrations.AddField(
model_name='anatomyquestion',
name='structure',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='anatomy.structure'),
),
]
@@ -0,0 +1,47 @@
# Generated by Django 4.1.4 on 2023-06-19 10:51
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("longs", "0002_alter_longseries_modality"),
("atlas", "0003_alter_series_modality"),
("generic", "0002_modality_examination_modality"),
("anatomy", "0002_initial"),
]
operations = [
# migrations.DeleteModel(
# name='Modality',
# ),
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.DeleteModel(
name="Modality",
),
],
database_operations=[
migrations.AlterModelTable(
name="Modality",
table="generic_modality",
),
],
),
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.AlterField(
model_name="anatomyquestion",
name="modality",
field=models.ForeignKey(
help_text="Modality of the image",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="generic.modality",
),
),
],
database_operations=[],
),
]
@@ -1,40 +0,0 @@
# Generated by Django 3.1.3 on 2020-11-19 19:41
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0002_exam_active'),
]
operations = [
migrations.CreateModel(
name='Answer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('answer', models.CharField(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='anatomy.anatomyquestion')),
],
),
migrations.RemoveField(
model_name='halfmarkanswers',
name='question',
),
migrations.RemoveField(
model_name='incorrectanswers',
name='question',
),
migrations.DeleteModel(
name='Answers',
),
migrations.DeleteModel(
name='HalfMarkAnswers',
),
migrations.DeleteModel(
name='IncorrectAnswers',
),
]
@@ -1,25 +0,0 @@
# Generated by Django 3.1.2 on 2020-11-23 16:19
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0003_auto_20201119_1941'),
]
operations = [
migrations.AddField(
model_name='exam',
name='recreate_json',
field=models.BooleanField(default=False, help_text='If the json cache needs updating'),
),
migrations.AlterField(
model_name='anatomyquestion',
name='image',
field=models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='http://161.35.163.87//media/anatomy/', location='/home/ross/sites/rad/media//anatomy/'), upload_to=anatomy.models.image_directory_path),
),
]
@@ -0,0 +1,43 @@
# Generated by Django 4.1.4 on 2023-07-24 09:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0003_alter_anatomyquestion_modality_delete_modality'),
]
operations = [
migrations.AddField(
model_name='exam',
name='candidates_only',
field=models.BooleanField(default=True, help_text='If the exam/collection is only available to candidates who have been added'),
),
migrations.AlterField(
model_name='exam',
name='active',
field=models.BooleanField(default=False, help_text='If an exam/collection should be available to take'),
),
migrations.AlterField(
model_name='exam',
name='archive',
field=models.BooleanField(default=False, help_text='Archived exams/collections will remain on the test system but will not be displayed by default'),
),
migrations.AlterField(
model_name='exam',
name='name',
field=models.CharField(help_text='Name of the exam/collection', max_length=200),
),
migrations.AlterField(
model_name='exam',
name='open_access',
field=models.BooleanField(default=False, help_text='If the exam/collection is freely accessible (to view and edit on the test system)'),
),
migrations.AlterField(
model_name='exam',
name='publish_results',
field=models.BooleanField(default=False, help_text='If an exam/collections results should be available'),
),
]
@@ -1,39 +0,0 @@
# Generated by Django 3.1.3 on 2020-11-26 17:30
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0004_auto_20201123_1619'),
]
operations = [
migrations.RenameField(
model_name='structure',
old_name='bodypart',
new_name='structure',
),
migrations.RemoveField(
model_name='exam',
name='recreate_json',
),
#migrations.AddField(
# model_name='anatomyquestion',
# name='open_access',
# field=models.BooleanField(default=True, help_text='If an question should be freely available to browse'),
#),
migrations.AlterField(
model_name='anatomyquestion',
name='image',
field=models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='http://161.35.163.87//media/anatomy/', location='/home/ross/scripts/sites/rad/media//anatomy/'), upload_to=anatomy.models.image_directory_path),
),
migrations.AlterField(
model_name='exam',
name='exam_questions',
field=models.ManyToManyField(blank='true', related_name='exams', to='anatomy.AnatomyQuestion'),
),
]
@@ -1,25 +0,0 @@
# Generated by Django 3.1.3 on 2020-11-29 09:55
from django.db import migrations, models
import sortedm2m.fields
from sortedm2m.operations import AlterSortedManyToManyField
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0005_auto_20201126_1730'),
]
operations = [
#migrations.AddField(
# model_name='anatomyquestion',
# name='open_access',
# field=models.BooleanField(default=True, help_text='If an question should be freely available to browse'),
#),
AlterSortedManyToManyField(
model_name='exam',
name='exam_questions',
field=sortedm2m.fields.SortedManyToManyField(blank='true', help_text=None, related_name='exams', to='anatomy.AnatomyQuestion'),
),
]
@@ -1,23 +0,0 @@
# Generated by Django 3.1.3 on 2020-12-01 23:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0006_auto_20201129_0955'),
]
operations = [
#migrations.AddField(
#model_name='anatomyquestion',
#name='open_access',
#field=models.BooleanField(default=True, help_text='If an question should be freely available to browse'),
#),
migrations.AddField(
model_name='exam',
name='recreate_json',
field=models.BooleanField(default=False, help_text='If the json cache needs updating'),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.1.3 on 2020-12-03 09:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0007_auto_20201201_2358'),
]
operations = [
migrations.AddField(
model_name='anatomyquestion',
name='open_access',
field=models.BooleanField(default=True, help_text='If an question should be freely available to browse'),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.1.3 on 2020-12-06 11:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0008_anatomyquestion_open_access'),
]
operations = [
migrations.AddField(
model_name='anatomyquestion',
name='image_annotations',
field=models.TextField(blank=True, help_text='Stores a JSON representation of annotations to be applied by cornerstonetools'),
),
]
@@ -1,26 +0,0 @@
# Generated by Django 3.1.3 on 2020-12-06 17:16
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0009_anatomyquestion_image_annotations'),
]
operations = [
migrations.CreateModel(
name='Region',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('region', models.CharField(max_length=200)),
],
),
migrations.AddField(
model_name='anatomyquestion',
name='region',
field=models.ForeignKey(blank=True, help_text='Region the image covers', null=True, on_delete=django.db.models.deletion.SET_NULL, to='anatomy.region'),
),
]
@@ -1,20 +0,0 @@
# Generated by Django 3.1.3 on 2020-12-06 18:12
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('anatomy', '0010_auto_20201206_1716'),
]
operations = [
migrations.AddField(
model_name='anatomyquestion',
name='author',
field=models.ManyToManyField(blank=True, help_text='Author(s) of question', related_name='anatomy_authored_questions', to=settings.AUTH_USER_MODEL),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.1.3 on 2020-12-10 23:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0011_anatomyquestion_author'),
]
operations = [
migrations.AddField(
model_name='exam',
name='publish_results',
field=models.BooleanField(default=True, help_text='If an exams results should be available'),
),
]
@@ -1,35 +0,0 @@
# Generated by Django 3.1.3 on 2020-12-27 12:00
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0012_exam_publish_results'),
]
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),
),
migrations.AlterField(
model_name='answer',
name='answer',
field=models.TextField(max_length=500),
),
migrations.AlterField(
model_name='ciduseranswer',
name='answer',
field=models.TextField(blank=True, max_length=500),
),
migrations.AlterField(
model_name='useranswer',
name='answer',
field=models.TextField(blank=True, max_length=500),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.1.3 on 2020-12-28 10:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0013_auto_20201227_1200'),
]
operations = [
migrations.AddField(
model_name='exam',
name='time_limit',
field=models.IntegerField(default=False, help_text='Exam time limit (in seconds)'),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.1.3 on 2020-12-28 22:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0014_exam_time_limit'),
]
operations = [
migrations.AlterField(
model_name='exam',
name='time_limit',
field=models.IntegerField(default=5400, help_text='Exam time limit (in seconds)'),
),
]
@@ -1,25 +0,0 @@
# Generated by Django 3.1.2 on 2021-01-04 13:10
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0015_auto_20201228_2235'),
]
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),
),
migrations.AlterField(
model_name='useranswer',
name='cid',
field=models.BigIntegerField(blank=True, null=True),
),
]
@@ -1,23 +0,0 @@
# Generated by Django 3.1.2 on 2021-01-04 13:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0016_auto_20210104_1310'),
]
operations = [
migrations.AlterField(
model_name='ciduseranswer',
name='cid',
field=models.BigIntegerField(blank=True, help_text='Candidate ID (limitied by BigIntegerField size)', null=True),
),
migrations.AlterField(
model_name='useranswer',
name='cid',
field=models.BigIntegerField(blank=True, help_text='Candidate ID (limitied by BigIntegerField size)', null=True),
),
]
@@ -1,34 +0,0 @@
# Generated by Django 3.1.3 on 2021-01-12 17:26
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0017_auto_20210104_1355'),
]
operations = [
migrations.AddField(
model_name='answer',
name='answer_compare',
field=models.TextField(default='temp', max_length=500),
preserve_default=False,
),
migrations.AddField(
model_name='ciduseranswer',
name='answer_compare',
field=models.TextField(blank=True, max_length=500),
),
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),
),
migrations.DeleteModel(
name='UserAnswer',
),
]
@@ -1,20 +0,0 @@
# 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),
),
]
@@ -1,20 +0,0 @@
# 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),
),
]
@@ -1,20 +0,0 @@
# 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),
),
]
@@ -1,20 +0,0 @@
# 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),
),
]
@@ -1,20 +0,0 @@
# Generated by Django 3.1.5 on 2021-01-25 15:34
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0022_auto_20210121_1806'),
]
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),
),
]
@@ -1,20 +0,0 @@
# Generated by Django 3.1.3 on 2021-01-30 15:15
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0023_auto_20210125_1534'),
]
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),
),
]
@@ -1,23 +0,0 @@
# Generated by Django 3.1.3 on 2021-02-05 11:06
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('generic', '0007_site'),
('anatomy', '0024_auto_20210130_1515'),
]
operations = [
migrations.AlterField(
model_name='anatomyquestion',
name='examination',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='generic.examination'),
),
migrations.DeleteModel(
name='Examination',
),
]
@@ -1,17 +0,0 @@
# Generated by Django 3.1.3 on 2021-02-05 11:09
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0025_auto_20210205_1106'),
]
operations = [
migrations.RemoveField(
model_name='anatomyquestion',
name='examination',
),
]
@@ -1,20 +0,0 @@
# Generated by Django 3.1.3 on 2021-02-05 11:10
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('generic', '0007_site'),
('anatomy', '0026_remove_anatomyquestion_examination'),
]
operations = [
migrations.AddField(
model_name='anatomyquestion',
name='examination',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='generic.examination'),
),
]
@@ -1,24 +0,0 @@
# Generated by Django 3.1.3 on 2021-02-06 09:05
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0027_anatomyquestion_examination'),
]
operations = [
migrations.AlterField(
model_name='anatomyquestion',
name='open_access',
field=models.BooleanField(default=True, help_text='If a question should be freely available to browse'),
),
migrations.AlterField(
model_name='anatomyquestion',
name='question_type',
field=models.ForeignKey(default=1, null=True, on_delete=django.db.models.deletion.SET_NULL, to='anatomy.questiontype'),
),
]
@@ -1,20 +0,0 @@
# Generated by Django 3.1.3 on 2021-02-09 22:39
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0028_auto_20210206_0905'),
]
operations = [
migrations.AlterField(
model_name='anatomyquestion',
name='image',
field=models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='/media/anatomy/', location='/home/ross/rad/media//anatomy/'), upload_to=anatomy.models.image_directory_path),
),
]
@@ -1,20 +0,0 @@
# Generated by Django 3.1.3 on 2021-02-14 20:28
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0029_auto_20210209_2239'),
]
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),
),
]
@@ -1,20 +0,0 @@
# Generated by Django 3.1.5 on 2021-02-15 10:43
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0030_auto_20210214_2028'),
]
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),
),
]
@@ -1,20 +0,0 @@
# Generated by Django 3.1.5 on 2021-02-15 11:44
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0031_auto_20210215_1043'),
]
operations = [
migrations.AlterField(
model_name='anatomyquestion',
name='image',
field=models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='/media/anatomy/', location='/home/django/rad/media//anatomy/'), upload_to=anatomy.models.image_directory_path),
),
]
@@ -1,20 +0,0 @@
# Generated by Django 3.1.5 on 2021-02-15 11:47
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0032_auto_20210215_1144'),
]
operations = [
migrations.AlterField(
model_name='anatomyquestion',
name='image',
field=models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='/media//', location='/home/django/rad/media//'), upload_to=anatomy.models.image_directory_path),
),
]
@@ -1,19 +0,0 @@
# Generated by Django 3.1.5 on 2021-02-15 12:00
import anatomy.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0033_auto_20210215_1147'),
]
operations = [
migrations.AlterField(
model_name='anatomyquestion',
name='image',
field=models.ImageField(upload_to=anatomy.models.image_directory_path),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.1.3 on 2021-03-09 15:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0034_auto_20210215_1200'),
]
operations = [
migrations.AddField(
model_name='exam',
name='json_creation_time',
field=models.DateTimeField(blank=True, default=None, null=True),
),
]
-18
View File
@@ -1,18 +0,0 @@
# Generated by Django 3.1.3 on 2021-03-19 11:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0035_exam_json_creation_time'),
]
operations = [
migrations.AddField(
model_name='exam',
name='exam_mode',
field=models.BooleanField(default=True, help_text='If an exam should be take in exam_mode'),
),
]
@@ -1,35 +0,0 @@
# Generated by Django 3.1.3 on 2021-04-22 11:43
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('anatomy', '0036_exam_exam_mode'),
]
operations = [
migrations.AddField(
model_name='exam',
name='author',
field=models.ManyToManyField(blank=True, help_text='Author of exam', related_name='anatomy_exam_author', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='exam',
name='active',
field=models.BooleanField(default=False, help_text='If an exam should be available'),
),
migrations.AlterField(
model_name='exam',
name='exam_mode',
field=models.BooleanField(default=True, help_text='If an exam should be taken in exam mode'),
),
migrations.AlterField(
model_name='exam',
name='publish_results',
field=models.BooleanField(default=False, help_text='If an exams results should be available'),
),
]
@@ -1,17 +0,0 @@
# Generated by Django 3.1.3 on 2021-04-29 19:04
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0037_auto_20210422_1143'),
]
operations = [
migrations.AlterModelOptions(
name='anatomyquestion',
options={'permissions': ()},
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.1.3 on 2021-05-03 11:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0038_auto_20210429_1904'),
]
operations = [
migrations.AddField(
model_name='answer',
name='proposed',
field=models.BooleanField(default=False),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.2.4 on 2021-07-06 19:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0039_answer_proposed'),
]
operations = [
migrations.AlterField(
model_name='exam',
name='exam_mode',
field=models.BooleanField(default=False, help_text='If an exam should be taken in exam mode (users results will be submited to the server for marking)'),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.2.4 on 2021-07-09 11:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0040_alter_exam_exam_mode'),
]
operations = [
migrations.AlterField(
model_name='exam',
name='name',
field=models.CharField(help_text='Name of the exam', max_length=200),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.2.4 on 2021-08-01 08:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0041_alter_exam_name'),
]
operations = [
migrations.AddField(
model_name='exam',
name='json_creation_id',
field=models.IntegerField(default=1, help_text='auto incrementing field when json recreated'),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.2.4 on 2021-08-01 08:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0042_exam_json_creation_id'),
]
operations = [
migrations.RenameField(
model_name='exam',
old_name='json_creation_id',
new_name='exam_json_id',
),
]
@@ -1,23 +0,0 @@
# Generated by Django 3.2.6 on 2021-08-16 12:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0043_rename_json_creation_id_exam_exam_json_id'),
]
operations = [
migrations.AddField(
model_name='exam',
name='archive',
field=models.BooleanField(default=False, help_text='Archived exams will remain on the test system but will not be displayed by default'),
),
migrations.AlterField(
model_name='exam',
name='active',
field=models.BooleanField(default=False, help_text='If an exam should be available to take'),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.2.8 on 2021-10-17 17:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0044_auto_20210816_1306'),
]
operations = [
migrations.AddField(
model_name='exam',
name='open_access',
field=models.BooleanField(default=False, help_text='If the exam is freely accessible'),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.2.8 on 2021-10-17 17:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0045_exam_open_access'),
]
operations = [
migrations.AlterField(
model_name='exam',
name='open_access',
field=models.BooleanField(default=False, help_text='If the exam is freely accessible (to view and edit on the test system)'),
),
]
@@ -1,28 +0,0 @@
# Generated by Django 3.2.8 on 2021-10-22 19:38
import anatomy.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0046_alter_exam_open_access'),
]
operations = [
migrations.RemoveField(
model_name='structure',
name='id',
),
migrations.AlterField(
model_name='anatomyquestion',
name='image',
field=models.ImageField(help_text="The image to use for the question. Ideally use use unmarked images and annotate (arrow) them on the test system. If you wish to reuse an image that is already uploaded 'clone' the question that contains it.As a preference", upload_to=anatomy.models.image_directory_path),
),
migrations.AlterField(
model_name='structure',
name='structure',
field=models.CharField(max_length=200, primary_key=True, serialize=False),
),
]
@@ -1,25 +0,0 @@
# Generated by Django 3.2.8 on 2021-10-25 10:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0047_auto_20211022_2038'),
]
operations = [
migrations.CreateModel(
name='Structure2',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('structure', models.CharField(max_length=200, unique=True)),
],
),
migrations.AlterField(
model_name='anatomyquestion',
name='structure',
field=models.CharField(max_length=255, null=True),
),
]
@@ -1,16 +0,0 @@
# Generated by Django 3.2.8 on 2021-10-25 10:07
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0048_auto_20211025_1102'),
]
operations = [
migrations.DeleteModel(
name='Structure',
),
]
@@ -1,17 +0,0 @@
# Generated by Django 3.2.8 on 2021-10-25 10:08
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0049_delete_structure'),
]
operations = [
migrations.RenameModel(
old_name='Structure2',
new_name='Structure',
),
]
@@ -1,19 +0,0 @@
# Generated by Django 3.2.8 on 2021-10-25 10:08
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0050_rename_structure2_structure'),
]
operations = [
migrations.AlterField(
model_name='anatomyquestion',
name='structure',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='anatomy.structure'),
),
]
@@ -1,17 +0,0 @@
# Generated by Django 3.2.8 on 2021-10-25 10:15
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0051_alter_anatomyquestion_structure'),
]
operations = [
migrations.RemoveField(
model_name='anatomyquestion',
name='structure',
),
]
@@ -1,19 +0,0 @@
# Generated by Django 3.2.8 on 2021-10-25 10:17
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0052_remove_anatomyquestion_structure'),
]
operations = [
migrations.AddField(
model_name='anatomyquestion',
name='structure',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='anatomy.structure'),
),
]
@@ -1,19 +0,0 @@
# Generated by Django 3.2.8 on 2021-12-11 22:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('generic', '0015_ciduser_ciduserexam'),
('anatomy', '0053_anatomyquestion_structure'),
]
operations = [
migrations.AddField(
model_name='exam',
name='valid_users',
field=models.ManyToManyField(blank=True, related_name='anatomy_exams', to='generic.CidUser'),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.2.8 on 2021-12-15 18:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0054_exam_valid_users'),
]
operations = [
migrations.AddField(
model_name='ciduseranswer',
name='score',
field=models.CharField(choices=[('', 'Unmarked'), ('0', 'Incorrect'), ('1', 'Half mark'), ('2', 'Correct')], default='', max_length=1),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.2.8 on 2021-12-15 20:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0055_ciduseranswer_score'),
]
operations = [
migrations.AlterField(
model_name='ciduseranswer',
name='score',
field=models.CharField(blank=True, choices=[('', 'Unmarked'), ('0', 'Incorrect'), ('1', 'Half mark'), ('2', 'Correct')], default='', max_length=1),
),
]
@@ -1,53 +0,0 @@
# Generated by Django 3.2.8 on 2021-12-15 20:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0056_alter_ciduseranswer_score'),
]
operations = [
migrations.AddField(
model_name='exam',
name='stats_candidates',
field=models.FloatField(default=0),
),
migrations.AddField(
model_name='exam',
name='stats_graph',
field=models.TextField(default=0),
),
migrations.AddField(
model_name='exam',
name='stats_max',
field=models.FloatField(default=0),
),
migrations.AddField(
model_name='exam',
name='stats_max_possible',
field=models.FloatField(default=0),
),
migrations.AddField(
model_name='exam',
name='stats_mean',
field=models.FloatField(default=0),
),
migrations.AddField(
model_name='exam',
name='stats_median',
field=models.FloatField(default=0),
),
migrations.AddField(
model_name='exam',
name='stats_min',
field=models.FloatField(default=0),
),
migrations.AddField(
model_name='exam',
name='stats_mode',
field=models.CharField(default=0, max_length=25),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.2.10 on 2021-12-31 10:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0057_auto_20211215_2036'),
]
operations = [
migrations.AddField(
model_name='exam',
name='user_scores',
field=models.JSONField(default=dict),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.2.10 on 2022-04-04 21:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0058_exam_user_scores'),
]
operations = [
migrations.AddField(
model_name='exam',
name='include_history',
field=models.BooleanField(default=False, help_text='If an exam should include history when taking'),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.2.13 on 2022-05-03 17:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0059_exam_include_history'),
]
operations = [
migrations.AddField(
model_name='exam',
name='authors_only',
field=models.BooleanField(default=False, help_text='If true only exam authors will be able to view.'),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.2.13 on 2022-05-20 10:45
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0060_exam_authors_only'),
]
operations = [
migrations.RenameField(
model_name='exam',
old_name='valid_users',
new_name='valid_cid_users',
),
]
@@ -1,20 +0,0 @@
# Generated by Django 3.2.13 on 2022-05-20 12:42
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('anatomy', '0061_rename_valid_users_exam_valid_cid_users'),
]
operations = [
migrations.AddField(
model_name='exam',
name='valid_user_users',
field=models.ManyToManyField(blank=True, related_name='user_anatomy_exams', to=settings.AUTH_USER_MODEL),
),
]
@@ -1,21 +0,0 @@
# Generated by Django 3.2.13 on 2022-05-20 19:51
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('anatomy', '0062_exam_valid_user_users'),
]
operations = [
migrations.AddField(
model_name='ciduseranswer',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user_anatomy_user_answers', to=settings.AUTH_USER_MODEL),
),
]
@@ -1,24 +0,0 @@
# Generated by Django 3.2.13 on 2022-05-21 09:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('generic', '0032_userprofile_registration_number'),
('anatomy', '0063_ciduseranswer_user'),
]
operations = [
migrations.AddField(
model_name='exam',
name='cid_user_groups',
field=models.ManyToManyField(blank=True, help_text='These groups define which candidates are able to be added to the exams/collection.', related_name='anatomy_cid_user_groups', to='generic.CidUserGroup'),
),
migrations.AddField(
model_name='exam',
name='user_user_groups',
field=models.ManyToManyField(blank=True, help_text='These groups define which candidates are able to be added to the exams/collection.', related_name='anatomy_user_user_groups', to='generic.UserUserGroup'),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.2.13 on 2022-06-26 15:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0064_auto_20220521_1020'),
]
operations = [
migrations.AddField(
model_name='exam',
name='exam_results_emailed',
field=models.DateTimeField(default=None, null=True),
),
]
@@ -1,17 +0,0 @@
# Generated by Django 3.2.13 on 2022-06-30 20:45
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0065_exam_exam_results_emailed'),
]
operations = [
migrations.RemoveField(
model_name='exam',
name='stats_graph',
),
]
@@ -1,19 +0,0 @@
# Generated by Django 3.2.13 on 2022-11-14 09:44
import anatomy.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0066_remove_exam_stats_graph'),
]
operations = [
migrations.AlterField(
model_name='anatomyquestion',
name='image',
field=models.ImageField(help_text="The image to use for the question. Ideally use use unmarked images and annotate (arrow) them on the test system. If you wish to reuse an image that is already uploaded 'clone' the question that contains it.", upload_to=anatomy.models.image_directory_path),
),
]
+14 -28
View File
@@ -16,7 +16,7 @@ from sortedm2m.fields import SortedManyToManyField
import string import string
from generic.models import CidUser, CidUserGroup, ExamUserStatus, Examination, ExamBase, QuestionNote, UserUserGroup from generic.models import CidUser, CidUserGroup, ExamUserStatus, Examination, ExamBase, QuestionBase, QuestionNote, UserAnswerBase, UserUserGroup, Modality
from collections import defaultdict from collections import defaultdict
from helpers.images import image_as_base64 from helpers.images import image_as_base64
@@ -57,11 +57,12 @@ class Region(models.Model):
return self.region return self.region
class Modality(models.Model): ## TODO: ??? Move to generic app
modality = models.CharField(max_length=200) #class Modality(models.Model):
# modality = models.CharField(max_length=200)
def __str__(self): #
return self.modality # def __str__(self):
# return self.modality
class QuestionType(models.Model): class QuestionType(models.Model):
@@ -72,7 +73,7 @@ class QuestionType(models.Model):
@reversion.register @reversion.register
class AnatomyQuestion(models.Model): class AnatomyQuestion(QuestionBase):
question_type = models.ForeignKey( question_type = models.ForeignKey(
QuestionType, on_delete=models.SET_NULL, null=True, default=1 QuestionType, on_delete=models.SET_NULL, null=True, default=1
) )
@@ -114,11 +115,6 @@ class AnatomyQuestion(models.Model):
structure = models.ForeignKey( structure = models.ForeignKey(
Structure, on_delete=models.SET_NULL, null=True, blank=True Structure, on_delete=models.SET_NULL, null=True, blank=True
) )
created_date = models.DateTimeField(default=timezone.now)
open_access = models.BooleanField(
help_text="If a question should be freely available to browse", default=True
)
author = models.ManyToManyField( author = models.ManyToManyField(
settings.AUTH_USER_MODEL, settings.AUTH_USER_MODEL,
@@ -127,8 +123,6 @@ class AnatomyQuestion(models.Model):
related_name="anatomy_authored_questions", related_name="anatomy_authored_questions",
) )
notes = GenericRelation(QuestionNote)
class Meta: class Meta:
permissions = () permissions = ()
@@ -152,6 +146,9 @@ class AnatomyQuestion(models.Model):
a.answer, exams, self.modality, self.structure a.answer, exams, self.modality, self.structure
) )
def get_link(self):
return format_html("<a href='{}'>{}</a>", self.get_absolute_url(), self)
def get_absolute_url(self): def get_absolute_url(self):
return reverse("anatomy:question_detail", kwargs={"pk": self.pk}) return reverse("anatomy:question_detail", kwargs={"pk": self.pk})
@@ -467,8 +464,9 @@ class Exam(ExamBase):
@reversion.register @reversion.register
class CidUserAnswer(models.Model): class UserAnswer(UserAnswerBase):
"""User answers by candidate""" """User answers by candidate"""
app_name = "anatomy"
question = models.ForeignKey( question = models.ForeignKey(
AnatomyQuestion, related_name="cid_user_answers", on_delete=models.CASCADE AnatomyQuestion, related_name="cid_user_answers", on_delete=models.CASCADE
@@ -477,12 +475,6 @@ class CidUserAnswer(models.Model):
answer_compare = 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)",
)
user = models.ForeignKey( user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True, related_name="user_anatomy_user_answers" settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True, related_name="user_anatomy_user_answers"
) )
@@ -492,9 +484,6 @@ class CidUserAnswer(models.Model):
Exam, related_name="cid_user_answers", on_delete=models.CASCADE, null=True 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)
score = models.CharField( score = models.CharField(
max_length=1, max_length=1,
choices=Answer.MarkOptions.choices, choices=Answer.MarkOptions.choices,
@@ -513,7 +502,7 @@ class CidUserAnswer(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.clean() self.clean()
return super(CidUserAnswer, self).save(*args, **kwargs) return super(UserAnswer, self).save(*args, **kwargs)
def clean(self): def clean(self):
if self.answer: if self.answer:
@@ -524,9 +513,6 @@ class CidUserAnswer(models.Model):
self.answer_compare = s self.answer_compare = s
def get_absolute_url(self):
return reverse("anatomy:user_answer_view", kwargs={"pk": self.pk})
# def get_compare_string(self): # def get_compare_string(self):
# # strip here should be unneccasry (providing clean is now working) # # strip here should be unneccasry (providing clean is now working)
# s = self.answer.lower().strip() # s = self.answer.lower().strip()
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

+5 -13
View File
@@ -1,7 +1,9 @@
import django_tables2 as tables import django_tables2 as tables
from django_tables2.utils import A from django_tables2.utils import A
from .models import AnatomyQuestion, CidUserAnswer from generic.tables import SingleImageColumn
from .models import AnatomyQuestion, UserAnswer
from django.utils.html import format_html from django.utils.html import format_html
@@ -11,16 +13,6 @@ from easy_thumbnails.exceptions import InvalidImageFormatError
#from generic.tables import UserAnswerTable #from generic.tables import UserAnswerTable
class ImageColumn(tables.Column):
def render(self, value):
try:
thumbnailer = get_thumbnailer(value)
return format_html('<img src="/media/{}" />', thumbnailer["exam-list"])
except InvalidImageFormatError:
return format_html('<span title="{}">Invalid image url<span>', value)
class AnatomyQuestionTable(tables.Table): class AnatomyQuestionTable(tables.Table):
edit = tables.LinkColumn( edit = tables.LinkColumn(
"anatomy:anatomy_question_update", text="Edit", args=[A("pk")], orderable=False "anatomy:anatomy_question_update", text="Edit", args=[A("pk")], orderable=False
@@ -28,7 +20,7 @@ class AnatomyQuestionTable(tables.Table):
view = tables.LinkColumn( view = tables.LinkColumn(
"anatomy:question_detail", text="View", args=[A("pk")], orderable=False "anatomy:question_detail", text="View", args=[A("pk")], orderable=False
) )
image = ImageColumn("image", orderable=False) image = SingleImageColumn("image", orderable=False)
# clone = tables.LinkColumn('anatomy:anatomy_question_clone', # clone = tables.LinkColumn('anatomy:anatomy_question_clone',
# text='Clone', # text='Clone',
# args=[A('pk')], # args=[A('pk')],
@@ -74,7 +66,7 @@ class AnatomyUserAnswerTable(tables.Table):
args=[A('pk')], args=[A('pk')],
orderable=False) orderable=False)
class Meta: class Meta:
model = CidUserAnswer model = UserAnswer
template_name = "django_tables2/bootstrap4.html" template_name = "django_tables2/bootstrap4.html"
fields = ( fields = (
"cid", "cid",
@@ -7,5 +7,5 @@
Answer: {{ciduseranswer.answer}}<br /> Answer: {{ciduseranswer.answer}}<br />
Score: {{ciduseranswer.get_answer_score}}<br /> Score: {{ciduseranswer.get_answer_score}}<br />
<a href="{% url 'anatomy:user_answer_delete' ciduseranswer.id %}">Delete answer</a> <a href="{% url 'anatomy:user_answer_delete' ciduseranswer.id %}">Delete answer</a>
<a href="{% url 'admin:anatomy_ciduseranswer_change' ciduseranswer.id %}">Admin Edit</a> <a href="{% url 'admin:anatomy_useranswer_change' ciduseranswer.id %}">Admin Edit</a>
{% endblock content %} {% endblock content %}
+7 -7
View File
@@ -41,7 +41,7 @@ from .forms import (
from .models import ( from .models import (
AnatomyQuestion, AnatomyQuestion,
BodyPart, BodyPart,
CidUserAnswer, UserAnswer,
# Examination, # Examination,
Structure, Structure,
Exam, Exam,
@@ -384,7 +384,7 @@ def exam_scores_refresh(request, pk):
questions = exam.exam_questions.all() questions = exam.exam_questions.all()
cids = CidUserAnswer.objects.filter(question__in=questions, exam__id=pk) cids = UserAnswer.objects.filter(question__in=questions, exam__id=pk)
# Force a score update # Force a score update
for c in cids: for c in cids:
@@ -733,16 +733,16 @@ def question_save_annotation(request, pk):
GenericExamViews = ExamViews( GenericExamViews = ExamViews(
Exam, AnatomyQuestion, Answer, CidUserAnswer, "anatomy", "anatomy" Exam, AnatomyQuestion, Answer, UserAnswer, "anatomy", "anatomy"
) )
class UserAnswerView(SuperuserRequiredMixin, DetailView): class UserAnswerView(SuperuserRequiredMixin, DetailView):
model = CidUserAnswer model = UserAnswer
class UserAnswerTableView(SuperuserRequiredMixin, SingleTableMixin, FilterView): class UserAnswerTableView(SuperuserRequiredMixin, SingleTableMixin, FilterView):
model = CidUserAnswer model = UserAnswer
table_class = AnatomyUserAnswerTable table_class = AnatomyUserAnswerTable
template_name = "anatomy/user_answer_question_view.html" template_name = "anatomy/user_answer_question_view.html"
@@ -750,7 +750,7 @@ class UserAnswerTableView(SuperuserRequiredMixin, SingleTableMixin, FilterView):
class UserAnswerDelete(SuperuserRequiredMixin, DeleteView): class UserAnswerDelete(SuperuserRequiredMixin, DeleteView):
model = CidUserAnswer model = UserAnswer
template_name = "user_answer_delete.html" template_name = "user_answer_delete.html"
success_url = reverse_lazy("anatomy:user_answer_table_view") success_url = reverse_lazy("anatomy:user_answer_table_view")
@@ -797,7 +797,7 @@ class ExamAuthorUpdate(RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, U
GenericViews = GenericViewBase("anatomy", AnatomyQuestion, CidUserAnswer, Exam) GenericViews = GenericViewBase("anatomy", AnatomyQuestion, UserAnswer, Exam)
class ExamClone(ExamCloneMixin, ExamCreate): class ExamClone(ExamCloneMixin, ExamCreate):
View File
+2
View File
@@ -13,6 +13,7 @@ from .models import (
Structure, Structure,
PathologicalProcess, PathologicalProcess,
Presentation, Presentation,
UncategorisedDicom,
) )
from django.forms import ModelForm from django.forms import ModelForm
@@ -38,6 +39,7 @@ admin.site.register(Differential)
admin.site.register(PathologicalProcess) admin.site.register(PathologicalProcess)
admin.site.register(Presentation) admin.site.register(Presentation)
admin.site.register(CaseDetail) admin.site.register(CaseDetail)
admin.site.register(UncategorisedDicom)
class DifferentialInline(admin.TabularInline): class DifferentialInline(admin.TabularInline):
+201
View File
@@ -0,0 +1,201 @@
from collections import defaultdict
from typing import List
from django.shortcuts import get_object_or_404
from ninja import ModelSchema, Router, Schema, Field
# from .decorators import user_is_author_or_rapid_checker
from django.core.exceptions import PermissionDenied
from generic.decorators import check_user_in_group
from generic.constants import Group
from ninja.security import django_auth
from ninja import NinjaAPI, File
from ninja.files import UploadedFile
from generic.models import Modality
from .models import Case, DuplicateDicom, Series, SeriesImage, UncategorisedDicom
router = Router()
class SeriesSchema(ModelSchema):
case_id: List[int] = []
class Config:
model = Series
model_fields = [
"id",
"modality",
"examination",
"series_instance_uid",
"author",
]
@staticmethod
def resolve_case_id(obj):
return [i.id for i in obj.case.all()]
class CaseSchema(ModelSchema):
class Config:
model = Case
model_fields = ["id", "title"]
@router.post("/upload_dicom", auth=django_auth)
def upload_dicom(request, files: List[UploadedFile] = File(...)):
uploaded = []
duplicate = []
for file in files:
# data = file.read()
try:
ud = UncategorisedDicom(image=file, user=request.user)
ud.save()
uploaded.append((file.name, ud.image_md5_hash))
except DuplicateDicom:
print(ud.check_for_duplicates(ud.image_md5_hash))
duplicate.append((file.name, ud.image_md5_hash))
pass
return {"uploaded": uploaded, "duplicates": duplicate}
@router.get("/clear_dicoms", auth=django_auth)
def clear_dicoms(request):
dicoms = UncategorisedDicom.objects.filter(user=request.user)
dicoms.delete()
return True
@router.get("/uncategorised_dicoms", auth=django_auth)
def uncategorised_dicoms(request):
dicoms = UncategorisedDicom.objects.filter(user=request.user)
data = defaultdict(list)
for d in dicoms:
tags = d.get_basic_dicom_tags()
data[tags["SeriesInstanceUID"]].append(tags)
data["image_hash"].append(d.image_md5_hash)
return data
def import_dicoms_helper(request, case_id: int | None = None):
dicoms = UncategorisedDicom.objects.filter(user=request.user)
data = defaultdict(list)
for d in dicoms:
tags = d.get_basic_dicom_tags()
data[tags["SeriesInstanceUID"]].append((d, tags))
series_list = []
for series_uid in data:
modality = get_object_or_404(Modality, short_code=tags["Modality"])
tags = data[series_uid][0][1]
# Check if series with the id already exists (in which case we just add to htat)
try:
series = Series.objects.get(series_instance_uid=tags["SeriesInstanceUID"])
except Series.DoesNotExist:
series = Series(
modality=modality,
description=tags["SeriesDescription"],
series_instance_uid=tags["SeriesInstanceUID"],
)
series.save()
# We might only want to add the author during creation....
series.author.add(request.user)
if case_id is not None:
series.case.add(case_id)
for dicom, dicom_tags in data[series_uid]:
series_image = SeriesImage(
image=dicom.image,
series=series,
is_dicom=True,
image_md5_hash=dicom.image_md5_hash,
)
series_image.save()
# dicom.image.delete()
dicom.delete()
series_list.append(series)
return series_list
@router.get("/import_dicoms", auth=django_auth, response=List[SeriesSchema])
def import_dicoms(request):
return import_dicoms_helper(request)
@router.get("/import_dicoms/{case_id}", auth=django_auth, response=List[SeriesSchema])
def import_dicoms_case(request, case_id: int):
return import_dicoms_helper(request, case_id=case_id)
@router.get("/orphan_series", auth=django_auth, response=List[SeriesSchema])
def orphan_series(request):
# print(request.user.series.filter(case=None))
return request.user.series.filter(case=None)
@router.get("/series_remove_duplicate_images", auth=django_auth)
def series_remove_duplicate_images(request, series_id: int):
series = get_object_or_404(Series, pk=series_id)
img_ids = set()
dupes = set()
for series_image in series.images.all():
if series_image.image_md5_hash in img_ids:
dupes.add(series_image.id)
img_ids.add(series_image.image_md5_hash)
if dupes:
SeriesImage.objects.filter(id__in=dupes).delete()
return len(dupes)
@router.get("/get_cases_user", auth=django_auth, response=List[CaseSchema])
def get_cases_user(request):
print("HELLO")
return Case.objects.filter(author=request.user)
@router.get("/check_image_hash/{hash}", auth=django_auth)
def check_image_hash(request, hash: str):
try:
print(hash)
series_image = SeriesImage.objects.get(image_md5_hash=hash)
data = {
"status": "success",
"id": series_image.pk,
"url": series_image.series.get_absolute_url(),
}
return data
except SeriesImage.DoesNotExist:
data = {"status": "success", "id": False}
return data
@router.get("/generate_image_hash/{id}", auth=django_auth)
def generate_image_hash(request, id: int):
series_image = SeriesImage.objects.get(pk=id)
series_image.image_md5_hash = ""
series_image.save()
print(series_image)
+44 -4
View File
@@ -1,7 +1,9 @@
from django import forms
from django.contrib.admin import widgets from django.contrib.admin import widgets
from django.forms import ( from django.forms import (
BaseInlineFormSet, BaseInlineFormSet,
Form, Form,
HiddenInput,
ModelForm, ModelForm,
ModelMultipleChoiceField, ModelMultipleChoiceField,
ModelChoiceField, ModelChoiceField,
@@ -9,6 +11,7 @@ from django.forms import (
CharField, CharField,
ValidationError, ValidationError,
modelformset_factory, modelformset_factory,
CheckboxSelectMultiple
) )
from django.forms import inlineformset_factory from django.forms import inlineformset_factory
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
@@ -20,11 +23,15 @@ from atlas.models import (
CidReportAnswer, CidReportAnswer,
Differential, Differential,
Finding, Finding,
SelfReview,
Series, Series,
SeriesImage, SeriesImage,
SeriesFinding, SeriesFinding,
Condition, Condition,
Structure, Structure,
Subspecialty,
UncategorisedDicom,
UserReportAnswer,
) )
from anatomy.models import Modality from anatomy.models import Modality
@@ -315,6 +322,8 @@ class CaseForm(ModelForm):
"presentation": autocomplete.ModelSelect2Multiple( "presentation": autocomplete.ModelSelect2Multiple(
url="atlas:presentation-autocomplete" url="atlas:presentation-autocomplete"
), ),
"subspecialty": CheckboxSelectMultiple(),
"pathological_process": CheckboxSelectMultiple()
} }
def save(self, commit=True): def save(self, commit=True):
@@ -435,10 +444,8 @@ SeriesImageFormSet = inlineformset_factory(
max_num=2000, max_num=2000,
) )
class BaseReportAnswerForm(ModelForm):
class CidReportAnswerForm(ModelForm):
class Meta: class Meta:
model = CidReportAnswer
fields = ["answer"] fields = ["answer"]
widgets = { widgets = {
@@ -455,9 +462,16 @@ class CidReportAnswerForm(ModelForm):
# } # }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(CidReportAnswerForm, self).__init__(*args, **kwargs) super(BaseReportAnswerForm, self).__init__(*args, **kwargs)
self.fields["answer"].required = False self.fields["answer"].required = False
class CidReportAnswerForm(BaseReportAnswerForm):
class Meta(BaseReportAnswerForm.Meta):
model = CidReportAnswer
class UserReportAnswerForm(BaseReportAnswerForm):
class Meta(BaseReportAnswerForm.Meta):
model = UserReportAnswer
class CidReportAnswerMarkForm(ModelForm): class CidReportAnswerMarkForm(ModelForm):
class Meta: class Meta:
@@ -560,3 +574,29 @@ class AddCollectionToCaseForm(Form):
raise ValidationError("Invalid collection") raise ValidationError("Invalid collection")
return collection_set return collection_set
class SelfReviewForm(ModelForm):
class Meta:
model = SelfReview
fields = ["user_exam", "case", "comments", "findings", "interpretation"]
widgets = {"user_exam": HiddenInput(), "case": HiddenInput()}
def __init__(self, *args, **kwargs):
if "user_exam_id" in kwargs:
user_exam_id = kwargs.pop("user_exam_id")
if "case_id" in kwargs:
case_id = kwargs.pop("case_id")
ModelForm.__init__(self, *args, **kwargs)
super(SelfReviewForm, self).__init__(*args, **kwargs)
class UncategorisedDicomForm(ModelForm):
class Meta:
model = UncategorisedDicom
fields = ["image"]#, "user"]
#UncategorisedDicomFormset = formset_factory(UncategorisedDicomForm)
+149 -24
View File
@@ -1,12 +1,11 @@
# Generated by Django 3.2.8 on 2021-11-23 19:28 # Generated by Django 4.1.4 on 2023-06-12 10:07
import atlas.models import atlas.models
from django.conf import settings from django.conf import settings
import django.core.validators
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
import sortedm2m.fields
import tagulous.models.fields
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -14,24 +13,157 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('anatomy', '0053_anatomyquestion_structure'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('generic', '0014_alter_questionnote_note'),
] ]
operations = [ operations = [
migrations.CreateModel(
name='Case',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(default='', help_text='Title of the case', max_length=255)),
('description', models.TextField(blank=True, help_text='Description of the case')),
('history', models.TextField(blank=True, help_text='A (brief) summary of the relevant history', null=True)),
('discussion', models.TextField(blank=True, null=True)),
('report', models.TextField(blank=True, help_text='A model (sample) report for the case.', null=True)),
('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 a case should be freely available to browse')),
],
),
migrations.CreateModel(
name='CaseCollection',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('publish_results', models.BooleanField(default=False, help_text='If a collection should published')),
('active', models.BooleanField(default=True, help_text='If a collection should be available')),
('show_title_pre', models.BooleanField(default=False, help_text='Show the title of the cases (pre exam)')),
('show_history_pre', models.BooleanField(default=False, help_text='Show the history of the cases (pre exam)')),
('show_description_pre', models.BooleanField(default=False, help_text='Show the description of the cases (pre exam)')),
('show_discussion_pre', models.BooleanField(default=False, help_text='Show the case discussion (pre exam)')),
('show_report_pre', models.BooleanField(default=False, help_text='Show the case report (pre exam)')),
('show_title_post', models.BooleanField(default=False, help_text='Show the title of the cases (post exam)')),
('show_history_post', models.BooleanField(default=False, help_text='Show the history of the cases (post exam)')),
('show_description_post', models.BooleanField(default=False, help_text='Show the description of the cases (post exam)')),
('show_discussion_post', models.BooleanField(default=False, help_text='Show the case discussion (post exam)')),
('show_report_post', models.BooleanField(default=False, help_text='Show the case report (post exam)')),
('archive', models.BooleanField(default=False)),
('exam_mode', models.BooleanField(default=False)),
('open_access', models.BooleanField(default=False, help_text='If the exam is freely accessible (to view and edit on the test system)')),
('collection_type', models.CharField(choices=[('REV', 'Review'), ('REP', 'Report')], default='REP', max_length=3)),
],
),
migrations.CreateModel(
name='CaseDetail',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sort_order', models.IntegerField(default=1000)),
],
options={
'ordering': ('sort_order',),
},
),
migrations.CreateModel(
name='CidReportAnswer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('answer', models.TextField(blank=True)),
('feedback', models.TextField(blank=True)),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('score', models.IntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(10), django.core.validators.MinValueValidator(0)])),
('cid', models.BigIntegerField(help_text='Candidate ID')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Condition',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('primary', models.BooleanField(default='True')),
],
bases=(atlas.models.SynMixin, models.Model),
),
migrations.CreateModel(
name='Differential',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text', models.TextField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='Finding',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('primary', models.BooleanField(default='True')),
],
bases=(atlas.models.SynMixin, models.Model),
),
migrations.CreateModel(
name='PathologicalProcess',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name='Presentation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
],
),
migrations.CreateModel( migrations.CreateModel(
name='Series', name='Series',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('description', models.TextField(blank=True, help_text='Description of stack, for admin organisation, will not be visible when taking')), ('description', models.TextField(blank=True, help_text='Description of stack, for admin organisation, will not be visible when taking')),
('open_access', models.BooleanField(default=True, help_text='If a question should be freely available to browse')), ('open_access', models.BooleanField(default=True, help_text='If a series should be freely available to browse')),
('author', models.ManyToManyField(blank=True, related_name='series', to=settings.AUTH_USER_MODEL)), ('author', models.ManyToManyField(blank=True, related_name='series', to=settings.AUTH_USER_MODEL)),
('contrast', models.ForeignKey(blank=True, help_text='MRI / CT contrast', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='atlas_series_contrast', to='generic.contrast')),
('examination', models.ForeignKey(help_text='Name of the examination', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='atlas_series_examination', to='generic.examination')),
('modality', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='atlas_series_modality', to='anatomy.modality')),
('plane', models.ForeignKey(blank=True, help_text='Plane of the examination', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='atlas_series_plane', to='generic.plane')),
], ],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Subspecialty',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name='UserReportAnswer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('answer', models.TextField(blank=True)),
('feedback', models.TextField(blank=True)),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('score', models.IntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(10), django.core.validators.MinValueValidator(0)])),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='atlas.casedetail')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Structure',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('primary', models.BooleanField(default='True')),
('synonym', models.ManyToManyField(blank=True, to='atlas.structure')),
],
bases=(atlas.models.SynMixin, models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name='SeriesImage', name='SeriesImage',
@@ -47,22 +179,15 @@ class Migration(migrations.Migration):
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Case', name='SeriesFinding',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('description', models.TextField(blank=True, help_text='Description of the case, for admin organisation, will not be visible when taking')), ('description', models.TextField(blank=True, help_text='Findings on the series / stack', null=True)),
('history', models.TextField(blank=True, null=True)), ('annotation_json', models.TextField(blank=True, null=True)),
('findings', models.TextField(blank=True, null=True)), ('viewport_json', models.TextField(blank=True, null=True)),
('subpecialty', models.CharField(choices=[('BR', 'Breast'), ('CA', 'Cardiac'), ('GI', 'Gastrointestinal and hepatobiliary'), ('HN', 'Head and Neck'), ('MS', 'Musculoskeletal'), ('NE', 'Neuroradiology'), ('OG', 'Obstectric and Gynaecological'), ('PA', 'Paediatric'), ('UR', 'Uroradiology'), ('VA', 'Vascular'), ('HA', 'Haemotology and Oncology')], max_length=2)), ('findings', models.ManyToManyField(blank=True, to='atlas.finding')),
('verified', models.BooleanField(default=False)), ('series', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='findings', to='atlas.series')),
('created_date', models.DateTimeField(default=django.utils.timezone.now)), ('structures', models.ManyToManyField(blank=True, to='atlas.structure')),
('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 a question should be freely available to browse')),
('author', models.ManyToManyField(blank=True, help_text='Author of question', related_name='atlas_authored_questions', to=settings.AUTH_USER_MODEL)),
('condition', 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='generic.Condition')),
('series', sortedm2m.fields.SortedManyToManyField(help_text=None, related_name='atlas', to='atlas.Series')),
('sign', tagulous.models.fields.TagField(_set_tag_meta=True, blank=True, help_text='Radiological signs in the question', to='generic.Sign')),
], ],
), ),
] ]
@@ -1,24 +0,0 @@
# Generated by Django 3.2.8 on 2021-11-23 22:40
from django.db import migrations, models
import sortedm2m.fields
class Migration(migrations.Migration):
dependencies = [
('atlas', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='case',
name='title',
field=models.CharField(default='', help_text='Title of the case', max_length=255),
),
migrations.AlterField(
model_name='case',
name='series',
field=sortedm2m.fields.SortedManyToManyField(help_text=None, related_name='case', to='atlas.Series'),
),
]
+156
View File
@@ -0,0 +1,156 @@
# Generated by Django 4.1.4 on 2023-06-12 10:07
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import sortedm2m.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
('generic', '0001_initial'),
('anatomy', '0002_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('atlas', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='series',
name='contrast',
field=models.ForeignKey(blank=True, help_text='MRI / CT contrast', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='atlas_series_contrast', to='generic.contrast'),
),
migrations.AddField(
model_name='series',
name='examination',
field=models.ForeignKey(help_text='Name of the examination', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='atlas_series_examination', to='generic.examination'),
),
migrations.AddField(
model_name='series',
name='modality',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='atlas_series_modality', to='anatomy.modality'),
),
migrations.AddField(
model_name='series',
name='plane',
field=models.ForeignKey(blank=True, help_text='Plane of the examination', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='atlas_series_plane', to='generic.plane'),
),
migrations.AddField(
model_name='presentation',
name='subspecialty',
field=models.ManyToManyField(blank=True, to='atlas.subspecialty'),
),
migrations.AddField(
model_name='finding',
name='synonym',
field=models.ManyToManyField(blank=True, to='atlas.finding'),
),
migrations.AddField(
model_name='differential',
name='case',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='differentialcase', to='atlas.case'),
),
migrations.AddField(
model_name='differential',
name='condition',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='atlas.condition'),
),
migrations.AddField(
model_name='condition',
name='parent',
field=models.ManyToManyField(blank=True, to='atlas.condition'),
),
migrations.AddField(
model_name='condition',
name='subspecialty',
field=models.ManyToManyField(blank=True, to='atlas.subspecialty'),
),
migrations.AddField(
model_name='condition',
name='synonym',
field=models.ManyToManyField(blank=True, to='atlas.condition'),
),
migrations.AddField(
model_name='cidreportanswer',
name='question',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='atlas.casedetail'),
),
migrations.AddField(
model_name='casedetail',
name='case',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='atlas.case'),
),
migrations.AddField(
model_name='casedetail',
name='collection',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='atlas.casecollection'),
),
migrations.AddField(
model_name='casecollection',
name='author',
field=models.ManyToManyField(blank=True, help_text='Author of the collection', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='casecollection',
name='cases',
field=models.ManyToManyField(through='atlas.CaseDetail', to='atlas.case'),
),
migrations.AddField(
model_name='casecollection',
name='cid_user_groups',
field=models.ManyToManyField(blank=True, help_text='These groups define which candidates are able to be added to the exams/collection.', to='generic.cidusergroup'),
),
migrations.AddField(
model_name='casecollection',
name='valid_cid_users',
field=models.ManyToManyField(blank=True, related_name='casecollection_exams', to='generic.ciduser'),
),
migrations.AddField(
model_name='case',
name='author',
field=models.ManyToManyField(blank=True, help_text='Author of the case', related_name='atlas_authored_cases', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='case',
name='condition',
field=models.ManyToManyField(blank=True, to='atlas.condition'),
),
migrations.AddField(
model_name='case',
name='differential',
field=models.ManyToManyField(related_name='casedifferential', through='atlas.Differential', to='atlas.condition'),
),
migrations.AddField(
model_name='case',
name='editor',
field=models.ManyToManyField(blank=True, related_name='atlas_edited_cases', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='case',
name='pathological_process',
field=models.ManyToManyField(blank=True, to='atlas.pathologicalprocess'),
),
migrations.AddField(
model_name='case',
name='presentation',
field=models.ManyToManyField(blank=True, to='atlas.presentation'),
),
migrations.AddField(
model_name='case',
name='previous_case',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='next_case', to='atlas.case'),
),
migrations.AddField(
model_name='case',
name='series',
field=sortedm2m.fields.SortedManyToManyField(help_text=None, related_name='case', to='atlas.series'),
),
migrations.AddField(
model_name='case',
name='subspecialty',
field=models.ManyToManyField(blank=True, to='atlas.subspecialty'),
),
]
@@ -0,0 +1,29 @@
# Generated by Django 4.1.4 on 2023-06-19 10:51
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("generic", "0002_modality_examination_modality"),
("atlas", "0002_initial"),
]
operations = [
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.AlterField(
model_name="series",
name="modality",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="atlas_series_modality",
to="generic.modality",
),
),
],
database_operations=[],
)
]
@@ -1,18 +0,0 @@
# Generated by Django 3.2.8 on 2021-11-24 18:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('atlas', '0002_auto_20211123_2240'),
]
operations = [
migrations.RenameField(
model_name='case',
old_name='subpecialty',
new_name='subspecialty',
),
]
@@ -1,26 +0,0 @@
# Generated by Django 3.2.8 on 2021-11-25 22:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('atlas', '0003_rename_subpecialty_case_subspecialty'),
]
operations = [
migrations.CreateModel(
name='Condition',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('synonym', models.ManyToManyField(blank=True, related_name='_atlas_condition_synonym_+', to='atlas.Condition')),
],
),
migrations.AlterField(
model_name='case',
name='condition',
field=models.ManyToManyField(blank=True, to='atlas.Condition'),
),
]
@@ -1,4 +1,4 @@
# Generated by Django 3.2.10 on 2022-04-01 22:15 # Generated by Django 4.1.4 on 2023-07-03 12:52
from django.db import migrations, models from django.db import migrations, models
@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('atlas', '0034_auto_20220401_2235'), ('atlas', '0003_alter_series_modality'),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='casecollection', model_name='casecollection',
name='show_history', name='self_review',
field=models.BooleanField(default=False, help_text='Show the history of the cases'), field=models.BooleanField(default=False, help_text='If true allows users self complete and review cases in a self directed way.'),
), ),
] ]
@@ -0,0 +1,36 @@
# Generated by Django 4.1.4 on 2023-07-24 09:06
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('generic', '0006_alter_ciduserexam_completed'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('atlas', '0004_casecollection_self_review'),
]
operations = [
migrations.AddField(
model_name='casecollection',
name='user_user_groups',
field=models.ManyToManyField(blank=True, help_text='These groups define which candidates are able to be added to the exams/collection.', related_name='casecollection_user_user_groups', to='generic.userusergroup'),
),
migrations.AddField(
model_name='casecollection',
name='valid_user_users',
field=models.ManyToManyField(blank=True, related_name='user_casecollection_exams', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='casecollection',
name='author',
field=models.ManyToManyField(blank=True, help_text='Author of the collection', related_name='casecollection_authored_cases', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='casecollection',
name='cid_user_groups',
field=models.ManyToManyField(blank=True, help_text='These groups define which candidates are able to be added to the exams/collection.', related_name='casecollection_cid_user_groups', to='generic.cidusergroup'),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.2.8 on 2021-11-25 22:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('atlas', '0004_auto_20211125_2216'),
]
operations = [
migrations.AddField(
model_name='condition',
name='primary_name',
field=models.BooleanField(default='True'),
),
]
@@ -1,34 +0,0 @@
# Generated by Django 3.2.4 on 2021-11-28 14:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('atlas', '0005_condition_primary_name'),
]
operations = [
migrations.CreateModel(
name='Subspecialty',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
],
),
migrations.RemoveField(
model_name='case',
name='subspecialty',
),
migrations.AddField(
model_name='condition',
name='subspecialty',
field=models.ManyToManyField(blank=True, to='atlas.Subspecialty'),
),
migrations.AddField(
model_name='case',
name='subspecialty',
field=models.ManyToManyField(blank=True, to='atlas.Subspecialty'),
),
]
@@ -0,0 +1,43 @@
# Generated by Django 4.1.4 on 2023-07-24 09:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('atlas', '0005_casecollection_user_user_groups_and_more'),
]
operations = [
migrations.AddField(
model_name='casecollection',
name='candidates_only',
field=models.BooleanField(default=True, help_text='If the exam/collection is only available to candidates who have been added'),
),
migrations.AlterField(
model_name='casecollection',
name='active',
field=models.BooleanField(default=False, help_text='If an exam/collection should be available to take'),
),
migrations.AlterField(
model_name='casecollection',
name='archive',
field=models.BooleanField(default=False, help_text='Archived exams/collections will remain on the test system but will not be displayed by default'),
),
migrations.AlterField(
model_name='casecollection',
name='name',
field=models.CharField(help_text='Name of the exam/collection', max_length=200),
),
migrations.AlterField(
model_name='casecollection',
name='open_access',
field=models.BooleanField(default=False, help_text='If the exam/collection is freely accessible (to view and edit on the test system)'),
),
migrations.AlterField(
model_name='casecollection',
name='publish_results',
field=models.BooleanField(default=False, help_text='If an exam/collections results should be available'),
),
]
@@ -1,87 +0,0 @@
# Generated by Django 3.2.4 on 2021-11-28 16:22
import atlas.models
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("atlas", "0006_auto_20211128_1448"),
]
operations = [
migrations.CreateModel(
name="Finding",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("primary_name", models.BooleanField(default="True")),
(
"synonym",
models.ManyToManyField(
blank=True,
related_name="_atlas_finding_synonym_+",
to="atlas.Finding",
),
),
],
bases=(atlas.models.SynMixin, models.Model),
),
migrations.RemoveField(
model_name="case",
name="findings",
),
migrations.AlterField(
model_name="case",
name="description",
field=models.TextField(blank=True, help_text="Description of the case"),
),
migrations.AlterField(
model_name="series",
name="description",
field=models.TextField(blank=True, help_text="Description of stack"),
),
migrations.CreateModel(
name="SeriesFindings",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"description",
models.TextField(
blank=True,
help_text="Findings on the series / stack",
null=True,
),
),
("annotation_json", models.TextField(blank=True, null=True)),
("findings", models.ManyToManyField(blank=True, to="atlas.Finding")),
(
"series",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="findings",
to="atlas.series",
),
),
],
),
]
+29
View File
@@ -0,0 +1,29 @@
# Generated by Django 4.1.4 on 2023-07-24 11:42
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('generic', '0008_delete_selfreview'),
('atlas', '0006_casecollection_candidates_only_and_more'),
]
operations = [
migrations.CreateModel(
name='SelfReview',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('comments', models.TextField(blank=True, null=True)),
('findings', models.IntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(5), django.core.validators.MinValueValidator(1)])),
('interpretation', models.IntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(5), django.core.validators.MinValueValidator(1)])),
('review_date', models.DateTimeField(auto_now_add=True)),
('review_update_date', models.DateTimeField(auto_now=True)),
('case', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='atlas.case')),
('user_exam', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='generic.ciduserexam')),
],
),
]
@@ -0,0 +1,22 @@
# Generated by Django 4.1.4 on 2023-08-07 08:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('atlas', '0007_selfreview'),
]
operations = [
migrations.RemoveField(
model_name='case',
name='scrapped',
),
migrations.AddField(
model_name='case',
name='archive',
field=models.BooleanField(default=False, help_text='Case has been archived, this will remain on the system but will not be shown'),
),
]
@@ -1,17 +0,0 @@
# Generated by Django 3.2.8 on 2021-11-29 19:10
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('atlas', '0007_auto_20211128_1622'),
]
operations = [
migrations.RenameModel(
old_name='SeriesFindings',
new_name='SeriesFinding',
),
]
@@ -0,0 +1,18 @@
# Generated by Django 4.1.4 on 2023-08-07 21:00
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('atlas', '0008_remove_case_scrapped_case_archive'),
]
operations = [
migrations.RenameField(
model_name='series',
old_name='description',
new_name='info',
),
]

Some files were not shown because too many files have changed in this diff Show More