What I Learned Today: Django model_to_dict and Missing Fields

This was actually a couple weeks ago, but it is still fresh enough in my memory that I find it interesting and therefore worth sharing.

Background

I had a Django Model that looked something like this:

class MyModel(models.Model):
date_added = models.DateTimeField('date added', auto_now_add=True, db_index=True, editable=False)
title = models.CharField(max_length=32, db_index=True)
title_overridden = models.BooleanField('title overridden', db_index=True, editable=False)
# some other unimportant fields

I had set editable=False to those fields because I did not want users to be able to edit the values of those fields directly if/when I enabled the admin for this model. The initial data for this model was coming from an API source, but an administrator should be able to override the title. When they do, I was setting the title_overridden flag. The main reason is that the title should be kept up-to-date from the API source, unless the administrator has overridden the title then it should keep the administrator's changes.

Problem

I was also creating a view that would return these items as JSON. The quickest way to do that seemed to be django.forms.models.model_to_dict to convert my models to a dictionary, to which I was also adding additional data from the API source, and then using simplejson.dumps to output it as JSON. But for some reason several fields were missing from the dictionary.

The reason is that editable=False fields are automatically ignored in model_to_dict. And as an added gotcha, auto_now_add and auto_now automatically set editable=False.

Solution

Remove editable=False and auto_now_add=True, and create my own ModelManager.

class MyModelManager(models.Manager):

def create(self, **kwargs):
kwargs['date_added'] = datetime.now()

item = super(ScrapbookItemManager, self).create(**kwargs)
return item

class MyModel(models.Model):
date_added = models.DateTimeField('date added', db_index=True)
title = models.CharField(max_length=32, db_index=True)
title_overridden = models.BooleanField('title overridden', db_index=True)
# some other unimportant fields

objects = MyModelManager()

Then when I enable the admin pages for this model, I can create a custom ModelForm using the exclude attribute.

Filed under:

Comments

model_to_dict is simply not right for your purpose, if you used {field.name: field.value_from_object(instance) for field in instance._meta.fields} in its place you could have retained everything else as normal Django and avoided future head scratching.

I was looking for this, thanks Mr.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <cpp>, <java>, <php>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and email addresses turn into links automatically.
  • Lines and paragraphs break automatically.

Ready for transformation?