Django queryset with matches for multiple related objects
Asked Answered
K

1

0

My Model:

class Pattern(models.Model):
    name = CICharField("Pattern Name", max_length=200, unique=True)
    symptoms = models.ManyToManyField(Symptom, through='PatternSymptom', related_name='patterns')
    tongue_colour = models.ManyToManyField(Color, verbose_name="Tongue Body Colour", blank=True, related_name='patterns')
    tongue_shape = models.ManyToManyField(Shape, verbose_name="Tongue Body Shape", blank=True, related_name='patterns')

class Shape(models.Model):
    name = CICharField(max_length=300, unique=True)

class Color(models.Model):
    name = CICharField(max_length=300, unique=True)

class Symptom(models.Model):
    name = CICharField(max_length=300, unique=True)

On the front end, Users can select multiple Symptoms, colors, and Shape to find patterns that will pass to the Pattern model. I have the following get_queryset on Pattern > views.py

    def get_queryset(self):
        params = self.request.query_params
        query_symptoms = self.request.GET.getlist('symptoms_selected')
        tongue_colour = self.request.GET.get('tongue_colour')
        tongue_shape = self.request.GET.get('tongue_shape')

        if query_symptoms:
            queryset = Pattern.objects.filter(
                symptoms__id__in=query_symptoms
            ).annotate(
                symptom_matched=Count('symptoms')
            )
        else:
            queryset = Pattern.objects.all().filter(is_searchable=True)

        if tongue_colour is not None and tongue_colour.isnumeric():
            queryset = queryset.filter(tongue_colour__id__in=tongue_colour).annotate(tongue_color_matches=Count('tongue_colour'));

        if tongue_shape is not None and tongue_shape.isnumeric():
            queryset = queryset.filter(tongue_shape__id__exact=tongue_shape).annotate(tongue_shape_matches=Count('tongue_shape'));

        return queryset

With this code I can get, queryset with matches symptoms AND tongue_colour AND tongue_shape. But, I want to show queryset with OR/all combinations with what matched.

I am using Django REST API to pass the result data.

class PatternSerializer(serializers.ModelSerializer):
    
    symptoms_matched = serializers.SerializerMethodField()

    class Meta:
        model = Pattern
        fields = ('id', 'name', 'symptoms_matched')

    def get_symptoms_matched(self, obj):
        return getattr(obj, 'symptoms_matched', None)

For eg: Pattern Data:

Pattern A
symptoms: A, B, C, D
tongue_colour: TC1,TC2,TC5
tongue_shape: TS1,TS3,TS5

Pattern B
symptoms: A, D, P, Q
tongue_colour: TC2,TC3,TC6
tongue_shape: TS1,TS2,TS6

Pattern C 
symptoms: A, Q, X, Y
tongue_colour: TC1,TC4,TC7
tongue_shape: TS1,TS4,TS7

For example, if Users select: symptoms: A, Y. tongue_colour: TC1 tongue_shape: TS7

It returns Null. Because there is no exact match. Instead of Null, I want to show users matches with any combination by showing what matched symptoms, color, shape, etc. I want queryset will return all combinations:

Expected Result of above example be like:

3 patterns matched.

[
    {
        "name": "Pattern A",
        "symptoms_matched": 1,
        "tongue_color_matched": 1,
        "tongue_shape_matched": 0,
    },
    {
        "name": "Pattern B",
        "symptoms_matched": 1,
        "tongue_color_matched": 0,
        "tongue_shape_matched": 0,
    },
    {
        "name": "Pattern C",
        "symptoms_matched": 1,
        "tongue_color_matched": 0,
        "tongue_shape_matched": 1,
    },
]

Can anyone tell me how can I achieve this?

Kasandrakasevich answered 23/10, 2022 at 21:0 Comment(1)
T
0

You can use a Q-object in order to do a query with OR.

from django.db.models import Q 

queryset = Pattern.objects.filter(
    Q(symptoms__id__in=symptom) |
    Q(tongue_shape__id__exact=tongue_shape) |
    Q(tongue_colour__id__in=tongue_colour))
Threnody answered 23/10, 2022 at 22:20 Comment(3)
Thank you for the response. But how do you know, what was matched on each Pattern? all the variables are not set always, How do you check if there is no tongue_shape?Kasandrakasevich
Hello, Yes you might need to check which params the user has selected before commiting to the query and have different queries depending on the params (unless theres a better way i don't know about). But in this case I think this will work. Let's say your first OR-clause returns 10 results. If the user havn't selected a tongue_shape they won't alter that result since no tongue_shape has None or Blank as ID.Threnody
Hi, on the serializer it will return all related tongue_shape objects, not the matched one. How can I pass the matched tongue_shape one ?Kasandrakasevich

© 2022 - 2024 — McMap. All rights reserved.