Open dralley opened 2 years ago
@dralley
Well it's possible (I guess it's even somewhat OpenAPI compatible)
from django.contrib.auth.models import User
from ninja import NinjaAPI, Schema, Query
api = NinjaAPI()
class UserSchema(Schema):
id: int = None # !!! important that ALL schema fields are all optional
username: str = None
email: str = None
first_name: str = None
last_name: str = None
@api.get("/some", response=List[UserSchema], exclude_unset=True) # !!!! exclude_unset
def some(request, fields: List[str] = Query(...)):
qs = User.objects.all()
return qs.values(*fields)
result:
I guess the only thing you need to keep in mind is validate incoming list of fields (but this can be automated with some function that will create both schema for result and schema for fields)
So yeah - it's possible - should it be in ninja by default - I think no - but a good candidate for external library
@vitalik what about nested fields? Example:
from typing import List
from django.db import models
from ninja import NinjaAPI, Query, Schema
from api.core.models import User
api = NinjaAPI()
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
class UserSchema(Schema):
id: int = None # !!! important that ALL schema fields are all optional
username: str = None
email: str = None
first_name: str = None
last_name: str = None
post__id: str = None
@api.get("/some", response=List[UserSchema], exclude_unset=True) # !!!! exclude_unset
def some(request, fields: List[str] = Query(...)):
qs = User.objects.all()
return qs.values(*fields)
I would expect to return a nested dict like:
[
{
"id": 1,
"email": "",
"post": {"id": "1"}
}
]
I created a custom query set as a workaround to handle it, but I'm looking for a more generic solution for all endpoints.
Do you still think that it doesn't make sense for Django Ninja to support it? It looks like the built-in Pagination support for me.
Hi @lucasrcezimbra
Query params do not have a "standard" way to pass structured data, but since all query params are atumatically flattened by django ninja you can achive it like this:
class PostSchema(Schema):
id: int = Field(None, alias="post__id") # unique ALIAS is important !!!
title: str = Field(None, alias="post__title") # unique ALIAS is important !!!
class UserSchema(Schema):
id: int = None
username: str = None
email: str = None
post: PostSchema = None
@api.get("/some")
def some(request, params: UserSchema = Query(...)):
return params.dict()
Thanks for the quick answer.
It works for filtering by the nested values, but I couldn't make it work for field selection/dynamic response.
...
class PostSchema(Schema):
id: int = Field(None, alias="post__id")
class UserSchema(Schema):
id: int = None # !!! important that ALL schema fields are all optional
username: str = None
email: str = None
first_name: str = None
last_name: str = None
post: PostSchema = None
...
I expected:
[
{
"id": 1,
"email": "",
"post": {"id": 1}
}
]
@lucasrcezimbra not sure if this is what you need:
class PostSchema(Schema):
id: int = Field(None, alias="post__id")
title: str = Field(None, alias="post__title")
class UserSchema(Schema):
id: int = None
username: str = None
email: str = None
post: PostSchema = None
@api.get("/some", response=UserSchema, exclude_unset=True)
def some(request, params: UserSchema = Query(...)):
return params
not sure if this is what you need
What I'm suggesting is to have the ability to select the response fields using the query parameters. As suggested by the issue author:
That is, given a request parameter parameter ?fields=field1,field2 or ?exclude_fields=field3, would it be possible to dynamically change the schema of the response?
@lucasrcezimbra
What I'm suggesting is to have the ability to select the response fields using the query parameters.
well this is not something that is standardised in OpenAPI (django ninja tries to be fully compatible)
but you should be able to achieve it with some custom decorator:
def add_exclude_fields(func):
func._ninja_contribute_args = [ # adding automatically extra param exclude_fields
(
"exclude_fields",
list[str],
Query([]),
),
]
@wraps(func)
def wrapper(request, *a, *kw):
exclude_fields = kw.pop('exclude_fields')
result = func(request, *a, **kw)
# >>> do exclude magic here<<<
return result
return wrapper
...
@api.get('/some')
@add_exclude_fields
def my_view(request):
return...
What would "exclude magic" look like? :)
Related: is there a way to pass exclude
to model_dump
?
fastapi has response_model_exclude
.
You can also use the path operation decorator parameters
response_model_include
andresponse_model_exclude
.They take a
set
ofstr
with the name of the attributes to include (omitting the rest) or to exclude (including the rest).This can be used as a quick shortcut if you have only one Pydantic model and want to remove some data from the output.
Hello,
Is it currently possible to implement functionality similar to djangorestframework-queryfields with Django Ninja? That is, given a request parameter parameter
?fields=field1,field2
or?exclude_fields=field3
, would it be possible to dynamically change the schema of the response?The biggest reason you might want to do this is with a heavy endpoint that produces a great deal of data, you may want to avoid as much serialization cost as possible, and potentially restrict database IO to only the fields you care about (djangorestframework-queryfields cannot do this out of the box but with medium effort and a tolerance for hackiness you can implement it).