Skip to content

Instantly share code, notes, and snippets.

@allynt
Created June 18, 2019 23:45
Show Gist options
  • Save allynt/c051cad8c4996822988250e06f0109df to your computer and use it in GitHub Desktop.
Save allynt/c051cad8c4996822988250e06f0109df to your computer and use it in GitHub Desktop.
bulk_update_or_create
def bulk_update_or_create(model_class, model_data):
"""
Performs update_or_create in bulk (w/ only 3 db hits)
Parameters
----------
model_class : django.db.models.Model
model to update_or_create
model_data : list
data to update/create. Example: [{'field1': 'value', 'field2': 'value'}, ...]
Returns
-------
tuple
the number of objects created & updated
"""
# get all instances of the model...
existing_objects = list(model_class.objects.all())
# get all the fields that uniquely identify a model object...
# TODO: deal w/ unique_together fields
unique_field_names = [
field.name for field in model_class._meta.get_fields()
if field.unique and not field.primary_key
]
all_data_record_field_names = set()
objects_to_create = []
objects_to_update = []
for data_record in model_data:
# for every dictionary in model_data,
# extract the fields that can uniquely identify an object,
# and check if there is an existing object w/ those values,
# if so update that object w/ the field values and store it to be UPDATED,
# and remove it from the list of existing objects (so the next time around the check is faster),
# if not store it to be CREATED
all_data_record_field_names.update(data_record.keys())
unique_data_record_fields = {
# raises a KeyError if data doesn't include unique_fields
field_name: data_record[field_name]
for field_name in unique_field_names
}
matching_object = next(
(obj for obj in existing_objects if all([getattr(obj, k) == v for k, v in unique_data_record_fields.items()])),
None
)
if matching_object:
for k, v in data_record.items():
setattr(matching_object, k, v() if callable(v) else v)
objects_to_update.append(matching_object)
existing_objects.remove(matching_object)
else:
objects_to_create.append(model_class(**data_record))
model_class.objects.bulk_create(objects_to_create)
model_class.objects.bulk_update(objects_to_update, all_data_record_field_names)
return (len(objects_to_create), len(objects_to_update))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment