user nutrition types + ingredient nutrtion calculation

This commit is contained in:
vabene1111 2023-02-24 22:12:52 +01:00
parent 5651beffb2
commit a2b987352f
5 changed files with 105 additions and 14 deletions

View File

@ -15,7 +15,7 @@ from .models import (BookmarkletImport, Comment, CookLog, Food, FoodInheritField
Recipe, RecipeBook, RecipeBookEntry, RecipeImport, SearchPreference, ShareLink, Recipe, RecipeBook, RecipeBookEntry, RecipeImport, SearchPreference, ShareLink,
ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog, Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog,
TelegramBot, Unit, UserFile, UserPreference, ViewLog, Automation, UserSpace, UnitConversion) TelegramBot, Unit, UserFile, UserPreference, ViewLog, Automation, UserSpace, UnitConversion, NutritionType, FoodNutrition)
class CustomUserAdmin(UserAdmin): class CustomUserAdmin(UserAdmin):
@ -327,6 +327,20 @@ class ShareLinkAdmin(admin.ModelAdmin):
admin.site.register(ShareLink, ShareLinkAdmin) admin.site.register(ShareLink, ShareLinkAdmin)
class NutritionTypeAdmin(admin.ModelAdmin):
list_display = ('id', 'name')
admin.site.register(NutritionType, NutritionTypeAdmin)
class FoodNutritionAdmin(admin.ModelAdmin):
list_display = ('id', 'food_amount', 'food_unit', 'food', 'nutrition_amount', 'nutrition_type')
admin.site.register(FoodNutrition, FoodNutritionAdmin)
class NutritionInformationAdmin(admin.ModelAdmin): class NutritionInformationAdmin(admin.ModelAdmin):
list_display = ('id',) list_display = ('id',)

View File

