nested serializers and basic recipe editing working
This commit is contained in:
parent
5e20833b7e
commit
c178aea363
@ -1,4 +1,5 @@
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from drf_writable_nested import WritableNestedModelSerializer, UniqueFieldsMixin
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
from rest_framework.fields import CurrentUserDefault
|
from rest_framework.fields import CurrentUserDefault
|
||||||
@ -38,71 +39,50 @@ class SyncLogSerializer(serializers.ModelSerializer):
|
|||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class KeywordSerializer(serializers.ModelSerializer):
|
class KeywordSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Keyword
|
model = Keyword
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
validators = []
|
|
||||||
extra_kwargs = {
|
|
||||||
"name": {
|
|
||||||
"validators": [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
|
|
||||||
class UnitSerializer(serializers.ModelSerializer):
|
class UnitSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Unit
|
model = Unit
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class FoodSerializer(serializers.ModelSerializer):
|
class FoodSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Food
|
model = Food
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class IngredientSerializer(serializers.ModelSerializer):
|
class IngredientSerializer(WritableNestedModelSerializer):
|
||||||
food = FoodSerializer(read_only=True)
|
food = FoodSerializer()
|
||||||
unit = UnitSerializer(read_only=True)
|
unit = UnitSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Ingredient
|
model = Ingredient
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class StepSerializer(serializers.ModelSerializer):
|
class StepSerializer(WritableNestedModelSerializer):
|
||||||
ingredients = IngredientSerializer(many=True, read_only=True)
|
ingredients = IngredientSerializer(many=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Step
|
model = Step
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class RecipeSerializer(serializers.ModelSerializer):
|
class RecipeSerializer(WritableNestedModelSerializer):
|
||||||
steps = StepSerializer(many=True, read_only=True)
|
steps = StepSerializer(many=True)
|
||||||
keywords = KeywordSerializer(many=True, read_only=False, validators=[])
|
keywords = KeywordSerializer(many=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Recipe
|
model = Recipe
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
validators = []
|
validators = []
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
|
||||||
for k in validated_data['keyword']:
|
|
||||||
pass
|
|
||||||
return instance
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
|
||||||
print('test')
|
|
||||||
pass
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
|
|
||||||
class RecipeImportSerializer(serializers.ModelSerializer):
|
class RecipeImportSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -61,7 +61,82 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" @click="updateRecipe()">Save</button>
|
<template v-for="step, index in recipe.steps">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h3>Step [[index + 1]]</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<template v-for="ingredient, index in step.ingredients">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body" style="padding: 12px">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<input class="form-control" v-model="ingredient.amount">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<multiselect v-tabindex
|
||||||
|
ref="unit"
|
||||||
|
v-model="ingredient.unit"
|
||||||
|
:options="units"
|
||||||
|
:close-on-select="true"
|
||||||
|
:clear-on-select="true"
|
||||||
|
:allow-empty="true"
|
||||||
|
:preserve-search="true"
|
||||||
|
placeholder="{% trans 'Select one' %}"
|
||||||
|
tag-placeholder="{% trans 'Select' %}"
|
||||||
|
label="name"
|
||||||
|
:id="'unit_' + index"
|
||||||
|
track-by="id"
|
||||||
|
:multiple="false"
|
||||||
|
:loading="units_loading"
|
||||||
|
@search-change="searchUnits">
|
||||||
|
</multiselect>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<multiselect v-tabindex
|
||||||
|
ref="food"
|
||||||
|
v-model="ingredient.food"
|
||||||
|
:options="foods"
|
||||||
|
:close-on-select="true"
|
||||||
|
:clear-on-select="true"
|
||||||
|
:allow-empty="true"
|
||||||
|
:preserve-search="true"
|
||||||
|
placeholder="{% trans 'Select one' %}"
|
||||||
|
tag-placeholder="{% trans 'Select' %}"
|
||||||
|
label="name"
|
||||||
|
:id="'food_' + index"
|
||||||
|
track-by="id"
|
||||||
|
:multiple="false"
|
||||||
|
:loading="foods_loading"
|
||||||
|
@search-change="searchFoods">
|
||||||
|
</multiselect>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<input class="form-control" v-model="ingredient.note">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<textarea class="form-control" rows="8" v-model="step.instruction"
|
||||||
|
:id="'id_instruction_' + step.id"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<button type="button" @click="updateRecipe()" class="btn">Save</button>
|
||||||
|
<button type="button" @click="addStep()" class="btn">Add Step</button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -81,6 +156,10 @@
|
|||||||
recipe: undefined,
|
recipe: undefined,
|
||||||
keywords: [],
|
keywords: [],
|
||||||
keywords_loading: false,
|
keywords_loading: false,
|
||||||
|
foods: [],
|
||||||
|
foods_loading: false,
|
||||||
|
units: [],
|
||||||
|
units_loading: false,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
tabindex: {
|
tabindex: {
|
||||||
@ -91,6 +170,9 @@
|
|||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
this.loadRecipe()
|
this.loadRecipe()
|
||||||
|
this.searchUnits('')
|
||||||
|
this.searchFoods('')
|
||||||
|
this.searchKeywords('')
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loadRecipe: function () {
|
loadRecipe: function () {
|
||||||
@ -110,15 +192,41 @@
|
|||||||
console.log(err)
|
console.log(err)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
addStep: function () { //TODO see if default can be generated from options request
|
||||||
|
this.recipe.steps.push(
|
||||||
|
{'instruction': '', ingredients: []}
|
||||||
|
)
|
||||||
|
},
|
||||||
searchKeywords: function (query) {
|
searchKeywords: function (query) {
|
||||||
this.keywords_loading = true
|
this.keywords_loading = true
|
||||||
this.$http.get("{% url 'api:keyword-list' %}" + '?query=' + query).then((response) => {
|
this.$http.get("{% url 'api:keyword-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||||
this.keywords = response.data;
|
this.keywords = response.data;
|
||||||
this.keywords_loading = false
|
this.keywords_loading = false
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
searchUnits: function (query) {
|
||||||
|
this.units_loading = true
|
||||||
|
this.$http.get("{% url 'api:unit-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||||
|
this.units = response.data;
|
||||||
|
//TODO add back code to include custom created ingredients
|
||||||
|
this.units_loading = false
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
searchFoods: function (query) {
|
||||||
|
this.foods_loading = true
|
||||||
|
this.$http.get("{% url 'api:food-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||||
|
this.foods = response.data
|
||||||
|
//TODO add back code to include custom created ingredients
|
||||||
|
|
||||||
|
this.foods_loading = false
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -18,7 +18,7 @@ from rest_framework.mixins import RetrieveModelMixin, UpdateModelMixin, ListMode
|
|||||||
|
|
||||||
from cookbook.helper.permission_helper import group_required, CustomIsOwner, CustomIsAdmin, CustomIsUser
|
from cookbook.helper.permission_helper import group_required, CustomIsOwner, CustomIsAdmin, CustomIsUser
|
||||||
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
|
from cookbook.models import Recipe, Sync, Storage, CookLog, MealPlan, MealType, ViewLog, UserPreference, RecipeBook, Ingredient, Food, Step, Keyword, Unit
|
||||||
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, \
|
||||||
@ -112,16 +112,38 @@ class MealTypeViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class UnitViewSet(viewsets.ModelViewSet):
|
class UnitViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Food.objects.all()
|
queryset = Unit.objects.all()
|
||||||
serializer_class = FoodSerializer
|
serializer_class = FoodSerializer
|
||||||
permission_classes = [CustomIsUser]
|
permission_classes = [CustomIsUser]
|
||||||
|
|
||||||
|
def get_queryset(self): # TODO create standard filter/limit mixin
|
||||||
|
queryset = self.queryset
|
||||||
|
query = self.request.query_params.get('query', None)
|
||||||
|
if query is not None:
|
||||||
|
queryset = queryset.filter(name__icontains=query)
|
||||||
|
|
||||||
|
limit = self.request.query_params.get('limit', None)
|
||||||
|
if limit is not None:
|
||||||
|
queryset = queryset[:int(limit)]
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class FoodViewSet(viewsets.ModelViewSet):
|
class FoodViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Food.objects.all()
|
queryset = Food.objects.all()
|
||||||
serializer_class = FoodSerializer
|
serializer_class = FoodSerializer
|
||||||
permission_classes = [CustomIsUser]
|
permission_classes = [CustomIsUser]
|
||||||
|
|
||||||
|
def get_queryset(self): # TODO create standard filter/limit mixin
|
||||||
|
queryset = self.queryset
|
||||||
|
query = self.request.query_params.get('query', None)
|
||||||
|
if query is not None:
|
||||||
|
queryset = queryset.filter(name__icontains=query)
|
||||||
|
|
||||||
|
limit = self.request.query_params.get('limit', None)
|
||||||
|
if limit is not None:
|
||||||
|
queryset = queryset[:int(limit)]
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class IngredientViewSet(viewsets.ModelViewSet):
|
class IngredientViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Ingredient.objects.all()
|
queryset = Ingredient.objects.all()
|
||||||
@ -147,7 +169,7 @@ class RecipeViewSet(viewsets.ModelViewSet):
|
|||||||
serializer_class = RecipeSerializer
|
serializer_class = RecipeSerializer
|
||||||
permission_classes = [permissions.IsAuthenticated] # TODO split read and write permission for meal plan guest
|
permission_classes = [permissions.IsAuthenticated] # TODO split read and write permission for meal plan guest
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self): # TODO create standard filter/limit mixin
|
||||||
queryset = Recipe.objects.all()
|
queryset = Recipe.objects.all()
|
||||||
query = self.request.query_params.get('query', None)
|
query = self.request.query_params.get('query', None)
|
||||||
if query is not None:
|
if query is not None:
|
||||||
@ -171,7 +193,7 @@ class KeywordViewSet(viewsets.ModelViewSet):
|
|||||||
serializer_class = KeywordSerializer
|
serializer_class = KeywordSerializer
|
||||||
permission_classes = [CustomIsUser]
|
permission_classes = [CustomIsUser]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self): # TODO create standard filter/limit mixin
|
||||||
queryset = Keyword.objects.all()
|
queryset = Keyword.objects.all()
|
||||||
query = self.request.query_params.get('query', None)
|
query = self.request.query_params.get('query', None)
|
||||||
if query is not None:
|
if query is not None:
|
||||||
|
@ -9,6 +9,7 @@ django-emoji-picker==0.0.6
|
|||||||
django-filter==2.2.0
|
django-filter==2.2.0
|
||||||
django-tables2==2.3.1
|
django-tables2==2.3.1
|
||||||
djangorestframework==3.11.0
|
djangorestframework==3.11.0
|
||||||
|
drf-writable-nested==0.6.0
|
||||||
gunicorn==20.0.4
|
gunicorn==20.0.4
|
||||||
lxml==4.5.1
|
lxml==4.5.1
|
||||||
Markdown==3.2.2
|
Markdown==3.2.2
|
||||||
|
Loading…
Reference in New Issue
Block a user