added ingredient merging to shopping list
This commit is contained in:
parent
9fd87dbf23
commit
a16ad2c887
@ -5,7 +5,7 @@ from .models import (Comment, CookLog, Food, Ingredient, InviteLink, Keyword,
|
|||||||
RecipeBook, RecipeBookEntry, RecipeImport, ShareLink,
|
RecipeBook, RecipeBookEntry, RecipeImport, ShareLink,
|
||||||
ShoppingList, ShoppingListEntry, ShoppingListRecipe,
|
ShoppingList, ShoppingListEntry, ShoppingListRecipe,
|
||||||
Space, Step, Storage, Sync, SyncLog, Unit, UserPreference,
|
Space, Step, Storage, Sync, SyncLog, Unit, UserPreference,
|
||||||
ViewLog)
|
ViewLog, Supermarket, SupermarketCategory)
|
||||||
|
|
||||||
|
|
||||||
class SpaceAdmin(admin.ModelAdmin):
|
class SpaceAdmin(admin.ModelAdmin):
|
||||||
@ -42,6 +42,9 @@ class SyncAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
admin.site.register(Sync, SyncAdmin)
|
admin.site.register(Sync, SyncAdmin)
|
||||||
|
|
||||||
|
admin.site.register(Supermarket)
|
||||||
|
admin.site.register(SupermarketCategory)
|
||||||
|
|
||||||
|
|
||||||
class SyncLogAdmin(admin.ModelAdmin):
|
class SyncLogAdmin(admin.ModelAdmin):
|
||||||
list_display = ('sync', 'status', 'msg', 'created_at')
|
list_display = ('sync', 'status', 'msg', 'created_at')
|
||||||
|
@ -212,7 +212,7 @@ class KeywordForm(forms.ModelForm):
|
|||||||
class FoodForm(forms.ModelForm):
|
class FoodForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Food
|
model = Food
|
||||||
fields = ('name', 'recipe')
|
fields = ('name', 'description', 'ignore_shopping', 'recipe', 'supermarket_category')
|
||||||
widgets = {'recipe': SelectWidget}
|
widgets = {'recipe': SelectWidget}
|
||||||
|
|
||||||
|
|
||||||
|
46
cookbook/migrations/0102_auto_20210125_1147.py
Normal file
46
cookbook/migrations/0102_auto_20210125_1147.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Generated by Django 3.1.5 on 2021-01-25 10:47
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cookbook', '0101_storage_path'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Supermarket',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=128, unique=True, validators=[django.core.validators.MinLengthValidator(1)])),
|
||||||
|
('description', models.TextField(blank=True, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SupermarketCategory',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=128, unique=True, validators=[django.core.validators.MinLengthValidator(1)])),
|
||||||
|
('description', models.TextField(blank=True, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='food',
|
||||||
|
name='description',
|
||||||
|
field=models.TextField(blank=True, default=''),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='storage',
|
||||||
|
name='method',
|
||||||
|
field=models.CharField(choices=[('DB', 'Dropbox'), ('NEXTCLOUD', 'Nextcloud'), ('LOCAL', 'Local')], default='DB', max_length=128),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='food',
|
||||||
|
name='supermarket_category',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.supermarketcategory'),
|
||||||
|
),
|
||||||
|
]
|
18
cookbook/migrations/0103_food_ignore_shopping.py
Normal file
18
cookbook/migrations/0103_food_ignore_shopping.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.1.5 on 2021-01-25 13:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cookbook', '0102_auto_20210125_1147'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='food',
|
||||||
|
name='ignore_shopping',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
@ -144,6 +144,22 @@ class Sync(models.Model):
|
|||||||
return self.path
|
return self.path
|
||||||
|
|
||||||
|
|
||||||
|
class Supermarket(models.Model):
|
||||||
|
name = models.CharField(unique=True, max_length=128, validators=[MinLengthValidator(1)])
|
||||||
|
description = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class SupermarketCategory(models.Model):
|
||||||
|
name = models.CharField(unique=True, max_length=128, validators=[MinLengthValidator(1)])
|
||||||
|
description = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class SyncLog(models.Model):
|
class SyncLog(models.Model):
|
||||||
sync = models.ForeignKey(Sync, on_delete=models.CASCADE)
|
sync = models.ForeignKey(Sync, on_delete=models.CASCADE)
|
||||||
status = models.CharField(max_length=32)
|
status = models.CharField(max_length=32)
|
||||||
@ -169,9 +185,7 @@ class Keyword(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class Unit(models.Model):
|
class Unit(models.Model):
|
||||||
name = models.CharField(
|
name = models.CharField(unique=True, max_length=128, validators=[MinLengthValidator(1)])
|
||||||
unique=True, max_length=128, validators=[MinLengthValidator(1)]
|
|
||||||
)
|
|
||||||
description = models.TextField(blank=True, null=True)
|
description = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -179,12 +193,11 @@ class Unit(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class Food(models.Model):
|
class Food(models.Model):
|
||||||
name = models.CharField(
|
name = models.CharField(unique=True, max_length=128, validators=[MinLengthValidator(1)])
|
||||||
unique=True, max_length=128, validators=[MinLengthValidator(1)]
|
recipe = models.ForeignKey('Recipe', null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
)
|
supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
recipe = models.ForeignKey(
|
ignore_shopping = models.BooleanField(default=False)
|
||||||
'Recipe', null=True, blank=True, on_delete=models.SET_NULL
|
description = models.TextField(default='', blank=True)
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -11,7 +11,7 @@ from cookbook.models import (Comment, CookLog, Food, Ingredient, Keyword,
|
|||||||
RecipeBook, RecipeBookEntry, RecipeImport,
|
RecipeBook, RecipeBookEntry, RecipeImport,
|
||||||
ShareLink, ShoppingList, ShoppingListEntry,
|
ShareLink, ShoppingList, ShoppingListEntry,
|
||||||
ShoppingListRecipe, Step, Storage, Sync, SyncLog,
|
ShoppingListRecipe, Step, Storage, Sync, SyncLog,
|
||||||
Unit, UserPreference, ViewLog)
|
Unit, UserPreference, ViewLog, SupermarketCategory)
|
||||||
from cookbook.templatetags.custom_tags import markdown
|
from cookbook.templatetags.custom_tags import markdown
|
||||||
|
|
||||||
|
|
||||||
@ -140,7 +140,14 @@ class UnitSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
|||||||
read_only_fields = ('id',)
|
read_only_fields = ('id',)
|
||||||
|
|
||||||
|
|
||||||
|
class SupermarketCategorySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SupermarketCategory
|
||||||
|
fields = ('id', 'name')
|
||||||
|
|
||||||
|
|
||||||
class FoodSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
class FoodSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
||||||
|
supermarket_category = SupermarketCategorySerializer()
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
# since multi select tags dont have id's
|
# since multi select tags dont have id's
|
||||||
@ -153,7 +160,7 @@ class FoodSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Food
|
model = Food
|
||||||
fields = ('id', 'name', 'recipe')
|
fields = ('id', 'name', 'recipe', 'ignore_shopping', 'supermarket_category')
|
||||||
read_only_fields = ('id',)
|
read_only_fields = ('id',)
|
||||||
|
|
||||||
|
|
||||||
@ -309,8 +316,8 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class ShoppingListEntrySerializer(WritableNestedModelSerializer):
|
class ShoppingListEntrySerializer(WritableNestedModelSerializer):
|
||||||
food = FoodSerializer(allow_null=True)
|
food = FoodSerializer(allow_null=True, read_only=True)
|
||||||
unit = UnitSerializer(allow_null=True)
|
unit = UnitSerializer(allow_null=True, read_only=True)
|
||||||
amount = CustomDecimalField()
|
amount = CustomDecimalField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -305,7 +305,7 @@
|
|||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block script %}
|
{% block script %}
|
||||||
|
|
||||||
<script src="{% url 'javascript-catalog' %}"></script>
|
<script src="{% url 'javascript-catalog' %}"></script>
|
||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
let csrftoken = Cookies.get('csrftoken');
|
let csrftoken = Cookies.get('csrftoken');
|
||||||
@ -358,16 +358,22 @@
|
|||||||
display_entries() {
|
display_entries() {
|
||||||
let entries = []
|
let entries = []
|
||||||
|
|
||||||
//TODO merge multiple ingredients of same unit
|
|
||||||
|
|
||||||
this.shopping_list.entries.forEach(element => {
|
this.shopping_list.entries.forEach(element => {
|
||||||
let item = {}
|
let item = {}
|
||||||
Object.assign(item, element);
|
Object.assign(item, element);
|
||||||
if (item.list_recipe !== null) {
|
if (entries.filter(item => item.food.id === element.food.id).length > 0) {
|
||||||
item.amount = item.amount * this.servings_cache[item.list_recipe]
|
let entry = entries.filter(item => item.food.id === element.food.id)[0]
|
||||||
|
entry.amount += item.amount * this.servings_cache[item.list_recipe]
|
||||||
|
entry.entries.push(item.id)
|
||||||
|
} else {
|
||||||
|
if (item.list_recipe !== null) {
|
||||||
|
item.amount = item.amount * this.servings_cache[item.list_recipe]
|
||||||
|
}
|
||||||
|
item.unit = ((element.unit !== undefined && element.unit !== null) ? element.unit : {'name': ''})
|
||||||
|
item.entries = [element.id]
|
||||||
|
entries.push(item)
|
||||||
}
|
}
|
||||||
item.unit = ((element.unit !== undefined && element.unit !== null) ? element.unit : {'name': ''})
|
|
||||||
entries.push(item)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
@ -380,20 +386,6 @@
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/*
|
|
||||||
watch: {
|
|
||||||
recipe: {
|
|
||||||
deep: true,
|
|
||||||
handler() {
|
|
||||||
this.recipe_changed = this.recipe_changed !== undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
window.addEventListener('beforeunload', this.warnPageLeave)
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
this.loadShoppingList()
|
this.loadShoppingList()
|
||||||
|
|
||||||
@ -430,14 +422,6 @@
|
|||||||
} = e;
|
} = e;
|
||||||
this.onLine = type === 'online';
|
this.onLine = type === 'online';
|
||||||
},
|
},
|
||||||
/*
|
|
||||||
warnPageLeave: function (event) {
|
|
||||||
if (this.recipe_changed) {
|
|
||||||
event.returnValue = ''
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
makeToast: function (title, message, variant = null) {
|
makeToast: function (title, message, variant = null) {
|
||||||
//TODO remove duplicate function in favor of central one
|
//TODO remove duplicate function in favor of central one
|
||||||
this.$bvToast.toast(message, {
|
this.$bvToast.toast(message, {
|
||||||
@ -561,9 +545,8 @@
|
|||||||
console.log("IMPLEMENT ME", this.display_entries)
|
console.log("IMPLEMENT ME", this.display_entries)
|
||||||
},
|
},
|
||||||
entryChecked: function (entry) {
|
entryChecked: function (entry) {
|
||||||
console.log("checked entry: ", entry)
|
|
||||||
this.shopping_list.entries.forEach((item) => {
|
this.shopping_list.entries.forEach((item) => {
|
||||||
if (item.id === entry.id) { //TODO unwrap once same entries are merged
|
if (entry.entries.includes(item.id)) {
|
||||||
item.checked = entry.checked
|
item.checked = entry.checked
|
||||||
this.$http.put("{% url 'api:shoppinglistentry-detail' 123456 %}".replace('123456', item.id), item, {}).then((response) => {
|
this.$http.put("{% url 'api:shoppinglistentry-detail' 123456 %}".replace('123456', item.id), item, {}).then((response) => {
|
||||||
|
|
||||||
@ -572,7 +555,6 @@
|
|||||||
this.makeToast(gettext('Error'), gettext('There was an error updating a resource!') + err.bodyText, 'danger')
|
this.makeToast(gettext('Error'), gettext('There was an error updating a resource!') + err.bodyText, 'danger')
|
||||||
this.loading = false
|
this.loading = false
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -625,22 +607,25 @@
|
|||||||
"recipe_name": recipe.name,
|
"recipe_name": recipe.name,
|
||||||
"servings": servings,
|
"servings": servings,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.shopping_list.recipes.push(slr)
|
this.shopping_list.recipes.push(slr)
|
||||||
|
|
||||||
for (let s of recipe.steps) {
|
this.$http.get('{% url 'api:recipe-detail' 123456 %}'.replace('123456', recipe.id)).then((response) => {
|
||||||
for (let i of s.ingredients) {
|
for (let s of response.data.steps) {
|
||||||
if (!i.is_header && i.food !== null) {
|
for (let i of s.ingredients) {
|
||||||
this.shopping_list.entries.push({
|
if (!i.is_header && i.food !== null) {
|
||||||
'list_recipe': slr.id,
|
this.shopping_list.entries.push({
|
||||||
'food': i.food,
|
'list_recipe': slr.id,
|
||||||
'unit': i.unit,
|
'food': i.food,
|
||||||
'amount': i.amount,
|
'unit': i.unit,
|
||||||
'order': 0
|
'amount': i.amount,
|
||||||
})
|
'order': 0
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}).catch((err) => {
|
||||||
|
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||||
|
})
|
||||||
},
|
},
|
||||||
removeRecipeFromList: function (slr) {
|
removeRecipeFromList: function (slr) {
|
||||||
this.shopping_list.entries = this.shopping_list.entries.filter(item => item.list_recipe !== slr.id)
|
this.shopping_list.entries = this.shopping_list.entries.filter(item => item.list_recipe !== slr.id)
|
||||||
|
Loading…
Reference in New Issue
Block a user