Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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):     normal_value = models.IntegerField()     readonly_value = models.IntegerField(editable=False)     auto_now_add = models.DateTimeField(auto_now_add=True)     foreign_key = models.ForeignKey(OtherModel, related_name="ref1")     many_to_many = models.ManyToManyField(OtherModel, related_name="ref2") 

In the terminal, I have done the following:

other_model = OtherModel() other_model.save() instance = SomeModel() instance.normal_value = 1 instance.readonly_value = 2 instance.foreign_key = other_model instance.save() instance.many_to_many.add(other_model) instance.save() 

I want to convert this to the following dictionary:

{'auto_now_add': datetime.datetime(2015, 3, 16, 21, 34, 14, 926738, tzinfo=<UTC>),  'foreign_key': 1,  'id': 1,  'many_to_many': [1],  'normal_value': 1,  'readonly_value': 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?

like image 363
Zags Avatar asked Feb 21 '14 05:02

Zags


People also ask

What does .all do in Django?

Definition of the all() manager method: all() Returns a copy of the current QuerySet (or QuerySet subclass). This can be useful in situations where you might want to pass in either a model manager or a QuerySet and do further filtering on the result.

Is there a list field in Django models?

If you are using Google App Engine or MongoDB as your backend, and you are using the djangoappengine library, there is a built in ListField that does exactly what you want.

What is model relationship Django?

Django models operate by default on relational database systems (RDBMS) and support relationships. Django Follows the 3 model Relationships: 1- One-To-One Relationship. 2- One-To-Many Relationship. 3- Many-To-Many Relatiosnship.


2 Answers

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


1. instance.__dict__

instance.__dict__ 

which returns

{'_foreign_key_cache': <OtherModel: OtherModel object>,  '_state': <django.db.models.base.ModelState at 0x7ff0993f6908>,  'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),  'foreign_key_id': 2,  'id': 1,  'normal_value': 1,  'readonly_value': 2} 

This is by far the simplest, but is missing many_to_many, foreign_key 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

{'foreign_key': 2,  'id': 1,  'many_to_many': [<OtherModel: OtherModel object>],  'normal_value': 1} 

This is the only one with many_to_many, 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

{'foreign_key': 2, 'id': 1, 'normal_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

{'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),  'foreign_key_id': 2,  'id': 1,  'normal_value': 1,  'readonly_value': 2} 

This is the same output as instance.__dict__ but without the extra fields. foreign_key_id is still wrong and many_to_many 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 and getting the ids of foreign keys for many to many fields results in the following code which behaves as desired:

from itertools import chain  def to_dict(instance):     opts = instance._meta     data = {}     for f in chain(opts.concrete_fields, opts.private_fields):         data[f.name] = f.value_from_object(instance)     for f in opts.many_to_many:         data[f.name] = [i.id for i in f.value_from_object(instance)]     return data 

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

{'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),  'foreign_key': 2,  'id': 1,  'many_to_many': [2],  'normal_value': 1,  'readonly_value': 2} 

6. Use Serializers

Django Rest Framework's ModelSerialzer allows you to build a serializer automatically from a model.

from rest_framework import serializers class SomeModelSerializer(serializers.ModelSerializer):     class Meta:         model = SomeModel         fields = "__all__"  SomeModelSerializer(instance).data 

returns

{'auto_now_add': '2018-12-20T21:34:29.494827Z',  'foreign_key': 2,  'id': 1,  'many_to_many': [2],  'normal_value': 1,  'readonly_value': 2} 

This is almost as good as the custom function, but auto_now_add is a string instead of a datetime object.


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 itertools import chain  class PrintableModel(models.Model):     def __repr__(self):         return str(self.to_dict())      def to_dict(instance):         opts = instance._meta         data = {}         for f in chain(opts.concrete_fields, opts.private_fields):             data[f.name] = f.value_from_object(instance)         for f in opts.many_to_many:             data[f.name] = [i.id for i in f.value_from_object(instance)]         return data      class Meta:         abstract = True 

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

class OtherModel(PrintableModel): pass  class SomeModel(PrintableModel):     normal_value = models.IntegerField()     readonly_value = models.IntegerField(editable=False)     auto_now_add = models.DateTimeField(auto_now_add=True)     foreign_key = models.ForeignKey(OtherModel, related_name="ref1")     many_to_many = models.ManyToManyField(OtherModel, related_name="ref2") 

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

{'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),  'foreign_key': 2,  'id': 1,  'many_to_many': [2],  'normal_value': 1,  'readonly_value': 2} 
like image 68
Zags Avatar answered Sep 30 '22 14:09

Zags


I found a neat solution to get to result:

Suppose you have an model object o:

Just call:

type(o).objects.filter(pk=o.pk).values().first() 
like image 27
Alfred Huang Avatar answered Sep 30 '22 13:09

Alfred Huang