Django comparing model instances for equality

DjangoDjango Models

Django Problem Overview


I understand that, with a singleton situation, you can perform such an operation as:

spam == eggs

and if spam and eggs are instances of the same class with all the same attribute values, it will return True. In a Django model, this is natural because two separate instances of a model won't ever be the same unless they have the same .pk value.

The problem with this is that if a reference to an instance has attributes that have been updated by middleware somewhere along the way and it hasn't been saved, and you're trying to it to another variable holding a reference to an instance of the same model, it will return False of course because they have different values for some of the attributes. Obviously, I don't need something like a singleton, but I'm wondering if there some official Djangonic (ha, a new word) method for checking this, or if I should simply check that the .pk value is the same, by running:

spam.pk == eggs.pk

I'm sorry if this was a huge waste of time, but it just seems like there might be a method for doing this and something I'm missing that I'll regret down the road if I don't find it.

UPDATE (02-27-2015)

You should disregard the first part of this question since you shouldn't compare singletons with ==, but rather with is. Singletons really have nothing to do with this question.

Django Solutions


Solution 1 - Django

From Django documentation:

To compare two model instances, just use the standard Python comparison operator, the double equals sign: ==. Behind the scenes, that compares the primary key values of two models.

Solution 2 - Django

spam.pk == eggs.pk is a good way to do that.

You may add __eq__ to your model but I will avoid that, because it is confusing as == can mean different things in different contexts, e.g. I may want == to mean content is same, id may differ, so again best way is

spam.pk == eggs.pk

Edit: btw in django 1.0.2 Model class has defined __eq__ as

def __eq__(self, other):
    return isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val() 

which seems to be same as spam.pk == eggs.pk as pk is property which uses _get_pk_val so I don't see why spam == eggs is not working ?

Solution 3 - Django

As of Django 3.0.2, the source code for model instance equality is this:

def __eq__(self, other):
    if not isinstance(other, Model):
        return False
    if self._meta.concrete_model != other._meta.concrete_model:
        return False
    my_pk = self.pk
    if my_pk is None:
        return self is other
    return my_pk == other.pk

That is, two model instances are equal if they come from the same database table and have the same primary key. If either primary key is None they're only equal if they're the same object.

(So getting back to the OP's question, simply comparing the instances would be fine.)

Solution 4 - Django

You can define the Class' __eq__ method to chage that behaviour:

http://docs.python.org/reference/datamodel.html

Solution 5 - Django

Just for the record, comparing:

    spam == eggs

is dangerous if there is any chance that either of them could be a deferred model instance created by Model.objects.raw() query or by .defer() applied to a 'normal' QuerySet.

I put more details here: https://stackoverflow.com/questions/3617886/django-queryset-defer-problem-bug-or-feature

Solution 6 - Django

As orokusaki comments, "if neither instance has a primary key, it will return true always". If you want this to work, you could extend your model like so:

def __eq__(self, other):
    eq = isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val()

    if eq and self._get_pk_val() is None:
        return id(self) == id(other)
    return eq

Solution 7 - Django

from https://djangosnippets.org/snippets/2281/

i changed to compare two instance and return Boolean value

def is_same(self, obj):
    excluded_keys = 'timestamp', 'creator_id' # creater_id is one foreign key ins table
    return _is_same(self, obj, excluded_keys)


def _is_same(obj1, obj2, excluded_keys):
    d1, d2 = obj1.__dict__, obj2.__dict__
    for k, v in d1.items():
         # print('check key: ' + k)
        if k in excluded_keys or k in ['_state', '_django_cleanup_original_cache']: # _state make difference so automatically exclude it
            # print(k + ' is in excluded keys')
            continue

        if v != d2[k]:
            # print('value in not equal in second object')
            return False
        else:
            # print('it is same')
            continue

    # print('all keys checked, so both object is same')
    return True

Solution 8 - Django

It would be strange if two model instances compared as equal if they had different attributes. Most of the time that would be undesirable.

What you want is a special case. Comparing spam.pk == eggs.pk is a good idea. If there's no pk yet, because they haven't been saved, then it's harder to define which instances are "really" the same if some attributes are different.

How about adding a custom attribute to your instances when creating them, eg: spam.myid=1, eggs.myid=2

That way at some point in your code when spamcopy1.seasoning=ketchup and spamcopy2.seasoning=blackpepper you can compare their myid attribute to see if they're really the "same" spam.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionorokusakiView Question on Stackoverflow
Solution 1 - DjangoGunnarView Answer on Stackoverflow
Solution 2 - DjangoAnurag UniyalView Answer on Stackoverflow
Solution 3 - DjangoKevin Christopher HenryView Answer on Stackoverflow
Solution 4 - DjangopiotrView Answer on Stackoverflow
Solution 5 - DjangoTomasz ZielińskiView Answer on Stackoverflow
Solution 6 - DjangoAidan FitzpatrickView Answer on Stackoverflow
Solution 7 - DjangoKosarView Answer on Stackoverflow
Solution 8 - DjangoAnentropicView Answer on Stackoverflow