pythondjangovalidationpydanticdjango-ninja

Pydantic/Django Ninja use only existing keys (even with None)


having an app in Django Ninja with schemas:

class NumericalFilterSchema(Schema):
    gt: Optional[int] = None
    lt: Optional[int] = None
    gte: Optional[int] = None
    lte: Optional[int] = None
    exact: Optional[int] = None


    class Config(Schema.Config):
        extra = "forbid"


class StringFilterSchema(Schema):
    contains: Optional[str] = None
    icontains: Optional[str] = None
    exact: Optional[str] = None

    class Config(Schema.Config):
        extra = "forbid"


class InputsSchema(Schema):
    major_version: Optional[NumericalFilterSchema] = None
    app_name: Optional[StringFilterSchema] = None

    class Config(Schema.Config):
        extra = "forbid"

class InputSchema(Schema):
    filters: InputsSchema

    class Config(Schema.Config):
        extra = "forbid"

which I then use in the endpoint like this:


@router_v1.post(
    "/apps",
    tags=["..."],
    auth=AuthBearer(),
)
def dynamic_filter(request: HttpRequest, filters: InputsSchema):

    query = Q()

    # import ipdb

    # ipdb.set_trace()


    for key, value in filters.dict(exclude_none=True).items():
        # key = translate_field(key) # just abstraction between endpoint keys to db keys
        if isinstance(value, dict):
            for k, v in value.items():
                if v is not None:
                    query &= Q(**{f"{key}__{k}": v})
        else:
            query &= Q(**{key: value})

    results = Apps.objects.filter(query)
    ...

Problem:

As you can see in the query building I am excluding all the None values which is fine in the most cases for example:

{
  "major_version": {
    "exact": 3
  },
  "app_name": {
    "icontains": "google"
  }
}

this will return schema

InputsSchema(major_version=NumericalFilterSchema(gt=None, lt=None, gte=None, lte=None, exact=3), app_name=StringFilterSchema(contains=None, icontains='google', exact=None))

which is great... but what if my input value is None?

for example:

{
  "major_version": {
    "exact": null
  },
  "app_name": {
    "icontains": "google"
  }
}

here the exact key value pair will resolve to "exact": None which will be the same as other keys after pydantic/ninja validation:

InputsSchema(major_version=NumericalFilterSchema(gt=None, lt=None, gte=None, lte=None, exact=None), app_name=StringFilterSchema(contains=None, icontains='google', exact=None))

which "sucks" for me bcs I use exclude_none=True which filters out all Nones - even the value I gave.

is there a way to avoid creating the non existing keys in created model? So after the request validation I will have something like:

InputsSchema(major_version=NumericalFilterSchema(exact=None), app_name=StringFilterSchema(icontains='google'))

so I don't have to use the exclude_none=True and pass the None to the query?

Thanks!


Solution

  • Django's __exact lookup [Django-doc] indeed looks if the object is NULL, and thus __exact=None will convert it to an … IS NULL condition.

    But you can also filter with the __isnull lookup [Django-doc], if you set it to __isnull=True, then this will convert it to … IS NULL condition, and for __isnull=False, you thus use … IS NOT NULL.

    You thus probably can add isnull as filter condition, which will, if True/true thus enforce the value to be NULL/None.