0

I’m a beginner, I discover many things and sometimes I feel overwhelmed about all the informations and tricks to learn.

I try to do a django project for a pizzeria. People can order pizza online, the order is simple : you can choose one or more pizza and optionally add some Extras (adding cheese, ham or whatever…) for each pizza you ordered and it’s added to the cart.

My problem is to calculate automatically the price for each pizza.

Basically here’s my Model file:

class Pizza(models.Model):

    nom = models.CharField(max_length=30)
    ingrédients = models.CharField(max_length=500)
    prix = models.DecimalField(max_digits=4, decimal_places=2)

nom means name

class Extra(models.Model):

    sup = models.CharField(max_length=30)
    prix = models.DecimalField(max_digits=3, decimal_places=2)

sup is the name of the extra.

class IndividualChoice(models.Model):

    pizzaChosen = models.ForeignKey(‘Pizza’, default="", null=True, on_delete=models.CASCADE)
    extraChosen = models.ManyToManyField(‘Extra’, blank=True)
    panier = models.ForeignKey(‘Cart’, default="", on_delete=models.CASCADE)
    calzone = models.BooleanField(default=False)
    prix = models.DecimalField(max_digits=5, decimal_places=2, default=0)

IndividualChoice is a bit weird. It’s a model which stores each choice, here “panier” means Cart which has a model but I don’t think it’s useful to make it appear here.

I learned a bit about django signals, so I tried to create one:

def prix_extra_calcul(sender, instance, action, *args, **kwargs):
    instance.prix = 0
    if action == “post_add” or action == “post_remove” or action == “post_clear”:
        for extra in instance.extraChosen.all():
            instance.prix += extra.prix
        instance.prix += instance.pizzaChosen.prix
        instance.save()

m2m_changed.connect(prix_extra_calcul, sender=IndividualChoice.extraChosen.through)

It works well when I create a pizza with extra but if only change the pizza the m2m signal won’t be fired. So I tried to find another solution but it’s stupid:

def prix_pizza_calcul(sender, instance, *args, **kwargs):
    instance.prix = 0
    for extra in instance.extraChosen.all():
        instance.prix += extra.prix
    instance.prix += instance.pizzaChosen.prix

def prix_extra_calcul(sender, instance, action, *args, **kwargs):
    if action == “post_add” or action == “post_remove” or action == “post_clear”:
    # no need to write code because prix_pizza_calcul will be fired by instance.save()
        instance.save()

pre_save.connect(prix_pizza_calcul, sender=IndividualChoice)
m2m_changed.connect(prix_extra_calcul, sender=IndividualChoice.extraChosen.through)

The second solution "works well" when I want to modify a choice but when I create a new pizza I have this error message “maximum recursion depth exceeded while calling a Python object”. And I think it’s because i’m looping over something not saved.

I’m completely stuck with this problem, I tried to solve it for several days. Should I create a Signal, is a signal is appropriate for this kind of problem ?

(What a loooong way to be a web developer…)

Thanks for reading !

Sassem
  • 3
  • 3

2 Answers2

0

May be you can try to use the @receiver decorator for that. A signal will be emit on each post_save.

from django.dispatch import receiver

@receiver(post_save, sender=IndividualChoice)
def prix_extra_calcul(sender, instance, created, **kwargs):
    ...
Arthur
  • 96
  • 4
0

I don't know your knowledge about database normalization, but here with the price on IndividualChoice, you're duplicating data: the price is just a computation of already stored data. If you avoid duplicating data, you'll avoid data anomalies.

If you have a first name and a last name, would you store the "full name" ? If your answer is yes, please read the documentation about @property here: https://docs.djangoproject.com/en/3.0/topics/db/models/#model-methods

A solution is probably removing the DecimalField prix on the IndividualChoice model and add something like this:

    @property
    def prix(self):
        "Returns the price of the item"
        price = self.pizzaChosen.prix
        for extra in self.extraChosen.all():
            price += extra.prix
        return price 
Florian Thuin
  • 920
  • 1
  • 7
  • 19
  • Thank you for your answer, and I only have little knowledge of database normalization... I'll look about property but I don't think it fits with my needs because, I have another Model called Cart where I'll do the sum of each IndividualChoice price – Sassem Mar 06 '20 at 18:32
  • You can do the same on cart (i.e. add a property `price` that will sum the price of the related `IndividualChoice`). Property can be used as if it was a stored field, the only difference is that you probably lose in performance with huge set of data, but if it's usually less than 100 pizzas with less than 100 extras per cart, you should be fine. – Florian Thuin Mar 06 '20 at 18:37
  • It works ! Merci beaucoup ! Is there another way to make it without losing performance ? I mean in my case, your solution worked but is there another solution ? Just give me the notions, and I'll look by myself – Sassem Mar 06 '20 at 19:19
  • The performance you'll lose is related to the fact that Django lazy loads the related fields, so it will make multiple SQL queries to compute the final result. The best way to reduce the number of queries if you know you're going to access related fields is to use `Prefetch` or `prefetch_related` when querying on `Cart`. An example in French with Pizza can be found here https://makina-corpus.com/blog/metier/2015/ameliorer-les-performances-de-prefetch_related-avec-lobjet-prefetch but you can find more resource if you search for them. – Florian Thuin Mar 06 '20 at 19:29