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()?