Browse Source

Made Model.__eq__ consider proxy models equivalent

Fixed #11892, fixed #16458, fixed #14492.
Anssi Kääriäinen 11 years ago
parent
commit
4668c142dc

+ 3 - 1
django/db/models/base.py

@@ -459,7 +459,9 @@ class Model(six.with_metaclass(ModelBase)):
         return '%s object' % self.__class__.__name__
 
     def __eq__(self, other):
-        return isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val()
+        return (isinstance(other, Model) and
+                self._meta.concrete_model == other._meta.concrete_model and
+                self._get_pk_val() == other._get_pk_val())
 
     def __ne__(self, other):
         return not self.__eq__(other)

+ 34 - 0
docs/ref/models/instances.txt

@@ -494,6 +494,40 @@ using ``__str__()`` like this::
             # first_name and last_name will be unicode strings.
             return force_bytes('%s %s' % (self.first_name, self.last_name))
 
+``__eq__``
+----------
+
+.. method:: Model.__eq__()
+
+The equality method is defined such that instances with the same primary
+key value and the same concrete class are considered equal. The term
+concrete class means proxy model's first non-proxy parent or the class
+itself if it isn't a proxy class.
+
+For example::
+
+    form django.db import models
+
+    class MyModel(models.Model):
+        id = models.AutoField(primary_key=True)
+
+    class MyProxyModel(MyModel):
+        class Meta:
+            proxy = True
+
+    class MultitableInherited(MyModel):
+        pass
+
+    MyModel(id=1) == MyModel(id=1)
+    MyModel(id=1) == MyProxyModel(id=1)
+    MyModel(id=1) != MultitableInherited(id=1)
+    MyModel(id=1) != MyModel(id=2)
+
+.. versionchanged:: 1.7
+
+  In previous versions only instances of the exact same class and same
+  primary key value were considered equal.
+
 ``get_absolute_url``
 --------------------
 

+ 5 - 0
docs/releases/1.7.txt

@@ -194,6 +194,11 @@ Miscellaneous
   removes the ability for visitors to generate spurious HTTP 500 errors by
   requesting static files that don't exist or haven't been collected yet.
 
+* The :meth:`django.db.models.Model.__eq__` method is now defined in a
+  way where instances of a proxy model and its base model are considered
+  equal when primary keys match. Previously only instances of exact same
+  class were considered equal on primary key match.
+
 Features deprecated in 1.7
 ==========================
 

+ 4 - 0
tests/basic/tests.py

@@ -707,6 +707,10 @@ class ModelTest(TestCase):
         with self.assertRaises(ObjectDoesNotExist):
             SelfRef.objects.get(selfref=sr)
 
+    def test_eq(self):
+        self.assertNotEqual(Article(id=1), object())
+        self.assertNotEqual(object(), Article(id=1))
+
 
 class ConcurrentSaveTests(TransactionTestCase):
 

+ 6 - 0
tests/defer/tests.py

@@ -183,3 +183,9 @@ class DeferTests(TestCase):
         with self.assertNumQueries(0):
             bc_deferred.id
         self.assertEqual(bc_deferred.pk, bc_deferred.id)
+
+    def test_eq(self):
+        s1 = Secondary.objects.create(first="x1", second="y1")
+        s1_defer = Secondary.objects.only('pk').get(pk=s1.pk)
+        self.assertEqual(s1, s1_defer)
+        self.assertEqual(s1_defer, s1)

+ 5 - 0
tests/model_inheritance/tests.py

@@ -318,3 +318,8 @@ class ModelInheritanceTests(TestCase):
             sql = query['sql']
             if 'UPDATE' in sql:
                 self.assertEqual(expected_sql, sql)
+
+    def test_eq(self):
+        # Equality doesn't transfer in multitable inheritance.
+        self.assertNotEqual(Place(id=1), Restaurant(id=1))
+        self.assertNotEqual(Restaurant(id=1), Place(id=1))

+ 3 - 0
tests/proxy_models/tests.py

@@ -362,6 +362,9 @@ class ProxyModelTests(TestCase):
         p = MyPerson.objects.get(pk=100)
         self.assertEqual(p.name, 'Elvis Presley')
 
+    def test_eq(self):
+        self.assertEqual(MyPerson(id=100), Person(id=100))
+
 
 class ProxyModelAdminTests(TestCase):
     fixtures = ['myhorses']