def __init__(self, obj, attribute, **kwargs):
        self.obj = obj
        self.attribute = attribute
        self.from_donor = kwargs.get('from_donor', False)
        self.is_through = has_explicit_through_class(self.obj, attribute)
        if self.is_through:
            self.resolved_attribute = obj.through_attribute(obj.many_field(attribute))
            through_class = obj.through_class(attribute)
            self.self_foreign_key_attribute = foreign_key_field_of_related_class(through_class, obj.__class__).name
            self.foreign_key_attribute = foreign_key_field_of_related_class(through_class, obj.many_field(attribute).model).name
        else:
            self.resolved_attribute = attribute
            self.self_foreign_key_attribute = self.foreign_key_attribute = None

        self.related = getattr(obj,attribute).model
    def _remove(self, attribute, *values, **kwargs):
        """
            Remove the given values from the collection of attribute, first adopting values from the donor in case the
            caller wishes to remove adopted values
        :param attribute:
        :param values:
        :param kwargs: The only options is 'skip_adopt', to prevent infinite recursion in internal calls
        :return:
        """

        # Oftentimes we'll call this method with no values to remove
        if not values:
            return

        # Adopt the donor values in case the caller wishes to remove some of those from self
        if not kwargs.get('skip_adopt', False):
            self._adopt_from_donor(attribute)
        if has_explicit_through_class(self, attribute):
            # If the Many has an explict through class, we must instead delete the through instances that reference the given values
            foreign_key_attribute = foreign_key_field_of_related_class(self.many_field(attribute).through, self.many_field(attribute).model).name
            filter = {"{0}__id__in".format(foreign_key_attribute):map(lambda v: v.id, values)}
            throughs = self.through_set(attribute).filter(**filter)
            self._remove_throughs(attribute, *throughs, **kwargs)
        else:
            # No explicit through, simply remove the values
            # If we're dealing with a ForeignKey's reverse related_manager, we can't remove the instances
            # We should never have duplicate instances defined that reference the donor and donee, so this
            # is an error case
            manager = getattr(self, attribute)
            if hasattr(manager, 'remove'):
                manager.remove(*values)
            else:
                key_property = '__'.join(resolve_key_property(values[0]).split('.'))
                values_to_delete = manager.filter(**{'%s__in' % key_property: map(lambda value: value.key, values)})
                values_to_delete.delete()
    def save_m2m(self, bundle):
        """
            Overrides the super method in order to handle saving many-to-many collection instances of an explicit through class. For some reason tastypie has no handling for this, but we want to deliver the through class instances to the user that have references to the related attribute (e.g. DbEntityInterest instances are delivered and each has a reference to DbEntity). We also want to allow the client to modify, add, and remove these instances. Thus we must intercept them here and save them properly. Tastypie assumes non-explict Through classes and just dumbly tries to add them to the related field with add(), which fails for explicitly through classes.
        :param bundle:
        :return:
        """
        # This is an exact copy of the super method up until the add() line
        for field_name, field_object in self.fields.items():
            if not getattr(field_object, 'is_m2m', False):
                continue

            if not field_object.attribute:
                continue

            if field_object.readonly:
                continue

            # Get the manager.
            related_mngr = None

            if isinstance(field_object.attribute, basestring):
                related_mngr = getattr(bundle.obj, field_object.attribute)
            elif callable(field_object.attribute):
                related_mngr = field_object.attribute(bundle)

            if related_mngr is None:
                continue

                # This condition is an enhancement to the super method. It allows an add method defined on the field to indicate how to add the many-to-many items
                # We don't use this since our items are handled more carefully below
                #if hasattr(related_mngr, 'clear'):
                # Clear it out, just to be safe.
            #    related_mngr.clear()

            existing_related_objs = related_mngr.all()
            related_objs_to_add = []

            # TODO handle remove and clear
            if hasattr(field_object, 'add'):
                # This condition is an enhancement to the super method.
                # It allows an add method defined on the field to indicate how to add the many-to-many items
                related_objs_to_add = map(lambda bundle: bundle.obj, bundle.data[field_name])
                # Call the custom defined add
                field_object.add(bundle, *related_objs_to_add)
                related_objs_to_remove = list(set(existing_related_objs)-set(related_objs_to_add))
                # Optionally call remove. The add function might take care of removing existing instances instead
                if hasattr(field_object, 'remove') and hasattr(field_object.remove, '__call__'):
                    field_object.remove(bundle, *related_objs_to_remove)
            else:
                explicit_through_class = has_explicit_through_class(bundle.obj, field_object.instance_name)
                if explicit_through_class:
                    to_model_attr = foreign_key_field_of_related_class(related_bundle.obj.__class__, bundle.obj.__class__).name,
                existing_related_objs = related_mngr.all()
                for related_bundle in bundle.data[field_name]:
                    # This if statement is a change from the super method. If we are handling explict through instances we need to give the incoming instance a reference to the bundle.obj. The through instances are never dehydrated with this reference since it simply refers back to the container (bundle.data)
                    if explicit_through_class:
                        # Set one side of the relationship to bundle.obj. This might have already been done on the client, but this overrides
                        setattr(
                            related_bundle.obj,
                            # Figure out the correct field
                            to_model_attr,
                            bundle.obj)
                    # Save the relatedd instance instance no matter what if the toMany relationship is full
                    # We never want to save references because nothing can be changed in
                    # the toMany reference except membership in the toMany
                    if field_object.full:
                        related_bundle.obj.save()
                    # Create a list of objects to add to the manager
                    related_objs_to_add.append(related_bundle.obj)
                # Create the set of objects to remove (ones that existed but weren't in the incoming related_bundle)
                related_objs_to_remove = list(set(existing_related_objs)-set(related_objs_to_add))
                # If we are handling explict through instances the save above is adequate. We don't want to try to add the item to the manager.
                # These methods are thus only for implicit related fields (no explicit through class)
                if hasattr(related_mngr, 'add'):
                    related_mngr.add(*related_objs_to_add)
                if hasattr(related_mngr, 'remove'):
                    related_mngr.remove(*related_objs_to_remove)
