Django ManytoMany Field Queryset для точного и похожего соответствия

У меня есть следующие модели:

class Disease(models.Model):
    name = CICharField("Disease Name", max_length=200, unique=True)
    symptoms = models.ManyToManyField(Symptom, through='DiseaseSymptom', related_name='diseases')


class Symptom(models.Model):
    name = CICharField("Symptom Name", max_length=200, unique=True)

На фронт-энде у меня есть несколько полей выбора, где пользователи могут выбрать несколько симптомов, чтобы найти заболевание, и это будет передано в модель Disease как symptoms_selected params.

У меня есть следующий get_queryset на Disease > views.py


def get_queryset(self):
        params = self.request.query_params
        query_symptoms = self.request.GET.getlist('symptoms_selected')
        if query_symptoms:
            i = 0
            queryset = Disease.objects.all()
            while i < (len(query_symptoms)):
                symptom = [query_symptoms[i]]
                queryset = queryset.filter(symptoms__id__in=symptom)
                i=i+1
        else:
            queryset = Disease.objects.all()
    return queryset

Например: Данные о заболевании:

Болезнь А: Симптомы: A, B, C, D

Болезнь В: Симптомы: A, D, P, Q

Болезнь С: Симптомы: A, Q, X, Y

В настоящее время, если пользователь выбирает 3 симптома: A, D, Y, то возвращается Null. Вместо Null я хочу показать пользователям несколько похожих совпадений с 2 и 1 симптомами.

Я хочу, чтобы queryset вернул:

Точное совпадение заболеваний, т.е. 3 симптома совпали с заболеваниями

2 Симптомы соответствовали заболеваниям

1 симптом, соответствующий болезни

Может ли кто-нибудь сказать мне, как я могу этого достичь?

Один фильтр __in получит все заболевания, у которых есть хотя бы один совпадающий симптом

Тогда вы можете аннотировать запрос количеством совпадений и затем упорядочить его по этой аннотации, чтобы сначала получить заболевания с наибольшим количеством совпадений

from django.db.models import Count

Disease.objects.filter(
    symptoms__id__in=query_symptoms
).annotate(
    matches=Count('symptoms')
).order_by('-matches')

Тег regroup можно использовать для группировки результатов по количеству совпадений в вашем шаблоне

{% regroup diseases by matches as diseases_grouped %}
<ul>
{% for match_count in diseases_grouped %}
    <li>{{ match_count.grouper }} matches
    <ul>
        {% for disease in match_count.list %}
          <li>{{ disease }}</li>
        {% endfor %}
    </ul>
    </li>
{% endfor %}
</ul>

Используйте SerializerMethodField для добавления поля в ваш сериализатор для доступа к аннотации

class DiseaseSerializer(serializers.ModelSerializer):

    num_matches = serializers.SerializerMethodField()

    class Meta:
        model = Disease
        fields = ('id', 'name', 'symptoms')

    def get_num_matches(self, obj):
        return getattr(obj, 'matches', None)
Вернуться на верх