コード例 #1
0
ファイル: abstract.py プロジェクト: jonozzz/iRack
def duplicate(obj, value, field):
    """
    Duplicate all related objects of `obj` setting
    `field` to `value`. If one of the duplicate
    objects has an FK to another duplicate object
    update that as well. Return the duplicate copy
    of `obj`.  
    """
    collected_objs = CollectedObjects()
    obj._collect_sub_objects(collected_objs)
    related_models = collected_objs.keys()
    root_obj = None
    # Traverse the related models in reverse deletion order.    
    for model in reversed(related_models):
        # Find all FKs on `model` that point to a `related_model`.
        fks = []
        for f in model._meta.fields:
            if isinstance(f, ForeignKey) and f.rel.to in related_models:
                fks.append(f)
        # Replace each `sub_obj` with a duplicate.
        sub_obj = collected_objs[model]
        for pk_val, obj in sub_obj.iteritems():
            for fk in fks:
                fk_value = getattr(obj, "%s_id" % fk.name)
                # If this FK has been duplicated then point to the duplicate.
                if fk_value in collected_objs[fk.rel.to]:
                    dupe_obj = collected_objs[fk.rel.to][fk_value]
                    setattr(obj, fk.name, dupe_obj)
            # Duplicate the object and save it.
            obj.id = None
            setattr(obj, field, value)
            obj.save()
            if root_obj is None:
                root_obj = obj
    return root_obj
コード例 #2
0
    def skip(self):
        """ Determine whether or not this object should be skipped.
        If this model is a parent of a single subclassed instance, skip it.
        The subclassed instance will create this parent instance for us.
        TODO: Allow the user to force its creation?
        """

        if self.skip_me is not None:
            return self.skip_me

        try:
            # Django trunk since r7722 uses CollectedObjects instead of dict
            from django.db.models.query import CollectedObjects
            sub_objects = CollectedObjects()
        except ImportError:
            # previous versions don't have CollectedObjects
            sub_objects = {}
        self.instance._collect_sub_objects(sub_objects)
        if reduce(
                lambda x, y: x + y,
            [self.model in so._meta.parents
             for so in sub_objects.keys()]) == 1:
            pk_name = self.instance._meta.pk.name
            key = '%s_%s' % (self.model.__name__,
                             getattr(self.instance, pk_name))
            self.context[key] = None
            self.skip_me = True
        else:
            self.skip_me = False

        return self.skip_me
コード例 #3
0
ファイル: models.py プロジェクト: jsnhff/rhizome.org
    def _publisher_delete_marked(self, collect=True):
        """If this instance, or some remote instances are marked for deletion
        kill them.
        """
        if self.publisher_is_draft:
            # escape soon from draft models
            return

        if collect:
            from django.db.models.query import CollectedObjects
            seen = CollectedObjects()
            self._collect_delete_marked_sub_objects(seen)
            for cls in seen.unordered_keys():
                items = seen[cls]
                if issubclass(cls, Publisher):
                    for item in items.values():
                        item._publisher_delete_marked(collect=False)

        if self.publisher_state == Publisher.PUBLISHER_STATE_DELETE:
            try:
                self.delete()
            except AttributeError:
                # this exception may happen because of the plugin relations
                # to CMSPlugin and mppt way of _meta assignment
                pass
コード例 #4
0
    def skip(self):
        """ Determine whether or not this object should be skipped.
            If this model is a parent of a single subclassed instance, skip it.
            The subclassed instance will create this parent instance for us.

            TODO: Allow the user to force its creation?
        """

        if self.skip_me is not None:
            return self.skip_me

        try:
            # Django trunk since r7722 uses CollectedObjects instead of dict
            from django.db.models.query import CollectedObjects
            sub_objects = CollectedObjects()
        except ImportError:
            # previous versions don't have CollectedObjects
            sub_objects = {}
        self.instance._collect_sub_objects(sub_objects)
        if reduce(lambda x, y: x+y, [self.model in so._meta.parents for so in sub_objects.keys()]) == 1:
            pk_name = self.instance._meta.pk.name
            # MMH: print "*** pk_name = %s" % (pk_name)
            key = '%s_%s' % (self.model.__name__, getattr(self.instance, pk_name))
            self.context[key] = None
            self.skip_me = True
        else:
            self.skip_me = False

        return self.skip_me
