mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	Fixed model.__eq__ and __hash__ for no pk value cases
The __eq__ method now considers two instances without primary key value equal only when they have same id(). The __hash__ method raises TypeError for no primary key case. Fixed #18864, fixed #18250 Thanks to Tim Graham for docs review.
This commit is contained in:
		| @@ -459,14 +459,21 @@ class Model(six.with_metaclass(ModelBase)): | ||||
|         return '%s object' % self.__class__.__name__ | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         return (isinstance(other, Model) and | ||||
|                 self._meta.concrete_model == other._meta.concrete_model and | ||||
|                 self._get_pk_val() == other._get_pk_val()) | ||||
|         if not isinstance(other, Model): | ||||
|             return False | ||||
|         if self._meta.concrete_model != other._meta.concrete_model: | ||||
|             return False | ||||
|         my_pk = self._get_pk_val() | ||||
|         if my_pk is None: | ||||
|             return self is other | ||||
|         return my_pk == other._get_pk_val() | ||||
|  | ||||
|     def __ne__(self, other): | ||||
|         return not self.__eq__(other) | ||||
|  | ||||
|     def __hash__(self): | ||||
|         if self._get_pk_val() is None: | ||||
|             raise TypeError("Model instances without primary key value are unhashable") | ||||
|         return hash(self._get_pk_val()) | ||||
|  | ||||
|     def __reduce__(self): | ||||
|   | ||||
| @@ -631,7 +631,11 @@ class BaseModelFormSet(BaseFormSet): | ||||
|             seen_data = set() | ||||
|             for form in valid_forms: | ||||
|                 # get data for each field of each of unique_check | ||||
|                 row_data = tuple([form.cleaned_data[field] for field in unique_check if field in form.cleaned_data]) | ||||
|                 row_data = (form.cleaned_data[field] | ||||
|                             for field in unique_check if field in form.cleaned_data) | ||||
|                 # Reduce Model instances to their primary key values | ||||
|                 row_data = tuple(d._get_pk_val() if hasattr(d, '_get_pk_val') else d | ||||
|                                  for d in row_data) | ||||
|                 if row_data and not None in row_data: | ||||
|                     # if we've already seen it then we have a uniqueness failure | ||||
|                     if row_data in seen_data: | ||||
|   | ||||
| @@ -521,6 +521,25 @@ For example:: | ||||
|   In previous versions only instances of the exact same class and same | ||||
|   primary key value were considered equal. | ||||
|  | ||||
| ``__hash__`` | ||||
| ------------ | ||||
|  | ||||
| .. method:: Model.__hash__() | ||||
|  | ||||
| The ``__hash__`` method is based on the instance's primary key value. It | ||||
| is effectively hash(obj.pk). If the instance doesn't have a primary key | ||||
| value then a ``TypeError`` will be raised (otherwise the ``__hash__`` | ||||
| method would return different values before and after the instance is | ||||
| saved, but changing the ``__hash__`` value of an instance `is forbidden | ||||
| in Python`_). | ||||
|  | ||||
| .. versionchanged:: 1.7 | ||||
|  | ||||
|   In previous versions instance's without primary key value were | ||||
|   hashable. | ||||
|  | ||||
| .. _is forbidden in Python: http://docs.python.org/reference/datamodel.html#object.__hash__ | ||||
|  | ||||
| ``get_absolute_url`` | ||||
| -------------------- | ||||
|  | ||||
|   | ||||
| @@ -199,6 +199,14 @@ Miscellaneous | ||||
|   equal when primary keys match. Previously only instances of exact same | ||||
|   class were considered equal on primary key match. | ||||
|  | ||||
| * The :meth:`django.db.models.Model.__eq__` method has changed such that | ||||
|   two ``Model`` instances without primary key values won't be considered | ||||
|   equal (unless they are the same instance). | ||||
|    | ||||
| * The :meth:`django.db.models.Model.__hash__` will now raise ``TypeError`` | ||||
|   when called on an instance without a primary key value. This is done to | ||||
|   avoid mutable ``__hash__`` values in containers. | ||||
|  | ||||
| Features deprecated in 1.7 | ||||
| ========================== | ||||
|  | ||||
|   | ||||
| @@ -708,9 +708,20 @@ class ModelTest(TestCase): | ||||
|             SelfRef.objects.get(selfref=sr) | ||||
|  | ||||
|     def test_eq(self): | ||||
|         self.assertEqual(Article(id=1), Article(id=1)) | ||||
|         self.assertNotEqual(Article(id=1), object()) | ||||
|         self.assertNotEqual(object(), Article(id=1)) | ||||
|         a = Article() | ||||
|         self.assertEqual(a, a) | ||||
|         self.assertNotEqual(Article(), a) | ||||
|  | ||||
|     def test_hash(self): | ||||
|         # Value based on PK | ||||
|         self.assertEqual(hash(Article(id=1)), hash(1)) | ||||
|         with self.assertRaises(TypeError): | ||||
|             # No PK value -> unhashable (because save() would then change | ||||
|             # hash) | ||||
|             hash(Article()) | ||||
|  | ||||
| class ConcurrentSaveTests(TransactionTestCase): | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user