Django multi level category

My project is about laboratory reporting system. I have three models TestCategory, Test and Report

class TestCategory(MPTTModel):
name = models.CharField(max_length=100)
parent = TreeForeignKey(‘self’, on_delete=models.CASCADE, null=True, blank=True, related_name=‘children’)
…

class Test(models.Model):
test_category = TreeForeignKey(TestCategory, on_delete=models.CASCADE, related_name=‘test_category’)
name = models.CharField(max_length=50)
…

class Report(models.Model):
patient_id = models.ForeignKey(‘Patient’, related_name=‘patients’, on_delete=models.CASCADE)
test_id = models.ManyToManyField(Test, verbose_name='Select Tests', related_name='tests')

I am using django-mptt, it works great in the sense to add category sub category from admin site but i am really having a hard to render the proper data in front end. I tried to follow mptt documentation but could only retrieve the category and sub category name for TestCategory model.
what i want is a form where user should be able to select the test they want to perform.
Example

* Bio Chemistry (Category name from TestCategory model)
  - (input checkbox) Glucose, Random (Test Model that has Bio Chemistry as Parent category)
  - (input checkbox) Glucose, Fasting (Test Model that has Bio Chemistry as Parent category)

* Parasitology (Category name from TestCategory model)
  ** Urine Test (parasitology sub category)
     *** Physical Examination (urine test sub category)
           - (input checkbox) Color (Test Model that has Physical Examination as Parent category)

*** Microscopic Examination
    - -(input checkbox) RBC (Test Model that has Microscopic Examination as Parent category)

Similarly there can be Stool Test and they other sub categories inside stool like physical, chemical etc.

I have attached a screen shot of my app. This one is hard coded and very badly coded app. All the categories, subcategories are used for Text or Label purpose only so frontend was hard coded with fields. I developed this years ago using php and codeigniter and now rewriting the same app in django.

Also in other note, I don’t need to use mptt. I don’t need any features like moving nodes or something like that. What i am looking for is a multi level category and list the test items. I will love it keep it very simple. Categories and sub categories will only have a name field that will be used to group the tests that are associated.

Thanks in advance

Hi,

I can demonstrate how I am doing which I believe is what you are also trying to do, if I have interpreted your post correctly.

I have models which represent a medical test, which in turn has multiple parameters and a parameter has a single result. Like what you are doing, tests belong to either a category or a subcategory. A subcategory has a parent category.

class Category(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4)
    name = models.CharField(max_length=128)
    parent = models.ForeignKey(
        "Category", related_name="categories", null=True, blank=True, on_delete=models.CASCADE
    )
    group = models.ForeignKey(Group, related_name="categories", on_delete=models.CASCADE)

I have a model Test which has tests which link to a category by way of a foreign key.

class Test(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4)
    name = models.CharField(max_length=64)
    category = models.ForeignKey(Category, related_name="tests", on_delete=models.CASCADE)
    parameters = models.ManyToManyField(Parameter, related_name="tests")

In order to return a queryset with only categories and not subcategory, I filter with parent=None

class TestCategoriesViewSet(generics.ListAPIView):
    serializer_class = CategorySerializer
    permission_classes = [IsAuthenticated]

    def get_object(self):
        return get_object_or_404(self.get_queryset(), case__id=self.kwargs["case"])

    def get_queryset(self):
        return CaseCategory.objects.all().filter(
            case__id=self.kwargs["case"],
            case__group__name__exact=self.kwargs["group"],
            parent=None,
        )

I use the serializer CategorySerializer to return the subcategory by way of the relation categories as defined in my model’s parent attribute. You’ll also note that I am using a method to fetch the tests which belong to a category or subcategory using the related name tests so I can access all of a category’s tests using category.tests

class SubCategorySerializer(serializers.ModelSerializer):
    tests = serializers.SerializerMethodField()
    display_order = serializers.SerializerMethodField()

    class Meta:
        model = Category
        fields = ("id", "name", "tests")

    @staticmethod
    def get_tests(obj):
        return TestSerializer(obj.case_tests, many=True, read_only=True).data


class CategorySerializer(serializers.ModelSerializer):
    tests = serializers.SerializerMethodField()
    sub_categories = serializers.SerializerMethodField()

    class Meta:
        model = Category
        fields = ("id", "name", "tests", "sub_categories")

    @staticmethod
    def get_tests(obj):
        return TestSerializer(obj.case_tests, many=True, read_only=True).data

    @staticmethod
    def get_sub_categories(obj):
        return SubCategorySerializer(obj.categories, many=True, read_only=True).data

The combination of the models in the first two code examples, the queryset in the third piece of example code and the serializers renders JSON data to the frontend like this:

[
    {
        "id": "36d6f7a3-7c9f-45d6-8c20-fc2070dc7ef8",
        "name": "Pathology",
        "tests": [
            {
              "id": "428f8b79",
              "name": "category level tests can be here",
            },
        ],
        "sub_categories": [
            {
                "id": "9ab16b09",
                "name": "General",
                "tests": [
                    {
                        "id": "428f8b79",
                        "name": "Fecal occult blood",
                    },
                    {
                        "id": "5b34b27a",
                        "name": "Exploratory laparotomy",
                    }
                ],
            },

Had I wanted to, I could have returned a test’s parameters and a parameter’s result in the above JSON, but the frontend fetches them serpately as to not expose results before a user chooses to run a test.

Hope this help and best of luck with it.

Conor

2 Likes

Thank you so much for the response Conor. Really appreciate. I will definitely try your idea.