How to create GeneratedField with lookup from settings file?

While using Django 5.1, I'm trying to create a GeneratedField which should return True or False depending on which file has been uploaded. The model will accept both image- and video files. I want to do this in order to be able to filter on e.g. CampMedia.objects.filter(is_image=True).

# models.py

from django.conf import settings
from django.db import models
from django.db.models import Case, Q, When


class CampMedia(models.Model):
    media = models.FileField(upload_to="camps")
    is_image = models.GeneratedField(
        expression=Case(
            When(
                condition=Q([Q(media__endswith=ext) for ext in settings.VALID_IMAGE_FILETYPES], Q.OR),
                then=True,
            ),
            default=False,
        ),
        output_field=models.BooleanField(),
        db_persist=True,
    )

# settings.py

VALID_IMAGE_FILETYPES = ["png", "jpg", "jpeg", "gif", "webp"]

This is what's generated in the migration file:

# migrations/0007_image.py

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ("camps", "0006_auto"),
    ]

    operations = [
        migrations.AddField(
            model_name="campmedia",
            name="image",
            field=models.GeneratedField(
                db_persist=True,
                expression=models.Case(
                    models.When(
                        condition=models.Q(
                            [
                                models.Q(("media__endswith", "png")),
                                models.Q(("media__endswith", "jpg")),
                                models.Q(("media__endswith", "jpeg")),
                                models.Q(("media__endswith", "gif")),
                                models.Q(("media__endswith", "webp")),
                            ],
                            "OR",
                        ),
                        then=True,
                    ),
                    default=False,
                ),
                output_field=models.BooleanField(),
            ),
        ),
    ]

However, this generates this error when running python manage.py migrations:

$ ./manage.py migrate camps
Operations to perform:
  Apply all migrations: camps
Running migrations:
  Applying camps.0007_image...Traceback (most recent call last):
  File "C:\xampp\htdocs\manage.py", line 15, in <module>
    execute_from_command_line(sys.argv)
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\core\management\__init__.py", line 442, in execute_from_command_line
    utility.execute()
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\core\management\__init__.py", line 436, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\core\management\base.py", line 413, in run_from_argv
    self.execute(*args, **cmd_options)
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\core\management\base.py", line 459, in execute
    output = self.handle(*args, **options)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\core\management\base.py", line 107, in wrapper
    res = handle_func(*args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\core\management\commands\migrate.py", line 357, in handle
    post_migrate_state = executor.migrate(
                         ^^^^^^^^^^^^^^^^^
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\db\migrations\executor.py", line 135, in migrate
    state = self._migrate_all_forwards(
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\db\migrations\executor.py", line 167, in _migrate_all_forwards
    state = self.apply_migration(
            ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\db\migrations\executor.py", line 255, in apply_migration
    state = migration.apply(state, schema_editor)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\db\migrations\migration.py", line 132, in apply
    operation.database_forwards(
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\db\migrations\operations\fields.py", line 110, in database_forwards
    schema_editor.add_field(
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\db\backends\base\schema.py", line 722, in add_field
    definition, params = self.column_sql(model, field, include_default=True)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\db\backends\base\schema.py", line 383, in column_sql
    " ".join(
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\db\backends\base\schema.py", line 350, in _iter_column_sql
    generated_sql, generated_params = self._column_generated_sql(field)
                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\db\backends\base\schema.py", line 448, in _column_generated_sql
    expression_sql, params = field.generated_sql(self.connection)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\db\models\fields\generated.py", line 58, in generated_sql
    resolved_expression = self.expression.resolve_expression(
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\db\models\expressions.py", line 1629, in resolve_expression
    c.cases[pos] = case.resolve_expression(
                   ^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\db\models\expressions.py", line 1548, in resolve_expression
    c.condition = c.condition.resolve_expression(
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\db\models\query_utils.py", line 90, in resolve_expression
    clause, joins = query._add_q(
                    ^^^^^^^^^^^^^
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\db\models\sql\query.py", line 1641, in _add_q
    child_clause, needed_inner = self.build_filter(
                                 ^^^^^^^^^^^^^^^^^^
  File "C:\Users\Owner\.virtualenvs\p1pRKLs2\Lib\site-packages\django\db\models\sql\query.py", line 1488, in build_filter
    arg, value = filter_expr
    ^^^^^^^^^^
ValueError: too many values to unpack (expected 2)

I've tried to manually edit the migration file so that it doesn't contain nested tuples, but this produced the same error:

                        condition=models.Q(
                            [
                                models.Q("media__endswith", "png"),
                                models.Q("media__endswith", "jpg"),
                                models.Q("media__endswith", "jpeg"),
                                models.Q("media__endswith", "gif"),
                                models.Q("media__endswith", "webp"),
                            ],
                            "OR",
                        ),

While I know that this is possible to achieve my desired result through post_save signals, it becomes pretty messy pretty quickly imho.

TYIA!

The condition is not constructed correctly, it should be:

class CampMedia(models.Model):
    media = models.FileField(upload_to='camps')
    is_image = models.GeneratedField(
        expression=Case(
            When(
                condition=Q(
                    *[
                        Q(media__endswith=ext)
                        for ext in settings.VALID_IMAGE_FILETYPES
                    ],
                    _connector=Q.OR
                ),
                then=True,
            ),
            default=False,
        ),
        output_field=models.BooleanField(),
        db_persist=True,
    )
Вернуться на верх