first parts of shopping rework

This commit is contained in:
vabene1111 2020-08-11 15:24:12 +02:00
parent 8055754455
commit be55e034bf
6 changed files with 160 additions and 42 deletions

View File

@ -4,7 +4,8 @@ from rest_framework import serializers
from rest_framework.exceptions import APIException, ValidationError from rest_framework.exceptions import APIException, ValidationError
from rest_framework.fields import CurrentUserDefault from rest_framework.fields import CurrentUserDefault
from cookbook.models import MealPlan, MealType, Recipe, ViewLog, UserPreference, Storage, Sync, SyncLog, Keyword, Unit, Ingredient, Comment, RecipeImport, RecipeBook, RecipeBookEntry, ShareLink, CookLog, Food, Step 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
from cookbook.templatetags.custom_tags import markdown from cookbook.templatetags.custom_tags import markdown
@ -193,6 +194,34 @@ class MealPlanSerializer(serializers.ModelSerializer):
fields = ('id', 'title', 'recipe', 'note', 'note_markdown', 'date', 'meal_type', 'created_by', 'shared', 'recipe_name', 'meal_type_name') fields = ('id', 'title', 'recipe', 'note', 'note_markdown', 'date', 'meal_type', 'created_by', 'shared', 'recipe_name', 'meal_type_name')
class ShoppingListRecipeSerializer(serializers.ModelSerializer):
recipe = RecipeSerializer(read_only=True)
def create(self, validated_data):
return ShoppingListRecipe.objects.create(**validated_data)
def update(self, instance, validated_data):
return super(ShoppingListRecipeSerializer, self).update(instance, validated_data)
class Meta:
model = ShoppingListRecipe
fields = ('recipe', 'multiplier')
class ShoppingListEntrySerializer(serializers.ModelSerializer):
class Meta:
model = ShoppingListEntry
fields = ('list_recipe', 'food', 'unit', 'amount', 'order', 'checked')
class ShoppingListSerializer(serializers.ModelSerializer):
recipes = ShoppingListRecipeSerializer(many=True, allow_null=True, read_only=True)
class Meta:
model = ShoppingList
fields = ('id', 'uuid', 'note', 'recipes', 'shared', 'created_by', 'created_at',)
class ShareLinkSerializer(serializers.ModelSerializer): class ShareLinkSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = ShareLink model = ShareLink

View File

@ -7,11 +7,124 @@
{% block title %}{% trans "Shopping List" %}{% endblock %} {% block title %}{% trans "Shopping List" %}{% endblock %}
{% block extra_head %} {% block extra_head %}
{% include 'include/vue_base.html' %}
<link rel="stylesheet" href="{% static 'css/vue-multiselect-bs4.min.css' %}">
<script src="{% static 'js/vue-multiselect.min.js' %}"></script>
<script src="{% static 'js/Sortable.min.js' %}"></script>
<script src="{% static 'js/vuedraggable.umd.min.js' %}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="row">
<div class="col col-9">
<h2>{% trans 'Shopping List' %}</h2>
</div>
<div class="col col-3">
<b-form-checkbox switch size="lg" v-model="edit_mode">{% trans 'Edit' %}</b-form-checkbox>
</div>
</div>
<div v-if="edit_mode">
<div class="row">
<div class="col col-6">
<input type="text" class="form-control" v-model="recipe_query" @keyup="getRecipes"
placeholder="{% trans 'Search Recipe' %}">
<ul class="list-group" style="margin-top: 8px">
<li class="list-group-item" v-for="x in recipes">[[x.name]]</li>
</ul>
</div>
</div>
</div>
<div v-else>
Non Edit
</div>
{% endblock %}
{% block script %}
<script type="application/javascript">
let csrftoken = Cookies.get('csrftoken');
Vue.http.headers.common['X-CSRFToken'] = csrftoken;
Vue.component('vue-multiselect', window.VueMultiselect.default)
let app = new Vue({
components: {
Multiselect: window.VueMultiselect.default
},
delimiters: ['[[', ']]'],
el: '#id_base_container',
data: {
edit_mode: true,
recipe_query: '',
recipes: [],
},
directives: {
tabindex: {
inserted(el) {
el.setAttribute('tabindex', 0);
}
}
},
/*
watch: {
recipe: {
deep: true,
handler() {
this.recipe_changed = this.recipe_changed !== undefined;
}
}
},
created() {
window.addEventListener('beforeunload', this.warnPageLeave)
},
*/
mounted: function () {
},
methods: {
/*
warnPageLeave: function (event) {
if (this.recipe_changed) {
event.returnValue = ''
return ''
}
},
*/
getRecipes: function () {
let url = "{% url 'api:recipe-list' %}?limit=5"
if (this.recipe_query !== '') {
url += '&query=' + this.recipe_query;
}
this.$http.get(url).then((response) => {
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')
})
},
searchKeywords: function (query) {
this.keywords_loading = true
this.$http.get("{% url 'api:keyword-list' %}" + '?query=' + query + '&limit=10').then((response) => {
this.keywords = response.data;
this.keywords_loading = false
}).catch((err) => {
console.log(err)
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
})
},
}
});
</script>
{% endblock %} {% endblock %}

View File

@ -23,6 +23,7 @@ router.register(r'recipe', api.RecipeViewSet)
router.register(r'ingredient', api.IngredientViewSet) router.register(r'ingredient', api.IngredientViewSet)
router.register(r'meal-plan', api.MealPlanViewSet) router.register(r'meal-plan', api.MealPlanViewSet)
router.register(r'meal-type', api.MealTypeViewSet) router.register(r'meal-type', api.MealTypeViewSet)
router.register(r'shopping-list', api.ShoppingListViewSet)
router.register(r'view-log', api.ViewLogViewSet) router.register(r'view-log', api.ViewLogViewSet)
urlpatterns = [ urlpatterns = [
@ -34,6 +35,7 @@ urlpatterns = [
path('plan/', views.meal_plan, name='view_plan'), path('plan/', views.meal_plan, name='view_plan'),
path('plan/entry/<int:pk>', views.meal_plan_entry, name='view_plan_entry'), path('plan/entry/<int:pk>', views.meal_plan_entry, name='view_plan_entry'),
path('shopping/', views.shopping_list, name='view_shopping'), path('shopping/', views.shopping_list, name='view_shopping'),
path('shopping/<int:pk>', views.shopping_list, name='view_shopping'),
path('settings/', views.user_settings, name='view_settings'), path('settings/', views.user_settings, name='view_settings'),
path('history/', views.history, name='view_history'), path('history/', views.history, name='view_history'),
path('test/', views.test, name='view_test'), path('test/', views.test, name='view_test'),

View File

@ -28,11 +28,11 @@ from rest_framework.viewsets import ViewSetMixin
from cookbook.helper.permission_helper import group_required, CustomIsOwner, CustomIsAdmin, CustomIsUser, CustomIsGuest, CustomIsShare from cookbook.helper.permission_helper import group_required, CustomIsOwner, CustomIsAdmin, CustomIsUser, CustomIsGuest, CustomIsShare
from cookbook.helper.recipe_url_import import get_from_html from cookbook.helper.recipe_url_import import get_from_html
from cookbook.models import Recipe, Sync, Storage, CookLog, MealPlan, MealType, ViewLog, UserPreference, RecipeBook, Ingredient, Food, Step, Keyword, Unit, SyncLog from cookbook.models import Recipe, Sync, Storage, CookLog, MealPlan, MealType, ViewLog, UserPreference, RecipeBook, Ingredient, Food, Step, Keyword, Unit, SyncLog, ShoppingListRecipe, ShoppingList
from cookbook.provider.dropbox import Dropbox from cookbook.provider.dropbox import Dropbox
from cookbook.provider.nextcloud import Nextcloud from cookbook.provider.nextcloud import Nextcloud
from cookbook.serializer import MealPlanSerializer, MealTypeSerializer, RecipeSerializer, ViewLogSerializer, UserNameSerializer, UserPreferenceSerializer, RecipeBookSerializer, IngredientSerializer, FoodSerializer, StepSerializer, \ from cookbook.serializer import MealPlanSerializer, MealTypeSerializer, RecipeSerializer, ViewLogSerializer, UserNameSerializer, UserPreferenceSerializer, RecipeBookSerializer, IngredientSerializer, FoodSerializer, StepSerializer, \
KeywordSerializer, RecipeImageSerializer, StorageSerializer, SyncSerializer, SyncLogSerializer, UnitSerializer KeywordSerializer, RecipeImageSerializer, StorageSerializer, SyncSerializer, SyncLogSerializer, UnitSerializer, ShoppingListSerializer
class UserNameViewSet(viewsets.ReadOnlyModelViewSet): class UserNameViewSet(viewsets.ReadOnlyModelViewSet):
@ -233,6 +233,16 @@ class RecipeViewSet(viewsets.ModelViewSet, StandardFilterMixin):
return Response(serializer.errors, 400) return Response(serializer.errors, 400)
class ShoppingListViewSet(viewsets.ModelViewSet):
queryset = ShoppingList.objects.all()
serializer_class = ShoppingListSerializer
permission_classes = [CustomIsOwner]
def get_queryset(self):
queryset = self.queryset.filter(created_by=self.request.user).all()
return queryset
class ViewLogViewSet(viewsets.ModelViewSet): class ViewLogViewSet(viewsets.ModelViewSet):
queryset = ViewLog.objects.all() queryset = ViewLog.objects.all()
serializer_class = ViewLogSerializer serializer_class = ViewLogSerializer

View File

@ -50,7 +50,7 @@ def shopping_list(request):
table = ShoppingListTable(ShoppingList.objects.filter(created_by=request.user).all()) table = ShoppingListTable(ShoppingList.objects.filter(created_by=request.user).all())
RequestConfig(request, paginate={'per_page': 25}).configure(table) RequestConfig(request, paginate={'per_page': 25}).configure(table)
return render(request, 'generic/list_template.html', {'title': _("Shopping Lists"), 'table': table, 'create_url': 'new_storage'}) return render(request, 'generic/list_template.html', {'title': _("Shopping Lists"), 'table': table, 'create_url': 'view_shopping'})
@group_required('admin') @group_required('admin')

View File

@ -164,44 +164,8 @@ def meal_plan_entry(request, pk):
@group_required('user') @group_required('user')
def shopping_list(request): def shopping_list(request, pk=None):
markdown_format = True return render(request, 'shopping_list.html', {})
if request.method == "POST":
form = ShoppingForm(request.POST)
if form.is_valid():
recipes = form.cleaned_data['recipe']
markdown_format = form.cleaned_data['markdown_format']
else:
recipes = []
else:
raw_list = request.GET.getlist('r')
recipes = []
for r in raw_list:
if re.match(r'^([1-9])+$', r):
if Recipe.objects.filter(pk=int(r)).exists():
recipes.append(int(r))
markdown_format = False
form = ShoppingForm(initial={'recipe': recipes, 'markdown_format': False})
ingredients = []
for r in recipes:
for s in r.steps.all():
for ri in s.ingredients.exclude(unit__name__contains='Special:').all():
index = None
for x, ig in enumerate(ingredients):
if ri.food == ig.food and ri.unit == ig.unit:
index = x
if index:
ingredients[index].amount = ingredients[index].amount + ri.amount
else:
ingredients.append(ri)
return render(request, 'shopping_list.html', {'ingredients': ingredients, 'recipes': recipes, 'form': form, 'markdown_format': markdown_format})
@group_required('guest') @group_required('guest')