Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to override django save method to update some field dynamically?

Tags:

python

orm

django

Here i have two models.In these models i want to make the value of amount_to_pay dynamic in Ledger model.For Example i have two different forms for these two models and while saving expense form if the user select the payment_option which comes from ledger model and gives some value for the amount_to_pay field then if only ledger.id and expense.payment_option_id are same then the value of amount_to_pay in ledger model should be replaced with that value.how can i do it ?

models.py

     class Expense(models.Model):
        pay_from = models.CharField(max_length=200)
        payment_option = models.ForeignKey('Ledger', on_delete=models.CASCADE)
        amount_to_pay = models.IntegerField(default=0)
        expense_date = models.DateField(default=datetime.date.today)
        expense_type = models.ForeignKey(ExpenseType, on_delete=models.CASCADE)
        note = models.TextField()
        created = models.DateTimeField(auto_now_add=True)
        updated = models.DateTimeField(auto_now=True)
        slug = AutoSlugField(unique_with='id', populate_from='expense_type')

        def get_amount_to_pay(self):
            return self.amount_to_pay


    class Ledger(models.Model):
        name = models.CharField(max_length=200)
        account_number = models.CharField(max_length=250, unique=True)
        account_type = models.CharField(max_length=200)
        opening_balance = models.IntegerField(default=0)
        amount_to_pay = models.IntegerField(default=0, blank=True, null=True)
        current_balance = models.IntegerField(default=0, blank=True, null=True)
        created = models.DateTimeField(auto_now_add=True)
        updated = models.DateTimeField(auto_now=True)
        slug = AutoSlugField(unique_with='id', populate_from='name')

        def save(self, *args, **kwargs):
             self.amount_to_pay = Expense.get_amount_to_pay(self)

# here how can i save the amount_to_pay from expense form if the ledger.id and expense.payment_option.id matches??
#i got stuck here.           
 self.current_balance = self.opening_balance - self.amount_to_pay

            super(Ledger, self).save(*args, **kwargs)
like image 428
arjun Avatar asked May 28 '19 08:05

arjun


People also ask

What is the use of save method in Django?

Overriding the save method – Django Models Last Updated: 11-11-2019. The save method is an inherited method from models.Model which is executed to save an instance into a particular Model. Whenever one tries to create an instance of a model either from admin interface or django shell, save() function is run.

How to prevent a Django model from updating every time it saves?

Saving a Django model object updates all your columns every single time you call a save () method. To prevent this from happening you must be explicit. Consider a Django model called Record which has the following fields:

How to override save behaviour for Django?

3. How to override save behaviour for Django admin? ¶ ModelAdmin has a save_model method, which is used for creating and updating model objects. By overriding this, you can customize the save behaviour for admin.

How to update all the columns in a Django table?

For instance when you update a row in your table, you might want to update only a subset of the columns in that row and not all of the columns. Saving a Django model object updates all your columns every single time you call a save () method.


1 Answers

Solution One:

I think instead of changing in Ledger model, you should change in Expense model, like this:

class Expense(models.Model):
    ...
    def save(self, *args, **kwargs):
       self.payment_option.amount_to_pay = self.payment_option.amount_to_pay + self.amount_to_pay
       self.payment_option.save()
       super(Expense, self).save(*args, **kwargs)

Solution Two:

But to be honest, Solution One does not seem good to me. Reason is that you are saving same data in 2 places(in both expense and ledger). Instead, it should be once, then the amount_to_pay value in Ledger should be calculated dynamically. Like this:

from django.db.models import Sum

class Ledger(...):

     @property
     def amount_to_pay(self):
         # I am using a property method to show the amount_to_pay value.
         # FYI: in this way, you need to remove amount_to_pay field from Ledger model
         return self.opening_balance - self.expense_set.all().aggregate(a_sum=Sum('amount_to_pay')).get('a_sum', 0)

In that way, with each ledger, the value amount_to_pay will be dynamically calculated at runtime. For example:

 for l in Ledger.objects.all():
     l.amount_to_pay

Solution Three:

If you are wary of making DB hits with each l.amount_to_pay(as it calculates amount_to_pay from DB dynamically) from previous solution, then you can always annotate the value. Like this:

For this solution, you need to change your Expense model and add a related_name:

class Expense(models.Model):
    pay_from = models.CharField(max_length=200)
    payment_option = models.ForeignKey('Ledger', on_delete=models.CASCADE, related_name='expenses')

Then use that related_name in query like this(FYI: You can't keep def amount_to_pay(...) method in Ledger model for the following usage example):

from django.db.models import Sum, F, ExpressionWrapper, IntegerField

ledgers = Ledger.objects.all().annotate(expense_sum=Sum('expenses__amount_to_pay')).annotate(amount_to_pay=ExpressionWrapper(F('opening_balance') - F('expense_sum'), output_field=IntegerField()))

# usage one
for l in ledgers:
   l.amount_to_pay

# usage two
ledgers.values('amount_to_pay')
like image 72
ruddra Avatar answered Oct 18 '22 20:10

ruddra