Thanks Ken,
I will do my best so you will be able to help me
in the app “purchase” (everything is working fine, I normally don’t need help there):
models.py
from django.db import models
"""all the available units to purchase, store and use the ingredients"""
UNIT = [('kg', 'kg'), ('litre', 'litre'), ('pièce', 'pièce'), ('paquet', 'paquet'), ('colis', 'colis')]
class StoragePlace(models.Model):
place_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=60, verbose_name='nom')
TEMPERATURE_CHOICES = [('T°<-18°C', 'T°<-18°C'),
('0°C<T°<2°C', '0°C<T°<2°C'),
('0°C<T°<4°C', '0°C<T°<4°C'),
('15°C<T°<20°C', '15°C<T°<20°C')]
temperature = models.CharField(max_length=60,choices=TEMPERATURE_CHOICES, verbose_name='température')
temperature_control_needed = models.BooleanField(default=False, verbose_name='besoin de contrôler la température')
class Meta:
verbose_name = 'emplacement de stockage'
verbose_name_plural = 'emplacements de stockage'
def __str__(self):
return '{}'.format(self.name)
class Supplier(models.Model):
"""to create suppliers"""
supplier_id = models.AutoField(primary_key=True)
company_name = models.CharField(max_length=60, verbose_name='raison sociale')
adress = models.CharField(max_length=60, blank=True, verbose_name='adresse')
adress_information = models.CharField(max_length=60, blank=True, verbose_name="complement d'adresse")
zipcode = models.IntegerField(blank=True, null=True, verbose_name='code postal')
city = models.CharField(max_length=60, blank=True, verbose_name='ville')
phone_number = models.CharField(max_length=15, blank=True, verbose_name='telephone')
email = models.EmailField(blank=True,)
rcs = models.CharField(max_length=60, blank=True, verbose_name="n°RCS + tribunal")
siret = models.CharField(max_length=60, blank=True, verbose_name="n°SIRET")
VAT = models.CharField(max_length=60, blank=True, verbose_name="n°TVA")
def __str__(self):
return '{}'.format(self.company_name)
class Meta:
verbose_name = 'fournisseur'
class SupplierContact(models.Model):
"""to create different contacts for the supplier, eg: director, accountant..."""
contact_id = models.AutoField(primary_key=True)
supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE, verbose_name='fournisseur')
name = models.CharField(max_length=60, verbose_name='nom')
surname = models.CharField(max_length=60, verbose_name='prénom')
job_description = models.CharField(max_length=60, blank=True, verbose_name='poste',
help_text='ex: dirigeant, comptable')
phone_number = models.CharField(max_length=15, blank=True, verbose_name='telephone')
mobile_number = models.CharField(max_length=15, blank=True, verbose_name='portable')
email = models.EmailField(blank=True)
def __str__(self):
return '{} {}'.format(self.name, self.surname)
class Meta:
verbose_name = 'contact fournisseur'
class IngredientCategory(models.Model):
"""to define categories of ingredients like food, packaging, vegetables, fruits..."""
ingredient_category_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=60, verbose_name='nom', help_text='ex: ingrédient, épice, emballage...')
class Meta:
verbose_name = "type d'ingredient"
verbose_name_plural = "type d'ingredient"
def __str__(self):
return self.name
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')
supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE, verbose_name='fournisseur')
supplier_item_number = models.CharField(max_length=20, blank=True, verbose_name='n° article fournisseur')
supplier_item_name = models.CharField(max_length=30, blank=True, verbose_name='nom article fournisseur')
ean13 = models.DecimalField(max_digits=13, decimal_places=0, blank=True, null=True)
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")
invoiced_price = models.DecimalField(max_digits=7, decimal_places=3, verbose_name='prix facturé')
net_weight = models.DecimalField(max_digits=6, decimal_places=3, blank=True,
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')
is_vegan = models.BooleanField(default=False, verbose_name='végan')
is_veggie = models.BooleanField(default=False, verbose_name='végératien')
storage_place = models.ForeignKey(StoragePlace, on_delete=models.PROTECT, blank=True, null=True,
verbose_name='lieu de stockage')
storage_number = models.CharField(max_length=10, null=True, blank=True, verbose_name='emplacement')
# 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 _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.invoiced_price
else:
return self.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.invoiced_price / (self.number_of_packs_per_purchase_unit *
self.number_of_number_pieces_per_pack)
elif self.purchase_unit == 'pièce':
return self.invoiced_price
elif self.purchase_unit == 'paquet':
return self.invoiced_price / self.number_of_number_pieces_per_pack
else:
return 0
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
in the app produce (which is working fine too, but I need help here):
from django.db import models
from django.db.models import Sum
from purchase.models import Ingredient
class Production(models.Model):
"""used to create a production number in order to insure traçability"""
production_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=60, blank=True, null=True, verbose_name='nom')
date = models.DateField(blank=True, null=True)
def __str__(self):
return 'lot n° {} {}'.format(self.production_id, self.name)
class RecipeCategory(models.Model):
"""defines categories for the recipes (eg for a restaurant: desert, main course..."""
recipe_category_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=60, verbose_name='nom', help_text='ex: entrée, dessert, plat...')
class Meta:
verbose_name = "type de recette"
verbose_name_plural = "type de recette"
def __str__(self):
return self.name
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')
recipe_category = models.ForeignKey(RecipeCategory, on_delete=models.CASCADE, verbose_name="type de recette")
recipe_steps = models.TextField(max_length=2000, verbose_name='mode opératoire')
recipe_yield = models.DecimalField(max_digits=3, decimal_places=2, verbose_name='rendement', default=1,
help_text='ex: pour 93% mettre 0.93')
number_of_serving = models.IntegerField(default=1, verbose_name="nombre de parts, tranches...")
UNIT_CHOICES = [("kg", "kg"), ("litre", "litre"), ("piece", "pièce")]
unit = models.CharField(max_length=6, choices=UNIT_CHOICES, default='kg', verbose_name='unité')
shelf_life = models.PositiveSmallIntegerField(default=0, verbose_name='durée de vie', help_text='en jours')
# 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'
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.ForeignKey(Production, on_delete=models.CASCADE, blank=True, null=True, editable=False)
@property
def _ingredient_price(self):
if self.ingredient.use_unit == 'kg':
return round(self.ingredient.invoiced_price / (self.ingredient.net_weight * self.ingredient.ingredient_yield) * self.quantity, 3)
elif self.ingredient.use_unit == 'litre':
return round(self.ingredient.invoiced_price / (self.ingredient.net_weight * self.ingredient.ingredient_yield) * self.quantity, 3)
elif self.ingredient.use_unit == 'colis':
return round(self.ingredient.invoiced_price * self.quantity, 3)
elif self.ingredient.use_unit == 'paquet':
return round(self.ingredient.invoiced_price, 3)
else:
return round(self.ingredient.price_per_piece * self.quantity, 3)
@property
def _quantity_per_serving(self):
return round(self.quantity / self.recipe.number_of_serving, 3)
@property
def _price_per_serving(self):
return round(self._ingredient_price / self.recipe.number_of_serving, 3)
def save(self, *args, **kwargs):
self.unit = self.ingredient.use_unit
self.ingredient_price = self._ingredient_price
self.quantity_per_serving = self._quantity_per_serving
self.price_per_serving = self._price_per_serving
super().save(*args, **kwargs)
class Meta:
verbose_name = 'liste des ingrédients par recette'
verbose_name_plural = 'liste des ingrédients par recette'
def __str__(self):
return '{}'.format(self.ingredient)
class ProductionRecipe(models.Model):
production = models.ForeignKey(Production, on_delete=models.CASCADE)
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, verbose_name='recette')
quantity = models.DecimalField(max_digits=10, decimal_places=3)
def __str__(self):
return '{} {}'.format(self.production, self.recipe)
class Meta:
verbose_name = 'recettes par production'
in admin.py
from django.contrib import admin
from .models import Recipe, RecipeCategory, RecipeIngredient, Production, ProductionRecipe
class RecipeIngredientAdminInLine(admin.StackedInline):
model = RecipeIngredient
fields = [('recipe', 'ingredient', 'quantity', 'unit'),
('_ingredient_price', '_quantity_per_serving', '_price_per_serving')]
readonly_fields = ['unit', '_ingredient_price', '_quantity_per_serving', '_price_per_serving']
extra = 0
class RecipeAdmin(admin.ModelAdmin):
inlines = [RecipeIngredientAdminInLine]
save_on_top = True
save_as = True
fields = [('recipe_category', 'name'),'recipe_steps', ('recipe_yield', 'number_of_serving', 'unit', 'shelf_life')]
ordering = ['name']
search_fields = ['name']
search_help_text = 'recherche par nom'
list_display = ['name', 'recipe_category', 'recipe_yield', 'number_of_serving', 'unit',
'_recipe_weight', '_weight_per_serving', '_recipe_price', '_price_per_serving']
class ProductionRecipeAdminInLine(admin.StackedInline):
model = ProductionRecipe
extra = 0
# readonly_fields = ['best_before']
fields = [('recipe', 'quantity')]
class ProductionAdmin(admin.ModelAdmin):
inlines = [ProductionRecipeAdminInLine]
save_on_top = True
save_as = True
fields = [('name', 'date')]
ordering = ['name']
search_fields = ['name']
search_help_text = 'recherche par nom'
class ProductionRecipeAdmin(admin.ModelAdmin):
list_display = ["production", "recipe", "quantity"]
class RecipeIngredientAdmin(admin.ModelAdmin):
list_display = ['ingredient', 'recipe', 'production']
admin.site.register(Recipe, RecipeAdmin)
admin.site.register(RecipeCategory)
admin.site.register(Production, ProductionAdmin)
admin.site.register(ProductionRecipe, ProductionRecipeAdmin)
admin.site.register(RecipeIngredient, RecipeIngredientAdmin)
I need an admin view that will list:
- all the ingredients
- there production (so an ingredient that is used in 10 different productions will have 10 lines)
- I will have to calculate the sum of the quantities of all the ingredients I need in order to make my productions (usually I am ok with the aggregation, the filters…)
I will need to display the supplier so I can filter and prepare the order form(I do not need help there)
I will modify the admin.py so I can export the lines I want as csv or excel (I do not need help there)
as I do not want to modify the ingredients I think that I will have to begin wtih something like this:
models.py
class IngredientProxy(Ingredient):
production = models.ForeignKey("somecode I do not know what to do", on_delete=models.CASCADE)
class Meta:
proxy = True
and now I am stucked
Thanks for your help