Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
jcushman opened this issue May 3, 2012 · 6 comments

Comments

@jcushman
Copy link

jcushman commented May 3, 2012

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

@jcushman
Copy link
Author

jcushman commented May 5, 2012

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

@jonashaag
Copy link
Contributor

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

@ReneVolution
Copy link

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

@ReneVolution
Copy link

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

@mtiller
Copy link

mtiller commented Oct 25, 2012

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.

@koleror
Copy link

koleror commented Aug 26, 2013

Thanks for sharing this code! Very helpful!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants