app/django/db/models/related.py
author Todd Larsen <tlarsen@google.com>
Fri, 26 Sep 2008 19:47:07 +0000
changeset 204 7075c27170f2
parent 54 03e267d67478
child 323 ff1a9aa48cfd
permissions -rw-r--r--
Add ModelWithFieldAttributes *before* PolyModel, so that everything does not become a "ModelWithFieldAttributes" in the Datastore. The way PolyModel works, each Model class hierarchy is stored in the Datastore as the class that directly inherited from PolyModel. So, we do not want to insert ModelWithFieldAttributes *after* PolyModel by inheriting *from* PolyModel, or everything would just be a ModelWithFieldAttributes in the Datastore, instead of Person, Work, Group, etc.

class BoundRelatedObject(object):
    def __init__(self, related_object, field_mapping, original):
        self.relation = related_object
        self.field_mappings = field_mapping[related_object.name]

    def template_name(self):
        raise NotImplementedError

    def __repr__(self):
        return repr(self.__dict__)

class RelatedObject(object):
    def __init__(self, parent_model, model, field):
        self.parent_model = parent_model
        self.model = model
        self.opts = model._meta
        self.field = field
        self.edit_inline = field.rel.edit_inline
        self.name = '%s:%s' % (self.opts.app_label, self.opts.module_name)
        self.var_name = self.opts.object_name.lower()

    def flatten_data(self, follow, obj=None):
        new_data = {}
        rel_instances = self.get_list(obj)
        for i, rel_instance in enumerate(rel_instances):
            instance_data = {}
            for f in self.opts.fields + self.opts.many_to_many:
                # TODO: Fix for recursive manipulators.
                fol = follow.get(f.name, None)
                if fol:
                    field_data = f.flatten_data(fol, rel_instance)
                    for name, value in field_data.items():
                        instance_data['%s.%d.%s' % (self.var_name, i, name)] = value
            new_data.update(instance_data)
        return new_data

    def extract_data(self, data):
        """
        Pull out the data meant for inline objects of this class,
        i.e. anything starting with our module name.
        """
        return data # TODO

    def get_list(self, parent_instance=None):
        "Get the list of this type of object from an instance of the parent class."
        if parent_instance is not None:
            attr = getattr(parent_instance, self.get_accessor_name())
            if self.field.rel.multiple:
                # For many-to-many relationships, return a list of objects
                # corresponding to the xxx_num_in_admin options of the field
                objects = list(attr.all())

                count = len(objects) + self.field.rel.num_extra_on_change
                if self.field.rel.min_num_in_admin:
                    count = max(count, self.field.rel.min_num_in_admin)
                if self.field.rel.max_num_in_admin:
                    count = min(count, self.field.rel.max_num_in_admin)

                change = count - len(objects)
                if change > 0:
                    return objects + [None] * change
                if change < 0:
                    return objects[:change]
                else: # Just right
                    return objects
            else:
                # A one-to-one relationship, so just return the single related
                # object
                return [attr]
        else:
            if self.field.rel.min_num_in_admin:
                return [None] * max(self.field.rel.num_in_admin, self.field.rel.min_num_in_admin)
            else:
                return [None] * self.field.rel.num_in_admin

    def get_db_prep_lookup(self, lookup_type, value):
        # Defer to the actual field definition for db prep
        return self.field.get_db_prep_lookup(lookup_type, value)
        
    def editable_fields(self):
        "Get the fields in this class that should be edited inline."
        return [f for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field]

    def get_follow(self, override=None):
        if isinstance(override, bool):
            if override:
                over = {}
            else:
                return None
        else:
            if override:
                over = override.copy()
            elif self.edit_inline:
                over = {}
            else:
                return None

        over[self.field.name] = False
        return self.opts.get_follow(over)

    def get_manipulator_fields(self, opts, manipulator, change, follow):
        if self.field.rel.multiple:
            if change:
                attr = getattr(manipulator.original_object, self.get_accessor_name())
                count = attr.count()
                count += self.field.rel.num_extra_on_change
            else:
                count = self.field.rel.num_in_admin
            if self.field.rel.min_num_in_admin:
                count = max(count, self.field.rel.min_num_in_admin)
            if self.field.rel.max_num_in_admin:
                count = min(count, self.field.rel.max_num_in_admin)
        else:
            count = 1

        fields = []
        for i in range(count):
            for f in self.opts.fields + self.opts.many_to_many:
                if follow.get(f.name, False):
                    prefix = '%s.%d.' % (self.var_name, i)
                    fields.extend(f.get_manipulator_fields(self.opts, manipulator, change,
                                                           name_prefix=prefix, rel=True))
        return fields

    def __repr__(self):
        return "<RelatedObject: %s related to %s>" % (self.name, self.field.name)

    def bind(self, field_mapping, original, bound_related_object_class=BoundRelatedObject):
        return bound_related_object_class(self, field_mapping, original)

    def get_accessor_name(self):
        # This method encapsulates the logic that decides what name to give an
        # accessor descriptor that retrieves related many-to-one or
        # many-to-many objects. It uses the lower-cased object_name + "_set",
        # but this can be overridden with the "related_name" option.
        if self.field.rel.multiple:
            # If this is a symmetrical m2m relation on self, there is no reverse accessor.
            if getattr(self.field.rel, 'symmetrical', False) and self.model == self.parent_model:
                return None
            return self.field.rel.related_name or (self.opts.object_name.lower() + '_set')
        else:
            return self.field.rel.related_name or (self.opts.object_name.lower())