django-nonrel / djangotoolbox

Django tools for building nonrel backends

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Enhancement: raw JSON admin fields for DictField and ListField (with sample code)

jcushman opened this issue · comments

Hi --

I've been thinking that, until someone gets around to putting a nice interface on DictFields and ListFields, it would be nice to be able to edit them as raw JSON in the admin -- just show a Textarea with the contents of the field.

So to that end, I put together the following SerializedObjectWidget:

from django.core.serializers import serialize, deserialize, json
from django.db.models.query import QuerySet
from django.db import models
from django.forms.widgets import Textarea
from django.utils import simplejson

class DjangoJSONEncoder(json.DjangoJSONEncoder):
    """
        Version of DjangoJSONEncoder that knows how to encode embedded QuerySets or Models.
    """
    def default(self, obj):
        if isinstance(obj, QuerySet):
            return serialize('python', obj)
        if isinstance(obj, models.Model):
            return serialize('python', [obj])[0]
        return super(DjangoJSONEncoder, self).default(obj)

def deserialize_model(obj):
    if 'model' in obj and 'pk' in obj:
        obj = list(deserialize('python', [obj]))[0].object
    return obj

class SerializedObjectWidget(Textarea):
    def render(self, name, value, attrs=None):
        value = simplejson.dumps(value, indent=2, cls=DjangoJSONEncoder)
        return super(SerializedObjectWidget, self).render(name, value, attrs)

    def value_from_datadict(self, data, files, name):
        val = data.get(name, None)
        if not val:
            return None
        return simplejson.loads(val, object_hook=deserialize_model)

All this does is take an arbitrary Python object (which can include model instances), encode it as JSON, and put it in a Textarea to display. Then on save it takes the results and decodes them back into a Python object including model instances. So it's pretty agnostic about what you use it for, but in particular it should work fine for DictFields and ListFields. Here's how I'm using it in my project (in models.py):

from .widgets import SerializedObjectWidget
from djangotoolbox.fields import ListField as DTListField, DictField as DTDictField, AbstractIterableField

class ListField(DTListField):
    def formfield(self, **kwargs):
        defaults = {'form_class': Field, 'widget': SerializedObjectWidget}
        defaults.update(kwargs)
        return super(AbstractIterableField, self).formfield(**defaults)

class DictField(DTDictField, Field):
    def formfield(self, **kwargs):
        defaults = {'form_class': Field, 'widget': SerializedObjectWidget}
        defaults.update(kwargs)
        return super(AbstractIterableField, self).formfield(**defaults)

The result in the admin looks like this: http://imgur.com/qExPZ

So, not something you'd want just any user to have access to, but usable if you know what you're doing.

This is working for me so far, but I haven't used it much and there might be all kinds of edge cases and security holes. I just wanted to throw it out there as an option to fold into DictField and ListField until something better comes along.

What do you think?

Thanks,
Jack

(I just edited the SerializedObjectWidget code to remove a pointless serialize-deserialize step in DjangoJSONEncoder, which was messing up sub-sub-objects.)

Any opinions from other contributors? I don't have any (yet).

Hey, first of all thanks for sharing this. As this looked like it would pretty much solve my trouble with the ListField i tried to implement it, but ...

i run into an issue with the Field part in
defaults = {'form_class': Field, 'widget': SerializedObjectWidget}

If i use it as stated i get:
'global name 'Field' is not defined' -> which is truely understandable

then i thought i need to define the Field-Type i'm using inside the ListField, so i tried:

defaults = {'form_class': EmbeddedModelField, 'widget': SerializedObjectWidget}

with the following result:
'init() got an unexpected keyword argument 'widget''

As a beginner here i would be grateful if you could point me into a direction to solve this.

Thanks a lot,
Reen

Oh no .... damn it ..... feeling like the ultra noob ;)

Just fixed it ....

it was just an import missing in my models.py:

from django.forms import Field

Thank you really for sharing your code ...

Best,
Reen

FWIW, I think this should be the default behavior for DictField fields. This then opens up the admin interface by default and enables other things like djangorestframework to work properly.

Thanks for sharing this code! Very helpful!