Как фильтровать записи в сложной БД по полям в связанных моделях в DRF?

Мы хотим дать возможность находить во всех таблицах записи по полям в связанных таблицах. На самом деле мы хотели бы перебрать их все.

Например код:

/models.py

class Tourney(models.Model):
    tourney = models.CharField()    
    tourney_1 = models.CharField() 
    tourney_2 = models.CharField()    

class Stage(models.Model):
    tourney = models.ForeignKey(Tourney, on_delete=models.CASCADE)
    stage = models.CharField()
    stage_1 = models.CharField()
    stage_2 = models.CharField()

class Group(models.Model):
    stage = models.ForeignKey(Stage, on_delete=models.CASCADE)
    group = models.CharField()
    group_1 = models.CharField()
    group_2 = models.CharField()

Group имеет отношение к Stage, которое имеет отношение к Tourney.

Итак, теперь мы хотим настроить для них API. Представьте, что у нас есть для них простые сериализаторы, которые включают все их поля и называются TourneySerializer, StageSerializer и GroupSerializer.

Теперь попробуем отфильтровать модель Group.

/views.py

class GroupViewSet(viewsets.ModelViewSet):
    queryset = Group.objects.all()
    serializer_class = serializers.GroupSerializer

    def list(self, request, *args, **kwargs):
        queryset = self.get_queryset()

        if 'tourney' in request.GET:
            queryset = queryset.filter(stage__tourney__tourney=request.GET['tourney'])
        if 'tourney_1' in request.GET:
            queryset = queryset.filter(stage__tourney__tourney_1=request.GET['tourney_1'])
        if 'tourney_2' in request.GET:
            queryset = queryset.filter(stage__tourney__tourney_2=request.GET['tourney_2'])
        if 'stage' in request.GET:
            queryset = queryset.filter(stage__stage=request.GET['stage'])
        if 'stage_1' in request.GET:
            queryset = queryset.filter(stage__stage_1=request.GET['stage_1'])
        if 'stage_2' in request.GET:
            queryset = queryset.filter(stage__stage_2=request.GET['stage_2'])
        if 'group' in request.GET:
            queryset = queryset.filter(group=request.GET['group'])
        if 'group_1' in request.GET:
            queryset = queryset.filter(group_1=request.GET['group_1'])
        if 'group_2' in request.GET:
            queryset = queryset.filter(group_2=request.GET['group_2'])

        serializer = self.get_serializer_class()(
            queryset,
            many=True)

        return Response(serializer.data)

Здесь у нас есть один ViewSet с кучей очевидного кода, и их будет больше, если в таблицах будет больше таблиц и больше полей. У меня есть до 20 таблиц, и последняя таблица в этой связанной цепочке может фильтровать около 40 полей. Таким образом, можно иметь около 400 правил фильтрации для всех моделей, так что это примерно 800 строк глупого кода только для одной последней модели. Совсем не хорошо.

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


person Alex Burla    schedule 28.07.2020    source источник


Ответы (1)


Самый быстрый способ - изменить имя параметра, которое вы передаете этому представлению. GET ['tourney_2'] должен быть доступен как GET ['stage__tourney__tourney_2']. Тогда вся фильтрация будет:

queryset = self.get_queryset().filter(**request.GET)

Конечно, вы должны проверить имя параметров, чтобы избежать SQL-инъекций, таких как request.GET ['deleted'] = True

В реальном проекте вы не можете использовать ** request.GET. Вы должны преобразовать его в обычный Python dict filters = dict (request.GET) и проверить фильтры против атак sql-инъекций.

queryset = self.get_queryset().filter(**filters)

Пожалуйста, посмотрите, как динамически строить запросы.

Как динамически указать имя поля поиска в запросе Django? < / а>

person baklarz2048    schedule 28.07.2020
comment
Да, достаточно элегантно, возможно в виде однострочного решения. На самом деле, если я не буду указывать в запросе, какую таблицу мне нужно исследовать, и у меня будет несколько таблиц с аналогичными полями, я не смогу узнать, какое поле и какую модель использовать для фильтрации. Так что возможно остановлюсь на вашем решении. Спасибо бакларц :) - person Alex Burla; 29.07.2020
comment
Что-то не так с этим методом. Когда я отправляю запрос типа api / tourney /? Tourney = Bang (существует одна такая запись), я получаю пустой список. Но это работает, если я создаю dict в коде типа {'tourney': 'Bang'} и передаю его вместо request.GET. Также, если я использую filter(tourney=request.GET['tourney']) вместо filter(**request.GET), он работает хорошо. - person Alex Burla; 29.07.2020
comment
Я вижу, что filter(**request.GET) дает (tourney=['Bang']) в качестве параметров, поэтому его значение - массив - person Alex Burla; 29.07.2020
comment
@AlexBurla Я вижу, что вы используете Django Rest Framework вместо ** request.GET, вы должны использовать ** request.query_params Вы не можете использовать ** request.query_params | GET напрямую, вы должны скопировать интересующие поля в новый dict filter_dict = dict (request. query_params) и передайте ** filter_dict, не забудьте проверить ** filter_dict, чтобы предотвратить атаки sql-инъекций. - person baklarz2048; 30.07.2020