コード例 #5
0
    def _publisher_delete_marked(self, collect=True):
        """If this instance, or some remote instances are marked for deletion
        kill them.
        """
        if self.publisher_is_draft:
            # escape soon from draft models
            return

        if collect:
            from django.db.models.query import CollectedObjects
            seen = CollectedObjects()
            self._collect_delete_marked_sub_objects(seen)
            for cls in seen.unordered_keys():
                items = seen[cls]
                if issubclass(cls, Page):
                    for item in items.values():
                        item._publisher_delete_marked(collect=False)

        if self.publisher_state == self.PUBLISHER_STATE_DELETE:
            try:
                self.delete()
            except AttributeError:
                # this exception may happen because of the plugin relations
                # to CMSPlugin and mppt way of _meta assignment
                pass
コード例 #6
0
def duplicate(obj, update=None, model_order=None):
    """
    Duplicate all related objects of obj updating attributes using dict update.
    If a duplicated objects has a FK to another duplicated object then update 
    that. Returns the duplicate copy of obj.
        
    model_order is a list of models which specify in which order objects should
    be saved.

    This function offers acceptable performance on small object trees.
    """
    #TODO: The reference to obj is lost - can we save it somehow?
    #TODO: What about m2m relationships?
    
    # We lose the reference to obj so store this so we can look it up again.
    obj_pk = getattr(obj, obj._meta.pk.name)
    
    collected_objs = CollectedObjects()
    obj._collect_sub_objects(collected_objs)
    related_models = collected_objs.keys()
    root_obj = None
    
    # Sometimes it's good enough just to save in reverse deletion order.
    if model_order is None:
        model_order = reversed(related_models)
        
    for model in model_order:
        if model not in collected_objs:
            continue

        # Find all FKs on model that point to a related_model.
        fks = []
        for f in model._meta.fields:        
            if isinstance(f, ForeignKey) and f.rel.to in related_models:
                fks.append(f)                

        # Replace each `sub_obj` with a duplicate.
        sub_objs = collected_objs[model]
        for pk_val, sub_obj in sub_objs.iteritems():
            for fk in fks:
                fk_value = getattr(sub_obj, "%s_id" % fk.name)
                # If this FK has been duplicated then point to the duplicate.
                if fk_value in collected_objs[fk.rel.to]:
                    dupe_obj = collected_objs[fk.rel.to][fk_value]
                    setattr(sub_obj, fk.name, dupe_obj)
            
            # Duplicate the object and save it.
            sub_obj.id = None
            for k, v in update.items():
                setattr(sub_obj, k, v)
            sub_obj.save()
            if root_obj is None:
                root_obj = sub_obj

    # Restore the reference to obj.
    obj = obj._default_manager.get(pk=obj_pk)

    return root_obj
コード例 #7
0
def update_related_fields(obj, update):
    """
    Update all attributes in dict update for all objects related to obj.
    Based on the delete object code: http://bit.ly/osrZf
    """
    collected_objs = CollectedObjects()
    obj._collect_sub_objects(collected_objs)
    models = collected_objs.keys()
    for model in models:
        instance_tuple = collected_objs[model].items()
        pk_list = [pk for pk, instance in instance_tuple]
        # Update fields in the model.
        updates = update.copy()
        [updates.pop(fn) for fn in updates.keys() if not _has_field(model, fn)]
        model._default_manager.filter(id__in=pk_list).update(**updates)
コード例 #8
0
ファイル: base.py プロジェクト: meliascosta/juegosdemente
    def delete(self):
        assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)

        # Find all the objects than need to be deleted.
        seen_objs = CollectedObjects()
        self._collect_sub_objects(seen_objs)

        # Actually delete the objects.
        delete_objects(seen_objs)
コード例 #9
0
ファイル: tests.py プロジェクト: 0xmilk/appscale
    def test_delete(self):
        ## Second, test the usage of CollectedObjects by Model.delete()

        # Due to the way that transactions work in the test harness, doing
        # m.delete() here can work but fail in a real situation, since it may
        # delete all objects, but not in the right order. So we manually check
        # that the order of deletion is correct.

        # Also, it is possible that the order is correct 'accidentally', due
        # solely to order of imports etc.  To check this, we set the order that
        # 'get_models()' will retrieve to a known 'nice' order, and then try
        # again with a known 'tricky' order.  Slightly naughty access to
        # internals here :-)

        # If implementation changes, then the tests may need to be simplified:
        #  - remove the lines that set the .keyOrder and clear the related
        #    object caches
        #  - remove the second set of tests (with a2, b2 etc)

        a1 = A.objects.create()
        b1 = B.objects.create(a=a1)
        c1 = C.objects.create(b=b1)
        d1 = D.objects.create(c=c1, a=a1)

        o = CollectedObjects()
        a1._collect_sub_objects(o)
        self.assertEqual(o.keys(), [D, C, B, A])
        a1.delete()

        # Same again with a known bad order
        self.order_models("d", "c", "b", "a")
        self.clear_rel_obj_caches(A, B, C, D)

        a2 = A.objects.create()
        b2 = B.objects.create(a=a2)
        c2 = C.objects.create(b=b2)
        d2 = D.objects.create(c=c2, a=a2)

        o = CollectedObjects()
        a2._collect_sub_objects(o)
        self.assertEqual(o.keys(), [D, C, B, A])
        a2.delete()
コード例 #10
0
ファイル: tests.py プロジェクト: yekhneek/django-old
    def test_nullable_related_fields_collected_objects_model_delete(self):
        ## Second, test the usage of CollectedObjects by Model.delete()

        e1 = E()
        e1.save()
        f1 = F(e=e1)
        f1.save()
        e1.f = f1
        e1.save()

        # Since E.f is nullable, we should delete F first (after nulling out
        # the E.f field), then E.

        o = CollectedObjects()
        e1._collect_sub_objects(o)
        self.assertQuerysetEqual(o.keys(),
                                 ["<class 'modeltests.delete.models.F'>",
                                  "<class 'modeltests.delete.models.E'>"])

        # temporarily replace the UpdateQuery class to verify that E.f
        # is actually nulled out first

        original_class = django.db.models.sql.UpdateQuery
        django.db.models.sql.UpdateQuery = LoggingUpdateQuery

        # this is ugly, but it works
        global test_last_cleared_field
        test_last_cleared_field = ''
        e1.delete()
        self.assertEqual(test_last_cleared_field, 'f')
        

        e2 = E()
        e2.save()
        f2 = F(e=e2)
        f2.save()
        e2.f = f2
        e2.save()

        # Same deal as before, though we are starting from the other object.

        o = CollectedObjects()
        f2._collect_sub_objects(o)
        o.keys()
        ["<class 'modeltests.delete.models.F'>", "<class 'modeltests.delete.models.E'>"]

        test_last_cleared_field = ''
        f2.delete()
        self.assertEqual(test_last_cleared_field, 'f')

        # Put this back to normal
        django.db.models.sql.UpdateQuery = original_class

        # Restore the app cache to previous condition so that all
        # models are accounted for.
        cache.app_models['delete'].keyOrder = ['a', 'b', 'c', 'd', 'e', 'f']
        clear_rel_obj_caches([A, B, C, D, E, F])
コード例 #11
0
ファイル: tests.py プロジェクト: 0xmilk/appscale
    def test_delete_nullable(self):
        e1 = E.objects.create()
        f1 = F.objects.create(e=e1)
        e1.f = f1
        e1.save()

        # Since E.f is nullable, we should delete F first (after nulling out
        # the E.f field), then E.

        o = CollectedObjects()
        e1._collect_sub_objects(o)
        self.assertEqual(o.keys(), [F, E])

        # temporarily replace the UpdateQuery class to verify that E.f is
        # actually nulled out first

        logged = []
        class LoggingUpdateQuery(sql.UpdateQuery):
            def clear_related(self, related_field, pk_list, using):
                logged.append(related_field.name)
                return super(LoggingUpdateQuery, self).clear_related(related_field, pk_list, using)
        original = sql.UpdateQuery
        sql.UpdateQuery = LoggingUpdateQuery

        e1.delete()
        self.assertEqual(logged, ["f"])
        logged = []

        e2 = E.objects.create()
        f2 = F.objects.create(e=e2)
        e2.f = f2
        e2.save()

        # Same deal as before, though we are starting from the other object.
        o = CollectedObjects()
        f2._collect_sub_objects(o)
        self.assertEqual(o.keys(), [F, E])
        f2.delete()
        self.assertEqual(logged, ["f"])
        logged = []

        sql.UpdateQuery = original
コード例 #12
0
ファイル: tests.py プロジェクト: yekhneek/django-old
    def test_collected_objects_data_structure(self):
        ## Test the CollectedObjects data structure directly

        g = CollectedObjects()
        self.assertFalse(g.add("key1", 1, "item1", None))
        self.assertEqual(g["key1"], {1: 'item1'})
        self.assertFalse(g.add("key2", 1, "item1", "key1"))
        self.assertFalse(g.add("key2", 2, "item2", "key1"))
        self.assertEqual(g["key2"], {1: 'item1', 2: 'item2'})
        self.assertFalse(g.add("key3", 1, "item1", "key1"))
        self.assertTrue(g.add("key3", 1, "item1", "key2"))
        self.assertEqual(g.ordered_keys(), ['key3', 'key2', 'key1'])
        self.assertTrue(g.add("key2", 1, "item1", "key3"))
        self.assertRaises(CyclicDependency,
                          g.ordered_keys)
コード例 #13
0
ファイル: tests.py プロジェクト: yekhneek/django-old
    def test_nullable_related_fields_collected_objects(self):

        ## First, test the CollectedObjects data structure directly
        g = CollectedObjects()
        self.assertFalse(g.add("key1", 1, "item1", None))
        self.assertFalse(g.add("key2", 1, "item1", "key1", nullable=True))
        self.assertTrue(g.add("key1", 1, "item1", "key2"))
        self.assertEqual(g.ordered_keys(), ['key1', 'key2'])
コード例 #14
0
ファイル: tests.py プロジェクト: 0xmilk/appscale
    def test_collected_objects(self):
        g = CollectedObjects()
        self.assertFalse(g.add("key1", 1, "item1", None))
        self.assertEqual(g["key1"], {1: "item1"})

        self.assertFalse(g.add("key2", 1, "item1", "key1"))
        self.assertFalse(g.add("key2", 2, "item2", "key1"))

        self.assertEqual(g["key2"], {1: "item1", 2: "item2"})

        self.assertFalse(g.add("key3", 1, "item1", "key1"))
        self.assertTrue(g.add("key3", 1, "item1", "key2"))
        self.assertEqual(g.ordered_keys(), ["key3", "key2", "key1"])

        self.assertTrue(g.add("key2", 1, "item1", "key3"))
        self.assertRaises(CyclicDependency, g.ordered_keys)
