Ticket #16458: 16458_inheritance_eq.diff

File 16458_inheritance_eq.diff, 6.2 KB (added by akaariai, 4 years ago)

inherited models can be equal

  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index 71fd1f7..42efe54 100644
    a b class ModelBase(type): 
    129129        # Do the appropriate setup for any model parents.
    130130        o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields
    131131                if isinstance(f, OneToOneField)])
    132 
     132        # The concrete_models is a set of all multi-table models this model
     133        # represents, that is new_class if it is a concrete model, plus all
     134        # multi-table inherited parents. Needed for fast __eq__ implementation.
     135        new_class._meta.concrete_models = set()
     136        if not is_proxy and not abstract:
     137            new_class._meta.concrete_models.add(new_class)
    133138        for base in parents:
    134139            original_base = base
    135140            if not hasattr(base, '_meta'):
    class ModelBase(type): 
    152157                while base._meta.proxy:
    153158                    # Skip over a proxy class to the "real" base it proxies.
    154159                    base = base._meta.proxy_for_model
     160                # Found a concrete model, and this class represents it.
     161                new_class._meta.concrete_models.update(base._meta.concrete_models)
    155162                if base in o2o_map:
    156163                    field = o2o_map[base]
    157164                elif not is_proxy:
    class Model(object): 
    382389        return '%s object' % self.__class__.__name__
    383390
    384391    def __eq__(self, other):
    385         return isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val()
     392        """
     393        Two models are considered equal if they share a common multitable-inherited parent
     394        or if one is an instance of the other. This can be used by checking the _meta
     395        .concrete_models variable.
     396        """
     397        return bool(isinstance(other, Model) and
     398               self._meta.concrete_models.intersection(other._meta.concrete_models) and
     399               self._get_pk_val() == other._get_pk_val())
    386400
    387401    def __ne__(self, other):
    388402        return not self.__eq__(other)
  • tests/modeltests/basic/tests.py

    diff --git a/tests/modeltests/basic/tests.py b/tests/modeltests/basic/tests.py
    index ff09d9b..c066f43 100644
    a b class ModelTest(TestCase): 
    206206        # Check that != and == operators behave as expecte on instances
    207207        self.assertTrue(a7 != a8)
    208208        self.assertFalse(a7 == a8)
     209        # And that they work when the other is not a Model instance
     210        self.assertFalse(a7 == None)
     211        self.assertFalse(a7 == self)
    209212        self.assertEqual(a8, Article.objects.get(id__exact=a8.id))
    210213
    211214        self.assertTrue(Article.objects.get(id__exact=a8.id) != Article.objects.get(id__exact=a7.id))
  • tests/modeltests/defer/tests.py

    diff --git a/tests/modeltests/defer/tests.py b/tests/modeltests/defer/tests.py
    index 5f6c53d..9cf32ee 100644
    a b class DeferTests(TestCase): 
    2323        p1 = Primary.objects.create(name="p1", value="xx", related=s1)
    2424
    2525        qs = Primary.objects.all()
    26 
    2726        self.assert_delayed(qs.defer("name")[0], 1)
    2827        self.assert_delayed(qs.only("name")[0], 2)
    2928        self.assert_delayed(qs.defer("related__first")[0], 0)
     29       
     30        # deferred instances are equal to their non-deferred counterpart
     31        deferred_p1 = qs.defer("name")[0]
     32        self.assertTrue(deferred_p1==p1)
     33        # The __eq__ operator is symmetric as well as the == operator
     34        self.assertTrue(p1==deferred_p1)
     35        self.assertTrue(deferred_p1.__eq__(p1))
     36        self.assertTrue(p1.__eq__(deferred_p1))
     37
    3038
    3139        obj = qs.select_related().only("related__first")[0]
    3240        self.assert_delayed(obj, 2)
  • tests/modeltests/model_inheritance/tests.py

    diff --git a/tests/modeltests/model_inheritance/tests.py b/tests/modeltests/model_inheritance/tests.py
    index 334297a..28b9258 100644
    a b class ModelInheritanceTests(TestCase): 
    6969            StudentWorker.objects.get, pk__lt=sw2.pk + 100
    7070        )
    7171
     72        # A multi-table inherited instance is considered equal to its base
     73        # class
     74        w = Worker(pk=1)
     75        sw = StudentWorker(pk=1)
     76        self.assertTrue(w==sw)
     77        # The __eq__ operator is symmetric as well as the '==' operator
     78        self.assertTrue(sw==w)
     79        self.assertTrue(w.__eq__(sw))
     80        self.assertTrue(sw.__eq__(w))
     81        # A common abstract base class does not lead to equality
     82        s = Student(pk=1)
     83        self.assertFalse(s==w)
     84       
     85
    7286    def test_multiple_table(self):
    7387        post = Post.objects.create(title="Lorem Ipsum")
    7488        # The Post model has distinct accessors for the Comment and Link models.
    class ModelInheritanceTests(TestCase): 
    269283        self.assertNumQueries(1,
    270284            lambda: ItalianRestaurant.objects.select_related("chef")[0].chef
    271285        )
     286        # Having a common parent works when there are models in the chain
     287        r = Place(pk=1)
     288        i = ItalianRestaurant(pk=1)
     289        self.assertTrue(r==i)
     290        # A common concrete (multitable-inherited) parent leads to equality
     291        s = Supplier(pk=1)
     292        self.assertTrue(i==s)
    272293
    273294    def test_mixin_init(self):
    274295        m = MixinModel()
  • tests/modeltests/proxy_models/tests.py

    diff --git a/tests/modeltests/proxy_models/tests.py b/tests/modeltests/proxy_models/tests.py
    index 0a46a25..a4c7d40 100644
    a b class ProxyModelTests(TestCase): 
    4545        self.assertEqual(MyPerson.objects.get(name="Foo McBar").id, person.id)
    4646        self.assertFalse(MyPerson.objects.get(id=person.id).has_special_name())
    4747
     48    def test_proxy_eq(self):
     49        """
     50        Proxied models are considered equal to their concrete base class.
     51        """
     52        p = Person(pk=1)
     53        mp = MyPerson(pk=1)
     54        self.assertTrue(p==mp)
     55        # the __eq__ operator is symmetric as well as the == operator
     56        self.assertTrue(mp==p)
     57        self.assertTrue(p.__eq__(mp))
     58        self.assertTrue(mp.__eq__(p))
     59
    4860    def test_no_proxy(self):
    4961        """
    5062        Person is not proxied by StatusPerson subclass.
Back to Top