Как правильно выполнить фильтрацию по свойству модели в queryset для поля ModelForm в Django?

Привет, друзья! 

Я работаю над проектом на Django и у меня есть модель: 

```python
class AccountModel(models.Model):
    uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
    user_name = models.CharField("Account name", null=True, blank=True, max_length=256)

    @property
    def task_inwork_count(self):
        return WorkModel.objects.filter(account=self.pk, status=1).count()


class WorkModel(models.Model):
    account = models.ForeignKey("AccountModel", on_delete=models.CASCADE)
    status = models.ForeignKey("StatusOfWork", default=1, on_delete=models.CASCADE)
```

Также у меня есть форма, в которой поле выбора для "account" должно заполняться моделями AccountModel, у которых свойство `task_inwork_count` равно 0. 

Вот код формы:

```python
class AddWorkForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(AddWorkForm, self).__init__(*args, **kwargs)
        choices = (
            x
            for x in AccountModel.objects.filter(
                status=2, user_id=args[0]["user_id"]
            )
            if x.task_inwork_count == 0
        )
        print(choices)
        self.fields["account"].queryset = choices 

    class Meta:
        model = WorkModel
        fields = ["account"]
```

Однако, при инициализации формы возникает ошибка:

```
File ".venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File ".venv\Lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
```

Не могли бы вы подсказать, в чем может быть проблема?
  • 3 октября 2024 г. 15:24
Ответы на вопрос 2
Ваша проблема заключается в том, что вы пытаетесь использовать свойство `task_inwork_count` в запросе при инициализации формы. При этом происходит выполнение отдельного SQL-запроса для каждой записи `AccountModel`, что может привести к ухудшению производительности и потенциальным ошибкам, особенно если в базе данных много записей.

Кроме того, ваш код неэффективен с точки зрения запросов к базе данных. Вам нужно отфильтровать `AccountModel` на уровне базы данных, а не в Python-коде. Для этого вы можете использовать `Subquery` или `OuterRef` для фильтрации записей с `task_inwork_count == 0` на уровне базы данных.

Вот как вы можете изменить ваш код:

```python
from django.db.models import Count, OuterRef, Subquery
from django import forms

class AddWorkForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(AddWorkForm, self).__init__(*args, **kwargs)
        
        # Подзапрос, который считает задачи со статусом=1
        subquery = WorkModel.objects.filter(
            account=OuterRef('pk'),
            status=1
        ).values('account').annotate(count=Count('id')).values('count')

        # Фильтр для выбора только тех AccountModel, у которых count = 0
        self.fields["account"].queryset = AccountModel.objects.filter(
            status=2,
            user_id=args[0]["user_id"]
        ).annotate(task_inwork_count=Subquery(subquery)).filter(task_inwork_count=0)

    class Meta:
        model = WorkModel
        fields = ["account"]
```

В этом примере:

1. Мы создаём подзапрос, который использует `OuterRef`, чтобы сослаться на `AccountModel`, и подсчитывает количество связанных `WorkModel` с `status=1`.
2. Далее мы аннотируем `AccountModel`, добавляя поле `task_inwork_count`, основанное на результате подзапроса.
3. Наконец, мы фильтруем аннотированный queryset, чтобы оставить только те записи, где `task_inwork_count` равно 0.

Этот подход более производителен, так как условия фильтрации выполняются непосредственно на уровне СУБД, что позволяет избежать множественных запросов из Python к базе данных.
Мне надо ему дать AccountModel.objects.filter(status=2, user_id=args[0]["user_id"]), но что бы она еще фильтровалась по @property task_inwork_count
task_inwork_count нужно переносить на уровень SQL, читай про annotate и aggregate
Похожие вопросы