'Recursively populates seen_objs with all objects related to this
object.
When done, seen_objs.items() will be in the format:
[(model_class, {pk_val: obj, pk_val: obj, ...}),
(model_class, {pk_val: obj, pk_val: obj, ...}), ...]'

def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):

    pk_val = self._get_pk_val()
    if seen_objs.add(self.__class__, pk_val, self, type(parent), parent, nullable):
        return
    for related in self._meta.get_all_related_objects():
        rel_opts_name = related.get_accessor_name()
        if (not related.field.rel.multiple):
            try:
                sub_obj = getattr(self, rel_opts_name)
            except ObjectDoesNotExist:
                pass
            else:
                sub_obj._collect_sub_objects(seen_objs, self, related.field.null)
        else:
            for cls in self.__class__.mro():
                if (rel_opts_name in cls.__dict__):
                    rel_descriptor = cls.__dict__[rel_opts_name]
                    break
            else:
                if (not related.field.rel.is_hidden()):
                    raise AssertionError('Should    never    get    here.')
                else:
                    continue
            delete_qs = rel_descriptor.delete_manager(self).all()
            for sub_obj in delete_qs:
                sub_obj._collect_sub_objects(seen_objs, self, related.field.null)
    for related in self._meta.get_all_related_many_to_many_objects():
        if related.field.rel.through:
            db = router.db_for_write(related.field.rel.through, instance=self)
            opts = related.field.rel.through._meta
            reverse_field_name = related.field.m2m_reverse_field_name()
            nullable = opts.get_field(reverse_field_name).null
            filters = {reverse_field_name: self}
            for sub_obj in related.field.rel.through._base_manager.using(db).filter(**filters):
                sub_obj._collect_sub_objects(seen_objs, self, nullable)
    for f in self._meta.many_to_many:
        if f.rel.through:
            db = router.db_for_write(f.rel.through, instance=self)
            opts = f.rel.through._meta
            field_name = f.m2m_field_name()
            nullable = opts.get_field(field_name).null
            filters = {field_name: self}
            for sub_obj in f.rel.through._base_manager.using(db).filter(**filters):
                sub_obj._collect_sub_objects(seen_objs, self, nullable)
        else:
            for sub_obj in f.value_from_object(self).all():
                sub_obj._collect_sub_objects(seen_objs, self, True)
    parent_stack = [p for p in self._meta.parents.values() if (p is not None)]
    while parent_stack:
        link = parent_stack.pop()
        parent_obj = getattr(self, link.name)
        if parent_obj._meta.parents:
            parent_stack.extend(parent_obj._meta.parents.values())
            continue
        parent_obj._collect_sub_objects(seen_objs)
