TandoorRecipes/cookbook/serializer.py
2021-12-28 12:03:31 -06:00

921 lines
34 KiB
Python

import random
from datetime import timedelta
from decimal import Decimal
from gettext import gettext as _
from django.contrib.auth.models import User
from django.db import transaction
from django.db.models import Avg, QuerySet, Sum
from django.urls import reverse
from django.utils import timezone
from drf_writable_nested import UniqueFieldsMixin, WritableNestedModelSerializer
from rest_framework import serializers
from rest_framework.exceptions import NotFound, ValidationError
from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, Food,
FoodInheritField, ImportLog, Ingredient, Keyword, MealPlan, MealType,
NutritionInformation, Recipe, RecipeBook, RecipeBookEntry,
RecipeImport, ShareLink, ShoppingList, ShoppingListEntry,
ShoppingListRecipe, Step, Storage, Supermarket, SupermarketCategory,
SupermarketCategoryRelation, Sync, SyncLog, Unit, UserFile,
UserPreference, ViewLog)
from cookbook.templatetags.custom_tags import markdown
class ExtendedRecipeMixin(serializers.ModelSerializer):
# adds image and recipe count to serializer when query param extended=1
image = serializers.SerializerMethodField('get_image')
numrecipe = serializers.SerializerMethodField('count_recipes')
recipe_filter = None
def get_fields(self, *args, **kwargs):
fields = super().get_fields(*args, **kwargs)
try:
api_serializer = self.context['view'].serializer_class
except KeyError:
api_serializer = None
# extended values are computationally expensive and not needed in normal circumstances
try:
if bool(int(
self.context['request'].query_params.get('extended', False))) and self.__class__ == api_serializer:
return fields
except AttributeError:
pass
except KeyError:
pass
try:
del fields['image']
del fields['numrecipe']
except KeyError:
pass
return fields
def get_image(self, obj):
# TODO add caching
recipes = Recipe.objects.filter(**{self.recipe_filter: obj}, space=obj.space).exclude(
image__isnull=True).exclude(image__exact='')
try:
if recipes.count() == 0 and obj.has_children():
obj__in = self.recipe_filter + '__in'
recipes = Recipe.objects.filter(**{obj__in: obj.get_descendants()}, space=obj.space).exclude(
image__isnull=True).exclude(image__exact='') # if no recipes found - check whole tree
except AttributeError:
# probably not a tree
pass
if recipes.count() != 0:
return recipes.order_by('?')[:1][0].image.url
else:
return None
def count_recipes(self, obj):
return Recipe.objects.filter(**{self.recipe_filter: obj}, space=obj.space).count()
class CustomDecimalField(serializers.Field):
"""
Custom decimal field to normalize useless decimal places
and allow commas as decimal separators
"""
def to_representation(self, value):
if not isinstance(value, Decimal):
value = Decimal(value)
return round(value, 3).normalize()
def to_internal_value(self, data):
if type(data) == int or type(data) == float:
return data
elif type(data) == str:
if data == '':
return 0
try:
return float(data.replace(',', ''))
except ValueError:
raise ValidationError('A valid number is required')
class SpaceFilterSerializer(serializers.ListSerializer):
def to_representation(self, data):
if (type(data) == QuerySet and data.query.is_sliced):
# if query is sliced it came from api request not nested serializer
return super().to_representation(data)
if self.child.Meta.model == User:
data = data.filter(userpreference__space=self.context['request'].space)
else:
data = data.filter(**{'__'.join(data.model.get_space_key()): self.context['request'].space})
return super().to_representation(data)
class SpacedModelSerializer(serializers.ModelSerializer):
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class MealTypeSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
return super().create(validated_data)
class Meta:
list_serializer_class = SpaceFilterSerializer
model = MealType
fields = ('id', 'name', 'order', 'icon', 'color', 'default', 'created_by')
read_only_fields = ('created_by',)
class UserNameSerializer(WritableNestedModelSerializer):
username = serializers.SerializerMethodField('get_user_label')
def get_user_label(self, obj):
return obj.get_user_name()
class Meta:
list_serializer_class = SpaceFilterSerializer
model = User
fields = ('id', 'username')
class FoodInheritFieldSerializer(UniqueFieldsMixin):
def create(self, validated_data):
# don't allow writing to FoodInheritField via API
return FoodInheritField.objects.get(**validated_data)
def update(self, instance, validated_data):
# don't allow writing to FoodInheritField via API
return FoodInheritField.objects.get(**validated_data)
class Meta:
model = FoodInheritField
fields = ['id', 'name', 'field', ]
class UserPreferenceSerializer(serializers.ModelSerializer):
# food_inherit_default = FoodInheritFieldSerializer(source='space.food_inherit', read_only=True)
food_ignore_default = serializers.SerializerMethodField('get_ignore_default')
def get_ignore_default(self, obj):
return FoodInheritFieldSerializer(Food.inherit_fields.difference(obj.space.food_inherit.all()), many=True).data
def create(self, validated_data):
if validated_data['user'] != self.context['request'].user:
raise NotFound()
return super().create(validated_data)
class Meta:
model = UserPreference
fields = (
'user', 'theme', 'nav_color', 'default_unit', 'default_page',
'search_style', 'show_recent', 'plan_share', 'ingredient_decimals',
'comments', 'shopping_auto_sync', 'mealplan_autoadd_shopping', 'food_ignore_default', 'default_delay',
'mealplan_autoinclude_related', 'mealplan_autoexclude_onhand', 'shopping_share'
)
class UserFileSerializer(serializers.ModelSerializer):
def check_file_limit(self, validated_data):
if 'file' in validated_data:
if self.context['request'].space.max_file_storage_mb == -1:
raise ValidationError(_('File uploads are not enabled for this Space.'))
try:
current_file_size_mb = \
UserFile.objects.filter(space=self.context['request'].space).aggregate(Sum('file_size_kb'))[
'file_size_kb__sum'] / 1000
except TypeError:
current_file_size_mb = 0
if ((validated_data['file'].size / 1000 / 1000 + current_file_size_mb - 5)
> self.context['request'].space.max_file_storage_mb != 0):
raise ValidationError(_('You have reached your file upload limit.'))
def create(self, validated_data):
self.check_file_limit(validated_data)
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
def update(self, instance, validated_data):
self.check_file_limit(validated_data)
return super().update(instance, validated_data)
class Meta:
model = UserFile
fields = ('name', 'file', 'file_size_kb', 'id',)
read_only_fields = ('id', 'file_size_kb')
extra_kwargs = {"file": {"required": False, }}
class UserFileViewSerializer(serializers.ModelSerializer):
def create(self, validated_data):
raise ValidationError('Cannot create File over this view')
def update(self, instance, validated_data):
return instance
class Meta:
model = UserFile
fields = ('name', 'file', 'id',)
read_only_fields = ('id', 'file')
class StorageSerializer(SpacedModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
return super().create(validated_data)
class Meta:
model = Storage
fields = (
'id', 'name', 'method', 'username', 'password',
'token', 'created_by'
)
read_only_fields = ('created_by',)
extra_kwargs = {
'password': {'write_only': True},
'token': {'write_only': True},
}
class SyncSerializer(SpacedModelSerializer):
class Meta:
model = Sync
fields = (
'id', 'storage', 'path', 'active', 'last_checked',
'created_at', 'updated_at'
)
class SyncLogSerializer(SpacedModelSerializer):
class Meta:
model = SyncLog
fields = ('id', 'sync', 'status', 'msg', 'created_at')
class KeywordLabelSerializer(serializers.ModelSerializer):
label = serializers.SerializerMethodField('get_label')
def get_label(self, obj):
return str(obj)
class Meta:
list_serializer_class = SpaceFilterSerializer
model = Keyword
fields = (
'id', 'label',
)
read_only_fields = ('id', 'label')
class KeywordSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
label = serializers.SerializerMethodField('get_label')
recipe_filter = 'keywords'
def get_label(self, obj):
return str(obj)
def create(self, validated_data):
# since multi select tags dont have id's
# duplicate names might be routed to create
validated_data['name'] = validated_data['name'].strip()
validated_data['space'] = self.context['request'].space
obj, created = Keyword.objects.get_or_create(**validated_data)
return obj
class Meta:
model = Keyword
fields = (
'id', 'name', 'icon', 'label', 'description', 'image', 'parent', 'numchild', 'numrecipe')
read_only_fields = ('id', 'label', 'image', 'parent', 'numchild', 'numrecipe')
class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
recipe_filter = 'steps__ingredients__unit'
def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip()
validated_data['space'] = self.context['request'].space
obj, created = Unit.objects.get_or_create(**validated_data)
return obj
def update(self, instance, validated_data):
validated_data['name'] = validated_data['name'].strip()
return super(UnitSerializer, self).update(instance, validated_data)
class Meta:
model = Unit
fields = ('id', 'name', 'description', 'numrecipe', 'image')
read_only_fields = ('id', 'numrecipe', 'image')
class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip()
validated_data['space'] = self.context['request'].space
obj, created = SupermarketCategory.objects.get_or_create(**validated_data)
return obj
def update(self, instance, validated_data):
return super(SupermarketCategorySerializer, self).update(instance, validated_data)
class Meta:
model = SupermarketCategory
fields = ('id', 'name', 'description')
class SupermarketCategoryRelationSerializer(WritableNestedModelSerializer):
category = SupermarketCategorySerializer()
class Meta:
model = SupermarketCategoryRelation
fields = ('id', 'category', 'supermarket', 'order')
class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer):
category_to_supermarket = SupermarketCategoryRelationSerializer(many=True, read_only=True)
class Meta:
model = Supermarket
fields = ('id', 'name', 'description', 'category_to_supermarket')
class RecipeSimpleSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField('get_url')
def get_url(self, obj):
return reverse('view_recipe', args=[obj.id])
class Meta:
model = Recipe
fields = ('id', 'name', 'url')
read_only_fields = ['id', 'name', 'url']
class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedRecipeMixin):
supermarket_category = SupermarketCategorySerializer(allow_null=True, required=False)
recipe = RecipeSimpleSerializer(allow_null=True, required=False)
shopping = serializers.SerializerMethodField('get_shopping_status')
ignore_inherit = FoodInheritFieldSerializer(allow_null=True, many=True, required=False)
recipe_filter = 'steps__ingredients__food'
def get_shopping_status(self, obj):
return ShoppingListEntry.objects.filter(space=obj.space, food=obj, checked=False).count() > 0
def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip()
validated_data['space'] = self.context['request'].space
# supermarket category needs to be handled manually as food.get or create does not create nested serializers unlike a super.create of serializer
if 'supermarket_category' in validated_data and validated_data['supermarket_category']:
validated_data['supermarket_category'], sc_created = SupermarketCategory.objects.get_or_create(
name=validated_data.pop('supermarket_category')['name'],
space=self.context['request'].space)
obj, created = Food.objects.get_or_create(**validated_data)
return obj
def update(self, instance, validated_data):
if name := validated_data.get('name', None):
validated_data['name'] = name.strip()
return super(FoodSerializer, self).update(instance, validated_data)
class Meta:
model = Food
fields = ('id', 'name', 'description', 'recipe', 'ignore_shopping', 'supermarket_category', 'image', 'parent', 'numchild', 'numrecipe', 'on_hand')
read_only_fields = ('id', 'numchild', 'parent', 'image', 'numrecipe')
class IngredientSerializer(WritableNestedModelSerializer):
food = FoodSerializer(allow_null=True)
unit = UnitSerializer(allow_null=True)
amount = CustomDecimalField()
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = Ingredient
fields = (
'id', 'food', 'unit', 'amount', 'note', 'order',
'is_header', 'no_amount'
)
class StepSerializer(WritableNestedModelSerializer, ExtendedRecipeMixin):
ingredients = IngredientSerializer(many=True)
ingredients_markdown = serializers.SerializerMethodField('get_ingredients_markdown')
ingredients_vue = serializers.SerializerMethodField('get_ingredients_vue')
file = UserFileViewSerializer(allow_null=True, required=False)
step_recipe_data = serializers.SerializerMethodField('get_step_recipe_data')
recipe_filter = 'steps'
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
def get_ingredients_vue(self, obj):
return obj.get_instruction_render()
def get_ingredients_markdown(self, obj):
return obj.get_instruction_render()
def get_step_recipes(self, obj):
return list(obj.recipe_set.values_list('id', flat=True).all())
def get_step_recipe_data(self, obj):
# check if root type is recipe to prevent infinite recursion
# can be improved later to allow multi level embedding
if obj.step_recipe and type(self.parent.root) == RecipeSerializer:
return StepRecipeSerializer(obj.step_recipe).data
class Meta:
model = Step
fields = (
'id', 'name', 'type', 'instruction', 'ingredients', 'ingredients_markdown',
'ingredients_vue', 'time', 'order', 'show_as_header', 'file', 'step_recipe', 'step_recipe_data', 'numrecipe'
)
class StepRecipeSerializer(WritableNestedModelSerializer):
steps = StepSerializer(many=True)
class Meta:
model = Recipe
fields = (
'id', 'name', 'steps',
)
class NutritionInformationSerializer(serializers.ModelSerializer):
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = NutritionInformation
fields = ('id', 'carbohydrates', 'fats', 'proteins', 'calories', 'source')
class RecipeBaseSerializer(WritableNestedModelSerializer):
def get_recipe_rating(self, obj):
try:
rating = obj.cooklog_set.filter(created_by=self.context['request'].user, rating__gt=0).aggregate(
Avg('rating'))
if rating['rating__avg']:
return rating['rating__avg']
except TypeError:
pass
return 0
def get_recipe_last_cooked(self, obj):
try:
last = obj.cooklog_set.filter(created_by=self.context['request'].user).last()
if last:
return last.created_at
except TypeError:
pass
return None
# TODO make days of new recipe a setting
def is_recipe_new(self, obj):
if obj.created_at > (timezone.now() - timedelta(days=7)):
return True
else:
return False
class RecipeOverviewSerializer(RecipeBaseSerializer):
keywords = KeywordLabelSerializer(many=True)
rating = serializers.SerializerMethodField('get_recipe_rating')
last_cooked = serializers.SerializerMethodField('get_recipe_last_cooked')
new = serializers.SerializerMethodField('is_recipe_new')
def create(self, validated_data):
pass
def update(self, instance, validated_data):
return instance
class Meta:
model = Recipe
fields = (
'id', 'name', 'description', 'image', 'keywords', 'working_time',
'waiting_time', 'created_by', 'created_at', 'updated_at',
'internal', 'servings', 'servings_text', 'rating', 'last_cooked', 'new'
)
read_only_fields = ['image', 'created_by', 'created_at']
class RecipeSerializer(RecipeBaseSerializer):
nutrition = NutritionInformationSerializer(allow_null=True, required=False)
steps = StepSerializer(many=True)
keywords = KeywordSerializer(many=True)
rating = serializers.SerializerMethodField('get_recipe_rating')
last_cooked = serializers.SerializerMethodField('get_recipe_last_cooked')
class Meta:
model = Recipe
fields = (
'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time',
'waiting_time', 'created_by', 'created_at', 'updated_at',
'internal', 'nutrition', 'servings', 'file_path', 'servings_text', 'rating', 'last_cooked',
)
read_only_fields = ['image', 'created_by', 'created_at']
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
def update(self, instance, validated_data):
return super().update(instance, validated_data)
class RecipeImageSerializer(WritableNestedModelSerializer):
class Meta:
model = Recipe
fields = ['image', ]
class RecipeImportSerializer(SpacedModelSerializer):
class Meta:
model = RecipeImport
fields = '__all__'
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
class RecipeBookSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
shared = UserNameSerializer(many=True)
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
return super().create(validated_data)
class Meta:
model = RecipeBook
fields = ('id', 'name', 'description', 'icon', 'shared', 'created_by')
read_only_fields = ('created_by',)
class RecipeBookEntrySerializer(serializers.ModelSerializer):
book_content = serializers.SerializerMethodField(method_name='get_book_content', read_only=True)
recipe_content = serializers.SerializerMethodField(method_name='get_recipe_content', read_only=True)
def get_book_content(self, obj):
return RecipeBookSerializer(context={'request': self.context['request']}).to_representation(obj.book)
def get_recipe_content(self, obj):
return RecipeOverviewSerializer(context={'request': self.context['request']}).to_representation(obj.recipe)
def create(self, validated_data):
book = validated_data['book']
recipe = validated_data['recipe']
if not book.get_owner() == self.context['request'].user:
raise NotFound(detail=None, code=None)
obj, created = RecipeBookEntry.objects.get_or_create(book=book, recipe=recipe)
return obj
class Meta:
model = RecipeBookEntry
fields = ('id', 'book', 'book_content', 'recipe', 'recipe_content',)
class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
recipe = RecipeOverviewSerializer(required=False, allow_null=True)
recipe_name = serializers.ReadOnlyField(source='recipe.name')
meal_type = MealTypeSerializer()
meal_type_name = serializers.ReadOnlyField(source='meal_type.name') # TODO deprecate once old meal plan was removed
note_markdown = serializers.SerializerMethodField('get_note_markdown')
servings = CustomDecimalField()
shared = UserNameSerializer(many=True)
def get_note_markdown(self, obj):
return markdown(obj.note)
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
mealplan = super().create(validated_data)
if self.context['request'].data.get('addshopping', False):
ShoppingListEntry.list_from_recipe(mealplan=mealplan, space=validated_data['space'], created_by=validated_data['created_by'])
return mealplan
class Meta:
model = MealPlan
fields = (
'id', 'title', 'recipe', 'servings', 'note', 'note_markdown',
'date', 'meal_type', 'created_by', 'shared', 'recipe_name',
'meal_type_name'
)
read_only_fields = ('created_by',)
# TODO deprecate
class ShoppingListRecipeSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField('get_name') # should this be done at the front end?
recipe_name = serializers.ReadOnlyField(source='recipe.name')
mealplan_note = serializers.ReadOnlyField(source='mealplan.note')
servings = CustomDecimalField()
def get_name(self, obj):
if not isinstance(value := obj.servings, Decimal):
value = Decimal(value)
value = value.quantize(Decimal(1)) if value == value.to_integral() else value.normalize() # strips trailing zero
return (
obj.name
or getattr(obj.mealplan, 'title', None)
or (d := getattr(obj.mealplan, 'date', None)) and ': '.join([obj.mealplan.recipe.name, str(d)])
or obj.recipe.name
) + f' ({value:.2g})'
def update(self, instance, validated_data):
if 'servings' in validated_data:
ShoppingListEntry.list_from_recipe(
list_recipe=instance,
servings=validated_data['servings'],
created_by=self.context['request'].user,
space=self.context['request'].space
)
return super().update(instance, validated_data)
class Meta:
model = ShoppingListRecipe
fields = ('id', 'recipe_name', 'name', 'recipe', 'mealplan', 'servings', 'mealplan_note')
read_only_fields = ('id',)
class ShoppingListEntrySerializer(WritableNestedModelSerializer):
food = FoodSerializer(allow_null=True)
unit = UnitSerializer(allow_null=True, required=False)
ingredient_note = serializers.ReadOnlyField(source='ingredient.note')
recipe_mealplan = ShoppingListRecipeSerializer(source='list_recipe', read_only=True)
amount = CustomDecimalField()
created_by = UserNameSerializer(read_only=True)
completed_at = serializers.DateTimeField(allow_null=True)
def get_fields(self, *args, **kwargs):
fields = super().get_fields(*args, **kwargs)
# autosync values are only needed for frequent 'checked' value updating
if self.context['request'] and bool(int(self.context['request'].query_params.get('autosync', False))):
for f in list(set(fields) - set(['id', 'checked'])):
del fields[f]
return fields
def run_validation(self, data):
if (
data.get('checked', False)
and self.root.instance
and not self.root.instance.checked
):
# if checked flips from false to true set completed datetime
data['completed_at'] = timezone.now()
elif not data.get('checked', False):
# if not checked set completed to None
data['completed_at'] = None
else:
# otherwise don't write anything
if 'completed_at' in data:
del data['completed_at']
return super().run_validation(data)
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
validated_data['created_by'] = self.context['request'].user
return super().create(validated_data)
class Meta:
model = ShoppingListEntry
fields = (
'id', 'list_recipe', 'food', 'unit', 'ingredient', 'ingredient_note', 'amount', 'order', 'checked', 'recipe_mealplan',
'created_by', 'created_at', 'completed_at', 'delay_until'
)
read_only_fields = ('id', 'created_by', 'created_at',)
# TODO deprecate
class ShoppingListEntryCheckedSerializer(serializers.ModelSerializer):
class Meta:
model = ShoppingListEntry
fields = ('id', 'checked')
# TODO deprecate
class ShoppingListSerializer(WritableNestedModelSerializer):
recipes = ShoppingListRecipeSerializer(many=True, allow_null=True)
entries = ShoppingListEntrySerializer(many=True, allow_null=True)
shared = UserNameSerializer(many=True)
supermarket = SupermarketSerializer(allow_null=True)
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
validated_data['created_by'] = self.context['request'].user
return super().create(validated_data)
class Meta:
model = ShoppingList
fields = (
'id', 'uuid', 'note', 'recipes', 'entries',
'shared', 'finished', 'supermarket', 'created_by', 'created_at'
)
read_only_fields = ('id', 'created_by',)
# TODO deprecate
class ShoppingListAutoSyncSerializer(WritableNestedModelSerializer):
entries = ShoppingListEntryCheckedSerializer(many=True, allow_null=True)
class Meta:
model = ShoppingList
fields = ('id', 'entries',)
read_only_fields = ('id',)
class ShareLinkSerializer(SpacedModelSerializer):
class Meta:
model = ShareLink
fields = '__all__'
class CookLogSerializer(serializers.ModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = CookLog
fields = ('id', 'recipe', 'servings', 'rating', 'created_by', 'created_at')
read_only_fields = ('id', 'created_by')
class ViewLogSerializer(serializers.ModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = ViewLog
fields = ('id', 'recipe', 'created_by', 'created_at')
read_only_fields = ('created_by',)
class ImportLogSerializer(serializers.ModelSerializer):
keyword = KeywordSerializer(read_only=True)
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = ImportLog
fields = (
'id', 'type', 'msg', 'running', 'keyword', 'total_recipes', 'imported_recipes', 'created_by', 'created_at')
read_only_fields = ('created_by',)
class AutomationSerializer(serializers.ModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = Automation
fields = (
'id', 'type', 'name', 'description', 'param_1', 'param_2', 'param_3', 'disabled', 'created_by',)
read_only_fields = ('created_by',)
# CORS, REST and Scopes aren't currently working
# Scopes are evaluating before REST has authenticated the user assiging a None space
# I've made the change below to fix the bookmarklet, other serializers likely need a similar/better fix
class BookmarkletImportSerializer(serializers.ModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].user.userpreference.space
return super().create(validated_data)
class Meta:
model = BookmarkletImport
fields = ('id', 'url', 'html', 'created_by', 'created_at')
read_only_fields = ('created_by', 'space')
# Export/Import Serializers
class KeywordExportSerializer(KeywordSerializer):
class Meta:
model = Keyword
fields = ('name', 'icon', 'description', 'created_at', 'updated_at')
class NutritionInformationExportSerializer(NutritionInformationSerializer):
class Meta:
model = NutritionInformation
fields = ('carbohydrates', 'fats', 'proteins', 'calories', 'source')
class SupermarketCategoryExportSerializer(SupermarketCategorySerializer):
class Meta:
model = SupermarketCategory
fields = ('name',)
class UnitExportSerializer(UnitSerializer):
class Meta:
model = Unit
fields = ('name', 'description')
class FoodExportSerializer(FoodSerializer):
supermarket_category = SupermarketCategoryExportSerializer(allow_null=True, required=False)
class Meta:
model = Food
fields = ('name', 'ignore_shopping', 'supermarket_category', 'on_hand')
class IngredientExportSerializer(WritableNestedModelSerializer):
food = FoodExportSerializer(allow_null=True)
unit = UnitExportSerializer(allow_null=True)
amount = CustomDecimalField()
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = Ingredient
fields = ('food', 'unit', 'amount', 'note', 'order', 'is_header', 'no_amount')
class StepExportSerializer(WritableNestedModelSerializer):
ingredients = IngredientExportSerializer(many=True)
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = Step
fields = ('name', 'type', 'instruction', 'ingredients', 'time', 'order', 'show_as_header')
class RecipeExportSerializer(WritableNestedModelSerializer):
nutrition = NutritionInformationSerializer(allow_null=True, required=False)
steps = StepExportSerializer(many=True)
keywords = KeywordExportSerializer(many=True)
class Meta:
model = Recipe
fields = (
'name', 'description', 'keywords', 'steps', 'working_time',
'waiting_time', 'internal', 'nutrition', 'servings', 'servings_text',
)
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class RecipeShoppingUpdateSerializer(serializers.ModelSerializer):
list_recipe = serializers.IntegerField(write_only=True, allow_null=True, required=False, help_text=_("Existing shopping list to update"))
ingredients = serializers.IntegerField(write_only=True, allow_null=True, required=False, help_text=_(
"List of ingredient IDs from the recipe to add, if not provided all ingredients will be added."))
servings = serializers.IntegerField(default=1, write_only=True, allow_null=True, required=False, help_text=_("Providing a list_recipe ID and servings of 0 will delete that shopping list."))
class Meta:
model = Recipe
fields = ['id', 'list_recipe', 'ingredients', 'servings', ]
class FoodShoppingUpdateSerializer(serializers.ModelSerializer):
amount = serializers.IntegerField(write_only=True, allow_null=True, required=False, help_text=_("Amount of food to add to the shopping list"))
unit = serializers.IntegerField(write_only=True, allow_null=True, required=False, help_text=_("ID of unit to use for the shopping list"))
delete = serializers.ChoiceField(choices=['true'], write_only=True, allow_null=True, allow_blank=True, help_text=_("When set to true will delete all food from active shopping lists."))
class Meta:
model = Recipe
fields = ['id', 'amount', 'unit', 'delete', ]