diff --git a/cookbook/admin.py b/cookbook/admin.py index e667a1614..6fc1d1408 100644 --- a/cookbook/admin.py +++ b/cookbook/admin.py @@ -172,3 +172,10 @@ class ShareLinkAdmin(admin.ModelAdmin): admin.site.register(ShareLink, ShareLinkAdmin) + + +class NutritionInformationAdmin(admin.ModelAdmin): + list_display = ('id',) + + +admin.site.register(NutritionInformation, NutritionInformationAdmin) diff --git a/cookbook/migrations/0089_auto_20201117_2222.py b/cookbook/migrations/0089_auto_20201117_2222.py new file mode 100644 index 000000000..5e5dca9cf --- /dev/null +++ b/cookbook/migrations/0089_auto_20201117_2222.py @@ -0,0 +1,36 @@ +# Generated by Django 3.1.1 on 2020-11-17 21:22 + +import datetime +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('cookbook', '0088_shoppinglist_finished'), + ] + + operations = [ + migrations.CreateModel( + name='NutritionInformation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('fats', models.DecimalField(decimal_places=16, default=0, max_digits=32)), + ('carbohydrates', models.DecimalField(decimal_places=16, default=0, max_digits=32)), + ('proteins', models.DecimalField(decimal_places=16, default=0, max_digits=32)), + ('calories', models.DecimalField(decimal_places=16, default=0, max_digits=32)), + ('source', models.CharField(blank=True, default='', max_length=512, null=True)), + ], + ), + migrations.AlterField( + model_name='invitelink', + name='valid_until', + field=models.DateField(default=datetime.date(2020, 12, 1)), + ), + migrations.AddField( + model_name='recipe', + name='nutrition', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.nutritioninformation'), + ), + ] diff --git a/cookbook/models.py b/cookbook/models.py index 875836bed..2dc4689d9 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -182,6 +182,17 @@ class Step(models.Model): ordering = ['order', 'pk'] +class NutritionInformation(models.Model): + fats = models.DecimalField(default=0, decimal_places=16, max_digits=32) + carbohydrates = models.DecimalField(default=0, decimal_places=16, max_digits=32) + proteins = models.DecimalField(default=0, decimal_places=16, max_digits=32) + calories = models.DecimalField(default=0, decimal_places=16, max_digits=32) + source = models.CharField(max_length=512, default="", null=True, blank=True) + + def __str__(self): + return f'Nutrition' + + class Recipe(models.Model): name = models.CharField(max_length=128) image = models.ImageField(upload_to='recipes/', blank=True, null=True) @@ -195,6 +206,7 @@ class Recipe(models.Model): working_time = models.IntegerField(default=0) waiting_time = models.IntegerField(default=0) internal = models.BooleanField(default=False) + nutrition = models.ForeignKey(NutritionInformation, blank=True, null=True, on_delete=models.CASCADE) created_by = models.ForeignKey(User, on_delete=models.PROTECT) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/cookbook/serializer.py b/cookbook/serializer.py index 8f5a115d3..2fbf11c4d 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -6,7 +6,7 @@ from rest_framework import serializers from rest_framework.exceptions import ValidationError from cookbook.models import MealPlan, MealType, Recipe, ViewLog, UserPreference, Storage, Sync, SyncLog, Keyword, Unit, Ingredient, Comment, RecipeImport, RecipeBook, RecipeBookEntry, ShareLink, CookLog, Food, Step, ShoppingList, \ - ShoppingListEntry, ShoppingListRecipe + ShoppingListEntry, ShoppingListRecipe, NutritionInformation from cookbook.templatetags.custom_tags import markdown @@ -140,13 +140,20 @@ class StepSerializer(WritableNestedModelSerializer): fields = ('id', 'name', 'type', 'instruction', 'ingredients', 'time', 'order', 'show_as_header') +class NutritionInformationSerializer(serializers.ModelSerializer): + class Meta: + model = NutritionInformation + fields = ('carbohydrates', 'fats', 'proteins', 'calories', 'source') + + class RecipeSerializer(WritableNestedModelSerializer): + nutrition = NutritionInformationSerializer(allow_null=True) steps = StepSerializer(many=True) keywords = KeywordSerializer(many=True) class Meta: model = Recipe - fields = ['id', 'name', 'image', 'keywords', 'steps', 'working_time', 'waiting_time', 'created_by', 'created_at', 'updated_at', 'internal'] + fields = ['id', 'name', 'image', 'keywords', 'steps', 'working_time', 'waiting_time', 'created_by', 'created_at', 'updated_at', 'internal', 'nutrition'] read_only_fields = ['image', 'created_by', 'created_at'] diff --git a/cookbook/templates/forms/edit_internal_recipe.html b/cookbook/templates/forms/edit_internal_recipe.html index d890a78a3..50574fcc2 100644 --- a/cookbook/templates/forms/edit_internal_recipe.html +++ b/cookbook/templates/forms/edit_internal_recipe.html @@ -81,6 +81,34 @@ +
+
+
+
+

