django-filter icon indicating copy to clipboard operation
django-filter copied to clipboard

Unrecognized field type GeneratedField

Open roniemartinez opened this issue 1 year ago • 15 comments

I've got this error which is caused by using a GeneratedField. Before migrating to GeneratedField, we used to use a migration script to create the generated field and django-filter has no issue with it.

AssertionError: MyFilter resolved field 'my_field' with 'exact' lookup to an unrecognized field type GeneratedField. Try adding an override to 'Meta.filter_overrides'. See: https://django-filter.readthedocs.io/en/main/ref/filterset.html#customise-filter-generation-with-filter-overrides

I think it should be possible to know what filter to use since GeneratedField has an output_field.

roniemartinez avatar Jun 26 '24 09:06 roniemartinez

@roniemartinez Could you put together a proof-of-concept showing what you mean here, perhaps with tests showing different examples? Thanks.

carltongibson avatar Jul 01 '24 08:07 carltongibson

If you have a field in your model like:

code_full = models.GeneratedField(
        expression=If(Q(code_number__isnull=True), Value(None), Concat("code_prefix", "code_number")),
        db_persist=True,
        output_field=models.CharField(blank=True, null=True, max_length=15),
    )

Then an error like @roniemartinez posted above will be raised.

This can be fixed with overriding filter_for_field() (note the # Handle GeneratedFields block):

@classmethod
def filter_for_field(cls, field, field_name, lookup_expr=None):
    if lookup_expr is None:
        lookup_expr = settings.DEFAULT_LOOKUP_EXPR

    # Handle GeneratedFields
    if isinstance(field, GeneratedField):
        new_field = field.output_field
        new_field.model = field.model
        field = new_field

    field, lookup_type = resolve_field(field, lookup_expr)

    default = {
        "field_name": field_name,
        "lookup_expr": lookup_expr,
    }

    filter_class, params = cls.filter_for_lookup(field, lookup_type)
    default.update(params)

    assert filter_class is not None, (
                                         "%s resolved field '%s' with '%s' lookup to an unrecognized field "
                                         "type %s. Try adding an override to 'Meta.filter_overrides'. See: "
                                         "https://django-filter.readthedocs.io/en/main/ref/filterset.html"
                                         "#customise-filter-generation-with-filter-overrides"
                                     ) % (cls.__name__, field_name, lookup_expr, field.__class__.__name__)

    return filter_class(**default)

It might not be the perfect fix, but it's worked well for me in my testing.

violuke avatar Jul 19 '24 10:07 violuke

So there's two parts to this.

The is the error. That should be addressed by #1675, which will enable skipping unknown fields — if you haven't used filter_overrides.

The second is the generated field handling itself:

    # Handle GeneratedFields
    if isinstance(field, GeneratedField):
        new_field = field.output_field
        new_field.model = field.model
        field = new_field

Is it as simple as that? Can we add built-in support?

Disclaimer: I haven't used GeneratedField yet, so haven't looked into what works and doesn't here at all.

carltongibson avatar Jul 19 '24 10:07 carltongibson

Is it as simple as that? Can we add built-in support?

Built-in support would be great! I can't find a problem with this solution so far, and it's been deployed to production today, so 🤞

I'll let you know if we find any issues, but I think this is all that's needed.

violuke avatar Jul 19 '24 16:07 violuke

OK, but the correct place to add this would be to the FILTER_FOR_DBFIELD_DEFAULTS, probably with a specific Filter subclass.

If someone wants to take that on as an addition (with docs and tests) that would be very welcome.

carltongibson avatar Jul 20 '24 06:07 carltongibson

Just to confirm, this change has been working problem-free for use for over a month now 👍

violuke avatar Sep 03 '24 10:09 violuke

@violuke Would you fancy creating a PR, but adding a new Filter subclass, rather than inline in filter_for_field()?

carltongibson avatar Sep 04 '24 15:09 carltongibson

I think that you can not simply add it to FILTER_FOR_DBFIELD_DEFAULTS because you need to unwrap the actual field-type that is generated in the generated field first.

theodor-franke avatar Sep 24 '24 17:09 theodor-franke

@theodor-franke The field is available to inspect via the lambda passed as the extra key in FILTER_FOR_DBFIELD_DEFAULTS.

carltongibson avatar Oct 01 '24 08:10 carltongibson

Ah okay, I will adjust the PR accordingly

theodor-franke avatar Oct 01 '24 09:10 theodor-franke

@carltongibson I had a deeper look into this problem. Yes i can pass the actual field with the extra key in FILTER_FOR_DBFIELD_DEFAULTS but than I still need to map the correct field_class to the given output_field this is already implementet in BaseFilterSet.filter_for_lookup() and I don't want to implement this logic twice (I cant use this method because than I have cyclic imports). I could implement something like this in the BaseFilterSet.filter_for_lookup() method:

if filter_class == GeneratedFieldFilter:
    return cls.filter_for_lookup(field.output_field, lookup_type)

but iam not a huge fan of this. Should i extract BaseFilterSet.filter_for_lookup() into utils.py? Is there something that iam missing?

theodor-franke avatar Oct 21 '24 12:10 theodor-franke

@theodor-franke thanks for looking in it. Let me have a play

carltongibson avatar Oct 21 '24 13:10 carltongibson

Hello, can I ask for an update on this? @carltongibson

Wecros avatar Feb 03 '25 16:02 Wecros

I ran into this issue too. What's holding this up at the moment and is there any way to help?

991jo avatar Oct 04 '25 17:10 991jo

Hi, I'd like to work on this issue. I'm planning to add support for GeneratedField by detecting it in filter_for_field() and extracting the underlying output_field to determine the appropriate filter class. I'll also copy the model attribute to ensure field resolution works correctly. The implementation will be backwards compatible with Django < 5.0. I'll submit a PR shortly.

Vincent-Ngobeh avatar Dec 02 '25 14:12 Vincent-Ngobeh