bulk update in django admin

Hello,

I have the following code:
models.py:

class Ingredient(models.Model):
    """to create ingredients whether they are food, packaging or any other category,
    then to define their real cost according to a yield"""
    ingredient_id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=60, unique=False, verbose_name='nom')
    category = models.ForeignKey(IngredientCategory, on_delete=models.CASCADE, verbose_name='catégorie')
    purchase_unit = models.CharField(max_length=6, choices=UNIT, verbose_name="unité d'achat")
    use_unit = models.CharField(max_length=6, choices=UNIT, verbose_name="unité d'utilisation")
    stock_unit = models.CharField(max_length=6, choices=UNIT, verbose_name="unité de stockage")
    negociated_price = models.DecimalField(max_digits=7, decimal_places=3, verbose_name='prix négocié')
    net_weight = models.DecimalField(max_digits=6, decimal_places=3, verbose_name='poids net ou volume unitaire')
    number_of_packs_per_purchase_unit = models.DecimalField(max_digits=3, decimal_places=0,
                                                            verbose_name='nombre de paquets par colis',
                                                            default=1,
                                                            help_text='nombre de pièces par carton')
    number_of_number_pieces_per_pack = models.DecimalField(max_digits=3, decimal_places=0, default=1,
                                                           verbose_name='nombre de pièces par paquet',
                                                           help_text='nombre de pièce par paquet')
    ingredient_yield = models.DecimalField(max_digits=3, decimal_places=2, default=1, verbose_name='rendement',
                                           help_text='ex: pour 93% mettre 0.93')

# nested fields
    price_per_kg_ou_l = models.DecimalField(max_digits=7, decimal_places=3, blank=True, null=True,
                                            verbose_name='prix au kg ou l')
    net_weight_after_yield = models.DecimalField(max_digits=6, decimal_places=3, blank=True, null=True,
                                                 verbose_name='poids ou volume après rendement')
    price_per_kg_ou_l_after_yield = models.DecimalField(max_digits=7, decimal_places=3, blank=True, null=True,
                                                        verbose_name='prix de revient au kg ou l')
    price_per_piece = models.DecimalField(max_digits=6, decimal_places=3, blank=True, null=True,
                                          verbose_name='prix par pièce')

    @property
    def last_invoiced_price(self):
        pass
        if not self.ingredient_id:
            return 0
        else:
            if not self.receptioningredients_set.values('invoiced_price'):
                return self.negociated_price
            else:
                dict_1 = self.receptioningredients_set.values('invoiced_price').latest('reception_date')
                return dict_1['invoiced_price']
    @property
    def _price_per_kg_ou_l(self):
        """to calculate the price per kg or litre of the ingredient"""
        if self.purchase_unit == 'litre' or self.purchase_unit == 'kg':
            return self.last_invoiced_price
        else:
            return self.last_invoiced_price / self.net_weight

    @property
    def _net_weight_after_yield(self):
        """to calculate the wheight after a yield """
        return round(self.net_weight * self.ingredient_yield, 3)

    @property
    def _price_per_kg_ou_l_after_yield(self):
        """to calculate the price per kg after a yield"""
        return round(self._price_per_kg_ou_l / self.ingredient_yield, 3)

    @property
    def _price_per_piece(self):
        """to calculate the price per piece"""
        if self.purchase_unit == 'colis':
            return self.last_invoiced_price / (self.number_of_packs_per_purchase_unit * self.number_of_number_pieces_per_pack)
        elif self.purchase_unit == 'pièce':
            return self.last_invoiced_price
        elif self.purchase_unit == 'paquet':
            return self.last_invoiced_price / self.number_of_number_pieces_per_pack
        else:
            return self.last_invoiced_price * self.net_weight / (self.number_of_packs_per_purchase_unit * self.number_of_number_pieces_per_pack)

    def save(self, *args, **kwargs):
        self.price_per_kg_or_l = self._price_per_kg_ou_l
        self.net_weight_after_yield = self._net_weight_after_yield
        self.price_per_kg_ou_l_after_yield = self._price_per_kg_ou_l_after_yield
        self.price_per_piece = self._price_per_piece
        super().save(*args, **kwargs)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = 'ingrédient'
        verbose_name_plural = 'c - ingrédients'

class Reception(models.Model):
    reception_id = models.AutoField(primary_key=True)
    date = models.DateField()
    created_on = models.DateField(auto_now_add=True)
    updated_on = models.DateField(auto_now=True)
    supplier = models.ForeignKey(Supplier, on_delete=models.PROTECT, verbose_name='fournisseur')

    def get_total_amount(self):
        if not self.reception_id:
            pass
        else:
            dict_1 = self.receptioningredients_set.aggregate(result=Sum('invoiced_amount'))
            return dict_1['result']

    get_total_amount.short_description = 'montant'

    class Meta:
        verbose_name = 'réception'
        verbose_name_plural = 'd - réceptions'

    def __str__(self):
        return 'réception n° {}'.format(self.reception_id)


class ReceptionIngredients(models.Model):
    reception = models.ForeignKey(Reception, on_delete=models.PROTECT, verbose_name='réception')
    ingredient = models.ForeignKey(Ingredient, on_delete=models.PROTECT, verbose_name='ingrédient')
    tracability_number = models.CharField(max_length=60, blank=True, null=True, verbose_name='n° de lot')
    best_before = models.DateField(blank=True, null=True, verbose_name='DLC / DLUO')
    invoiced_quantity = models.DecimalField(max_digits=9, decimal_places=3, blank=True, null=True,
                                            verbose_name='quantité_facturée')
    invoiced_price = models.DecimalField(max_digits=9, decimal_places=3, blank=True, null=True,
                                         verbose_name='prix_facturé')
    invoiced_amount = models.DecimalField(max_digits=9, decimal_places=3, blank=True, null=True, editable=False,
                                          verbose_name='montant_facturé')
    is_closed = models.BooleanField(default=False, verbose_name='clôturé')
    reception_date = models.DateField(null=True, blank=True, editable=False, verbose_name='date de réception')

    class Meta:
        verbose_name = 'ingrédient'
        verbose_name_plural = 'e - ingrédients reçus'

    @property
    def get_invoiced_amount(self):
        return self.invoiced_price * self.invoiced_quantity

    def save(self, *args, **kwargs):
        self.invoiced_amount = self.get_invoiced_amount
        self.reception_date = self.reception.date
        super().save(*args, **kwargs)

