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"

Наконец-то я разобрался с этим, поэтому выкладываю ответ здесь, если это может кому-то помочь. Было две ошибки:

  1. При фильтрации на основе связанных объектов ManyToMany не следует использовать модель through (здесь block_conditions) для записи отношения в фильтре запроса, а вместо этого использовать непосредственно связанный объект (здесь custom_conditions)

  2. Когда необходимо использовать условие фильтра на основе полей объектов 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)
Вернуться на верх