Skip to content

Instantly share code, notes, and snippets.

@NeuronQ
Created July 19, 2019 10:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NeuronQ/dd741c581a5417feaef78c1e4192645d to your computer and use it in GitHub Desktop.
Save NeuronQ/dd741c581a5417feaef78c1e4192645d to your computer and use it in GitHub Desktop.
Django models code identical for postgres JSONField and sqlite TextField
## AUTHOR: Andrei Anton
## LICENSE: MIT
## USAGE EXAMPLE:
# @model_with_json_fields
# class MyThing(models.Model):
# JSON_FIELDS = (
# ['data', dict(default=dict, blank=True)],
# ['errors', dict(null=True, blank=True)],
# )
# ...
def model_with_json_fields(klass):
"""
Django model class decorator for adding json fields that are implemented as Postgres native JSONField when using postgres and as text fields with (de)serializing getters and setters on other DBs.
NOTE: when text fields are used, their column names get the '_str' suffix to not clash with the setters/getters
IMPORTANT: it's your job to tweak the migrations code, using `connection.vendor == 'postgresql'` in migrations to switch field types between django.contrib.postgres.fields.jsonb.JSONField and django.db.models.TextField
RATIONALE: all other more full-featured solutions for this problem are un-maintained and over-engineered / too heavy to timely debug - this requires some manual setup (migrations) and looks uglier, but has the advantage of simplicity
"""
for fld_name, ctor_args in klass.JSON_FIELDS:
if connection.vendor == 'postgresql':
# native postgres json field
JSONField(**ctor_args).contribute_to_class(klass, fld_name)
else:
# plain text field
str_fld_name = fld_name + '_str'
text_fld_ctor_args = ctor_args.copy()
if ctor_args.get('default', None) is dict:
text_fld_ctor_args['default'] = '{}'
elif ctor_args.get('default', None) is list:
text_fld_ctor_args['default'] = '[]'
models.TextField(**text_fld_ctor_args).contribute_to_class(klass, str_fld_name)
# but with property getter/setter (de)serializing to/from json
def getter(self):
return json.loads(getattr(self, str_fld_name))
prop_getter = property(getter)
def setter(self, val):
setattr(self, str_fld_name, json.dumps(val))
prop_getter_setter = prop_getter.setter(setter)
setattr(klass, fld_name, prop_getter_setter)
return klass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment