Convert Django Model object to dict with all of the fields intact

How does one convert a django Model object to a dict with all of its fields? All ideally includes foreign keys and fields with editable=False.

Let me elaborate. Let's say I have a django model like the following:

    from django.db import models

    class OtherModel(models.Model): pass

    class SomeModel(models.Model):
        value = models.IntegerField()
        value2 = models.IntegerField(editable=False)
        created = models.DateTimeField(auto_now_add=True)
        reference1 = models.ForeignKey(OtherModel, related_name="ref1")
        reference2 = models.ManyToManyField(OtherModel, related_name="ref2")

In the terminal, I have done the following:

    other_model = OtherModel()
    other_model.save()
    instance = SomeModel()
    instance.value = 1
    instance.value2 = 2
    instance.reference1 = other_model
    instance.save()
    instance.reference2.add(other_model)
    instance.save()

I want to convert this to the following dictionary:

    {'created': datetime.datetime(2015, 3, 16, 21, 34, 14, 926738, tzinfo=<UTC>),
     u'id': 1,
     'reference1': 1,
     'reference2': [1],
     'value': 1,
     'value2': 2}

Questions with unsatisfactory answers:

Django: Converting an entire set of a Model's objects into a single dictionary

How can I turn Django Model objects into a dictionary and still have their foreign keys?

There are many ways to convert instance to a dictionary, with varying degrees of corner case handling and closeness to the desired result.

1. instance.__dict__

    instance.__dict__

which returns

    {'_reference1_cache': <OtherModel: OtherModel object>,
     '_state': <django.db.models.base.ModelState at 0x1f63310>,
     'created': datetime.datetime(2014, 2, 21, 4, 38, 51, 844795, tzinfo=<UTC>),
     'id': 1L,
     'reference1_id': 1L,
     'value': 1,
     'value2': 2}

This is by far the simplest, but is missing reference2, reference1 is misnamed, and it has two unwanted extra things in it.

2. model_to_dict

    from django.forms.models import model_to_dict
    model_to_dict(instance)

which returns

    {u'id': 1L, 'reference1': 1L, 'reference2': [1L], 'value': 1}

This is the only one with reference2, but is missing the uneditable fields.

3. model_to_dict(..., fields=...)

    from django.forms.models import model_to_dict
    model_to_dict(instance, fields=[field.name for field in instance._meta.fields])

which returns

    {u'id': 1L, 'reference1': 1L, 'value': 1}

This is strictly worse than the standard model_to_dict invocation.

4. query_set.values()

    SomeModel.objects.filter(id=instance.id).values()[0]

which returns

    {'created': datetime.datetime(2014, 2, 21, 4, 38, 51, tzinfo=<UTC>),
     u'id': 1L,
     'reference1_id': 1L,
     'value': 1L,
     'value2': 2L}

This is the same output as instance.__dict__ but without the extra fields. reference1_id is still wrong and reference2 is still missing.

5. Custom Function

The code for django's model_to_dict had most of the answer. It explicitly removed non-editable fields, so removing that check results in the following code which behaves as desired:

    from django.db.models.fields.related import ManyToManyField

    def to_dict(instance):
        opts = instance._meta
        data = {}
        for f in opts.concrete_fields + opts.many_to_many:
            if isinstance(f, ManyToManyField):
                if instance.pk is None:
                    data[f.name] = []
                else:
                    data[f.name] = list(f.value_from_object(instance).values_list('pk', flat=True))
            else:
                data[f.name] = f.value_from_object(instance)
        return data

While this is the most complicated option, calling to_dict(instance) gives us exactly the desired result:

    {'created': datetime.datetime(2015, 3, 16, 21, 34, 14, 926738, tzinfo=<UTC>),
     u'id': 1,
     'reference1': 1,
     'reference2': [1],
     'value': 1,
     'value2': 2}

Bonus Round: better model printing

If you want a django model that has a better python command-line display, have your models child-class the following:

    from django.db import models
    from django.db.models.fields.related import ManyToManyField

    class PrintableModel(models.Model):
        def __repr__(self):
            return str(self.to_dict())

        def to_dict(self):
            opts = self._meta
            data = {}
            for f in opts.concrete_fields + opts.many_to_many:
                if isinstance(f, ManyToManyField):
                    if self.pk is None:
                        data[f.name] = []
                    else:
                        data[f.name] = list(f.value_from_object(self).values_list('pk', flat=True))
                else:
                    data[f.name] = f.value_from_object(self)
            return data

        class Meta:
            abstract = True

So, for example, if we define our models as such:

    class OtherModel(PrintableModel): pass

    class SomeModel(PrintableModel):
        value = models.IntegerField()
        value2 = models.IntegerField(editable=False)
        created = models.DateTimeField(auto_now_add=True)
        reference1 = models.ForeignKey(OtherModel, related_name="ref1")
        reference2 = models.ManyToManyField(OtherModel, related_name="ref2")

Calling SomeModel.objects.first() now gives output like this:

    {'created': datetime.datetime(2015, 3, 16, 21, 34, 14, 926738, tzinfo=<UTC>),
    'value': 1, 'value2': 2, 'reference1': 1, u'id': 1, 'reference2': [1]}

From: stackoverflow.com/q/21925671

Back to homepage or read more recommendations: