Django overriding model save method sets unspecified fields to NULL - python

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)

Related

ValueError at /admin/services/blog/add/blog objects need to have a primary key value before you can access their tags

i'm trying to create a blog by the admin panell. but i'm not able to save. Can you please help. Model.py
class blog(models.Model):
author = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
city_id = models.AutoField(primary_key=True)
blog_title=models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
slug = models.CharField(max_length=500, blank=True)
tags = TaggableManager()
blog_category_name=models.ForeignKey(blog_category,on_delete=models.CASCADE,null=True,blank=True)
blog_sub_category_name=models.ForeignKey(blog_sub_category,on_delete=models.CASCADE,null=True,blank=True)
written_by = models.CharField(max_length=200, default='sandeep')
image_banner= models.ImageField(upload_to='image_banner')
medium_thumbnail = models.ImageField(upload_to='medium_thumbnail')
content = RichTextField() # RichTextField is used for paragraphs
is_authentic=models.BooleanField(default=False)
class Meta: # Plurizing the class name explicitly
verbose_name_plural = 'blog'
def __str__(self): # Dundar Method
return self.blog_title
def save(self, *args, **kwargs): # Saving Modefied Changes
if not self.slug:
self.slug = slugify(self.blog_title)
For some reason when I'm trying to save the tags and the data I'm getting this error
You forgot call the super method in save. Then when django tries to save the related objects, the blog object has not been saved yet.
def save(self, *args, **kwargs): # Saving Modefied Changes
if not self.slug:
self.slug = slugify(self.blog_title)
super(blog, self).save(*args, **kwargs)

What is the use and sense of Meta Class used in Django under Models Section. Also Why and When we specify it and when not

class Comment(models.Model):
lesson_name = models.ForeignKey(Lesson , null=True, on_delete=models.CASCADE, related_name='comments')
comm_name= models.CharField(max_length=100 , blank=True)
author = models.ForeignKey(User , on_delete=models.CASCADE)
body = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
def save(self, *args, **kwargs):
self.slug = slugify("comment by" + "-" +str(self.author) + str(self.date_added))
super().save(*args, **kwargs)
def __str__(self):
return self.comm_name
class Meta:
ordering = ['-date_added']
According to django documentations:
Model metadata is “anything that’s not a field”, such as ordering
options (ordering), database table name (db_table), or human-readable
singular and plural names (verbose_name and verbose_name_plural). None
are required, and adding class Meta to a model is completely optional.
You can find all meta options here.

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

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

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

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.

Categories