Merge pull request #199 from sebimarkgraf/feature/nutrition
Add nutritional values
This commit is contained in:
commit
53b00cc4c8
@ -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)
|
||||
|
36
cookbook/migrations/0089_auto_20201117_2222.py
Normal file
36
cookbook/migrations/0089_auto_20201117_2222.py
Normal file
@ -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'),
|
||||
),
|
||||
]
|
@ -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)
|
||||
|
@ -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']
|
||||
|
||||
|
||||
|
@ -81,6 +81,34 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" v-if="recipe.nutrition" style="margin-top: 1vh">
|
||||
<div class="col-md-12">
|
||||
<div class="card border-grey">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">{% trans 'Nutrition' %}</h4>
|
||||
<div class="dropdown-menu dropdown-menu-right"
|
||||
aria-labelledby="dropdownMenuLink">
|
||||
<button class="dropdown-item" @click="removeStep(step)"><i
|
||||
class="fa fa-trash fa-fw"></i> {% trans 'Delete Step' %}</button>
|
||||
|
||||
</div>
|
||||
|
||||
<label for="id_name"> {% trans 'Calories' %}</label>
|
||||
<input class="form-control" id="id_calories" v-model="recipe.nutrition.calories">
|
||||
|
||||
<label for="id_name"> {% trans 'Carbohydrates' %}</label>
|
||||
<input class="form-control" id="id_carbohydrates" v-model="recipe.nutrition.carbohydrates">
|
||||
|
||||
<label for="id_name"> {% trans 'Fats' %}</label>
|
||||
<input class="form-control" id="id_fats" v-model="recipe.nutrition.fats">
|
||||
<label for="id_name"> {% trans 'Proteins' %}</label>
|
||||
<input class="form-control" id="id_proteins" v-model="recipe.nutrition.proteins">
|
||||
<br/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<draggable :list="recipe.steps" group="steps"
|
||||
:empty-insert-threshold="10" handle=".handle" @sort="sortSteps()">
|
||||
@ -335,6 +363,8 @@
|
||||
class="btn btn-info shadow-none">{% trans 'Save' %}</button>
|
||||
<button type="button" @click="addStep()"
|
||||
class="btn btn-primary shadow-none">{% trans 'Add Step' %}</button>
|
||||
<button type="button" @click="addNutrition()"
|
||||
class="btn btn-primary shadow-none">{% trans 'Add Nutrition' %}</button>
|
||||
<a href="{% url 'view_recipe' recipe.pk %}" @click="addStep()"
|
||||
class="btn btn-secondary shadow-none">{% trans 'View Recipe' %}</a>
|
||||
<a href="{% url 'delete_recipe' recipe.pk %}"
|
||||
@ -361,6 +391,9 @@
|
||||
<button type="button" @click="addStep()"
|
||||
class="btn btn-primary btn-block shadow-none">{% trans 'Add Step' %}</button>
|
||||
|
||||
<button type="button" @click="addNutrition()"
|
||||
class="btn btn-primary btn-block shadow-none">{% trans 'Add Nutrition' %}</button>
|
||||
|
||||
<a href="{% url 'view_recipe' recipe.pk %}"
|
||||
class="btn btn-secondary btn-block shadow-none">{% trans 'View Recipe' %}</a>
|
||||
<a href="{% url 'delete_recipe' recipe.pk %}"
|
||||
@ -649,6 +682,12 @@
|
||||
scrollToStep: function (step_index) {
|
||||
document.getElementById('id_step_' + step_index).scrollIntoView({behavior: 'smooth'});
|
||||
},
|
||||
addNutrition: function () {
|
||||
this.recipe.nutrition = {}
|
||||
},
|
||||
removeNutrition: function() {
|
||||
this.recipe.nutrition = undefined
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -208,6 +208,60 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if recipe.nutrition %}
|
||||
<div class="row mt-5">
|
||||
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2">
|
||||
<div class="card border-primary">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">{% trans 'Nutrition' %}</h4>
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<td style="padding-top: 8px!important; ">
|
||||
<b>{% trans 'Calories' %}</b>
|
||||
</td>
|
||||
<td style="text-align: right">{{ recipe.nutrition.calories|floatformat:2 }}</td>
|
||||
<td>kcal</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr>
|
||||
<td style="padding-top: 8px!important; ">
|
||||
<b>{% trans 'Carbohydrates' %}</b>
|
||||
</td>
|
||||
<td style="text-align: right">{{ recipe.nutrition.carbohydrates|floatformat:2 }}</td>
|
||||
<td>g</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr>
|
||||
<td style="padding-top: 8px!important; ">
|
||||
<b>{% trans 'Fats' %}</b>
|
||||
</td>
|
||||
<td style="text-align: right">{{ recipe.nutrition.fats|floatformat:2 }}</td>
|
||||
<td>g</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr>
|
||||
<td style="padding-top: 8px!important; ">
|
||||
<b>{% trans 'Proteins' %}</b>
|
||||
</td>
|
||||
<td style="text-align: right">{{ recipe.nutrition.proteins|floatformat:2 }}</td>
|
||||
<td>g</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
{% if recipe.nutrition.source %}
|
||||
Source: {{ recipe.nutrition.source }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div v-if="recipe !== undefined && recipe.steps.length > 0">
|
||||
<hr>
|
||||
<h3>{% trans 'Instructions' %}</h3>
|
||||
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user