create shopping from mealplan
This commit is contained in:
@ -264,11 +264,12 @@ class MealPlanForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = MealPlan
|
||||
fields = ('recipe', 'title', 'meal_type', 'note', 'date', 'shared')
|
||||
fields = ('recipe', 'title', 'meal_type', 'note', 'recipe_multiplier', 'date', 'shared')
|
||||
|
||||
help_texts = {
|
||||
'shared': _('You can list default users to share recipes with in the settings.'),
|
||||
'note': _('You can use markdown to format this field. See the <a href="/docs/markdown/">docs here</a>')
|
||||
'note': _('You can use markdown to format this field. See the <a href="/docs/markdown/">docs here</a>'),
|
||||
'recipe_multiplier': _('Scaling factor for recipe.')
|
||||
}
|
||||
|
||||
widgets = {'recipe': SelectWidget, 'date': DateWidget, 'shared': MultiSelectWidget}
|
||||
|
24
cookbook/migrations/0086_auto_20200929_1143.py
Normal file
24
cookbook/migrations/0086_auto_20200929_1143.py
Normal file
@ -0,0 +1,24 @@
|
||||
# Generated by Django 3.0.7 on 2020-09-29 09:43
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0085_auto_20200922_1235'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='mealplan',
|
||||
name='recipe_multiplier',
|
||||
field=models.IntegerField(default=1),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='invitelink',
|
||||
name='valid_until',
|
||||
field=models.DateField(default=datetime.date(2020, 10, 13)),
|
||||
),
|
||||
]
|
23
cookbook/migrations/0087_auto_20200929_1152.py
Normal file
23
cookbook/migrations/0087_auto_20200929_1152.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.0.7 on 2020-09-29 09:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0086_auto_20200929_1143'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='mealplan',
|
||||
name='recipe_multiplier',
|
||||
field=models.DecimalField(decimal_places=4, default=1, max_digits=4),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='shoppinglistrecipe',
|
||||
name='multiplier',
|
||||
field=models.DecimalField(decimal_places=4, default=1, max_digits=4),
|
||||
),
|
||||
]
|
23
cookbook/migrations/0088_auto_20200929_1202.py
Normal file
23
cookbook/migrations/0088_auto_20200929_1202.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.0.7 on 2020-09-29 10:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0087_auto_20200929_1152'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='mealplan',
|
||||
name='recipe_multiplier',
|
||||
field=models.DecimalField(decimal_places=4, default=1, max_digits=8),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='shoppinglistrecipe',
|
||||
name='multiplier',
|
||||
field=models.DecimalField(decimal_places=4, default=1, max_digits=8),
|
||||
),
|
||||
]
|
@ -254,6 +254,7 @@ class MealType(models.Model):
|
||||
|
||||
class MealPlan(models.Model):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, blank=True, null=True)
|
||||
recipe_multiplier = models.DecimalField(default=1, max_digits=8, decimal_places=4)
|
||||
title = models.CharField(max_length=64, blank=True, default='')
|
||||
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
shared = models.ManyToManyField(User, blank=True, related_name='plan_share')
|
||||
@ -275,7 +276,7 @@ class MealPlan(models.Model):
|
||||
|
||||
class ShoppingListRecipe(models.Model):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, null=True, blank=True)
|
||||
multiplier = models.IntegerField(default=1)
|
||||
multiplier = models.DecimalField(default=1, max_digits=8, decimal_places=4)
|
||||
|
||||
def __str__(self):
|
||||
return f'Shopping list recipe {self.id} - {self.recipe}'
|
||||
|
@ -192,17 +192,19 @@ class MealPlanSerializer(serializers.ModelSerializer):
|
||||
recipe_name = serializers.ReadOnlyField(source='recipe.name')
|
||||
meal_type_name = serializers.ReadOnlyField(source='meal_type.name')
|
||||
note_markdown = serializers.SerializerMethodField('get_note_markdown')
|
||||
recipe_multiplier = CustomDecimalField()
|
||||
|
||||
def get_note_markdown(self, obj):
|
||||
return markdown(obj.note)
|
||||
|
||||
class Meta:
|
||||
model = MealPlan
|
||||
fields = ('id', 'title', 'recipe', 'note', 'note_markdown', 'date', 'meal_type', 'created_by', 'shared', 'recipe_name', 'meal_type_name')
|
||||
fields = ('id', 'title', 'recipe', 'recipe_multiplier', 'note', 'note_markdown', 'date', 'meal_type', 'created_by', 'shared', 'recipe_name', 'meal_type_name')
|
||||
|
||||
|
||||
class ShoppingListRecipeSerializer(serializers.ModelSerializer):
|
||||
recipe_name = serializers.ReadOnlyField(source='recipe.name')
|
||||
multiplier = CustomDecimalField()
|
||||
|
||||
class Meta:
|
||||
model = ShoppingListRecipe
|
||||
|
@ -126,6 +126,9 @@
|
||||
class="text-muted">{% trans 'You can use markdown to format this field. See the <a href="/docs/markdown/" target="_blank" rel="noopener noreferrer">docs here</a>' %}</span></small>
|
||||
<br/>
|
||||
<br/>
|
||||
<input type="number" class="form-control" v-model="new_note_multiplier"
|
||||
placeholder="{% trans 'Recipe Multiplier' %}" style="margin-bottom: 8px">
|
||||
<br/>
|
||||
<draggable :list="pseudo_note_list"
|
||||
:group="{ name: 'plan', pull: 'clone', put: false }" :clone="cloneNote">
|
||||
<div class="list-group-item" v-for="(element, index) in pseudo_note_list"
|
||||
@ -349,6 +352,7 @@
|
||||
],
|
||||
new_note_title: '',
|
||||
new_note_text: '',
|
||||
new_note_multiplier: '',
|
||||
default_shared_users: [],
|
||||
user_id_update: [],
|
||||
user_names: {},
|
||||
@ -367,7 +371,7 @@
|
||||
this.getRecipes();
|
||||
},
|
||||
methods: {
|
||||
makeToast: function(title, message, variant=null) {
|
||||
makeToast: function (title, message, variant = null) {
|
||||
//TODO remove duplicate function in favor of central one
|
||||
this.$bvToast.toast(message, {
|
||||
title: title,
|
||||
@ -389,7 +393,7 @@
|
||||
this.plan_entries = response.data;
|
||||
}).catch((err) => {
|
||||
console.log("getPlanEntries error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
getPlanTypes: function () {
|
||||
@ -401,7 +405,7 @@
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log("getPlanTypes error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
buildGrid: function () {
|
||||
@ -451,7 +455,7 @@
|
||||
this.recipes = response.data;
|
||||
}).catch((err) => {
|
||||
console.log("getRecipes error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
getMdNote: function () {
|
||||
@ -464,7 +468,7 @@
|
||||
this.recipes = response.data;
|
||||
}).catch((err) => {
|
||||
console.log("getRecipes error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
updateUserNames: function () {
|
||||
@ -475,7 +479,7 @@
|
||||
|
||||
}).catch((err) => {
|
||||
console.log("updateUserNames error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
dragChanged: function (date, meal_type, evt) {
|
||||
@ -501,7 +505,7 @@
|
||||
this.$http.put(`{% url 'api:mealplan-list' %}${plan_entry.id}/`, plan_entry).then((response) => {
|
||||
}).catch((err) => {
|
||||
console.log("dragChanged update error", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -512,7 +516,7 @@
|
||||
this.meal_plan[entry.meal_type_name].days[entry.date].items = this.meal_plan[entry.meal_type_name].days[entry.date].items.filter(item => item !== entry)
|
||||
}).catch((err) => {
|
||||
console.log("deleteEntry error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
updatePlanTypes: function () {
|
||||
@ -526,14 +530,14 @@
|
||||
promise_list.push(this.$http.post("{% url 'api:mealtype-list' %}", x).then((response) => {
|
||||
}).catch((err) => {
|
||||
console.log("updatePlanTypes create error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
}))
|
||||
} else if (x.delete) {
|
||||
if (x.id !== undefined) {
|
||||
promise_list.push(this.$http.delete(`{% url 'api:mealtype-list' %}${x.id}/`, x).then((response) => {
|
||||
}).catch((err) => {
|
||||
console.log("updatePlanTypes delete error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
@ -541,7 +545,7 @@
|
||||
|
||||
}).catch((err) => {
|
||||
console.log("updatePlanTypes update error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -556,14 +560,21 @@
|
||||
}
|
||||
},
|
||||
cloneRecipe: function (recipe) {
|
||||
return {
|
||||
let r = {
|
||||
id: Math.round(Math.random() * 1000) + 10000,
|
||||
recipe: recipe.id,
|
||||
recipe_name: recipe.name,
|
||||
recipe_multiplier: (this.new_note_multiplier > 1) ? this.new_note_multiplier : 1,
|
||||
title: this.new_note_title,
|
||||
note: this.new_note_text,
|
||||
is_new: true
|
||||
}
|
||||
|
||||
this.new_note_title = ''
|
||||
this.new_note_text = ''
|
||||
this.new_note_multiplier = ''
|
||||
|
||||
return r
|
||||
},
|
||||
cloneNote: function () {
|
||||
let new_entry = {
|
||||
@ -579,6 +590,7 @@
|
||||
|
||||
this.new_note_title = ''
|
||||
this.new_note_text = ''
|
||||
this.new_note_multiplier = ''
|
||||
return new_entry
|
||||
},
|
||||
planElementName: function (element) {
|
||||
@ -618,10 +630,10 @@
|
||||
let first = true
|
||||
for (let se of this.shopping_list) {
|
||||
if (first) {
|
||||
url += `?r=${se.recipe}`
|
||||
url += `?r=[${se.recipe},${se.recipe_multiplier}]`
|
||||
first = false
|
||||
} else {
|
||||
url += `&r=${se.recipe}`
|
||||
url += `&r=[${se.recipe},${se.recipe_multiplier}]`
|
||||
}
|
||||
}
|
||||
return url
|
||||
|
@ -183,13 +183,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 4vh">
|
||||
<div class="col col-12 text-right">
|
||||
<button class="btn btn-success" @click="updateShoppingList()"><i
|
||||
class="fas fa-save"></i> {% trans 'Save' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
|
||||
@ -221,7 +214,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<b-button class="btn btn-success" v-b-modal.id_modal_export>{% trans 'Export' %}</b-button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 2vh">
|
||||
<div class="col" style="text-align: right">
|
||||
<b-button class="btn btn-info" v-b-modal.id_modal_export><i
|
||||
class="fas fa-file-export"></i> {% trans 'Export' %}</b-button>
|
||||
<button class="btn btn-success" @click="updateShoppingList()" v-if="edit_mode"><i
|
||||
class="fas fa-save"></i> {% trans 'Save' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<b-modal id="id_modal_export" title="{% trans 'Copy/Export' %}">
|
||||
<div class="row">
|
||||
@ -245,8 +250,6 @@
|
||||
|
||||
</b-modal>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
@ -339,12 +342,25 @@
|
||||
mounted: function () {
|
||||
this.loadShoppingList()
|
||||
|
||||
{% if recipes %}
|
||||
this.loading = true
|
||||
this.edit_mode = true
|
||||
let loadingRecipes = []
|
||||
{% for r in recipes %}
|
||||
loadingRecipes.push(this.loadInitialRecipe({{ r.recipe }}, {{ r.multiplier }}))
|
||||
{% endfor %}
|
||||
|
||||
Promise.allSettled(loadingRecipes).then(() => {
|
||||
this.loading = false
|
||||
})
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.userpreference.shopping_auto_sync > 0 %}
|
||||
setInterval(() => {
|
||||
if ((this.shopping_list_id !== null) && !this.edit_mode) {
|
||||
this.loadShoppingList(true)
|
||||
}
|
||||
}, {{ request.user.userpreference.shopping_auto_sync }} * 1000 )
|
||||
}, {% widthratio request.user.userpreference.shopping_auto_sync 1 1000 %})
|
||||
{% endif %}
|
||||
},
|
||||
methods: {
|
||||
@ -365,6 +381,14 @@
|
||||
solid: true
|
||||
})
|
||||
},
|
||||
loadInitialRecipe: function (recipe, multiplier) {
|
||||
return this.$http.get('{% url 'api:recipe-detail' 123456 %}'.replace('123456', recipe)).then((response) => {
|
||||
this.addRecipeToList(response.data, multiplier)
|
||||
}).catch((err) => {
|
||||
console.log("getRecipes error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
loadShoppingList: function (autosync = false) {
|
||||
|
||||
if (this.shopping_list_id) {
|
||||
@ -433,6 +457,8 @@
|
||||
|
||||
this.shopping_list = response.body
|
||||
this.shopping_list_id = this.shopping_list.id
|
||||
|
||||
window.history.pushState('shopping_list', '{% trans 'Shopping List' %}', "{% url 'view_shopping' 123456 %}".replace('123456', this.shopping_list_id));
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error creating a resource!' %}' + err.bodyText, 'danger')
|
||||
@ -511,13 +537,13 @@
|
||||
getRecipeUrl: function (id) { //TODO generic function that can be reused else were
|
||||
return '{% url 'view_recipe' 123456 %}'.replace('123456', id)
|
||||
},
|
||||
addRecipeToList: function (recipe) {
|
||||
addRecipeToList: function (recipe, multiplier = 1) {
|
||||
let slr = {
|
||||
"created": true,
|
||||
"id": Math.random() * 1000,
|
||||
"recipe": recipe.id,
|
||||
"recipe_name": recipe.name,
|
||||
"multiplier": 1
|
||||
"multiplier": multiplier
|
||||
}
|
||||
|
||||
this.shopping_list.recipes.push(slr)
|
||||
|
@ -165,7 +165,17 @@ def meal_plan_entry(request, pk):
|
||||
|
||||
@group_required('user')
|
||||
def shopping_list(request, pk=None):
|
||||
return render(request, 'shopping_list.html', {'shopping_list_id': pk})
|
||||
raw_list = request.GET.getlist('r')
|
||||
|
||||
recipes = []
|
||||
for r in raw_list:
|
||||
r = r.replace('[', '').replace(']', '')
|
||||
if re.match(r'^([1-9])+,([1-9])+[.]*([1-9])*$', r):
|
||||
rid, multiplier = r.split(',')
|
||||
if recipe := Recipe.objects.filter(pk=int(rid)).first():
|
||||
recipes.append({'recipe': recipe.id, 'multiplier': multiplier})
|
||||
|
||||
return render(request, 'shopping_list.html', {'shopping_list_id': pk, 'recipes': recipes})
|
||||
|
||||
|
||||
@group_required('guest')
|
||||
|
Reference in New Issue
Block a user