class Recipe(models.Model):
    """gives a name and describes the recipe and calculate its cost"""

    recipe_id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=60, verbose_name='nom')

    # nested in lines
    recipe_weight = models.DecimalField(max_digits=6, decimal_places=3, null=True, blank=True,
                                        verbose_name='poids ou volume')
    recipe_price = models.DecimalField(max_digits=6, decimal_places=3, null=True, blank=True,
                                       verbose_name='prix de revient')
    recipe_price_per_kg = models.DecimalField(max_digits=6, decimal_places=3, null=True, blank=True,
                                              verbose_name='prix de revient au kg ou l')

    @property
    def _recipe_weight(self):
        if self.recipeingredient_set.count() == 0:
            return 0
        else:
            dict_1 = self.recipeingredient_set.aggregate(recipe_weight=Sum('quantity'))
            return round(dict_1['recipe_weight'] * self.recipe_yield, 3)

    @property
    def _weight_per_serving(self):
        if self.recipeingredient_set.count() == 0:
            return 0
        else:
            return round(self._recipe_weight / self.number_of_serving, 3)

    @property
    def _recipe_price(self):
        if self.recipeingredient_set.count() == 0:
            return 0
        else:
            dict_1 = self.recipeingredient_set.aggregate(recipe_price=Sum('ingredient_price'))
            return round(dict_1['recipe_price'], 3)

    @property
    def _price_per_serving(self):
        if self.recipeingredient_set.count() == 0:
            return 0
        else:
            return round(self._recipe_price / self.number_of_serving, 3)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = 'recette'
        verbose_name_plural = 'b - recettes'


class RecipeIngredient(models.Model):
    recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, verbose_name='recette')
    ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE)
    quantity = models.DecimalField(max_digits=6, decimal_places=3, verbose_name='quantité')
    unit = models.CharField(max_length=10, editable=False, null=True, blank=True, verbose_name='unité')

    # nested in lines
    ingredient_price = models.DecimalField(max_digits=6, decimal_places=3, blank=True, null=True,
                                           verbose_name="prix total de l'ingrédient")
    quantity_per_serving = models.DecimalField(max_digits=6, decimal_places=3, blank=True, null=True,
                                               verbose_name="quantité d'ingrédient par part servie")
    price_per_serving = models.DecimalField(max_digits=6, decimal_places=3, blank=True, null=True,
                                            verbose_name="prix de l'ingrédient par part servie")
    production = models.OneToOneField(Production, on_delete=models.CASCADE, blank=True, null=True, editable=False)
    allergen = models.CharField(max_length=60, blank=True, null=True, editable=False, verbose_name='allergène(s)')

    @property
    def get_last_invoiced_price(self):
        pass
        if not self.ingredient.ingredient_id:
            return 0
        else:
            if not self.ingredient.receptioningredients_set.values('invoiced_price'):
                return self.ingredient.negociated_price
            else:
                dict_1 = self.ingredient.receptioningredients_set.values('invoiced_price').latest('reception_date')
                return dict_1['invoiced_price']

    @property
    def _ingredient_price(self):
        if self.ingredient.use_unit == 'kg':
            return round(self.get_last_invoiced_price / (self.ingredient.net_weight * self.ingredient.ingredient_yield) * self.quantity, 3)
        elif self.ingredient.use_unit == 'litre':
            return round(self.get_last_invoiced_price / (self.ingredient.net_weight * self.ingredient.ingredient_yield) * self.quantity, 3)
        elif self.ingredient.use_unit == 'colis':
            return round(self.get_last_invoiced_price * self.quantity, 3)
        elif self.ingredient.use_unit == 'paquet':
            return round(self.get_last_invoiced_price, 3)
        else:
            return round(self.ingredient.price_per_piece * self.quantity, 3)

    def save(self, *args, **kwargs):
        self.unit = self.ingredient.use_unit
        self.ingredient_price = self._ingredient_price
        super().save(*args, **kwargs)

    class Meta:
        verbose_name = 'ingrédient'
        verbose_name_plural = 'ingrédients'

    def __str__(self):
        return '{}'.format(self.ingredient)

as you can see I managed to calculate the last invoiced price almost everywhere, everything is working fine.
The only problem I have is that in the model Recipe in order to calculate the total price of the recipe I add the last invoiced price multiplied by the quantity (which in itself has several if/elif).
It means that in the model RecipeIngredient :
-_ingredient_price is always accurate whenever I receive a new ingredient

  • ingredient_price is not accurate so in the model Recipe the price of the recipe is wrong

I tried to update ingredient_price but it is not working:
in admin.py:

class RecipeIngredientAdmin(admin.ModelAdmin):
    list_display = ['recipe', 'production', 'ingredient', 'get_allergen_list', 'allergen',
                    'get_last_invoiced_price', 'ingredient_price', '_ingredient_price']

    actions = ['update_price']

    def update_price(obj, request, queryset):
        for obj in obj.queryset.all():
            queryset.bulk_update(obj, ['ingredient_price'], batch_size=None)

I there any other way (like aggregating all these formulas in the model Recipe)?