added ingredient merging to shopping list

This commit is contained in:
vabene1111 2021-01-25 17:24:03 +01:00
parent 9fd87dbf23
commit a16ad2c887
7 changed files with 131 additions and 59 deletions

View File

@ -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')

View File

@ -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}

View 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'),
),
]

View 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),
),
]

View File

@ -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

View File

@ -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:

View File

@ -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)