strawberry-graphql / strawberry-django

Strawberry GraphQL Django extension
MIT License
391 stars 115 forks source link

How to correctly define and resolve fields annotated to django querysets #423

Closed TWeidi closed 7 months ago

TWeidi commented 7 months ago

I have a django model (MyModel) with a custom manager (MyManager). The manager makes some annotations to my queryset, for instance, assume it adds a field 'my_annotation' to each instance within the queryset. If I add 'my_annotation' to the type definition of MyModel and run a GQL query for MyModel (DirectQuery), including the 'my_annotation' field, it perfectly resolves as expected. Now, I add a new model (MyRelatedModel) that relates to MyModel and add a type definiton for that as well, including the relation. If I run the following query (I use relay)

myRelatedModels {
  edges {
    node {
      myModel {
        my_annotation
    }
  }
}

I get an AttributeError: 'MyModel' object has no attribute my_annotation. It seems that the object instance the resolver works on misses the my_annotation field which was present in the case of the DirectQuery. Is this a bug or do I have to provide some hint in the type definition that states that my_annotation is not a model field but an Annotation?

Upvote & Fund

Fund with Polar

bellini666 commented 7 months ago

Hey @TWeidi ,

How is that manager defined in your model? strawberry_django will use your model._default_manager the queryset, and accordingly to the django documentation, it will be the first manager defined in your class.

Can you check how you are defining it there?

Also note that you can override the default queryset resolver. Like, instead of doing:

my_related_models: ListConnection[MyModel] = strawberry_django.connection()

You can do:

@strawberry_django.connection(ListConnection[MyModel])
def my_related_models(self) -> Iterable[MyModel]:
    return MyModel.objects.all() # or MyModel.my_custom_manager.all() and so on
TWeidi commented 7 months ago

Hi @bellini666,

It seems that the issue is not related to strawberry-graphql-django. It's rather that Django annotates the queryset only if one is using the manager directly, for intance with the all() method. If I query myRelatedModels and try to access the annotation field from it, the field is missing, so:

MyModel.objects.first().my_annotation

is working while

MyRelatedModel.objects.first().my_model.my_annotation

is not, since Django seems to never annotate my_model

sdobbelaere commented 7 months ago

Does this help you? https://docs.djangoproject.com/en/4.2/topics/db/managers/#base-managers

If the normal base manager class (django.db.models.Manager) isn’t appropriate for your circumstances, you can tell Django which class to use by setting Meta.base_manager_name.

TWeidi commented 7 months ago

Thank you @sdobbelaere, but unfortunarely that did not help, since I already use a custom manager. The problem is, that annotations are only available on the queryset, but if i query a related model and resolve the m2m field from it, the annotations are not present on that field, so strawberry cannot resolve it. A workaround for that might be the Database generated model field that comes with Django 5. With that it seems to me you can have model fields that are not stored in the DB but computed, if queried. Looks like that is exactly what i need : )

TWeidi commented 7 months ago

Closing, since it's not related to this repo