Skip to content

Commit

Permalink
Instead of conditional jsonform widget injection, make it pluggable o…
Browse files Browse the repository at this point in the history
…n the form/admin level definition
  • Loading branch information
surenkov committed Apr 1, 2024
1 parent 492a656 commit b2865ae
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 31 deletions.
11 changes: 1 addition & 10 deletions django_pydantic_field/v2/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import pydantic
import typing_extensions as te
from django.apps import apps
from django.core import checks, exceptions
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models.expressions import BaseExpression, Col, Value
Expand Down Expand Up @@ -179,16 +178,8 @@ def get_default(self) -> ty.Any:
return default_value

def formfield(self, **kwargs):
try:
if apps.is_installed("django_jsonform"):
form_cls = forms.JSONFormSchemaField
else:
form_cls = forms.SchemaField
except AttributeError:
form_cls = forms.SchemaField

field_kwargs = dict(
form_class=form_cls,
form_class=forms.SchemaField,
# Trying to resolve the schema before passing it to the formfield, since in Django < 4.0,
# formfield is unbound during form validation and is not able to resolve forward refs defined in the model.
schema=self.adapter.prepared_schema,
Expand Down
60 changes: 42 additions & 18 deletions django_pydantic_field/v2/forms.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import annotations

import typing as ty
import warnings

import pydantic
import typing_extensions as te
from django.core.exceptions import ValidationError
from django.forms.fields import InvalidJSONInput, JSONField, JSONString
from django.utils.translation import gettext_lazy as _
Expand All @@ -12,7 +12,12 @@

from . import types

__all__ = ("SchemaField",)
if ty.TYPE_CHECKING:
import typing_extensions as te
from django.forms.widgets import Widget


__all__ = ("SchemaField", "JSONFormSchemaWidget")


class SchemaField(JSONField, ty.Generic[types.ST]):
Expand All @@ -35,6 +40,11 @@ def __init__(
self.config = config
self.export_kwargs = types.SchemaAdapter.extract_export_kwargs(kwargs)
self.adapter = types.SchemaAdapter(schema, config, None, None, allow_null, **self.export_kwargs)

widget = kwargs.get("widget")
if widget is not None:
kwargs["widget"] = _prepare_jsonform_widget(widget, self.adapter)

super().__init__(*args, **kwargs)

def get_bound_field(self, form: ty.Any, field_name: str):
Expand Down Expand Up @@ -96,13 +106,40 @@ def _try_coerce(self, value):


try:
# Add the support of django-jsonform widgets, if installed
from django_jsonform.widgets import JSONFormWidget as _JSONFormWidget # type: ignore[import-untyped]
except ImportError:
pass
from django.forms.widgets import Textarea

def _prepare_jsonform_widget(widget, adapter: types.SchemaAdapter[types.ST]) -> Widget | type[Widget]:
return widget

class JSONFormSchemaWidget(Textarea):
def __init__(self, *args, **kwargs):
warnings.warn(
"The 'django_jsonform' package is not installed. Please install it to use the widget.",
ImportWarning,
)
super().__init__(*args, **kwargs)

else:

class JSONFormSchemaWidget(_JSONFormWidget, ty.Generic[types.ST]):
def _prepare_jsonform_widget(widget, adapter: types.SchemaAdapter[types.ST]) -> Widget | type[Widget]: # type: ignore[no-redef]
if not isinstance(widget, type):
return widget

if issubclass(widget, JSONFormSchemaWidget):
widget = widget(
schema=adapter.prepared_schema,
config=adapter.config,
export_kwargs=adapter.export_kwargs,
allow_null=adapter.allow_null,
)
elif issubclass(widget, _JSONFormWidget):
widget = widget(schema=adapter.json_schema()) # type: ignore[call-arg]

return widget

class JSONFormSchemaWidget(_JSONFormWidget, ty.Generic[types.ST]): # type: ignore[no-redef]
def __init__(
self,
schema: type[types.ST] | ty.ForwardRef | te.Annotated[type[types.ST], ...] | str,
Expand All @@ -115,16 +152,3 @@ def __init__(
export_kwargs = {}
adapter = types.SchemaAdapter[types.ST](schema, config, None, None, allow_null, **export_kwargs)
super().__init__(adapter.json_schema(), **kwargs)

class JSONFormSchemaField(SchemaField[types.ST]):
def __init__(
self,
schema: type[types.ST] | ty.ForwardRef | te.Annotated[type[types.ST], ...] | str,
config: pydantic.ConfigDict | None = None,
allow_null: bool | None = None,
*args,
**kwargs,
):
export_kwargs = types.SchemaAdapter.extract_export_kwargs(kwargs)
kwargs.setdefault("widget", JSONFormSchemaWidget(schema, config, allow_null, export_kwargs))
super().__init__(schema, config, allow_null, *args, **kwargs)
9 changes: 8 additions & 1 deletion tests/settings/django_test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,18 @@
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.admin",
"django_jsonform",
"tests.sample_app",
"tests.test_app",
]

try:
import django_jsonform # type: ignore[import-untyped]
except ImportError:
pass
else:
INSTALLED_APPS.append("django_jsonform")


MIDDLEWARE = [
"django.contrib.sessions.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
Expand Down
15 changes: 13 additions & 2 deletions tests/test_app/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
from django.contrib import admin

try:
from django_jsonform.widgets import JSONFormWidget
from django_pydantic_field.v2.fields import PydanticSchemaField
from django_pydantic_field.v2.forms import JSONFormSchemaWidget

json_formfield_overrides = {PydanticSchemaField: {"widget": JSONFormWidget}}
json_schema_formfield_overrides = {PydanticSchemaField: {"widget": JSONFormSchemaWidget}}
except ImportError:
json_formfield_overrides = {}
json_schema_formfield_overrides = {}

from . import models


Expand All @@ -10,12 +21,12 @@ class SampleModelAdmin(admin.ModelAdmin):

@admin.register(models.SampleForwardRefModel)
class SampleForwardRefModelAdmin(admin.ModelAdmin):
pass
formfield_overrides = json_formfield_overrides # type: ignore


@admin.register(models.SampleModelWithRoot)
class SampleModelWithRootAdmin(admin.ModelAdmin):
pass
formfield_overrides = json_schema_formfield_overrides # type: ignore


@admin.register(models.ExampleModel)
Expand Down

0 comments on commit b2865ae

Please sign in to comment.