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 @@