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
Related
I am using django-bootstrap-modal-forms and it works perfectly as in documentation when using fields form my model. Some of the fields are ForeignKeys and they are displayed properly for user to select a value from database table that is referenced by the key, but instead of that I need to put username of the current user.
I tried to change how the CreateView class handles fields, but with no luck. Probably doing something wrong.
models.py
class userSchoolYear(models.Model):
user_in_school = models.ForeignKey(get_user_model(), null=True, on_delete=models.CASCADE)
school = models.ForeignKey(sifMusicSchool, on_delete=models.CASCADE)
school_year = models.ForeignKey(sifSchoolYear, on_delete=models.CASCADE)
school_year_grade = models.CharField(max_length=4, choices=IZBOR_RAZREDA, default=None, null=True)
user_instrument = models.ForeignKey(instType, on_delete=models.CASCADE, default=None, null=True)
user_profesor = models.ForeignKey(profSchool, on_delete=models.CASCADE, default=None, null=True)
views.py
class SchoolYearCreateView(BSModalCreateView):
template_name = 'school_year.html'
form_class = SchoolYearForm
success_message = 'Success!'
success_url = reverse_lazy('school')
def __init__(self, *args, **kwargs):
self.form_class.user_in_school = 'johnny' ### HERE
print(user.username)
super().__init__(*args, **kwargs)
forms.py
class SchoolYearForm(BSModalForm):
class Meta:
model = userSchoolYear
fields = '__all__'
Thanks to the author Uroš Trstenjak I was able to find a solution. I was wrong trying to set field values from views.py, instead it should be done in forms.py. So, basically I had to write a init for the form and alter fields values. Uroš pointed out that at from level I can get current user from self.request.user and it did work.
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
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.
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)
I have this model:
class SearchPreference(models.Model):
"""Saves the preferred location and school_type of the User
"""
user = models.OneToOneField(User, related_name='search_preference')
location = models.ForeignKey(Location, null=True)
school_type = models.ForeignKey(SchoolType, null=True)
class Meta:
app_label = 'grants'
and this form:
class SearchPreferenceForm(forms.ModelForm):
location = forms.ChoiceField(queryset=Location.objects.all(),
to_field_name='slug',
required=False)
school_type = forms.ChoiceField(queryset=SchoolType.objects.all(),
to_field_name='slug',
required=False)
class Meta:
model = SearchPreference
fields = ('location', 'school_type')
I am trying to use the form to do validation of POST data, I am not displaying it in a template.
The problem is, the POST data can include a value which isn't in the Location or SchoolType table, so the form doesn't validate. The value is 'all', signifying 'all locations' or 'all school types', and I really want this to be saved as a SearchPreference with no location, i.e. location = null.
I could change 'all' to an empty value and that might work but then validation/logic has moved out of the form.
I thought I could use empty_value = 'all' but this doesn't work on a modelChoiceField.
Is there any way of doing this?
Your model needs blank=True as well and null=True
location = models.ForeignKey(Location, blank=True, null=True)
school_type = models.ForeignKey(SchoolType, blank=True, null=True)
This post talks about blank and null.
This worked in the end:
class SearchPreferenceForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SearchPreferenceForm, self).__init__(*args, **kwargs)
self.fields['location'].empty_values.append('all')
self.fields['school_type'].empty_values.append('all')