Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ielshareef/3011156 to your computer and use it in GitHub Desktop.
Save ielshareef/3011156 to your computer and use it in GitHub Desktop.
How to use EmbeddedModelField in Django's admin

How to Use EmbeddedModelField in Django's admin

by Ismail Elshareef

Problem

If you're using MongoDB with Django, you will need to manage ListField and EmbeddedModelField type fields in Django's admin module. Unfortunately, that's not so easy to do out of the box. You need to do some hacking to get it done. Here's a good link on how to use ListField in admin https://gist.github.com/1200165.

Now let's find out how we can use EmbeddedModelField with admin. Let's start hacking :)

Solution

Let's say you have an application that defines a collection (i.e. table) of users. Your models.py and admin.py will look something like this:

models.py

from django.db import models
from djangotoolbox.fields import ListField, EmbeddedModelField

class User(models.Model):
	name = EmbeddedModelField('Name')
	email = models.EmailField(max_length=255, db_index=True)
	def __unicode__(self):
		return '%s %s (%s)' % (self.name.fn, self.name.ln, self.email)
		
class Name(models.Model):
	fn = models.CharField(max_length=50)
	ln = models.CharField(max_length=50)
	def __unicode__(self):
		return '%s %s' % (self.fn, self.ln)

admin.py

from django.contrib.admin import site
from user_profile.models import User
	
site.register(User)

That's great, except now you won't be able to administer users in Django's admin because Django will treat "name" as a unicode string and not an EmbeddedModelField field. We need to change that.

First thing you need to do is to create a forms.py file in the same directory as models.py and admin.py. In the forms.py file, you need to define a class that handles EmbeddedModelField objects to make it work with Django's admin.

The class has two methods: prepare_value and to_python.

prepare_value transforms a MongoDB "Name" object (modeled after Name model) to a string in the format "key=value, key=value, ...etc" so it's editable in Django's admin.

to_python takes a string that looks like "key=value, key=value, ...etc" and transforms it into a dictionary representation of the Name model.

forms.py

from django import forms

class ObjectListField(forms.CharField):
	def prepare_value(self, value):
		if not value:
			return ''

		newvalue = {}
		for key, val in value.__dict__.items():
			if type(val) is unicode:
				newvalue[key] = val
		
		return ", ".join(["%s=%s" % (k, v) for k, v in newvalue.items()])

	def to_python(self, value):
		if not value:
			return {}
			
		obj = {}
		lst = [item.strip() for item in value.split(',')]
		for item in lst:
			val = item.split('=');
			obj[val[0]] = val[1]
        
		return obj 

Now that you have forms.py set up, you need to update models.py. You will need to define a class that overrides EmbeddedModelField to take advantage of the new form we just defined in forms.py.

models.py (updated w/ solution)

from django.db import models
from djangotoolbox.fields import EmbeddedModelField
from .forms import ObjectListField

class EmbedOverrideField(EmbeddedModelField):
    def formfield(self, **kwargs):
        return models.Field.formfield(self, ObjectListField, **kwargs)

class User(models.Model):
	name = EmbedOverrideField('Name')
	email = models.EmailField(max_length=255, db_index=True)
	def __unicode__(self):
		return '%s %s (%s)' % (self.name.fn, self.name.ln, self.email)
		
class Name(models.Model):
	fn = models.CharField(max_length=50)
	ln = models.CharField(max_length=50)
	def __unicode__(self):
		return '%s %s' % (self.fn, self.ln)

Now sync your db and test it out. You should be good to go!

from django.db import models
from djangotoolbox.fields import EmbeddedModelField
from .forms import ObjectListField
# Subclass EmbeddedModelField to make it wok with admin edit
class EmbedOverrideField(EmbeddedModelField):
def formfield(self, **kwargs):
return models.Field.formfield(self, ObjectListField, **kwargs)
# Create your models here.
class User(models.Model):
name = EmbedOverrideField('Name')
email = models.EmailField(max_length=255, db_index=True)
def __unicode__(self):
return '%s %s (%s)' % (self.name.fn, self.name.ln, self.email)
class Name(models.Model):
fn = models.CharField(max_length=50)
ln = models.CharField(max_length=50)
def __unicode__(self):
return '%s %s' % (self.fn, self.ln)
from django import forms
# Define ObjectListField to be used in overriding EmbeddedModelField for admin edit purposes
class ObjectListField(forms.CharField):
def prepare_value(self, value):
if not value:
return ''
newvalue = {}
for key, val in value.__dict__.items():
if type(val) is unicode:
newvalue[key] = val
return ", ".join(["%s=%s" % (k, v) for k, v in newvalue.items()])
def to_python(self, value):
if not value:
return {}
obj = {}
lst = [item.strip() for item in value.split(',')]
for item in lst:
val = item.split('=');
obj[val[0]] = val[1]
return obj
from django.contrib.admin import site
from user_profile.models import User
site.register(User)
@jonashaag
Copy link

How about using JSON in the admin + a JSON editor? The key=value syntax is very limited

@ielshareef
Copy link
Author

Great idea. I've recently gotten into Django so this is all new to me :-) Any pointers are highly appreciated! Thanks :)

@nanomag
Copy link

nanomag commented Jul 16, 2012

Thanks a lot!!

But I got this problem and I don´t understand why.

Exception Value: list index out of range
Exception Location: C:\Users\DARIO\Desktop\Escritorio\nanomag\askwer\forms.py in to_python, line 24

Sorry for my poor english.

I hope that you can help me. Thanks

@ielshareef
Copy link
Author

Make sure you're passing a string of this value: key=val, key=val, ...etc

@jperelli
Copy link

@jonashaag, I'd like something like this http://www.jsoneditoronline.org/
I'll try to do it. If i can, I'll make a push to the repo

@opnchaudhary
Copy link

This really helped me however I was looking if anyone has written a better implementation than the key=value input field?

@cotestting
Copy link

I have a question please . Officer if I have a similar model:

class Post(models.Model):
created_on = models.DateTimeField(auto_now_add=True, null=True)
title = models.CharField(max_length=255)
text = models.TextField()
tags = ListField()
comments = EmbedOverrideField('Comment')

def __unicode__(self):
    return '%s %s (%s)' % (self.title, self.text, self.comments.text)

class Comment(models.Model):
created_on = models.DateTimeField(auto_now_add=True)
author = EmbedOverrideField('Author')
text = models.TextField()

class Author(models.Model):
name = models.CharField(max_length=255)
email = models.CharField(max_length=255)

@cotestting
Copy link

that if I have a nesting of "EmbedOverrideField"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment