Opened 4 days ago

Closed 4 days ago

#36296 closed Bug (duplicate)

Constraint validation of models with generated fields using Q crashes with AttributeError: 'Q' object has no attribute 'replace_expressions'

Reported by: David Sanders Owned by:
Component: Database layer (models, ORM) Version: 5.2
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Given this trivial model

class Foo(models.Model):
    bar = models.CharField()
    is_buzz = models.GeneratedField(
        expression=models.Q(bar="buzz"),
        output_field=models.BooleanField(),
        db_persist=True,
    )
    unrelated = models.CharField()

    class Meta:
        constraints = [
            models.CheckConstraint(
                name="check_unrelated", condition=models.Q(unrelated="unrelated")
            ),
        ]

Running validate_constraints() will raise an AttributeError (see below)

This was previously working on 5.1, it appears to be cause by the introduction of constraint validation on generated fields themselves in https://github.com/django/django/commit/228128618bd

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../django/django/db/models/base.py:1576: in validate_constraints
    constraint.validate(model_class, self, exclude=exclude, using=using)
../../django/django/db/models/constraints.py:602: in validate
    field_expression_map = instance._get_field_expression_map(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <Foo: Foo object (None)>, meta = <Options for Foo>, exclude = set()

    def _get_field_expression_map(self, meta, exclude=None):
        if exclude is None:
            exclude = set()
        meta = meta or self._meta
        field_map = {}
        generated_fields = []
        for field in meta.local_concrete_fields:
            if field.name in exclude:
                continue
            if field.generated:
                if any(
                    ref[0] in exclude
                    for ref in self._get_expr_references(field.expression)
                ):
                    continue
                generated_fields.append(field)
                continue
            value = getattr(self, field.attname)
            if not value or not hasattr(value, "resolve_expression"):
                value = Value(value, field)
            field_map[field.name] = value
        if "pk" not in exclude:
            field_map["pk"] = Value(self.pk, meta.pk)
        if generated_fields:
            replacements = {F(name): value for name, value in field_map.items()}
            for generated_field in generated_fields:
                field_map[generated_field.name] = ExpressionWrapper(
>                   generated_field.expression.replace_expressions(replacements),
                    generated_field.output_field,
                )
E               AttributeError: 'Q' object has no attribute 'replace_expressions'

../../django/django/db/models/base.py:1326: AttributeError

Change History (2)

comment:1 by Simon Charette, 4 days ago

This relates to #34871 which has a working prototype that implements Q.replace_expressions.

Note that passing a Q to GeneratedFied.expression was never entirely supported #34805 (as it's not an expression per-se) and passing a lookup instead most likely solves your issue

is_buzz = models.GeneratedField(
     expression=Exact(F("bar"), Value("buzz")),
    output_field=models.BooleanField(),
    db_persist=True,
)

comment:2 by Sarah Boyce, 4 days ago

Resolution: duplicate
Status: newclosed

Thank you for the information Simon
Marking as a duplicate as I think #34871 and #34805 cover this request, feel free to reopen if you disagree

Note: See TracTickets for help on using tickets.
Back to Top