diff --git a/atlas/admin.py b/atlas/admin.py index bb79dcce..a43e60e4 100755 --- a/atlas/admin.py +++ b/atlas/admin.py @@ -7,6 +7,7 @@ from .models import ( Series, SeriesImage, Condition, + ConditionRelationship, Subspecialty, Finding, SeriesFinding, @@ -31,6 +32,7 @@ from generic.models import Examination # admin.site.register(Site) admin.site.register(SeriesImage) admin.site.register(Condition) +admin.site.register(ConditionRelationship) admin.site.register(Subspecialty) admin.site.register(Finding) admin.site.register(Structure) diff --git a/atlas/filters.py b/atlas/filters.py index 4e1b0610..4b809553 100755 --- a/atlas/filters.py +++ b/atlas/filters.py @@ -163,7 +163,7 @@ class ConditionFilter(django_filters.FilterSet): "name": ["icontains"], "primary": ["exact"], "synonym": ["exact"], - "parent": ["exact"], + #"parent": ["exact"], "subspecialty": ["exact"], "rcr_curriculum": ["exact"], } diff --git a/atlas/migrations/0026_alter_condition_parent.py b/atlas/migrations/0026_alter_condition_parent.py new file mode 100644 index 00000000..288ddd9e --- /dev/null +++ b/atlas/migrations/0026_alter_condition_parent.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.4 on 2023-11-06 09:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0025_case_diagnostic_certainty'), + ] + + operations = [ + migrations.AlterField( + model_name='condition', + name='parent', + field=models.ManyToManyField(blank=True, related_name='child', to='atlas.condition'), + ), + ] diff --git a/atlas/migrations/0027_remove_condition_parent_conditionrelationship_and_more.py b/atlas/migrations/0027_remove_condition_parent_conditionrelationship_and_more.py new file mode 100644 index 00000000..185b7be1 --- /dev/null +++ b/atlas/migrations/0027_remove_condition_parent_conditionrelationship_and_more.py @@ -0,0 +1,32 @@ +# Generated by Django 4.1.4 on 2023-11-06 10:09 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0026_alter_condition_parent'), + ] + + operations = [ + migrations.RemoveField( + model_name='condition', + name='parent', + ), + migrations.CreateModel( + name='ConditionRelationship', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('relationship_type', models.CharField(default='', max_length=255)), + ('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='atlas.condition')), + ('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='atlas.condition')), + ], + ), + migrations.AddField( + model_name='condition', + name='parents', + field=models.ManyToManyField(blank=True, related_name='children', through='atlas.ConditionRelationship', to='atlas.condition'), + ), + ] diff --git a/atlas/migrations/0028_remove_condition_parents_condition_parent.py b/atlas/migrations/0028_remove_condition_parents_condition_parent.py new file mode 100644 index 00000000..3c05c971 --- /dev/null +++ b/atlas/migrations/0028_remove_condition_parents_condition_parent.py @@ -0,0 +1,22 @@ +# Generated by Django 4.1.4 on 2023-11-06 10:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0027_remove_condition_parent_conditionrelationship_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='condition', + name='parents', + ), + migrations.AddField( + model_name='condition', + name='parent', + field=models.ManyToManyField(blank=True, related_name='child', through='atlas.ConditionRelationship', to='atlas.condition'), + ), + ] diff --git a/atlas/migrations/0029_alter_conditionrelationship_child_and_more.py b/atlas/migrations/0029_alter_conditionrelationship_child_and_more.py new file mode 100644 index 00000000..b9f7a4db --- /dev/null +++ b/atlas/migrations/0029_alter_conditionrelationship_child_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.1.4 on 2023-11-06 10:14 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0028_remove_condition_parents_condition_parent'), + ] + + operations = [ + migrations.AlterField( + model_name='conditionrelationship', + name='child', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='atlas.condition'), + ), + migrations.AlterField( + model_name='conditionrelationship', + name='parent', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='atlas.condition'), + ), + migrations.AlterField( + model_name='conditionrelationship', + name='relationship_type', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/atlas/models.py b/atlas/models.py index ddb77bb4..b499d7ea 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -144,7 +144,7 @@ class Condition(SynMixin, models.Model): name = models.CharField(max_length=255, unique=True) synonym = models.ManyToManyField("self", blank=True) - parent = models.ManyToManyField("self", blank=True) + parent = models.ManyToManyField("self", blank=True, related_name="child", symmetrical=False, through="ConditionRelationship") primary = models.BooleanField(default="True") @@ -157,6 +157,25 @@ class Condition(SynMixin, models.Model): def get_absolute_url(self): return reverse("atlas:condition_detail", kwargs={"pk": self.pk}) + def get_children(self): + return self.child.all() + + def get_descendents(self): + """TODO""" + + + def get_parents(self): + return self.parent.all() + +class ConditionRelationship(models.Model): + child = models.ForeignKey(Condition, on_delete=models.CASCADE) + parent = models.ForeignKey(Condition, on_delete=models.CASCADE, related_name="+") + + relationship_type = models.CharField(max_length=255, blank=True, null=True) + + def __str__(self): + return f"{self.parent} -> {self.child}" + class Presentation(models.Model): name = models.CharField(max_length=255) diff --git a/atlas/templates/atlas/condition_detail.html b/atlas/templates/atlas/condition_detail.html index f5f21399..8208b8dd 100755 --- a/atlas/templates/atlas/condition_detail.html +++ b/atlas/templates/atlas/condition_detail.html @@ -26,6 +26,10 @@ {% for parent in condition.parent.all %} {{parent}} {% endfor %}
+ Children: + {% for child in condition.child.all %} + {{child}} + {% endfor %}
RCR condition: {{condition.rcr_curriculum}}
{% if not condition.rcr_curriculum %} diff --git a/atlas/tests/test_atlas_models.py b/atlas/tests/test_atlas_models.py new file mode 100644 index 00000000..0b39434c --- /dev/null +++ b/atlas/tests/test_atlas_models.py @@ -0,0 +1,75 @@ + +from django.urls import reverse +import pytest +from atlas.models import Finding, Condition +#from atlas.views import GenericExamViews + +#@pytest.mark.django_db +def test_create_condition(db): + abnormality = Condition.objects.create(name="Abnorm") + assert abnormality.name == "Abnorm" + + +def test_condition_tree_structure(db): + parent = Condition.objects.create(name="parent") + + child1 = Condition.objects.create(name="child1") + child2 = Condition.objects.create(name="child2") + + parent.child.set([child1, child2]) + + child1_1 = Condition.objects.create(name="child1_1") + child1_2 = Condition.objects.create(name="child1_2") + + child1.child.set([child1_1, child1_2]) + + assert len(parent.get_children()) == 2 + assert len(child1.get_children()) == 2 + + assert len(child1.get_parents()) == 1 + assert parent in child1.get_parents() + + assert len(child2.get_parents()) == 1 + assert parent in child2.get_parents() + + print(parent.get_descendents()) + +#@pytest.fixture +#def create_basic_user(db, django_user_model): +# return django_user_model.objects.create_superuser("basicuser", "ross@xkjq.uk", "password") +# +#@pytest.fixture +#def create_exam(db): +# return Exam.objects.create(name="test exam", exam_mode=True) +# +#def test_exam_creation(create_exam): +# exams = Exam.objects.filter(name="test exam") +# assert exams.exists() +# +# exam = exams.first() +# +# assert exam.time_limit == 35 * 60 +# +#def test_exam_list(rf, admin_user, create_exam): +# request = rf.get('/rapids/exam/') +# # Remember that when using RequestFactory, the request does not pass +# # through middleware. If your view expects fields such as request.user +# # to be set, you need to set them explicitly. +# # The following line sets request.user to an admin user. +# request.user = admin_user +# response = GenericExamViews.exam_list(request) +# #print(response) +# assert response.status_code == 200 +# +#def test_exam_active_admin(rf, admin_user, create_exam, client): +# request = rf.get(reverse('rapids:active_exams')) +# request.user = admin_user +# response = GenericExamViews.active_exams(request) +# print(response.content) +# assert response.status_code == 200 +# +#def test_exam_active_basic_user(create_basic_user, create_exam, client): +# client.login(username="basicuser", password="password") +# response = client.get(reverse('rapids:active_exams')) +# print(response.content) +# assert response.status_code == 200 \ No newline at end of file diff --git a/generic/templates/generic/user_view.html b/generic/templates/generic/user_view.html index 08d2b99c..0152e1d9 100644 --- a/generic/templates/generic/user_view.html +++ b/generic/templates/generic/user_view.html @@ -1,5 +1,6 @@ {% extends 'generic/base.html' %} +{% load crispy_forms_tags %} {% load render_table from django_tables2 %} {% block css %} {% endblock %} @@ -12,7 +13,7 @@

Filter Users

- {{ filter.form }} + {{ filter.form| crispy }}
diff --git a/rcr/migrations/0002_alter_item_category_alter_item_level.py b/rcr/migrations/0002_alter_item_category_alter_item_level.py new file mode 100644 index 00000000..8f3b7ace --- /dev/null +++ b/rcr/migrations/0002_alter_item_category_alter_item_level.py @@ -0,0 +1,24 @@ +# Generated by Django 4.1.4 on 2023-11-06 09:21 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('rcr', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='item', + name='category', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rcr.category'), + ), + migrations.AlterField( + model_name='item', + name='level', + field=models.ManyToManyField(blank=True, to='rcr.level'), + ), + ] diff --git a/templates/base.html b/templates/base.html index 9f14c790..21381ff0 100644 --- a/templates/base.html +++ b/templates/base.html @@ -12,12 +12,12 @@ {% endblock %} {% comment %} {% endcomment %} - + - - + +