Jad S
Jad S

Reputation: 2995

Django: any way to execute consecutive model.save() operations as 1 single DB request?

I have a function that does operations on a Django model instance, and I'm trying to break it up into sub operations that can be used independently. However, this means that I end up calling .save on each change and trigger a DB request.

def subop1(instance):
    instance.a1 = 1
    instance.save()

def subop1(instance):
    instance.a2 = 2
    instance.save()

def subop1(instance):
    instance.a3 = 3
    instance.save()

def main_op(instance):
    subop1(instance) # triggers 1 DB request
    subop2(instance) # triggers 1 DB request
    subop3(instance) # triggers 1 DB request

Is there any way to avoid having .save triggering a DB request every time while still keeping each function independently functional? Maybe some way of deferring the .save operations until some context manager exits and then saving all at once with 1 DB request.

Tried to read through transaction documentation but I didn't find anything that fit.

Upvotes: 1

Views: 219

Answers (3)

Davide Pizzolato
Davide Pizzolato

Reputation: 715

Method 1:

The instance is the same so you can do this:

def subop1(instance):
    instance.a1 = 1

def subop1(instance):
    instance.a2 = 2

def subop1(instance):
    instance.a3 = 3

def main_op(instance):
    subop1(instance)
    subop2(instance)
    subop3(instance) 
    instance.save() # triggers 1 DB request

You can do this because instance is an object and it is passed by refence and not by value.

So, if you edit an instance in a function, the changes also remain outside that method

Method 2:

Or you can do something like this:

from threading import Timer

class Object():
    a1 = 0
    a2 = 0
    a3 = 0

    def save(self):
        print(self.a1)
        print(self.a2)
        print(self.a3)        

def subop1(instance):
    instance.a1 = 1
    restart_timer(instance)

def subop2(instance):
    instance.a2 = 2
    restart_timer(instance)


def subop3(instance):
    instance.a3 = 3
    restart_timer(instance)


def main_op(instance):
    subop1(instance)
    subop2(instance)
    subop3(instance)

timer = None

def restart_timer(instance):
    global timer
    if timer != None:
        timer.cancel() # this delete last timer
    timer = Timer(5.0, Object.save, [instance]) # This call the save method after 5 sec
    timer.start() # This start the new timer

if __name__ == '__main__':
    obj = Object()
    main_op(obj)

Pay attention however that this is not the best way to do it because the garbage collector could eliminate the instance so use this method only if you are sure that the object will remain global all the time.

Otherwise you have to add this method to the object class.

def __exit__(self, exc_type, exc_value, traceback):
    self.save()

Pay attention pt.2 In this case, your item will be saved whenever you dispose of it. So if you don't voluntarily save the object, it'll be saved anyway.

So the best method in the end is to do more transactions on the database.

Upvotes: 0

Alasdair
Alasdair

Reputation: 308869

You could you add an optional argument to your methods, for example:

def subop1(instance, commit=True):
    instance.a1 = 1
    if commit:
        instance.save()

Then call your instances with commit=False, and then call save() manually.

def main_op(instance):
    subop1(instance, commit=False)
    subop2(instance, commit=False)
    subop3(instance, commit=False)
    instance.save()

Upvotes: 2

Ekansh Singh
Ekansh Singh

Reputation: 41

You can do in the following manner.

def subop1(instance):
instance.a1 = 1

def subop1(instance):
instance.a2 = 2

def subop1(instance):
instance.a3 = 3

def main_op(instance):
subop1(instance)
subop2(instance)
subop3(instance) 
instance.save() 

Upvotes: 0

Related Questions