@ -1,4 +1,4 @@
# Generated by Django 4.1.4 on 2023-01-09 16:41 # Generated by Django 4.1.7 on 2023-02-24 19:47
import cookbook.models import cookbook.models
from django.conf import settings from django.conf import settings
@ -11,7 +11,7 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0186_automation_order_alter_automation_type'), ('cookbook', '0188_space_no_sharing_limit'),
] ]
operations = [ operations = [

View File

@ -0,0 +1,40 @@
# Generated by Django 4.1.7 on 2023-02-24 20:15
import cookbook.models
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0189_unitconversion'),
]
operations = [
migrations.CreateModel(
name='NutritionType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('unit', models.CharField(blank=True, max_length=64, null=True)),
('icon', models.CharField(blank=True, max_length=16, null=True)),
('description', models.CharField(blank=True, max_length=512, null=True)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='FoodNutrition',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('food_amount', models.DecimalField(decimal_places=2, default=0, max_digits=32)),
('nutrition_amount', models.DecimalField(decimal_places=4, default=0, max_digits=32)),
('food', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.food')),
('food_unit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.unit')),
('nutrition_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.nutritiontype')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
]

View File

@ -739,6 +739,33 @@ class Step(ExportModelOperationsMixin('step'), models.Model, PermissionModelMixi
indexes = (GinIndex(fields=["search_vector"]),) indexes = (GinIndex(fields=["search_vector"]),)
class NutritionType(models.Model, PermissionModelMixin):
name = models.CharField(max_length=128)
unit = models.CharField(max_length=64, blank=True, null=True)
icon = models.CharField(max_length=16, blank=True, null=True)
description = models.CharField(max_length=512, blank=True, null=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return f'{self.name}'
class FoodNutrition(models.Model, PermissionModelMixin):
food_amount = models.DecimalField(default=0, decimal_places=2, max_digits=32)
food_unit = models.ForeignKey(Unit, on_delete=models.CASCADE)
food = models.ForeignKey(Food, on_delete=models.CASCADE)
nutrition_amount = models.DecimalField(default=0, decimal_places=4, max_digits=32)
nutrition_type = models.ForeignKey(NutritionType, on_delete=models.PROTECT)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return f'{self.food_amount} {self.food_unit} {self.food}: {self.nutrition_amount} {self.nutrition_type.unit} {self.nutrition_type.name}'
class NutritionInformation(models.Model, PermissionModelMixin): class NutritionInformation(models.Model, PermissionModelMixin):
fats = models.DecimalField(default=0, decimal_places=16, max_digits=32) fats = models.DecimalField(default=0, decimal_places=16, max_digits=32)
carbohydrates = models.DecimalField( carbohydrates = models.DecimalField(
@ -755,14 +782,6 @@ class NutritionInformation(models.Model, PermissionModelMixin):
return f'Nutrition {self.pk}' return f'Nutrition {self.pk}'
# class NutritionType(models.Model, PermissionModelMixin):
# name = models.CharField(max_length=128)
# icon = models.CharField(max_length=16, blank=True, null=True)
# description = models.CharField(max_length=512, blank=True, null=True)
#
# space = models.ForeignKey(Space, on_delete=models.CASCADE)
# objects = ScopedManager(space='space')
class RecipeManager(models.Manager.from_queryset(models.QuerySet)): class RecipeManager(models.Manager.from_queryset(models.QuerySet)):
def get_queryset(self): def get_queryset(self):
return super(RecipeManager, self).get_queryset().annotate(rating=Avg('cooklog__rating')).annotate(last_cooked=Max('cooklog__created_at')) return super(RecipeManager, self).get_queryset().annotate(rating=Avg('cooklog__rating')).annotate(last_cooked=Max('cooklog__created_at'))

View File

@ -29,7 +29,7 @@ from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, Cu
RecipeBookEntry, RecipeImport, ShareLink, ShoppingList, RecipeBookEntry, RecipeImport, ShareLink, ShoppingList,
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync,
SyncLog, Unit, UserFile, UserPreference, UserSpace, ViewLog, UnitConversion) SyncLog, Unit, UserFile, UserPreference, UserSpace, ViewLog, UnitConversion, FoodNutrition, NutritionType)
from cookbook.templatetags.custom_tags import markdown from cookbook.templatetags.custom_tags import markdown
from recipes.settings import AWS_ENABLED, MEDIA_URL from recipes.settings import AWS_ENABLED, MEDIA_URL
@ -619,6 +619,7 @@ class IngredientSimpleSerializer(WritableNestedModelSerializer):
used_in_recipes = serializers.SerializerMethodField('get_used_in_recipes') used_in_recipes = serializers.SerializerMethodField('get_used_in_recipes')
amount = CustomDecimalField() amount = CustomDecimalField()
conversions = serializers.SerializerMethodField('get_conversions') conversions = serializers.SerializerMethodField('get_conversions')
nutritions = serializers.SerializerMethodField('get_nutritions')
def get_used_in_recipes(self, obj): def get_used_in_recipes(self, obj):
return list(Recipe.objects.filter(steps__ingredients=obj.id).values('id', 'name')) return list(Recipe.objects.filter(steps__ingredients=obj.id).values('id', 'name'))
@ -629,7 +630,7 @@ class IngredientSimpleSerializer(WritableNestedModelSerializer):
for c in UnitConversion.objects.filter(space=self.context['request'].space).filter(Q(food__isnull=True) | Q(food=obj.food)).filter(Q(base_unit=obj.unit) | Q(converted_unit=obj.unit)): for c in UnitConversion.objects.filter(space=self.context['request'].space).filter(Q(food__isnull=True) | Q(food=obj.food)).filter(Q(base_unit=obj.unit) | Q(converted_unit=obj.unit)):
if obj.unit == c.base_unit: if obj.unit == c.base_unit:
conversions.append({ conversions.append({
'amount': obj.amount * (c.converted_amount/c.base_amount), 'amount': obj.amount * (c.converted_amount / c.base_amount),
'unit': UnitSerializer(c.converted_unit, context={'request': self.context['request']}).data, 'unit': UnitSerializer(c.converted_unit, context={'request': self.context['request']}).data,
}) })
else: else:
@ -640,6 +641,23 @@ class IngredientSimpleSerializer(WritableNestedModelSerializer):
return conversions return conversions
def get_nutritions(self, ingredient):
nutritions = {}
for nt in NutritionType.objects.filter(space=self.context['request'].space).all():
nutritions[nt.id] = None
food_nutrition = ingredient.food.foodnutrition_set.all()
for fn in food_nutrition:
if fn.food_unit == ingredient.unit:
nutritions[fn.nutrition_type.id] = ingredient.amount / fn.food_amount * fn.nutrition_amount
else:
conversions = self.get_conversions(ingredient)
for c in conversions:
if fn.food_unit.id == c['unit']['id']:
nutritions[fn.nutrition_type.id] = c['amount'] / fn.food_amount * fn.nutrition_amount
return nutritions
def create(self, validated_data): def create(self, validated_data):
validated_data['space'] = self.context['request'].space validated_data['space'] = self.context['request'].space
return super().create(validated_data) return super().create(validated_data)
@ -651,7 +669,7 @@ class IngredientSimpleSerializer(WritableNestedModelSerializer):
class Meta: class Meta:
model = Ingredient model = Ingredient
fields = ( fields = (
'id', 'food', 'unit', 'amount', 'conversions', 'note', 'order', 'id', 'food', 'unit', 'amount', 'nutritions', 'conversions', 'note', 'order',
'is_header', 'no_amount', 'original_text', 'used_in_recipes', 'is_header', 'no_amount', 'original_text', 'used_in_recipes',
'always_use_plural_unit', 'always_use_plural_food', 'always_use_plural_unit', 'always_use_plural_food',
) )