model.py
class SaleE(models.Model):
qty = models.CharField(max_length=250)
rate = models.CharField(max_length=250)
tax = models.CharField(max_length=250,null="True")
slno= models.CharField(max_length=250)
ptype =models.CharField(max_length=250)
pdiscription = models.CharField(max_length=250)
pname = models.CharField(max_length=250)
amount = models.CharField(max_length=10)
Serializer.py
class SaleESerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SaleE
fields = ('id','slno','pname','ptype','pdiscription','qty','rate','tax','amount')
Here i want to do a simple calculation for an example
if i give a amount 200 in addition result should be like 400 in backend itself it should calculate so how to do that.
You can proceed for it in multiple ways: by overriding clean method, writing signal or override in serializer
Override model's clean method
class SaleE(models.Model):
....
your model fields
....
def clean(self):
# perform any calculation you want to perform at here..
# self: model instance of your current record
self.amount = self.amount * 2
in clean method you can evaluate or modify your instance before save.
Writing signals
#receiver(pre_save, sender=SaleE) # pre_save is signal type which will called before save method
def create_firebase_account(sender, instance, created, *args, **kwargs):
"""
sender: sender model from which you'll receive signal from
instance: model instance(record) which is saved (it will be instance of sender model)
"""
if created: # use this condition if you want to alter amount only at creation time
instance.amount = instance.amount * 2
If you wish to only modify field value before save only at API call and not from shell or any other view, you can override perform_create method in view or create method in serializers
When you write signals it will perform calculation whenever save() method is called for model instance, but if you override in serializer it will only be able to calculate when you try to update/create model instance via API only
Related
I'm trying to populate "balance" in Transaction model based on "beginning_balance" in the Accounts model, but I'm getting the following error message:
save() missing 2 required positional arguments: 'request' and 'pk'
What am I doing wrong. Thank you.
models.py
from django.db import models
class Accounts(models.Model):
account_nickname = models.CharField(max_length=15, unique=True)
beginning_balance = models.DecimalField(max_digits=12, decimal_places=2)
def __str__(self):
return self.account_nickname
class Transaction(models.Model):
transaction_date = models.DateField()
account_nickname = models.ForeignKey(Accounts, on_delete=models.CASCADE)
amount = models.FloatField()
balance = models.FloatField(null=True, blank=True, editable=False)
def save(self, request, pk):
get_beginning_balance = Accounts.objects.get(id=pk)
self.balance = get_beginning_balance
super().save(request, pk, *args, **kwargs)
def __str__(self):
return self.account_nickname
This the correct query. I realized I wasn't pulling the right data.
def save(self, *args, **kwargs):
get_beginning_balance =
Accounts.objects.filter(account_nickname=self.account_nickname).values("beginning_balance")
self.balance = get_beginning_balance
super().save(*args, **kwargs)
I believe that what you are trying to do in your save function in the Transaction model would best be done in your views.py file. You could get the Accounts model based on the pk id that was passed in your url as an int:pk with something like this:
def view_name(request,pk):
You could then get your desired Accounts object with a get_object_or_404. I would then create your account balance with Transactions.objects.create(your arguments). It should end up looking something like this:
def view_name(request,pk):
acc=get_object_or_404(Accounts, account_nickname=sample_account_nickname)
Transaction.objects.create(transaction_date=sample_date,
account_nickname=acc,
amount=sample_amount)
Good luck! :D
You are overriding Django Model save function with wrong number of arguments.
If you want to set something in Django Admin you should override proper method, in ModelAdmin in this case save_model() would be appropriate
The save_model method is given the HttpRequest, a model instance, a
ModelForm instance, and a boolean value based on whether it is adding
or changing the object. Overriding this method allows doing pre- or
post-save operations. Call super().save_model() to save the object
using Model.save().
TL;DR both my model and my form calculate the value of the field number_as_char. Can I avoid the double work, but still check uniqueness when using the model without the form?
I use Python 3 and Django 1.11
My model looks as follows:
class Account(models.Model):
parent_account = models.ForeignKey(
to='self',
on_delete=models.PROTECT,
null=True,
blank=True)
number_suffix = models.PositiveIntegerField()
number_as_char = models.CharField(
max_length=100,
blank=True,
default='',
unique=True)
#classmethod
def get_number_as_char(cls, parent_account, number_suffix):
# iterate over all parents
suffix_list = [str(number_suffix), ]
parent = parent_account
while parent is not None:
suffix_list.insert(0, str(parent.number_suffix))
parent = parent.parent_account
return '-'.join(suffix_list)
def save(self, *args, **kwargs):
self.number_as_char = self.get_number_as_char(
self.parent_account, self.number_suffix)
super().save(*args, **kwargs)
The field number_as_char is not supposed to be set by the user because it is calculated based on the selected parent_account: it is obtained by chaining the values of the field number_suffix of all the parent accounts and the current instance.
Here is an example with three accounts:
ac1 = Account()
ac1.parent_account = None
ac1.number_suffix = 2
ac1.save()
# ac1.number_as_char is '2'
ac2 = Account()
ac2.parent_account = ac1
ac2.number_suffix = 5
ac2.save()
# ac2.number_as_char is '2-5'
ac3 = Account()
ac3.parent_account = ac2
ac3.number_suffix = 1
ac3.save()
# ac3.number_as_char is '2-5-1'
It is NOT an option to drop the field and use a model property instead, because I need to ensure uniqueness and also use that field for sorting querysets with order_by().
My form looks as follows:
class AccountForm(forms.ModelForm):
class Meta:
model = Account
fields = [
'parent_account', 'number_suffix', 'number_as_char',
]
widgets = {
'number_as_char': forms.TextInput(attrs={'readonly': True}),
}
def clean(self):
super().clean()
self.cleaned_data['number_as_char'] = self.instance.get_number_as_char(
self.cleaned_data['parent_account'], self.cleaned_data['number_suffix'])
I included number_as_char in the form with widget attribute readonly and I use the forms clean() method to calculate number_as_char (it has to be calculated before validating uniqueness).
This all works (the model and the form), but after validating the form, the value of number_as_char will be calculated again by the models save() method. Its not a big problem, but is there a way to avoid this double calculation?
If I remove the calculation from the forms clean() method, then the uniqueness will not be validated with the new value (it will only check the old value).
I don't want to remove the calculation entirely from the model because I use the model in other parts without the form.
Do you have any suggestions what could be done differently to avoid double calculation of the field?
I can't see any way around doing this in two places (save() and clean()) given that you need it to work for non-form-based saves as well).
However I can offer two efficiency improvements to your get_number_as_char method:
Make it a cached_property so that the second time it is called, you simply return a cached value and eliminate double-calculation. Obviously you need to be careful that this isn't called before an instance is updated, otherwise the old number_as_char will be cached. This should be fine as long as get_number_as_char() is only called during a save/clean.
Based on the information you've provided above you shouldn't have to iterate over all the ancestors, but can simply take the number_as_char for the parent and append to it.
The following incorporates both:
#cached_property
def get_number_as_char(self, parent_account, number_suffix):
number_as_char = str(number_suffix)
if parent_account is not None:
number_as_char = '{}-{}'.format(parent_account.number_as_char, number_as_char)
return number_as_char
To be sure that the caching doesn't cause problems you could just clear the cached value after you're done saving:
def save(self, *args, **kwargs):
self.number_as_char = self.get_number_as_char(
self.parent_account, self.number_suffix)
super().save(*args, **kwargs)
# Clear the cache, in case something edits this object again.
del self.get_number_as_char
I tinkered with it a bit, and I think I found a better way.
By using the disabled property on the number_as_char field of your model form, you can entirely ignore users input (and make the field disabled in a single step).
Your model already calculates the number_as_char attribute in the save method. However, if the Unique constraint fails, then your admin UI will throw a 500 error. However, you can move your field calculation to the clean() method, leaving the save() method as it is.
So the full example will look similar to this:
The form:
class AccountForm(forms.ModelForm):
class Meta:
model = Account
fields = [
'parent_account', 'number_suffix', 'number_as_char',
]
widgets = {
'number_as_char': forms.TextInput(attrs={'disabled': True}),
}
The model:
class Account(models.Model):
# ...
def clean(self):
self.number_as_char = self.get_number_as_char(
self.parent_account, self.number_suffix
)
super().clean()
That way anything that generates form based on your model will throw a nice validation error (provided that it uses the built-in model validation, which is the case for Model Forms).
The only downside to this is that if you save a model that triggers the validation error, you will see an empty field instead of the value that failed the validation - but I guess there is some nice way to fix this as well - I'll edit my answer if I also find a solution to this.
After reading all the answers and doing some more digging through the docs, I ended up using the following:
#samu suggested using the models clean() method and #Laurent S suggested using unique_together for (parent_account, number_suffix). Since only using unique_together doesn't work for me because parent_account can be null, I opted for combining the two ideas: checking for existing (parent_account, number_suffix) combinations in the models clean() method.
As a consecuence, I removed number_as_char from the form and it is now only calculated in the save() method. By the way: thanks to #solarissmoke for suggesting to calculated it based on the first parent only, not iterating all the way to the top of the chain.
Another consecuence is that I now need to explicitly call the models full_clean() method to validate uniqueness when using the model without the form (otherwise I will get the database IntegrityError), but I can live with that.
So, now my model looks like this:
class Account(models.Model):
parent_account = models.ForeignKey(
to='self',
on_delete=models.PROTECT,
null=True,
blank=True)
number_suffix = models.PositiveIntegerField()
number_as_char = models.CharField(
max_length=100,
default='0',
unique=True)
def save(self, *args, **kwargs):
if self.parent_account is not None:
self.number_as_char = '{}-{}'.format(
self.parent_account.number_as_char,
self.number_suffix)
else:
self.number_as_char = str(self.number_suffix)
super().save(*args, **kwargs)
def clean(self):
qs = self._meta.model.objects.exclude(pk=self.pk)
qs = qs.filter(
parent_account=self.parent_account,
number_suffix=self.number_suffix)
if qs.exists():
raise ValidationError('... some message ...')
And my form ends up like this:
class AccountForm(forms.ModelForm):
class Meta:
model = Account
fields = [
'parent_account', 'number_suffix',
]
EDIT
I'll mark my own answer as accepted, because non of the suggestions fully suited my needs.
However, the bounty goes to #samus answer for pointing me in the right direction with using the clean() method.
Another way - probably not as good though - would be to use Django signals. You could make a pre_save signal that would set the correct value for number_as_char field on the instance that's about to get saved.
That way you don't have to have it done in a save() method of your model, OR in the clean() method of your ModelForm.
Using signals should ensure that any operation that uses the ORM to manipulate your data (which, by extend, should mean all ModelForms as well) will trigger your signal.
The disadvantage to this approach is that it is not clear from the code directly how is this property generated. One has to stumble upon the signal definition in order to discover that it's even there. If you can live with it though, I'd go with signals.
Where should I write my save() function in Django: in models.py in a model class, or in forms.py in a form?
For example :
models.py
class Customer(models.Model):
name = models.CharField(max_length=200)
created_by = models.ForeignKey(User)
def save():
........ some code to override it.......
forms.py
class Addcustomer(forms.ModelForm):
class Meta:
model = Customer
fields = ('name',)
def save():
........code to override it....
Where should I override my save function?
It really depends on what you are trying to achieve. Default realization of ModelForm's save calls Model's save. But it is usually better to override it on form because it also runs validation. So if you are already using form I would suggest overriding ModelForm.save. And by overriding I mean extending using super
Here is default realization of ModelForm.save
def save(self, commit=True):
"""
Save this form's self.instance object if commit=True. Otherwise, add
a save_m2m() method to the form which can be called after the instance
is saved manually at a later time. Return the model instance.
"""
if self.errors: # there validation is done
raise ValueError(
"The %s could not be %s because the data didn't validate." % (
self.instance._meta.object_name,
'created' if self.instance._state.adding else 'changed',
)
)
if commit:
# If committing, save the instance and the m2m data immediately.
self.instance.save()
self._save_m2m()
else:
# If not committing, add a method to the form to allow deferred
# saving of m2m data.
self.save_m2m = self._save_m2m
return self.instance
save.alters_data = True
I would like to implement a function that updates quantity in LibraryBook each time the admin adds a book in SingleBook on the admin site. I have been searching for means to do so but to no avail. Any pointers including links to documentation would be very much appreciated.
Here is my code:
#models.py
class LibraryBook(models.Model):
book_title = models.CharField(max_length=100, blank=False)
book_author_id = models.ForeignKey(BookAuthors, on_delete=models.CASCADE)
category = models.ForeignKey(BookCategory, on_delete=models.CASCADE)
quantity = models.IntegerField(blank=False, default=0)
number_borrowed = models.IntegerField(default=0)
def __unicode__(self):
return unicode(self.book_title)
class SingleBook(models.Model):
serial_number = models.CharField(primary_key=True , max_length=150, blank=False)
book_id = models.ForeignKey(LibraryBook, on_delete=models.CASCADE)
is_available_returned = models.BooleanField(default=True)
is_borrowed = models.BooleanField(default=False)
def __unicode__(self):
return unicode(self.book_id)
#admin.py
class SingleBookAdmin(admin.ModelAdmin):
list_display = ('book_id', 'serial_number')
class LibraryBookAdmin(admin.ModelAdmin):
list_display = ('book_title', 'book_author_id', 'quantity')
search_fields = ('book_title', 'book_author_id')
fields = ('book_title', 'book_author_id', 'quantity')
PS: I have omitted the import and admin.site.register code
Django==1.9.8
django-material==0.8.0
django-model-utils==2.5.1
psycopg2==2.6.2
wheel==0.24.0
override save_model
If you only want to make the changes when an admin updates a record, the best way is to override the save_model method in ModelAdmin
The save_model method is given the HttpRequest, a model instance, a
ModelForm instance and a boolean value based on whether it is adding
or changing the object. Here you can do any pre- or post-save
operations.
class SingleBookAdmin(admin.ModelAdmin):
list_display = ('book_id', 'serial_number')
def save_model(self, request, obj, form, change):
admin.ModelAdmin.save_model(self, request, obj, form, change)
if obj.is_borrowed:
do something to obj.book_id.quantity
else:
do something to obj.book_id.quantity
post_save signal
from django.dispatch.dispatcher import receiver
from django.db.models.signals import post_save
#receiver(post_save, sender=SingleBook)
def user_updated(sender,instance, **kwargs):
''' Fired when a SingleBook is updated or saved
we will use the opporunity to change quantity'''
# your logic here
Other pointers
If on the other hand, you wanted to make changes based on all user actions, catching the post_save signal is the way to go. In either case, you might want to override the from_db method in the model to keep track of which fields have changed.
You might also want to change quantity and number_borrowed to IntegerFields (unless you are only using sqlite in which case it doesn't matter)
Also book_author_id should probably be book_author and book_id should probably be book (this is not a rule, just a convention to avoid the ugly book_id_id reference)
Use signals. Just attach post_save signal to SingleBook model and update according LibraryBook in it. post_save signal takes created argument, so you can determine if book is newly created or edited and apply your action based on that.
Also attach post_delete signal to decrease counter when SingleBook is removed.
To avoid race conditions (when 2 admins are adding books at the same time), I'm suggesting use of queryset update method together with F on changing LibraryBook counter, example:
LibraryBook.objects.filter(id=single_book.book_id_id).update(quantity=F('quantity') + 1)
Doing it that way will ensure that actual math operation will be performed on database level.
I have the following (simplified) data structure:
Site
-> Zone
-> Room
-> name
I want the name of each Room to be unique for each Site.
I know that if I just wanted uniqueness for each Zone, I could do:
class Room(models.Model):
zone = models.ForeignKey(Zone)
name = models.CharField(max_length=255)
class Meta:
unique_together = ('name', 'zone')
But I can't do what I really want, which is:
class Room(models.Model):
zone = models.ForeignKey(Zone)
name = models.CharField(max_length=255)
class Meta:
unique_together = ('name', 'zone__site')
I tried adding a validate_unique method, as suggested by this question:
class Room(models.Model):
zone = models.ForeignKey(Zone)
name = models.CharField(max_length=255)
def validate_unique(self, exclude=None):
qs = Room.objects.filter(name=self.name)
if qs.filter(zone__site=self.zone__site).exists():
raise ValidationError('Name must be unique per site')
models.Model.validate_unique(self, exclude=exclude)
but I must be misunderstanding the point/implementation of validate_unique, because it is not being called when I save a Room object.
What would be the correct way to implement this check?
Methods are not called on their own when saving the model.
One way to do this is to have a custom save method that calls the validate_unique method when a model is saved:
class Room(models.Model):
zone = models.ForeignKey(Zone)
name = models.CharField(max_length=255)
def validate_unique(self, exclude=None):
qs = Room.objects.filter(name=self.name)
if qs.filter(zone__site=self.zone__site).exists():
raise ValidationError('Name must be unique per site')
def save(self, *args, **kwargs):
self.validate_unique()
super(Room, self).save(*args, **kwargs)
class Room(models.Model):
zone = models.ForeignKey(Zone)
name = models.CharField(max_length=255)
def validate_unique(self, *args, **kwargs):
super(Room, self).validate_unique(*args, **kwargs)
qs = Room.objects.filter(name=self.name)
if qs.filter(zone__site=self.zone__site).exists():
raise ValidationError({'name':['Name must be unique per site',]})
I needed to make similar program. It worked.
The Django Validation objects documentation explains the steps involved in validation including this snippet
Note that full_clean() will not be called automatically when you call your model's save() method
If the model instance is being created as a result of using a ModelForm, then validation will occur when the form is validated.
There are a some options in how you handle validation.
Call the model instance's full_clean() manually before saving.
Override the save() method of the model to perform validation on every save. You can choose how much validation should occur here, whether you want full validation or only uniqueness checks.
class Room(models.Model):
def save(self, *args, **kwargs):
self.full_clean()
super(Room, self).save(*args, **kwargs)
Use a Django pre_save signal handler which will automatically perform validation before a save. This provides a very simple way to add validation on exisiting models without any additional model code.
# In your models.py
from django.db.models.signals import pre_save
def validate_model_signal_handler(sender, **kwargs):
"""
Signal handler to validate a model before it is saved to database.
"""
# Ignore raw saves.
if not kwargs.get('raw', False):
kwargs['instance'].full_clean()
pre_save.connect(validate_model_signal_handler,
sender=Room,
dispatch_uid='validate_model_room')