"Safely" obtain a list of attributes for an
    # object.

    Python's dynamic properties are incredibly useful
    but there's a serious lack of good introspection
    tools. Python provides an inspect module which does
    introspection, but at its heart it relies on dir()
    and getattr(), both of which can be overridden by
    classes.

    Django of course overrides both of these, which
    means using Python's "proper" introspection class
    will change the object we're trying to display.
    Worse, if you have models with circular references,
    attempting to recursively introspect anything that
    touches the circular reference will trigger an
    infinite recursion loop, crashing the application.

    In particular, the trigger for this seems to be
    the use of getattr() inside the inspect.getmembers()
    method. To work around this, we access the object's
    native properties dict.

    Unfortunately, using the raw __dict__ will fail
    because it doesn't account for base class properties,
    methods, or anything else fancy. So this function
    attempts to enumerate all of those items. Like the
    inspect.getmembers() call, we accept a predicate
    which will be used to filter the results.

def safe_dir(obj, predicate = None):
    '''
    "Safely" obtain a list of attributes for an
    # object.

    Python's dynamic properties are incredibly useful
    but there's a serious lack of good introspection
    tools. Python provides an inspect module which does
    introspection, but at its heart it relies on dir()
    and getattr(), both of which can be overridden by
    classes.

    Django of course overrides both of these, which
    means using Python's "proper" introspection class
    will change the object we're trying to display.
    Worse, if you have models with circular references,
    attempting to recursively introspect anything that
    touches the circular reference will trigger an
    infinite recursion loop, crashing the application.

    In particular, the trigger for this seems to be
    the use of getattr() inside the inspect.getmembers()
    method. To work around this, we access the object's
    native properties dict.

    Unfortunately, using the raw __dict__ will fail
    because it doesn't account for base class properties,
    methods, or anything else fancy. So this function
    attempts to enumerate all of those items. Like the
    inspect.getmembers() call, we accept a predicate
    which will be used to filter the results.
    '''

    # safely get all the classes we need to look at
    obj_mro = [ obj ]
    if hasattr(obj.__class__, '__mro__'):
        obj_mro.extend(obj.__class__.__mro__)
    else:
        obj_mro.extend(obj.__class__)

    # a set of attributes we will test
    found_attrs = {}

    if hasattr(obj, '__dict__'):
        for c in obj_mro:
            # if hasattr(c, '__name__'):
            #     debug_name = c.__name__
            # else:
            #     debug_name = c.__class__.__name__ + ' instance'
            # print 'MRO item:', debug_name

            if hasattr(c, '__dict__'):
                keylist = c.__dict__.keys()
                for k in keylist:
                    if k not in found_attrs:
                        try:
                            v = obj.__dict__[k]
                        except KeyError: #, AttributeError:
                            # so actually AttributeError should
                            # never happen, but it seems a few
                            # classes will actually report they
                            # have the __dict__ attribute and
                            # then throw an AttributeError when
                            # you try to access it
                            continue
                        if predicate is None or predicate(v):
                            found_attrs[k] = v
                # print len(keylist), len(c.__dict__.keys())
                # print 'before:', keylist
                # print ' after:', c.__dict__.keys()

    return sorted(found_attrs.items(), lambda a,b: cmp(a[0],b[0]))