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

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)

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')

Best thing is you override the call in save method any way you have foreginkey.
def save(self, *args, **kwargs):
#self.date_created = timezone.now()
# YOUR LOGIC HERE
super(YOUR_OVERRIDING_MODEL , self).save(*args, **kwargs

check if Ledger have child and then update it:
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)

Override save method and update some field dynamically in django
For my case i wanted to update done column in answer model dynamically when i comment on an answer it should return true instead of False. So in my example i am checking for whether that column done which is boolean is False and if it's False,i change it's status to True using the answer object from Comment and then save the answer object, see more about update vs insert fields https://docs.djangoproject.com/en/3.2/ref/models/instances/
class Answer(models.Model):
user = models.ForeignKey("User", on_delete=models.CASCADE, blank=True, null=True)
username = models.CharField(max_length=250)
codeAnswer = models.CharField(max_length=250)
girAnswer = models.CharField(max_length=250)
correct = models.BooleanField(default=False)
firebaseToken = models.CharField(max_length=250)
done = models.BooleanField(default=False)
created_at = models.DateTimeField()
updated_at = models.DateTimeField(auto_now=True)
def __unicode__(self):
return "{0}: {1}".format(self.username, self.value)
def __str__(self):
return "%s" % (self.codeAnswer)
# So here i override the save method in the Comment model to do what i want as below.
class Comment(models.Model):
answer = models.ForeignKey("Answer", on_delete=models.CASCADE, blank=False , related_name="comments")
writerName = models.CharField(max_length=250)
content = models.CharField(max_length=250)
deleted = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return "%s" % (self.writerName)
def save(self, *args, **kwargs):
if self.answer.done is False:
self.answer.done = True
self.answer.save()
super(Comment, self).save(*args, **kwargs)
Hope it answers the question above thanks

Related

Django generic create view . Can we add conditions to save data from the form?