{% trans 'Nutrition' %}

+ + + + + + + + + + + + +
+
+
+
+
+ @@ -335,6 +363,8 @@ class="btn btn-info shadow-none">{% trans 'Save' %} + {% trans 'View Recipe' %} {% trans 'Add Step' %} + + {% trans 'View Recipe' %} diff --git a/cookbook/templates/recipe_view.html b/cookbook/templates/recipe_view.html index 58304e170..09a89694e 100644 --- a/cookbook/templates/recipe_view.html +++ b/cookbook/templates/recipe_view.html @@ -208,6 +208,60 @@ {% endif %} + {% if recipe.nutrition %} +
+
+
+
+

{% trans 'Nutrition' %}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {% trans 'Calories' %} + {{ recipe.nutrition.calories|floatformat:2 }}kcal
+ {% trans 'Carbohydrates' %} + {{ recipe.nutrition.carbohydrates|floatformat:2 }}g
+ {% trans 'Fats' %} + {{ recipe.nutrition.fats|floatformat:2 }}g
+ {% trans 'Proteins' %} + {{ recipe.nutrition.proteins|floatformat:2 }}g
+ {% if recipe.nutrition.source %} + Source: {{ recipe.nutrition.source }} + {% endif %} +
+
+
+
+ {% endif %} + +

{% trans 'Instructions' %}

@@ -478,7 +532,7 @@ this.$http.get("{% url 'api:recipe-detail' recipe.pk %}" {% if share %} + "?share={{ share }}"{% endif %}).then((response) => { this.recipe = response.data; - this.loading = false + this.loading = false; for (let step of this.recipe.steps) { if (step.ingredients.length > 0) { @@ -487,25 +541,25 @@ if (step.time !== 0) { this.has_times = true } - this.$set(step, 'time_finished', undefined) + this.$set(step, 'time_finished', undefined); for (let i of step.ingredients) { this.$set(i, 'checked', false) } } }).catch((err) => { - this.error = err.data - this.loading = false + this.error = err.data; + this.loading = false; console.log(err) }) }, roundDecimals: function (num) { let decimals = {% if request.user.userpreference.ingredient_decimals %} - {{ request.user.userpreference.ingredient_decimals }} {% else %} 2 {% endif %} + {{ request.user.userpreference.ingredient_decimals }} {% else %} 2; {% endif %} return +(Math.round(num + `e+${decimals}`) + `e-${decimals}`); }, updateTimes: function (step) { - let time_diff_first = 0 + let time_diff_first = 0; for (let s of this.recipe.steps) { if (this.recipe.steps.indexOf(s) < this.recipe.steps.indexOf(step)) { time_diff_first += s.time @@ -514,7 +568,7 @@ this.recipe.steps[0].time_finished = moment(step.time_finished).subtract(time_diff_first, 'minutes').format(moment.HTML5_FMT.DATETIME_LOCAL); - let time_diff = 0 + let time_diff = 0; for (let s of this.recipe.steps) { s.time_finished = moment(this.recipe.steps[0].time_finished).add(time_diff, 'minutes').format(moment.HTML5_FMT.DATETIME_LOCAL); time_diff += s.time