コード例 #15
0
    def skip(self):
        """ Determine whether or not this object should be skipped.
            If this model instance is a parent of a single subclassed
            instance, skip it. The subclassed instance will create this
            parent instance for us.

            TODO: Allow the user to force its creation?
        """

        if self.skip_me is not None:
            return self.skip_me

        def get_skip_version():
            """ Return which version of the skip code should be run

                Django's deletion code was refactored in r14507 which
                was just two days before 1.3 alpha 1 (r14519)
            """
            if not hasattr(self, '_SKIP_VERSION'):
                version = django.VERSION
                # no, it isn't lisp. I swear.
                self._SKIP_VERSION = (
                    version[0] > 1 or (  # django 2k... someday :)
                        version[0] == 1 and (  # 1.x
                            version[1] >= 4 or  # 1.4+
                            version[1] == 3 and not (  # 1.3.x
                                (version[3] == 'alpha' and version[1] == 0))))
                ) and 2 or 1  # NOQA
            return self._SKIP_VERSION

        if get_skip_version() == 1:
            try:
                # Django trunk since r7722 uses CollectedObjects instead of dict
                from django.db.models.query import CollectedObjects
                sub_objects = CollectedObjects()
            except ImportError:
                # previous versions don't have CollectedObjects
                sub_objects = {}
            self.instance._collect_sub_objects(sub_objects)
            sub_objects = sub_objects.keys()

        elif get_skip_version() == 2:
            from django.db.models.deletion import Collector
            from django.db import router
            cls = self.instance.__class__
            using = router.db_for_write(cls, instance=self.instance)
            collector = Collector(using=using)
            collector.collect([self.instance], collect_related=False)

            # collector stores its instances in two places. I *think* we
            # only need collector.data, but using the batches is needed
            # to perfectly emulate the old behaviour
            # TODO: check if batches are really needed. If not, remove them.
            sub_objects = sum([list(i) for i in collector.data.values()], [])

            for batch in collector.batches.values():
                # batch.values can be sets, which must be converted to lists
                sub_objects += sum([list(i) for i in batch.values()], [])

        sub_objects_parents = [so._meta.parents for so in sub_objects]
        if [self.model in p for p in sub_objects_parents].count(True) == 1:
            # since this instance isn't explicitly created, it's variable name
            # can't be referenced in the script, so record None in context dict
            pk_name = self.instance._meta.pk.name
            key = '%s_%s' % (self.model.__name__,
                             getattr(self.instance, pk_name))
            self.context[key] = None
            self.skip_me = True
        else:
            self.skip_me = False

        return self.skip_me
コード例 #16
0
ファイル: tests.py プロジェクト: 0xmilk/appscale
 def test_collected_objects_null(self):
     g = CollectedObjects()
     self.assertFalse(g.add("key1", 1, "item1", None))
     self.assertFalse(g.add("key2", 1, "item1", "key1", nullable=True))
     self.assertTrue(g.add("key1", 1, "item1", "key2"))
     self.assertEqual(g.ordered_keys(), ["key1", "key2"])
