django-ninja
django-ninja copied to clipboard
Django Ninja + GeoDjango
Currently using GeoDjango's custom fields breaks Django ninja with the following error:
Exception in thread django-main-thread:
...
< Omitted for brevity >
...
File "/opt/pysetup/.venv/lib/python3.9/site-packages/ninja/orm/fields.py", line 121, in get_schema_field
python_type = TYPES[internal_type]
KeyError: 'PointField'
Minimal reproduction models:
models.py
from django.contrib.gis.db import models
from ninja import ModelSchema
class Restaurant(models.Model):
location = models.PointField()
class RestaurantOut(ModelSchema):
class Config:
model = Restaurant
model_fields = ["location"]
and urls:
urls.py
from django.urls import path
from ninja import NinjaAPI
from .models import Restaurant, RestaurantOut
api = NinjaAPI()
@api.get("/restaurants", response=list[RestaurantOut])
def list_restaurants(request):
return Restaurant.objects.all()
urlpatterns = [path("api/", api.urls)]
Is GeoDjango support something that could be considered to be included in the project? Would using another package like geojson-pydantic help?
I managed to work around this issue for now by referring to https://github.com/vitalik/django-ninja/issues/53, and using a custom property on the model:
import json
from django.contrib.gis.db import models
from geojson_pydantic import Point
from ninja import ModelSchema
class Restaurant(models.Model):
name = models.CharField()
location = models.PointField()
@property
def location_geometry(self):
return json.loads(self.location.json)
class RestaurantOut(ModelSchema):
location_geometry: Point
class Config:
model = Restaurant
model_fields = ["name"]
However, it would be nice to have proper support for the GeoDjango functionalities: I tried some hacking around django-ninja
source, and by changing /ninja/orm/fields.py
:
from geojson_pydantic import Point
...
TYPES = {
...
"PointField": Point
}
I was first able to get the OpenAPI docs to work properly, and then by changing /ninja/schema.py
:
import json
from django.contrib.gis.geos import Point
...
class DjangoGetter(GetterDict):
def get(self, key: Any, default: Any = None) -> Any:
...
elif isinstance(result, Point):
return json.loads(result.json)
I got the endpoint working too. I further noted that overloading a getter_dict
in ModelSchema -> Config
with a minimal custom Getter
-class worked also.
Now I'm not sure how do you feel about supporting all the GeoDjango functionalities natively, these workarounds probably work fine for me, but personally I would of course prefer to have all these features supported automagically, like with rest of this great framework :) Also having django-ninja
depend on geojson-pydantic
might not be something that you want.
To reduce some of this boilerplate (and also pave way for some completely different django
field packages) maybe exposing the TYPES
mapping somehow could be considered? If one was able to extend it with third party validators and fields it would ease the development flow, manually overloading getter_dict
works fine for me and I've yet to come up with a less hacky way of transforming a django.contrib.gis.geos.Point
to a dict other than dumping and parsing JSON :smile:
Thanks for posting this workaround, I found it extremely useful. I think since GeoDjango is a django contrib, it seems like something django-ninja should probably support.
I've also experienced the same issue but a custom field with a custom internal type (long story...) so it would be good if there was a mechanism for registering custom fields and their counterpart Python type.
It's a temporary solution, but I was able to get it to work without hacking by putting this code in django settings.py.
from ninja.orm import fields
from geojson_pydantic import Point
fields.TYPES.update({"PointField": Point})