How to bind custom function to model field? - django

Say this is my simple models.py
class Order(models.Model):
quantity = models.IntegerField()
item_price = models.FloatField()
I'd like to have a function to calculate the total price:
def calc_total(self):
return self.quantity * self.item_price
Now, how do I create model field total_price so it gets populated automatically in database? I know I can use calc_total method in template but I need this in db also.

Override the save method in your model to set the value each time the model is saved:
class Order(models.Model):
quantity = models.IntegerField()
item_price = models.FloatField()
total_price = models.FloatField()
def calc_total(self):
return self.quantity * self.item_price
def save(self, *args, **kwargs):
self.total_price = self.calc_total()
super(Order, self).save(*args, **kwargs)

Related

Django aggregate sum of manytomany is adding up everything in its field instead of the ones selected

2 Classes involved in question class Appointment and class Service
appointmentApp.models class Service
class Service(models.Model):
service_name = models.CharField(max_length=15, blank=False)
service_time = models.IntegerField(blank=False)
def __str__(self):
return self.service_name
class Meta:
verbose_name_plural = "Services"
appointmentApp/models.py class Appointment
class Appointment(models.Model):
service_chosen = models.ManyToManyField(Service, blank=False)
total_time = models.IntegerField(blank=False, null=False, default=0)
#will add up the amount of time needed for each service
def save(self, *args, **kwargs):
self.total_time += Service.objects.all().aggregate(total_time=Sum('service_time'))['total_time']
super(Appointment, self).save(*args, **kwargs)
def __str__(self):
return self.client_dog_name
Services are chosen through a multiplechoice field and on save the service_chosen's service_time are added up
but what my save function is doing instead is adding up all the existing service.service_time instead of the ones selected, why is this happening?
ManyToManyFields are saved after the containing instance is saved, you need to create a signal handler to perform this update on m2m_changed
from django.db.models.signals import m2m_changed
class Appointment(models.Model):
...
def service_chosen_changed(sender, instance=None, action=None, **kwargs):
if action == 'post_add':
instance.total_time = instance.service_chosen.aggregate(total_time=Sum('service_time'))['total_time']
instance.save()
m2m_changed.connect(service_chosen_changed, sender=Appointment.service_chosen.through)

Multiplying the values of two different models in Django

Well i'm trying to create two models whereas the product model is:
class Product(models.Model):
id = models.AutoField(primary_key=True)
price = models.FloatField(null=False)
inStock = models.BooleanField(null=False)
and another model for cart where:
class Cart(models.Model):
productID = models.ForeignKey(Product,on_delete=models.CASCADE)
quantity = models.IntegerField()
total
I want total here to equal Product.price multiplied by the Cart.quantity
Is there a way how can I achieve it on Django?
You can use #property decorator
class Cart(models.Model):
productID = models.ForeignKey(Product,on_delete=models.CASCADE)
quantity = models.IntegerField()
#property
def total(self):
return self.quantity * self.productID.price
To use in serializer you can try this:
total = serializers.SerializerMethodField()
def get_total(self, obj):
return obj.total

changing django model filed data before saving to database

