Source

djutils / djutils / models / __init__.py

Full commit
__all__ = (
    'set_field_order',
    'copy_model_instance',
    'has_related_objects',
)


from django.db.models.base import ModelBase
from django.db.models.options import Options
def set_field_order(*requests):
    """Return a model metaclass that ensures a certain field order, as
    specified by ``requests``. This is useful in inheritance scenarios,
    where the order cannot be controlled declaratively.

    Example:
        class MyModel(AbstractBase):
            __metaclass__ = set_field_order(('name', 'aliases',))

    ``requests`` is expected to be tuple of field names, or an iterable of
    such tuples.

    Ultimately, the model field order should be irrelevant and the form
    system might be a better place to do this. Generic admin appliations
    should approach this as newforms-admin does, registering this sort
    of information with the application by way of a custom class, or
    something similar. Once newforms-admin lands, applications uses this
    should probably consider switching to using the same config data as
    newforms-admin.
    """

    if not requests:
        return None

    # support simplified syntax for single sort requests
    if not isinstance(requests[0], (list,tuple)):
        requests = (requests,)

    class OrderedOptions(Options):
        def _fill_fields_cache(self):
            super(OrderedOptions, self)._fill_fields_cache()
            cache = list(self._field_cache)
            def idx(name):
                for i in range(0, len(cache)):
                    if cache[i][0].name == name:
                        return i
                return None

            # handle each sort request in order
            for request in requests:
                anchor_idx = None
                for field_name in request:
                    field_idx = idx(field_name)
                    if field_idx is None:
                        raise ValueError('Invalid field name "%s"'%field_name)

                    # first field in the request is the anchor, all the
                    # other fields will be inserted after it.
                    if anchor_idx is None:
                        anchor_idx = field_idx
                    else:
                        field = cache[field_idx]
                        cache.insert(anchor_idx+1, field)
                        del cache[field_idx]
                        if field_idx > anchor_idx:
                            anchor_idx += 1

            # already initialized by parent; override with our new
            # data now, with the correct sort order.
            self._field_cache = cache
            self._field_name_cache = [x for x, _ in cache]

    class OrderMetaclass(ModelBase):
        def __new__(cls, name, bases, attrs):
            model = super(OrderMetaclass, cls).__new__(cls, name, bases, attrs)
            model._meta.__class__ = OrderedOptions
            model._meta._fill_fields_cache()  # already filled, regen
            return model

    return OrderMetaclass


from django.db.models import AutoField
def copy_model_instance(obj):
    """Create a copy of a model instance.

    Works in model inheritance case where ``instance.pk = None`` is
    not good enough, since the subclass instance refers to the
    parent_link's primary key during save.

    M2M relationships are currently not handled, i.e. they are not
    copied.

    See also Django #4027.
    """
    initial = dict([(f.name, getattr(obj, f.name))
                    for f in obj._meta.fields
                    if not isinstance(f, AutoField) and\
                       not f in obj._meta.parents.values()])
    return obj.__class__(**initial)


def has_related_objects(obj, ignore=[]):
    """Returns ``True`` if the model instance ``obj`` has any related
    objects depending on it.

    This allows you to check, for example, whether a delete would be
    cascading, i.e. cause other objects to be deleted, too.

    You may pass a list of model classes in ``ignore`` - dependencies
    from those models will then not be considered.

    With respect to model inheritance, parent model records will be
    ignored, while subclass model will count. For example, say ``B``
    inherits ``A``. Now, when deleting ``b``, ``a`` would not be
    considered a related object by this function. However, when
    deleting ``a``, ``b`` would.

    # TODO: untested with multi-level inheritance, multiple parents
    # TODO: add tests
    """

    related = obj._meta.get_all_related_objects()
    model = type(obj)
    for so_klass in related:
        # Auto-created m2m models we don't care about. The admin ignores
        # those in it's ``django.contrib.admin.util.NestedObjects`` class.
        if so_klass._meta.auto_created:
            continue
        # Also ignore any subclasses.
        if issubclass(model, so_klass):
            continue
        # Does the user explicitely ask us to ignore this model?
        if so_klass in ignore:
            continue
        # -> So there is at least one related object that counts
        return True
    return False