示例#4
0
    def save_m2m(self, bundle):
        """
            Overrides the super method in order to handle saving many-to-many collection instances of an explicit through class. For some reason tastypie has no handling for this, but we want to deliver the through class instances to the user that have references to the related attribute (e.g. DbEntityInterest instances are delivered and each has a reference to DbEntity). We also want to allow the client to modify, add, and remove these instances. Thus we must intercept them here and save them properly. Tastypie assumes non-explict Through classes and just dumbly tries to add them to the related field with add(), which fails for explicitly through classes.
        :param bundle:
        :return:
        """
        # This is an exact copy of the super method up until the add() line
        for field_name, field_object in self.fields.items():
            if not getattr(field_object, 'is_m2m', False):
                continue

            if not field_object.attribute:
                continue

            if field_object.readonly:
                continue

            # Get the manager.
            related_mngr = None

            if isinstance(field_object.attribute, basestring):
                related_mngr = getattr(bundle.obj, field_object.attribute)
            elif callable(field_object.attribute):
                related_mngr = field_object.attribute(bundle)

            if related_mngr is None:
                continue

                # This condition is an enhancement to the super method. It allows an add method defined on the field to indicate how to add the many-to-many items
                # We don't use this since our items are handled more carefully below
                #if hasattr(related_mngr, 'clear'):
                # Clear it out, just to be safe.
            #    related_mngr.clear()

            existing_related_objs = related_mngr.all()
            related_objs_to_add = []

            # TODO handle remove and clear
            if hasattr(field_object, 'add'):
                # This condition is an enhancement to the super method.
                # It allows an add method defined on the field to indicate how to add the many-to-many items
                related_objs_to_add = map(lambda bundle: bundle.obj, bundle.data[field_name])
                # Call the custom defined add
                field_object.add(bundle, *related_objs_to_add)
                related_objs_to_remove = list(set(existing_related_objs)-set(related_objs_to_add))
                # Optionally call remove. The add function might take care of removing existing instances instead
                if hasattr(field_object, 'remove') and hasattr(field_object.remove, '__call__'):
                    field_object.remove(bundle, *related_objs_to_remove)
            else:
                explicit_through_class = has_explicit_through_class(bundle.obj, field_object.instance_name)
                if explicit_through_class:
                    to_model_attr = foreign_key_field_of_related_class(related_bundle.obj.__class__, bundle.obj.__class__).name,
                existing_related_objs = related_mngr.all()
                for related_bundle in bundle.data[field_name]:
                    # This if statement is a change from the super method. If we are handling explict through instances we need to give the incoming instance a reference to the bundle.obj. The through instances are never dehydrated with this reference since it simply refers back to the container (bundle.data)
                    if explicit_through_class:
                        # Set one side of the relationship to bundle.obj. This might have already been done on the client, but this overrides
                        setattr(
                            related_bundle.obj,
                            # Figure out the correct field
                            to_model_attr,
                            bundle.obj)
                    # Save the relatedd instance instance no matter what if the toMany relationship is full
                    # We never want to save references because nothing can be changed in
                    # the toMany reference except membership in the toMany
                    if field_object.full:
                        related_bundle.obj.save()
                    # Create a list of objects to add to the manager
                    related_objs_to_add.append(related_bundle.obj)
                # Create the set of objects to remove (ones that existed but weren't in the incoming related_bundle)
                related_objs_to_remove = list(set(existing_related_objs)-set(related_objs_to_add))
                # If we are handling explict through instances the save above is adequate. We don't want to try to add the item to the manager.
                # These methods are thus only for implicit related fields (no explicit through class)
                if hasattr(related_mngr, 'add'):
                    related_mngr.add(*related_objs_to_add)
                if hasattr(related_mngr, 'remove'):
                    related_mngr.remove(*related_objs_to_remove)