Created
June 18, 2019 23:45
-
-
Save allynt/c051cad8c4996822988250e06f0109df to your computer and use it in GitHub Desktop.
bulk_update_or_create
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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