コード例 #17
0
    def skip(self):
        """ Determine whether or not this object should be skipped.
            If this model instance is a parent of a single subclassed
            instance, skip it. The subclassed instance will create this
            parent instance for us.

            TODO: Allow the user to force its creation?
        """

        if self.skip_me is not None:
            return self.skip_me

        def get_skip_version():
            """ Return which version of the skip code should be run

                Django's deletion code was refactored in r14507 which
                was just two days before 1.3 alpha 1 (r14519)
            """
            if not hasattr(self, '_SKIP_VERSION'):
                version = django.VERSION
                # no, it isn't lisp. I swear.
                self._SKIP_VERSION = (
                    version[0] > 1 or (  # django 2k... someday :)
                        version[0] == 1 and (  # 1.x
                            version[1] >= 4 or  # 1.4+
                            version[1] == 3 and not (  # 1.3.x
                                (version[3] == 'alpha' and version[1] == 0)
                            )
                        )
                    )
                ) and 2 or 1  # NOQA
            return self._SKIP_VERSION

        if get_skip_version() == 1:
            try:
                # Django trunk since r7722 uses CollectedObjects instead of dict
                from django.db.models.query import CollectedObjects
                sub_objects = CollectedObjects()
            except ImportError:
                # previous versions don't have CollectedObjects
                sub_objects = {}
            self.instance._collect_sub_objects(sub_objects)
            sub_objects = sub_objects.keys()

        elif get_skip_version() == 2:
            from django.db.models.deletion import Collector
            from django.db import router
            cls = self.instance.__class__
            using = router.db_for_write(cls, instance=self.instance)
            collector = Collector(using=using)
            collector.collect([self.instance], collect_related=False)

            # collector stores its instances in two places. I *think* we
            # only need collector.data, but using the batches is needed
            # to perfectly emulate the old behaviour
            # TODO: check if batches are really needed. If not, remove them.
            sub_objects = sum([list(i) for i in collector.data.values()], [])

            if hasattr(collector, 'batches'):
                # Django 1.6 removed batches for being dead code
                # https://github.com/django/django/commit/a170c3f755351beb35f8166ec3c7e9d524d9602
                for batch in collector.batches.values():
                    # batch.values can be sets, which must be converted to lists
                    sub_objects += sum([list(i) for i in batch.values()], [])

        sub_objects_parents = [so._meta.parents for so in sub_objects]
        if [self.model in p for p in sub_objects_parents].count(True) == 1:
            # since this instance isn't explicitly created, it's variable name
            # can't be referenced in the script, so record None in context dict
            pk_name = self.instance._meta.pk.name
            key = '%s_%s' % (self.model.__name__, getattr(self.instance, pk_name))
            self.context[key] = None
            self.skip_me = True
        else:
            self.skip_me = False

        return self.skip_me
コード例 #18
0
ファイル: tests.py プロジェクト: yekhneek/django-old
    def test_collected_objects_by_model_delete(self):
        ## Test the usage of CollectedObjects by Model.delete()

        # Due to the way that transactions work in the test harness,
        # doing m.delete() here can work but fail in a real situation,
        # since it may delete all objects, but not in the right order.
        # So we manually check that the order of deletion is correct.
        
        # Also, it is possible that the order is correct 'accidentally', due
        # solely to order of imports etc.  To check this, we set the order
        # that 'get_models()' will retrieve to a known 'nice' order, and
        # then try again with a known 'tricky' order.  Slightly naughty
        # access to internals here :-)

        # If implementation changes, then the tests may need to be simplified:
        #  - remove the lines that set the .keyOrder and clear the related
        #    object caches
        #  - remove the second set of tests (with a2, b2 etc)

        # Nice order
        cache.app_models['delete'].keyOrder = ['a', 'b', 'c', 'd']
        clear_rel_obj_caches([A, B, C, D])

        a1 = A()
        a1.save()
        b1 = B(a=a1)
        b1.save()
        c1 = C(b=b1)
        c1.save()
        d1 = D(c=c1, a=a1)
        d1.save()

        o = CollectedObjects()
        a1._collect_sub_objects(o)
        self.assertQuerysetEqual(o.keys(), 
                                 ["<class 'modeltests.delete.models.D'>",
                                  "<class 'modeltests.delete.models.C'>",
                                  "<class 'modeltests.delete.models.B'>",
                                  "<class 'modeltests.delete.models.A'>"])
        a1.delete()

        # Same again with a known bad order
        cache.app_models['delete'].keyOrder = ['d', 'c', 'b', 'a']
        clear_rel_obj_caches([A, B, C, D])

        a2 = A()
        a2.save()
        b2 = B(a=a2)
        b2.save()
        c2 = C(b=b2)
        c2.save()
        d2 = D(c=c2, a=a2)
        d2.save()

        o = CollectedObjects()
        a2._collect_sub_objects(o)
        self.assertQuerysetEqual(o.keys(),
                                 ["<class 'modeltests.delete.models.D'>",
                                  "<class 'modeltests.delete.models.C'>", 
                                  "<class 'modeltests.delete.models.B'>",
                                  "<class 'modeltests.delete.models.A'>"])
        a2.delete()