Callable schemas and initial values
Hello,
It seems that callable schemas don't have knowledge of initial values (e.g. using a CreateView), as the schema callable only receives the model instance as parameter, which is not initialised.
For example, using a model MyModel having a JSONField with a callable schema based on the model's some_property attribute, and using this view
class MetricCreateView(CreateView, LoginRequiredMixin):
model = MyModel
form_class = MyModelForm
def get_initial(self):
return {'some_property': self.kwargs['some_property']}
the callable schema will not be able to return a schema based on the initial some_property value.
Wouldn't it make sense to pass the initial values to the callable? They are passed to the kwargs of MyModelForm.
Hi, I think I kind of understand what you're proposing.
But if you could also provide a minimal example of the model and your callable schema function, it will be a bit clearer to me.
Hello, I've given a more complete example below, alongside my current (hacky) solution. I hope this clarifies things.
def schema_getter(some_property):
# .... <fetches schema based on value of `some_property`>
# Model
class MyModel(models.Model):
some_property = models.CharField(max_length=128)
def callable_config_schema(model_instance=None):
# Here it would be great to be able to know what the initial value of
# `some_property` is, as the model instance hasn't been instantiated yet
# for CreateView
if model_instance: return schema_getter(model_instance.some_property)
some_json = JSONField(schema=callable_config_schema)
# Form
class MyModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# This is my hack to make sure the schema is correctly selected
if kwargs.get("initial", {}).get("some_property"):
self.fields["some_json"].widget.schema = self.fields[
"some_json"
].widget.schema(MyModel(integration_id=kwargs["initial"]["some_property"]))
# manually set the current instance on the widget
# see https://django-jsonform.readthedocs.io/en/latest/fields-and-widgets.html#accessing-model-instance-in-callable-schema
self.fields["some_json"].widget.instance = self.instance
class Meta:
model = MyModel
fields = "__all__"
# View
class MyCreateView(CreateView):
model = MyModel
form_class = MyModelForm
def get_initial(self):
# self.kwargs contains parameters from the url
return {'some_property': self.kwargs['some_property']}
In urls.py:
urlpatterns = [
path(
"some/path/<some_property>/",
views.MyCreateView.as_view()
),
....
Okay, everything is clear now. I will provide a way to pass extra arguments to the callable in the next release. Probably introduce a new attribute called schema_kwargs. Something like this:
# declare extra arguments to pass to the callable schema
self.fields['some_json'].widget.schema_kwargs = {'key': value, ...}
Meanwhile, let me suggest a simpler hack for your use case.
The widget doesn't care what the value of the instance attribute is. You can set it to whatever value you want and it will be passed to the callable, i.e. you can treat it as a vehicle to carry any kind of data:
# form
class MyModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["some_json"].widget.instance = {
'instance': self.instance,
'some_property': kwargs['initial']['some_property']
}
# callable
def callable_config_schema(data):
"""
data is a dict containing these keys:
- instance: instance of the model
- some_property: value of some_property field
""""
if data['instance'] is not None:
# model instance is present
...
else:
# no instance found, so use data['some_property'] value
...
Good idea - thanks!