Django m2m field ссылается на поле id through (промежуточной) таблицы

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

class Package(models.Model):
    id = models.IntegerField(primary_key=True)
    tag_list = models.ManyToManyField('Tag', through='PackageTagLinks')

    class Meta:
        managed = True
        db_table = 'package'

class PackageTagLinks(models.Model):
    package = models.ForeignKey('Package', models.DO_NOTHING)
    tag = models.ForeignKey('Tag', models.DO_NOTHING)

    class Meta:
        managed = True
        db_table = 'package_tag_links'
        constraints = [models.UniqueConstraint(fields=['package', 'tag'], name='unique package tag link')]

class Tag(models.Model):
    id = models.IntegerField(primary_key=True)
    tag_name = models.CharField(max_length=50)
    tag_group = models.ForeignKey('TagGroup', models.DO_NOTHING)
    color = models.CharField(max_length=6)

    class Meta:
        managed = True
        db_table = 'tag'

Я написал сериализатор, который выводит подмножество тегов, принадлежащих пакету:

class ExtendedAccordionContentSerializer(ModelSerializer):
    user_loader = SerializerMethodField()
    date = SerializerMethodField()
    organization_source = SerializerMethodField()
    oil_gas_field_list = SerializerMethodField()
    tag_list = SerializerMethodField() # Обратите внимание на это поле
    
    def get_user_loader(self, instance):
        return(ExtendedAccordionUserSerializer(instance.user_loader).data)
    
    def get_date(self, instance):
        return(instance.date_time_download.date())
    
    def get_organization_source(self, instance):
        return(ExtendedAccordionTagSerialiser(instance.organization_source).data)
    
    def get_oil_gas_field_list(self, instance):
        _serialiser = ExtendedAccordionTagSerialiser(
            Tag.objects.filter(tag_group__name=oil_gas_field_group_name, package=instance).order_by('tag_name'),
            many=True
        )

        return _serialiser.data
    
    def get_tag_list(self, instance): # И на этот метод
        _serialiser = ExtendedAccordionTagSerialiser(
            Tag.objects.exclude(tag_group__name__in = (organization_source_group_name, oil_gas_field_group_name))
            .filter(package=instance).order_by('tag_name'),
            many=True
        )

        return _serialiser.data
    
    class Meta:
        model = Package
        fields = ['user_loader', 'date','organization_source', 'oil_gas_field_list', 'tag_list']
        depth = 1

Я хочу получить получить все пакеты, которые содержат хотя-бы один тег из списка параметров запроса:

class ExtAccordionContentList(ListAPIView):
    permission_classes = [IsAuthenticated]
    renderer_classes = [TemplateHTMLRenderer, ]
    template_name = 'theme/components/ext_acc/table_content.html'
    serializer_class = ExtendedAccordionContentSerializer

    def get_queryset(self):
        params = self.request.query_params
        packages = Package.objects

        if params:
            if 'tags' in params:
                query = parse.unquote(str(params['tags'])).split() # ['8', '9', '10']
                if query:
                    packages = packages.filter(tag_list__in = query)

            #packages = packages.distinct()
            log_print(packages.query)
                        
        return packages

packages.queryset генерирует следующий sql запрос:

SELECT "package"."id", "package"."name", "package"."path", "package"."date_time_download", "package"."organization_source_id", "package"."user_loader_id"
FROM "package"
INNER JOIN "package_tag_links" ON ("package"."id" = "package_tag_links"."package_id")
WHERE ("package_tag_links"."tag_id" IN (8, 9, 10))

Вместо ожидаемого:

SELECT "package"."id", "package"."name", "package"."path", "package"."date_time_download", "package"."organization_source_id", "package"."user_loader_id"
FROM "package"
INNER JOIN "package_tag_links" ON ("package"."id" = "package_tag_links"."package_id")
INNER JOIN "tag" ON ("package_tag_links"."tag_id" = "tag"."id")
WHERE ("tag"."id" IN (8, 9, 10))
GROUP BY "package"."id" 

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

Я закомментировал строку с distinct(), поскольку это выглядит как костыль. Я также пробовал ссылаться на "id" поле самого тега:

packages = packages.filter(tag_list__tag__in = query)
#FieldError at /main/packages
#Related Field got invalid lookup: tag

Скажите, пожалуйста, есть ли более рациональное решение помимо использования distinct() и фиктивных alias(trash='tag_name') и annotate(trash='tag_name')? Например с select_related() или prefetch_related()?


Ответы (0 шт):