diff --git a/django/db/models/base.py b/django/db/models/base.py
index 71fd1f7..42efe54 100644
a
|
b
|
class ModelBase(type):
|
129 | 129 | # Do the appropriate setup for any model parents. |
130 | 130 | o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields |
131 | 131 | 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) |
133 | 138 | for base in parents: |
134 | 139 | original_base = base |
135 | 140 | if not hasattr(base, '_meta'): |
… |
… |
class ModelBase(type):
|
152 | 157 | while base._meta.proxy: |
153 | 158 | # Skip over a proxy class to the "real" base it proxies. |
154 | 159 | 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) |
155 | 162 | if base in o2o_map: |
156 | 163 | field = o2o_map[base] |
157 | 164 | elif not is_proxy: |
… |
… |
class Model(object):
|
382 | 389 | return '%s object' % self.__class__.__name__ |
383 | 390 | |
384 | 391 | 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()) |
386 | 400 | |
387 | 401 | def __ne__(self, other): |
388 | 402 | return not self.__eq__(other) |
diff --git a/tests/modeltests/basic/tests.py b/tests/modeltests/basic/tests.py
index ff09d9b..c066f43 100644
a
|
b
|
class ModelTest(TestCase):
|
206 | 206 | # Check that != and == operators behave as expecte on instances |
207 | 207 | self.assertTrue(a7 != a8) |
208 | 208 | 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) |
209 | 212 | self.assertEqual(a8, Article.objects.get(id__exact=a8.id)) |
210 | 213 | |
211 | 214 | self.assertTrue(Article.objects.get(id__exact=a8.id) != Article.objects.get(id__exact=a7.id)) |
diff --git a/tests/modeltests/defer/tests.py b/tests/modeltests/defer/tests.py
index 5f6c53d..9cf32ee 100644
a
|
b
|
class DeferTests(TestCase):
|
23 | 23 | p1 = Primary.objects.create(name="p1", value="xx", related=s1) |
24 | 24 | |
25 | 25 | qs = Primary.objects.all() |
26 | | |
27 | 26 | self.assert_delayed(qs.defer("name")[0], 1) |
28 | 27 | self.assert_delayed(qs.only("name")[0], 2) |
29 | 28 | 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 | |
30 | 38 | |
31 | 39 | obj = qs.select_related().only("related__first")[0] |
32 | 40 | self.assert_delayed(obj, 2) |
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):
|
69 | 69 | StudentWorker.objects.get, pk__lt=sw2.pk + 100 |
70 | 70 | ) |
71 | 71 | |
| 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 | |
72 | 86 | def test_multiple_table(self): |
73 | 87 | post = Post.objects.create(title="Lorem Ipsum") |
74 | 88 | # The Post model has distinct accessors for the Comment and Link models. |
… |
… |
class ModelInheritanceTests(TestCase):
|
269 | 283 | self.assertNumQueries(1, |
270 | 284 | lambda: ItalianRestaurant.objects.select_related("chef")[0].chef |
271 | 285 | ) |
| 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) |
272 | 293 | |
273 | 294 | def test_mixin_init(self): |
274 | 295 | m = MixinModel() |
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):
|
45 | 45 | self.assertEqual(MyPerson.objects.get(name="Foo McBar").id, person.id) |
46 | 46 | self.assertFalse(MyPerson.objects.get(id=person.id).has_special_name()) |
47 | 47 | |
| 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 | |
48 | 60 | def test_no_proxy(self): |
49 | 61 | """ |
50 | 62 | Person is not proxied by StatusPerson subclass. |