i want to update model field data before saving to database.
class mynumber(TimeStampedModel):
text = models.TextField(max_length=10000, null=True, blank=True)
number = models.DecimalField(default=0, max_digits=9, decimal_places=2)
#serializer file
class mynumberSerializer(serializers.ModelSerializer):
class Meta:
model = mynumber
fields = "__all__"
#view
class mynumberViewSet(CreateListMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
serializer_class = mynumberSerializer
queryset = mynumber.objects.all()
i want to divide number field by 2 before saving to database.
what should be the best approach?
One approach is overriding the def save() method of your model class mynumber.
def save() method is going to run when example_object.save() is invoked on an object and you can access it's fields like this before saving into database. So you can override the save() method of your model,
class Example(models.Model):
text= models.TextField(null=True, blank=True)
number = models.DecimalField(max_digits=None, decimal_places=None)
def save(self, *args, **kwargs):
self.number = self.number / 2
super(Example, self).save(*args, **kwargs)

Many DB queries for string representation of model object

I have this models:
class Country(models.Model):
name = models.CharField(max_length=250)
def __str__(self):
return str(self.name)
class City(models.Model):
name = models.CharField(max_length=250)
country = models.ForeignKey(Country, default=None, blank=True)
def __str__(self):
return str(self.name)
class Airport(models.Model):
name = models.CharField(max_length=250)
city = models.ForeignKey(City, default=None, blank=True)
def __str__(self):
return "{0} - {1} - {2}".format(self.city, self.city.country, self.name)
class Tour(models.Model):
title = models.CharField(max_length=200)
tour_from = models.ForeignKey(Airport)
tour_to = models.ForeignKey(Airport)
def __str__(self):
return str(self.title)
For string representation of Airport Django sends many requests to DB:
302.06 ms (591 queries including 586 similar and 586 duplicates )
Queries screenshot:
At tour/create page I have a ModelForm for creating a tour and Django sends these queries for displaying form.
forms.py:
class TourCreateForm(forms.ModelForm):
class Meta:
model = Tour
fields = ['title', 'tour_from', 'tour_to']
views.py:
class DashboardTourCreate(CreateView):
model = Tour
template_name = "dashboard/tour/create.html"
form_class = TourCreateForm
def get_context_data(self, **kwargs):
context = super(DashboardTourCreate, self).get_context_data(**kwargs)
context['page_name'] = ['tour', 'tour-index']
context['page_title'] = "Create Tour"
return context
How I can reduce queries count?
Root Cause
def __str__(self):
return "{0} - {1} - {2}".format(self.city, self.city.country, self.name)
When the tour_to and tour_from fields are rendered as <option> in the <select> widget the Airport.__str__ method is called. Because Airport.__str__ has self.city.county and both of these are ForeignKey's, the Django ORM issues a query to grab the airports city and the citys country.
And it does this for every single Airport that is an <option> which means the problem will get progressively worse the more Airport's that are added.
Solution
Leverage select_related[1]. select_related will tell the Django ORM to pull in the related fields ('city', 'county') whenever it grabs an Airport.
class TourCreateForm(forms.ModelForm):
class Meta:
model = Tour
fields = ['title', 'tour_from', 'tour_to']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['tour_from'].queryset = Airport.objects.select_related(
'city__country',
)
self.fields['tour_to'].queryset = Airport.objects.select_related(
'city__country',
)
[1] https://docs.djangoproject.com/en/2.1/ref/models/querysets/#select-related
As f-string is a string literal expressions evaluated at run time link, this might be faster that other string format but i am not fully sure. I am expecting following modification may reduce the over all time.
class Airport(models.Model):
name = models.CharField(max_length=250)
city = models.ForeignKey(City, default=None, blank=True)
def __str__(self):
return f"{self.city} - {self.city.country} - {self.name}"
I fix this issue by adding Queryset to forms.py:
class TourCreateForm(BaseForm):
airports = Airport.objects.select_related('city', 'city__country').all()
tour_from = forms.ModelChoiceField(queryset=airports)
tour_to = forms.ModelChoiceField(queryset=airports)
But I think this is not correct!

Django model - set default charfield in lowercase

How to set default charfield in lowercase?
This is my model:
class User(models.Model):
username = models.CharField(max_length=100, unique=True)
password = models.CharField(max_length=64)
name = models.CharField(max_length=200)
phone = models.CharField(max_length=20)
email = models.CharField(max_length=200)
def __init__(self, *args, **kwargs):
self.username = self.username.lower()
I tried the __init__ but it doesn't work. I want to make the username in lowercase every time new record saved. Thanks.
While overwriting save() method is a valid solution. I found it useful to deal with this on a Field level as opposed to the Model level by overwriting get_prep_value() method.
This way if you ever want to reuse this field in a different model, you can adopt the same consistent strategy. Also the logic is separated from the save method, which you may also want to overwrite for different purposes.
For this case you would do this:
class NameField(models.CharField):
def get_prep_value(self, value):
return str(value).lower()
class User(models.Model):
username = models.CharField(max_length=100, unique=True)
password = models.CharField(max_length=64)
name = NameField(max_length=200)
phone = models.CharField(max_length=20)
email = models.CharField(max_length=200)
Just do it in the save method. ie, override the save method of Model class.
def save(self, *args, **kwargs):
self.username = self.username.lower()
return super(User, self).save(*args, **kwargs)
signals also works
from django.db.models.signals import pre_save
#receiver(pre_save, sender=YourModel)
def to_lower(sender, instance=None, **kwargs):
instance.text = instance.text.lower() if \
isinstance(instance.text, str) else ''
In my case I had a recipient_name field that I needed to make all lower case when it is stored on DB
class LowerField(models.CharField):
def get_prep_value(self, value):
return str(value).lower()
class Recipients(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='recipients', on_delete=models.CASCADE, )
recipient_account_number = models.IntegerField()
recipient_name = LowerField(max_length=30)
recipient_bank_name = models.CharField(max_length=30)
date = models.DateTimeField(auto_now=True, verbose_name='Transaction Date')
class Meta:
ordering = ['-date']
def __str__(self):
return self.recipient_name
def get_absolute_url(self):
return reverse('recipient-detail', kwargs={'pk': self.pk})
Similarly, you can apply to another table called Transactions in your app, like this
class Transactions(models.Model):
transaction_type = (
('transfer', 'Transfer'),
)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='transactions', on_delete=models.CASCADE, )
bank_name = LowerField(max_length=50)
def save(self, force_insert=False, force_update=False):
self.YourFildName = self.YourFildName.upper()
super(YourFomrName, self).save(force_insert, force_update)