Login

Compare objects list and get a list of object to inserted or updated

Author:
paridin
Posted:
July 9, 2015
Language:
Python
Version:
Pre .96
Score:
0 (after 0 ratings)

Problem You have an input json with which you will create a list of objects, you have to validate that the object will be created if it not exists, if exists determine whether to upgrade or discard depending of they have not undergone any changes.

Solution 1) With the input json will be created the list of objects of the class that we insert or updatee

2) Read all fields in the database, using one of the fields as key to creating a dictionary with the objects in the database

3) Compare the objects and determine if it will be updated, inserted or discarded

Django problem: by default only compares the level objects using the primary key (id). Compare field by field is the solution to determine if the object has changed. hints: The _state field is present in every object, and it will produce a random memory location, You can find cache fields so you need to remove these begins with underscore _. The fields excluded can be fk, and these fields produce field_id, so you will needs to exclude it

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)

Country.objects.create(**{'code': 'MX', 'name': 'Mexico'})  # creating the country
Client(**{'id':1, 'name':'pedro', 'country': country, 'rfc':12345})  # creating the client

obj_db = Client.objects.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 was expected this result when compare obj_db == obj_no_db because they are not equal

Solution to compare field by field

_obj_1 = [(k,v) for k,v in obj_db.__dict__.items() if k != '_state']
_obj_2 = [(k,v) for k,v in obj_no_db.__dict__.items() if k != '_state']
_obj_1 == _obj_2 # False

This is only for one object, and you can include in __eq__ method in your model, but what happen if you need compare a list of object to bulk for insert or update with django-bulk-update.

Well my snipped pretends solve that. so

How can use it.

obj_list = [<Object Client>, <Object Client>, <Object Client>, <Object Client>]
get_insert_update(Client, 'id', obj_list)
exclude_fields = ['country']
get_insert_update(Client, 'id', obj_list, exclude_fields=exclude_fields)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import re
def get_insert_update(obj, key, obj_list, 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'
    if key in exclude_fields:
        raise Exception('The key "{}" should not be included in exclude_fields -> {}'.format(key, exclude_fields))
    exclude_fields = exclude_fields or []
    if 'pk' in exclude_fields:
        exclude_fields[exclude_fields.index('pk')] = 'id'  # we change the field pk, because it doesn't exists
    exclude_fields_ids = ["{}_id".format(i) for i in exclude_fields]

    # dumping a 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]
        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 esq
            obj_db = [(k, v) for k, v in db[_key].__dict__.items() if not re.match('^_.*', k)  # excluding _state
                      and k not in exclude_fields_ids
                      and k not in exclude_fields]  # excluding field and fields_id
            obj_tmp = [(k, v) for k, v in _obj.__dict__.items() if not re.match('^_.*', k)  # not _state, _cache fields
                       and k not in exclude_fields_ids
                       and k not in exclude_fields]
            if obj_db != obj_tmp:
                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)

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

Please login first before commenting.