user nutrition types + ingredient nutrtion calculation
This commit is contained in:
parent
5651beffb2
commit
a2b987352f
@ -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',)
|
||||||
|
|
||||||
|
@ -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 = [
|
40
cookbook/migrations/0190_nutritiontype_foodnutrition.py
Normal file
40
cookbook/migrations/0190_nutritiontype_foodnutrition.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
@ -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'))
|
||||||
|
@ -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',
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user