Skip to content

Instantly share code, notes, and snippets.

@haginara
Last active March 28, 2024 15:35
Show Gist options
  • Save haginara/5f4288670fec5e58cd1d1085f9e2b17e to your computer and use it in GitHub Desktop.
Save haginara/5f4288670fec5e58cd1d1085f9e2b17e to your computer and use it in GitHub Desktop.
Custom model_to_dict.py that supports ManyToOne Fields
from itertools import chain
def model_to_dict(instance, fields=None, exclude=None):
"""
Returns a dict containing the data in ``instance`` suitable for passing as
a Form's ``initial`` keyword argument.
``fields`` is an optional list of field names. If provided, only the named
fields will be included in the returned dict.
``exclude`` is an optional list of field names. If provided, the named
fields will be excluded from the returned dict, even if they are listed in
the ``fields`` argument.
"""
# avoid a circular import
from django.db.models.fields.related import ManyToManyField, ManyToOneRel
opts = instance._meta
data = {}
for f in chain(opts.concrete_fields, opts.virtual_fields, opts.many_to_many, opts.get_all_related_objects()):
if not getattr(f, 'editable', False) and not isinstance(f, ManyToOneRel):
continue
if fields and f.name not in fields:
continue
if exclude and f.name in exclude:
continue
if isinstance(f, ManyToManyField):
# If the object doesn't have a primary key yet, just use an empty
# list for its m2m fields. Calling f.value_from_object will raise
# an exception.
if instance.pk is None:
data[f.name] = []
else:
# MultipleChoiceWidget needs a list of pks, not object instances.
qs = f.value_from_object(instance)
if qs._result_cache is not None:
data[f.name] = [item.pk for item in qs]
else:
data[f.name] = list(qs.values_list('pk', flat=True))
elif isinstance(f, ManyToOneRel):
data[f.name] = getattr(instance, f.get_accessor_name()).all()
else:
data[f.name] = f.value_from_object(instance)
return data
@MauritzFunke
Copy link

MauritzFunke commented Mar 28, 2024

This doesn't work in the newest version of django, I applied a fix and it works
qs._result_cache is not an attribtue of QuerySet anymore so I removed the check, also its now somewhat recursive so I added a check so it dosnt end up in an infinite loop crashing the application.

def model_to_dict(instance, includes=None, exclude=None, depth=2):
    # avoid a circular import
    from django.db.models.fields.related import ManyToManyField, ManyToOneRel
    opts = instance._meta
    data = {}
    for f in opts.get_fields():
        if not getattr(f, 'editable', False) and not isinstance(f, ManyToOneRel):
            continue
        if includes and f.name not in includes:
            continue
        if exclude and f.name in exclude:
            continue
        if isinstance(f, ManyToManyField):
            # If the object doesn't have a primary key yet, just use an empty
            # list for its m2m fields. Calling f.value_from_object will raise
            # an exception.
            if instance.pk is None:
                data[f.name] = []
            else:
                # MultipleChoiceWidget needs a list of pks, not object instances.
                qs = f.value_from_object(instance)
                if depth > 0:
                    data[f.name] = [model_to_dict(item, depth=depth-1) for item in qs]
        elif isinstance(f, ManyToOneRel):
            if depth > 0:
                data[f.name] = [model_to_dict(item, depth=depth-1) for item in getattr(instance, f.get_accessor_name()).all()]
        else:
            data[f.name] = f.value_from_object(instance)
    return data

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