vitalik / django-ninja

💨 Fast, Async-ready, Openapi, type hints based framework for building APIs
https://django-ninja.dev
MIT License
6.6k stars 396 forks source link

Error in generating schema for GeoDjango models #694

Open gurbaaz27 opened 1 year ago

gurbaaz27 commented 1 year ago

Please describe what you are trying to achieve I have a Django model with a location field (which is a PointField), and I am trying to generate schema from it using ModelSchema, but I guess django-ninja doesn't have support for these geofields. I wanted to know if I am doing an error, or is there any hack around the same? Thanks.

Please include code examples (like models code, schemes code, view function) to help understand the issue

models.py

from django.db import models
from django.contrib.gis.db import models as geomodels
from django.utils.translation import gettext_lazy as _

from uuid import uuid4

# Create your models here.
class Store(models.Model):
    id = models.UUIDField(_("ID"), primary_key=True, editable=False, default=uuid4) 
    name = models.CharField(_("Name"), max_length=200)
    location = geomodels.PointField(_("Location"), geography=True, srid=4326, blank=True, null=True)
    description = models.TextField(_("Description"), null=True, blank=True)
    contact_info = models.JSONField(_("Contact Information"), null=True)

schema.py

from ninja import ModelSchema
from stores.models import Store

class StoreSchema(ModelSchema):
    class Config:
        model = Store
        models_fields = "__all__"

Error

Watching for file changes with StatReloader
Performing system checks...

Exception in thread django-main-thread:
Traceback (most recent call last):
  File "/usr/lib/python3.10/threading.py", line 1016, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.10/threading.py", line 953, in run
    self._target(*self._args, **self._kwargs)
  File "/home/gurbaaz/servlog-backend/venv/lib/python3.10/site-packages/django/utils/autoreload.py", line 64, in wrapper
    fn(*args, **kwargs)
  File "/home/gurbaaz/servlog-backend/venv/lib/python3.10/site-packages/django/core/management/commands/runserver.py", line 134, in inner_run
    self.check(display_num_errors=True)
  File "/home/gurbaaz/servlog-backend/venv/lib/python3.10/site-packages/django/core/management/base.py", line 475, in check
    all_issues = checks.run_checks(
  File "/home/gurbaaz/servlog-backend/venv/lib/python3.10/site-packages/django/core/checks/registry.py", line 88, in run_checks
    new_errors = check(app_configs=app_configs, databases=databases)
  File "/home/gurbaaz/servlog-backend/venv/lib/python3.10/site-packages/django/core/checks/urls.py", line 14, in check_url_config
    return check_resolver(resolver)
  File "/home/gurbaaz/servlog-backend/venv/lib/python3.10/site-packages/django/core/checks/urls.py", line 24, in check_resolver
    return check_method()
  File "/home/gurbaaz/servlog-backend/venv/lib/python3.10/site-packages/django/urls/resolvers.py", line 494, in check
    for pattern in self.url_patterns:
  File "/home/gurbaaz/servlog-backend/venv/lib/python3.10/site-packages/django/utils/functional.py", line 57, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/home/gurbaaz/servlog-backend/venv/lib/python3.10/site-packages/django/urls/resolvers.py", line 715, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
  File "/home/gurbaaz/servlog-backend/venv/lib/python3.10/site-packages/django/utils/functional.py", line 57, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/home/gurbaaz/servlog-backend/venv/lib/python3.10/site-packages/django/urls/resolvers.py", line 708, in urlconf_module
    return import_module(self.urlconf_name)
  File "/usr/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/home/gurbaaz/servlog-backend/servlog/servlog/urls.py", line 20, in <module>
    from stores.api import router as stores_router
  File "/home/gurbaaz/servlog-backend/servlog/stores/api.py", line 12, in <module>
    from stores.schema import LocationQuerySchema, StoreSchema
  File "/home/gurbaaz/servlog-backend/servlog/stores/schema.py", line 16, in <module>
    class StoreSchema(ModelSchema):
  File "/home/gurbaaz/servlog-backend/venv/lib/python3.10/site-packages/ninja/orm/metaclass.py", line 62, in __new__
    model_schema = create_schema(
  File "/home/gurbaaz/servlog-backend/venv/lib/python3.10/site-packages/ninja/orm/factory.py", line 56, in create_schema
    python_type, field_info = get_schema_field(fld, depth=depth)
  File "/home/gurbaaz/servlog-backend/venv/lib/python3.10/site-packages/ninja/orm/fields.py", line 130, in get_schema_field
    python_type = TYPES[internal_type]
KeyError: 'PointField'
OtherBarry commented 1 year ago

You might need to make a custom class for points, like this.

gurbaaz27 commented 1 year ago

@OtherBarry thanks for the resource.

Right now I am extending TYPES like this. Is this the correct way to do?

from ninja.orm.fields import TYPES

from stores.models import Store

class PointClass(Point):
    @classmethod
    def __get_validators__(cls):
        yield cls.validate

    @classmethod
    def validate(cls, v):
        return cls(v)

    @classmethod
    def __modify_schema__(cls, field_schema):
        field_schema.update(type="tuple", example=(22.5, 22.5))

    def __repr__(self):
        return f"PointField({super().__repr__()})"

TYPES.update({"PointField": PointClass})

class StoreSchema(ModelSchema):
    class Config:
        model = Store
        model_fields = "__all__"

I haven't tested the API, but running the server doesn't throw any error.