Django queryset фильтр на ManyToMany с пользовательскими полями
У меня есть следующие модели Django Block
и CustomCondition
, которые связаны друг с другом через пользовательское отношение ManyToMany, которое имеет таможенные поля и пользовательскую таблицу DB:
from django.db import models
class CustomCondition(models.Model):
name = models.CharField("custom condition name")
class Meta:
db_table = "custom_conditions"
def __str__(self) -> str:
return self.name
class BlockCondition(models.Model):
block = models.ForeignKey(
"Block",
related_name="block_condition_as_block",
on_delete=models.CASCADE,
)
custom_condition = models.ForeignKey(
CustomCondition,
related_name="block_condition_as_custom_condition",
on_delete=models.CASCADE,
)
choice = models.CharField(
"choice",
choices=[
("NO_CONDITION", "No condition"),
("ACTIVATED", "Activated"),
("NOT_ACTIVATED", "Not activated"),
],
default="NO_CONDITION",
)
class Meta:
db_table = "block_conditions"
def __str__(self) -> str:
return self.custom_condition.name
class Block(models.Model):
name = models.CharField("name", null=False)
block_conditions = models.ManyToManyField(
CustomCondition,
through="BlockCondition",
blank=True,
default=None,
related_name="blocks_as_block_condition",
)
class Meta:
db_table = "blocks"
Теперь я хочу фильтровать блоки для каждого имени условия, когда это имя активировано:
for c in CustomCondition.objects.all():
if c.name:
filtered_blocks = Block.objects.filter(
block_conditions__custom_condition__name=c.name
)
filtered_blocks = filtered_blocks.filter(
block_conditions__choice__in=["ACTIVATED", "NO_CONDITION"]
)
print(filtered_blocks)
Но я получаю следующую ошибку Django:
django.core.exceptions.FieldError: Unsupported lookup 'custom_condition' for ForeignKey or join on the field not permitted.
Что я делаю не так?
Сообщение об ошибке Unsupported lookup 'custom_condition' for ForeignKey or join on the field not permitted указывает на то, что поиск block_conditions__custom_condition__name не разрешен, поскольку Django не поддерживает обход отношения ForeignKey (пользовательское_условие BlockCondition) подобным образом в запросе
Для достижения желаемого фильтра, в котором вы хотите отфильтровать объекты Block на основе связанного с ними имени CustomCondition и поля выбора в BlockCondition, вы можете подойти к этому по-другому
Один из способов - использовать аннотации и подзапросы для фильтрации блоков на основе условий
Вот как вы можете изменить свой код, чтобы добиться этого:
from django.db.models import Count, Case, When, IntegerField
# Get all CustomConditions
custom_conditions = CustomCondition.objects.all()
# Initialize a dictionary to store the filtered blocks for each condition
filtered_blocks_dict = {}
# Iterate over each CustomCondition
for condition in custom_conditions:
# Annotate the Block queryset with a count of matching BlockCondition entries
filtered_blocks = Block.objects.annotate(
matching_conditions=Count(
Case(
When(
block_conditions__custom_condition=condition,
block_conditions__choice__in=["ACTIVATED", "NO_CONDITION"],
then=1,
),
output_field=IntegerField(),
)
)
).filter(matching_conditions__gt=0)
# Store the filtered blocks in the dictionary with the condition name as key
filtered_blocks_dict[condition.name] = filtered_blocks
# Print the filtered blocks for each condition
for condition_name, filtered_blocks in filtered_blocks_dict.items():
print(f"Blocks for {condition_name}: {filtered_blocks}")
Сначала получаем все объекты CustomCondition. Затем мы перебираем каждое условие и используем аннотацию Django вместе с условными выражениями (Case и When) для подсчета количества совпадающих записей BlockCondition для каждого блока на основе имени условия и его выбора
Наконец, мы фильтруем набор Block queryset, основываясь на том, что количество совпадающих условий больше 0
Такой подход должен помочь вам отфильтровать объекты Block для каждого имени условия, когда это имя активировано или имеет значение "NO_CONDITION"
Наконец-то я разобрался с этим, поэтому выкладываю ответ здесь, если это может кому-то помочь. Было две ошибки:
При фильтрации на основе связанных объектов ManyToMany не следует использовать модель through (здесь
block_conditions
) для записи отношения в фильтре запроса, а вместо этого использовать непосредственно связанный объект (здесьcustom_conditions
)Когда необходимо использовать условие фильтра на основе полей объектов through, синтаксис фильтра должен использовать имя модели through (здесь
blockcondition
как имя модели в нижнем регистре), а не имя связанного поля (здесьblock_conditions
)
Так что в целом это будет:
for c in CustomCondition.objects.all():
if c.name:
filtered_blocks = Block.objects.filter(
custom_conditions__name=c.name
)
filtered_blocks = filtered_blocks.filter(
blockcondition__choice__in=["ACTIVATED", "NO_CONDITION"]
)
print(filtered_blocks)