Skip to content

Commit 1e6e011

Browse files
committed
Add bulk save method to HistoryModel
Unit tests in social_protection module due to dependency requirements
1 parent a54d5e1 commit 1e6e011

1 file changed

Lines changed: 80 additions & 0 deletions

File tree

core/models/history_model.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,86 @@ def copy(self, exclude_fields=["id", "uuid"]):
217217

218218
return new_instance
219219

220+
@classmethod
221+
def bulk_save(cls, data_list, user, batch_size=100):
222+
"""
223+
Efficiently update or create multiple instances based on 'id' field.
224+
All operations are atomic - either all succeed or all fail.
225+
226+
Args:
227+
data_list: List of dicts with instance data (with or without 'id')
228+
user: User performing the operation
229+
batch_size: Number of records to process per batch
230+
231+
Returns:
232+
dict with 'created' and 'updated' counts
233+
"""
234+
from django.db import transaction
235+
236+
if not data_list:
237+
return {'created': 0, 'updated': 0}
238+
239+
now = py_datetime.now()
240+
241+
ids_to_update = [d['id'] for d in data_list if d.get('id')]
242+
243+
existing = {obj.id: obj for obj in cls.objects.filter(id__in=ids_to_update, is_deleted=False)}
244+
245+
to_create = []
246+
to_update = []
247+
248+
exclude_fields = {'id', 'uuid', 'date_created', 'user_created', 'date_updated',
249+
'user_updated', 'version', 'is_deleted', 'date_valid_from',
250+
'date_valid_to', 'replacement_uuid'}
251+
252+
for data in data_list:
253+
record_id = data.get('id')
254+
255+
if record_id and record_id in existing:
256+
instance = existing[record_id]
257+
for field, value in data.items():
258+
if field not in exclude_fields:
259+
setattr(instance, field, value)
260+
instance.user_updated = user
261+
instance.date_updated = now
262+
instance.version = F('version') + 1
263+
to_update.append(instance)
264+
else:
265+
create_data = {k: v for k, v in data.items() if k not in exclude_fields}
266+
instance = cls(**create_data)
267+
instance.set_pk()
268+
instance.user_created = user
269+
instance.user_updated = user
270+
instance.date_created = now
271+
instance.date_updated = now
272+
instance.version = 1
273+
to_create.append(instance)
274+
275+
with transaction.atomic():
276+
created_count = 0
277+
updated_count = 0
278+
279+
if to_create:
280+
cls.objects.bulk_create(to_create, batch_size=batch_size)
281+
created_count = len(to_create)
282+
283+
if to_update:
284+
update_fields = [f for f in to_update[0].__dict__.keys()
285+
if not f.startswith('_') and f not in exclude_fields]
286+
update_fields += ['user_updated', 'date_updated', 'version']
287+
288+
cls.objects.bulk_update(to_update, update_fields, batch_size=batch_size)
289+
290+
ids = [obj.id for obj in to_update]
291+
updated_objects = cls.objects.filter(id__in=ids).only('id', 'version')
292+
version_map = {obj.id: obj.version for obj in updated_objects}
293+
for obj in to_update:
294+
obj.version = version_map.get(obj.id, obj.version)
295+
296+
updated_count = len(to_update)
297+
298+
return {'created': created_count, 'updated': updated_count}
299+
220300
@classmethod
221301
def filter_queryset(cls, queryset=None):
222302
if queryset is None:

0 commit comments

Comments
 (0)