Reputation: 439
I need compare two objects, to determine if a field has changed or not
class Country(models.Model): # country code 'MX' -> Mexico
code = models.CharField(max_length=2)
name = models.CharField(max_length=15)
class Client(models.Model): # id=1, name=pedro, country.code=MX, rfc=12345
name = models.CharField(max_length=100)
country = models.ForeignKey(Country)
rfc = models.CharField(max_length=13)
> obj_db = Client.object.get(id=1)
> country = Country.objects.get(code='MX')
obj_no_db = Client(**{'id':1, 'name':'pedro', 'country': country, 'rfc':12345})
> obj_db == obj_no_db # True
> obj_no_db = Client(**{'id':1, 'name':'pedro', 'country': country, 'rfc':1})
> obj_db == obj_no_db # True # but isn't True because the rfc has change, how can compare field by field
> obj_db.rfc == obj_no_db.rfc # False I expected this result
I need to build a function to do it generic, the problem i don't found information about it, i think i can use the ._meta options, but i'm not sure. I developed this function but i can't discover the way to compare field by field.
def get_insert_update(obj, key, obj_list, fields=None, exclude_fields=None):
"""
:param obj: The object for compare
:param key: a the key for compare to determine if we need to update or insert
:param obj_list: list objects to compare
:return: to_insert, _update
"""
db = {}
to_insert = []
to_update = []
if key == 'pk': # the field pk doesn't exists so we change to id, because its the same
key = 'id'
exclude_fields = exclude_fields or []
fields = fields or []
if 'pk' in fields:
fields[fields.index('pk')] = 'id' # we change the field pk, because it doesn't exists
if 'pk' in exclude_fields:
exclude_fields[exclude_fields.index('pk')] = 'id' # we change the field pk, because it doesn't exists
meta = obj._meta # we define meta object
if fields is None:
fields = meta.get_all_field_names()
fields = [f for f in meta.fields if f.attname in fields]
# dumping db into memory
for _obj in obj.objects.all():
if isinstance(key, list): # first check if is a list to create a custom key
_key = _get_key(_obj, key)
else:
_key = _obj.__dict__[key]
# if exclude fields exists
if exclude_fields:
d = {f.attname: _obj.__dict__[f.attname] for f in fields if f.attname not in exclude_fields}
db[_key] = obj(**d)
else: # we save the full object
db[_key] = _obj
# read local objects to determine if the record will be insert or update
for _obj in obj_list:
if isinstance(key, list): # first check if is a list to create a custom key
_key = _get_key(_obj, key)
else:
_key = _obj.__dict__[key]
if _key in db: # if the key is in db so we check if it equal
# if _obj.pk == 6: # debug
# print(_obj.__dict__, db[_key].__dict__, _obj.__dict__ == db[_key].__dict__)
if _obj != db[_key]: # HERE i need the determine if the fields are equal or not.
to_update.append(_obj) # if the object has changed, we update it
else:
pass # if the object is equal, we didn't do it anything
else:
to_insert.append(_obj) # because we didn't found into the database, we create it
return to_insert, to_update
def _get_key(obj, lst):
"""
create a string key using multiples keys
Example: obj.id -> 1, obj.name -> 'foo'
lst['id', 'name']
:param lst: list of keys
:return: 1_foo
"""
k = []
for t in lst:
k.append(str(obj.__dict__[t]))
return "_".split(k)
Upvotes: 5
Views: 4731
Reputation: 599460
Django's Model class defines the __eq__
method to compare based on the value of the pk
attribute, which is why your models compare equal.
One simple way to do this would be to override that method on your own model to compare the value of the __dict__
, which contains all the instance's values.
There's a slight gotcha with this, in that __dict__
also contains a hidden _state
object that will be compared by ID, so you'd need to filter that out of the comparison.
def __eq__(self, other):
values = [(k,v) for k,v in self.__dict__.items() if k != '_state']
other_values = [(k,v) for k,v in other.__dict__.items() if k != '_state']
return values == other_values
Upvotes: 11