Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper declaration of an empty Django PostgreSQL JSONField default value in migration file

I'm a little lost interpreting Django's explanation of applying a default value to a PostgreSQL JSONField:

If you give the field a default, ensure it’s a callable such as dict (for an empty default) or a callable that returns a dict (such as a function). Incorrectly using default={} creates a mutable default that is shared between all instances of JSONField.

So in my model file I've declared the default as such

foo = JSONField(default=dict())

however when I generate the migration operation for the new field this is the result

migrations.AddField(
    model_name='bar',
    name='foo',
    field=django.contrib.postgres.fields.jsonb.JSONField(default={}))

I'm just not sure whether or not this result is in accordance with the documentation's suggestion. Is this valid, or should I modify the generated default to call dict()?

like image 996
r3dw00d Avatar asked May 31 '18 21:05

r3dw00d


1 Answers

A callable is an object x that can be called, hence x() is valid, and will not raise an error because it is not callable (although there can be errors during the call, for example because the function somewhere yields an error).

dict() is actually completely equivalent to {}, that is not a callable, since {}(), will not result in constructing anything. But dict itself on the other hand is a reference to the dict class, and if we call it, we construct a new dict. So we should write it like:

# no brackets! We do not make a call, but pass the callable
foo = JSONField(default=dict)

So we do not call the dict class, we pass a reference to the class, and such classes are callable: if you call them, you typically construct a new instance (although this behavior can be changed).

Passing the callable is of vital importance here, since otherwise Django will each time use a reference to the same dictionary. As a result changes to one of the dictionaries will change the others that change the reference. If you store the dictionary and reload it, then this will be a different dictionary, but as long as you constructed two models, during the same Python run, these would be the same objects.

If you however pass a function, the function will be called, and thus produce two different objects, both empty dictionaries. But changes to the first dictionary will not reflect in the second one.

The same holds if you for instance would want to initialize a JSON field with a dictionary that contains data, instead of writing default={'a': 4}, one has to define it like:

def default_somemodel_dict():
    return {'a': 4}

class SomeModel(models.Model):
    foo = JSONField(default=default_somemodel_dict)
like image 196
Willem Van Onsem Avatar answered Oct 19 '22 18:10

Willem Van Onsem