Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for custom django fields (#1109) #1320

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion docs/docs/guides/response/django-pydantic.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,48 @@ def modify_data(request, pk: int, payload: PatchDict[GroupSchema]):

for attr, value in payload.items():
setattr(obj, attr, value)

obj.save()

```

in this example the `payload` argument will be a type of `dict` only fields that were passed in request and validated using `GroupSchema`

### Custom fields

For each Django field it encounters, `ModelSchema` uses the default `Field.get_internal_type` method
to find the correct representation in Pydantic schema. This process works fine for the built-in field
types, but there are cases where the user wants to create a custom field, with its own mapping to
Pydantic field. Consider the following (barebones) example field:

```python
class TranslatedTextField(models.JSONField):
description = "Translated string represented as a JSON field"
languages = {"en": _("English"), "fr": _("French")}

def formfield(self, **kwargs):
is_required = not self.blank
return TextDictField(
widget=TranslatedTextFieldWidget(languages=self.languages), required=is_required
)
```

In all cases where this field is used in a model, we would like to use the following schema:

```python
class TranslatedTextFieldSchema(Schema):
en: Optional[str] = None
fr: Optional[str] = None
```

To achieve that, we can add a `get_schema_type` function to the django field and add it to supported
types in Django Ninja:

```python
class TranslatedTextField(models.JSONField):
...
def get_schema_type(self):
return "TranslatedTextField"

ninja.orm.fields.TYPES["TranslatedTextField"] = TranslatedTextFieldSchema
RobertKolner marked this conversation as resolved.
Show resolved Hide resolved
```
11 changes: 9 additions & 2 deletions ninja/orm/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,15 @@ def get_schema_field(
null = field_options.get("null", False)
max_length = field_options.get("max_length")

internal_type = field.get_internal_type()
python_type = TYPES[internal_type]
if hasattr(field, 'get_schema_type'):
internal_type = field.get_schema_type()
else:
internal_type = field.get_internal_type()

try:
python_type = TYPES[internal_type]
except KeyError as e:
raise KeyError("Type '{0}' isn't registered in ninja.orm.fields.TYPES".format(internal_type)) from e

if field.primary_key or blank or null or optional:
default = None
Expand Down