This is what my code looks like and I want to add some condition to the Borrower create view like if the stock method of book returns 0 then don't list that book in field while creating a new borrower or if it isn't possible at least throw some error while adding borrower to that book.
models.py:
class Book(models.Model):
id = models.UUIDField(primary_key=True, unique=True,
default=uuid.uuid4, editable=False)
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
summary = models.TextField(
max_length=1000, help_text="Enter a brief description of the book")
isbn = models.CharField('ISBN', max_length=13,
help_text='13 Character https://www.isbn-international.org/content/what-isbn')
genre = models.ManyToManyField(
Genre, help_text="Select a genre for this book")
language = models.ForeignKey(
'Language', on_delete=models.SET_NULL, null=True)
total_copies = models.IntegerField()
pic = models.ImageField(blank=True, null=True, upload_to='books')
def stock(self):
total_copies = self.total_copies
available_copies = total_copies - \
Borrower.objects.filter(book=self).count()
if available_copies > 0:
return available_copies
else:
return 0
def __str__(self):
return self.title
class Borrower(models.Model):
id = models.UUIDField(primary_key=True, unique=True,
default=uuid.uuid4, editable=False)
student = models.ForeignKey('Account', on_delete=models.CASCADE)
book = models.ForeignKey('Book', on_delete=models.CASCADE)
issue_date = models.DateField(
null=True, blank=True, help_text='YYYY-MM-DD', default=date.today)
return_date = models.DateField(
null=True, blank=True, help_text='YYYY-MM-DD')
def __str__(self):
return self.student.name.title()+" borrowed "+self.book.title.title()
def fine(self):
today = date.today()
fine = 0
if self.return_date <= today:
fine += 5 * (today - self.return_date).days
return fine
views.py:
class BorrowerView(LoginRequiredMixin, ListView):
model=Borrower
context_object_name='borrowers'
template_name = 'library/borrower_list.html'
def get_context_data(self, **kwargs):
context=super().get_context_data(**kwargs)
if self.request.user.is_admin or self.request.user.is_superuser:
context['borrowers']=context['borrowers']
else:
context['borrowers']=context['borrowers'].filter(student = self.request.user.id)
return context
class BorrowerCreate(LoginRequiredMixin, UserAccessMixin, CreateView):
model=Borrower
permission_required= 'borrowers.add_borrowers'
fields='__all__'
success_url=reverse_lazy('library:borrower-list')
def form_valid(self, form):
form.instance.user=self.request.user
return super(BorrowerCreate, self).form_valid(form)
class BorrowerDetail(LoginRequiredMixin, DetailView):
model=Borrower()
context_object_name='borrower'
template_name='library/borrower.html'
class Book(models.Model):
id = models.UUIDField(primary_key=True, unique=True,
default=uuid.uuid4, editable=False)
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
summary = models.TextField(
max_length=1000, help_text="Enter a brief description of the book")
isbn = models.CharField('ISBN', max_length=13,
help_text='13 Character https://www.isbn-international.org/content/what-isbn')
genre = models.ManyToManyField(
Genre, help_text="Select a genre for this book")
language = models.ForeignKey(
'Language', on_delete=models.SET_NULL, null=True)
total_copies = models.IntegerField()
pic = models.ImageField(blank=True, null=True, upload_to='books')
#new, use this to keep track of available books
available_copies = models.IntegerField()
def __str__(self):
return self.title
When any borrower borrows a copy of the book, you will subtract it from the total copies.
class BorrowerCreate(LoginRequiredMixin, UserAccessMixin, CreateView):
model=Borrower
permission_required= 'borrowers.add_borrowers'
fields='__all__'
success_url=reverse_lazy('library:borrower-list')
#remember to get the object using slug or 404
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
book = Book.objects.get(id=instance.book.id)
#get the book id from the form and check if the book is still available, then subtract.
if book.available_copies > 0:
book.available_copies -= 1
book.save()
instance.save()
message.success(self.request, _("successful")
message.error(self.request, _("Book not in stock")
return super(BorrowerCreate, self).form_valid(form)
If user return the book and click returned. you can perform a similar action by adding to available copies.
This is not the solution, you can write a fat model with methods that takes care of both borrowing and return. Like this
def borrow(self):
self.available_copies -= 1
def returned(self):
self.available_copies += 1
You can call these two methods in different views or define a signal that call them using pre_save
Ist:
Instead of defining a new method which you called stock, why not add stock as a field instead of making a database query.
2nd:
calling the class inside the same class is not the best way to make queries inside the class.
3rd:
To add a condition while adding an object, you need to override the save method of the object like this.
def save(self, *args, **kwargs):
# do_something() here.....
# then
return super().save(*args, **kwargs)
The above code will enable you to perform any action before saving the object.
Another way you can do this is inside the form_valid function like this.
def form_valid(self, form):
instance = form.save(commit=False)
# commit = False will make sure that the form is not saved
# then you can now query the database and check conditions like
if Borrower.object.all().count() > 0:
instance.save()
messages.success(self.request, _("saved successfully")
else:
messages.error(self.request, _("Error")
return redirect("URL")

Update records depending on fields - Django

I want to override the save method of a model in Django.
Consider this model:
class OtherClass(models.Model):
name = models.CharField(max_length=255, blank=True, null=True)
class MyClass(models.Model):
admin = models.ForeignKey(OtherClass, on_delete=models.CASCADE)
myfield1 = models.CharField(max_length=128)
myfield2 = models.CharField(max_length=128)
myfield3 = models.CharField(max_length=128, blank=True, null=True)
myfield4 = models.CharField(max_length=128, blank=True, null=True)
myfield5 = models.TextField(blank=True)
What I want, is to update the model only if myfield5 is present in whatever form or view the user is filling. This is the current save method I have:
def save(*args, **kwargs):
fieldgroup = f"{self.myfield2}\n{self.myfield3}"
self.myfield5 = f"{fieldgroup}\n{self.field1} {self.field4} {self.field5}"
super().save(*args, **kwargs)
Any ideas?
Maybe it would be better to achieve this on a form rather than directly into the model?
You can check self.pk to determine whether the object was already previously saved or not (the first time self.pk is None).
So in combination with self.field5, you can do this:
if self.pk and self.field5:
self.field5 = ... # update field5 as you need
super().save(...)
elif not self.pk:
super().save(...)
# no saving if self.pk and not self.fields
You can use required=True in your Model field or Form field

Set model field set default to iterator

I'm trying to set reportNr field default to a number that iterates every time a report has been added. Is there any way to do this?
In models.py:
from django.db import models
class RapportQuerySet(models.query.QuerySet):
def active(self):
return self.filter()
class RapportManager(models.Manager):
def get_queryset(self):
return RapportQuerySet(self.model, using=self._db)
def all(self):
return self.get_queryset().active()
class Rapport(models.Model):
objects = RapportManager()
reportNr = models.TextField(blank=True, default="?")
avd = models.TextField(max_length=10, null=True, blank=True)
ritningNr = models.TextField(max_length=20, null=True, blank=True)
enhetsNr = models.TextField(max_length=40, null=True, blank=True)
atgard = models.TextField(max_length=100,null=True, blank=True)
namn = models.TextField(max_length=100,null=True, blank=True)
anstNr = models.TextField(max_length=100,null=True, blank=True)
date = models.DateTimeField()
file = models.FileField(null=True, blank=True)
def __str__(self):
return self.reportNr
what is the report (I do not see any relations with report), if it model instance then:
reportNr = = models.IntegerField(default=0) - must be integer
Also you can override create method
def save(self, *args, **kwargs):
super(Report, self).save(*args, **kwargs)
#Iterate the reportNr
Hopefully, I understand right) If not can you describe problem in details

check what field value changed in django model

I parse data from a restful API call using django-rest-framework ModelSerializer.
Here is the code:
url = "API URL HERE"
r = requests.get(url)
json = r.json()
serializer = myModelSerializer(data=json, many=True)
if serializer.is_valid():
serializer.save()
Here is the modelSerializer:
class myModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
MYModel:
class MyModel(models.Model):
City = models.NullBooleanField(default=False, null=True)
Status = models.CharField(max_length=100, null=True)
stateName = models.CharField(max_length=50)
marketingName = models.TextField(null=True)
county = models.CharField(max_length=200, null=True)
My problem is I need to find out what field value changed from the last time I called the restful api and updated data. OR If there is any new records. How do I achieve this?
First, you could add a column to your MyModel:
updated = models.DateTimeField(auto_now=True)
This will update whenever an instance is changed. If you filter on this field in your queryset, you can determine what rows changed and if there are new rows.
Finding out what field changed is harder, but here is an idea--add a string field to the model and write a custom save method for your model, like this:
class MyModel(models.Model):
City = models.NullBooleanField(default=False, null=True)
Status = models.CharField(max_length=100, null=True)
stateName = models.CharField(max_length=50)
marketingName = models.TextField(null=True)
county = models.CharField(max_length=200, null=True)
updated = models.DateTimeField(auto_now=True)
updated_fields = models.CharField(max_length=200, null=True)
def save(self, *args, **kwargs):
if not self.pk: # if this is new, just save
super(MyModel, self).save(*args, **kwargs)
else:
# get the original
old = MyModel.objects.get(id=self.pk)
# make a list of changed fields
changed_fields = []
for field in self._meta.get_all_field_names():
if getattr(self, field, None) != getattr(old, field, None):
if field not in ['updated', 'updated_fields']:
changed_fields.append(old)
# make a comma separated string
self.updated_fields = ','.join(changed_fields)
super(MyModel, self).save(*args, **kwargs)
Now the updated_fields column will contain the set of fields that were last updated.

Django overriding model save method sets unspecified fields to NULL

Explanation
I have an extension of my model in eiysTblModels because we are using inspectdb option of django manage.py. Since it overwrites the models.py,we do not touch models.py, instead write our extensions to eiysTblModels.
Problem
Anyway, when I call edit_group function, it sets the slug and dates correctly as specified but it overwrites the other fields such as is_active, isapproved etc to NULL, which are initially set to 1.
vieys.py
def edit_group(request,group_id):
groupinfo = request.POST
group = eiysTblGroup(id = group_id )
group.name = groupinfo.get('group-name','')
group.save()
eiysTblModels.py
class eiysTblGroup(TblGroup):
class Meta:
proxy = True
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
if not self.id:
self.date_created = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.isactive = 1
self.date_last_modified = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
super(TblGroup, self).save(*args, **kwargs)
models.py
class TblGroup(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=250, blank=True)
date_created = models.DateTimeField(blank=True, null=True)
date_last_modified = models.DateTimeField(blank=True, null=True)
creator = models.ForeignKey(AuthUser, blank=True, null=True)
group_photo_url = models.CharField(max_length=250, blank=True)
isactive = models.IntegerField(blank=True, null=True)
slug = models.CharField(max_length=250, blank=True)
code = models.IntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_group'
Summary
Basically, what I need is to automatically update date_last_modified, date_created and slug when I save them, and do NOT update any other part to NULL.
Obviously, my erroneous part is this in view:
group = eiysTblGroup(id = group_id )
I'm not sure how I made such a silly mistake.
The correct form should be:
group = eiysTblGroup.objects.get(id = group_id )
Then it works correctly...
I believe the answer for your question can be found here:
Automatic creation date for django model form objects?
You want to use those, because Django can set creation and modification dates for you automatically without any additional interactions needed.
models.DateTimeField(auto_now_add=True)
models.DateTimeField(auto_now=True)
As for the slug, shorten your save() method to:
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(eiysTblGroup, self).save(*args, **kwargs)

Categories