from decimal import Decimal from django.contrib.auth.models import User from drf_writable_nested import (UniqueFieldsMixin, WritableNestedModelSerializer) from rest_framework import serializers from rest_framework.exceptions import ValidationError from cookbook.models import (Comment, CookLog, Food, Ingredient, Keyword, MealPlan, MealType, NutritionInformation, Recipe, RecipeBook, RecipeBookEntry, RecipeImport, ShareLink, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Step, Storage, Sync, SyncLog, Unit, UserPreference, ViewLog, SupermarketCategory, Supermarket, SupermarketCategoryRelation) from cookbook.templatetags.custom_tags import markdown class CustomDecimalField(serializers.Field): """ Custom decimal field to normalize useless decimal places and allow commas as decimal separators """ def to_representation(self, value): if isinstance(value, Decimal): return value.normalize() else: return Decimal(value).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 UserNameSerializer(WritableNestedModelSerializer): username = serializers.SerializerMethodField('get_user_label') def get_user_label(self, obj): return obj.get_user_name() class Meta: model = User fields = ('id', 'username') class UserPreferenceSerializer(serializers.ModelSerializer): class Meta: model = UserPreference fields = ( 'user', 'theme', 'nav_color', 'default_unit', 'default_page', 'search_style', 'show_recent', 'plan_share', 'ingredient_decimals', 'comments' ) read_only_fields = ['user'] class StorageSerializer(serializers.ModelSerializer): class Meta: model = Storage fields = ( 'id', 'name', 'method', 'username', 'password', 'token', 'created_by' ) extra_kwargs = { 'password': {'write_only': True}, 'token': {'write_only': True}, } class SyncSerializer(serializers.ModelSerializer): class Meta: model = Sync fields = ( 'id', 'storage', 'path', 'active', 'last_checked', 'created_at', 'updated_at' ) class SyncLogSerializer(serializers.ModelSerializer): 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: model = Keyword fields = ( 'id', 'label', ) read_only_fields = ('id', 'label') class KeywordSerializer(UniqueFieldsMixin, serializers.ModelSerializer): label = serializers.SerializerMethodField('get_label') 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 obj, created = Keyword.objects.get_or_create(**validated_data) return obj class Meta: model = Keyword fields = ( 'id', 'name', 'icon', 'label', 'description', 'created_at', 'updated_at' ) read_only_fields = ('id',) class UnitSerializer(UniqueFieldsMixin, serializers.ModelSerializer): def create(self, validated_data): # since multi select tags dont have id's # duplicate names might be routed to create obj, created = Unit.objects.get_or_create(**validated_data) return obj class Meta: model = Unit fields = ('id', 'name', 'description') read_only_fields = ('id',) class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerializer): def create(self, validated_data): # since multi select tags dont have id's # duplicate names might be routed to create 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') class SupermarketCategoryRelationSerializer(serializers.ModelSerializer): category = SupermarketCategorySerializer() class Meta: model = SupermarketCategoryRelation fields = ('id', 'category', 'supermarket') class SupermarketSerializer(UniqueFieldsMixin, serializers.ModelSerializer): category_to_supermarket = SupermarketCategoryRelationSerializer(many=True, read_only=True) class Meta: model = Supermarket fields = ('id', 'name', 'category_to_supermarket') class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer): supermarket_category = SupermarketCategorySerializer(read_only=True) def create(self, validated_data): # since multi select tags dont have id's # duplicate names might be routed to create obj, created = Food.objects.get_or_create(**validated_data) return obj def update(self, instance, validated_data): return super(FoodSerializer, self).update(instance, validated_data) class Meta: model = Food fields = ('id', 'name', 'recipe', 'ignore_shopping', 'supermarket_category') class IngredientSerializer(WritableNestedModelSerializer): food = FoodSerializer(allow_null=True) unit = UnitSerializer(allow_null=True) amount = CustomDecimalField() class Meta: model = Ingredient fields = ( 'id', 'food', 'unit', 'amount', 'note', 'order', 'is_header', 'no_amount' ) class StepSerializer(WritableNestedModelSerializer): ingredients = IngredientSerializer(many=True) ingredients_markdown = serializers.SerializerMethodField('get_ingredients_markdown') ingredients_vue = serializers.SerializerMethodField('get_ingredients_vue') def get_ingredients_vue(self, obj): return obj.get_instruction_render() def get_ingredients_markdown(self, obj): return obj.get_instruction_render() class Meta: model = Step fields = ( 'id', 'name', 'type', 'instruction', 'ingredients', 'ingredients_markdown', 'ingredients_vue', 'time', 'order', 'show_as_header' ) # used for the import export. temporary workaround until that module is finally fixed class StepExportSerializer(WritableNestedModelSerializer): ingredients = IngredientSerializer(many=True) class Meta: model = Step fields = ( 'id', 'name', 'type', 'instruction', 'ingredients', 'time', 'order', 'show_as_header' ) class NutritionInformationSerializer(serializers.ModelSerializer): class Meta: model = NutritionInformation fields = ('carbohydrates', 'fats', 'proteins', 'calories', 'source') class RecipeOverviewSerializer(WritableNestedModelSerializer): keywords = KeywordLabelSerializer(many=True) class Meta: model = Recipe fields = ( 'id', 'name', 'description', 'image', 'keywords', 'working_time', 'waiting_time', 'created_by', 'created_at', 'updated_at', 'internal', 'servings', 'file_path' ) read_only_fields = ['image', 'created_by', 'created_at'] class RecipeSerializer(WritableNestedModelSerializer): nutrition = NutritionInformationSerializer(allow_null=True, required=False) steps = StepSerializer(many=True) keywords = KeywordSerializer(many=True) 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', ) read_only_fields = ['image', 'created_by', 'created_at'] def create(self, validated_data): validated_data['created_by'] = self.context['request'].user return super().create(validated_data) # used for the import export. temporary workaround until that module is finally fixed class RecipeExportSerializer(RecipeSerializer): steps = StepExportSerializer(many=True) class RecipeImageSerializer(WritableNestedModelSerializer): class Meta: model = Recipe fields = ['image', ] class RecipeImportSerializer(serializers.ModelSerializer): class Meta: model = RecipeImport fields = '__all__' class CommentSerializer(serializers.ModelSerializer): class Meta: model = Comment fields = '__all__' class RecipeBookSerializer(serializers.ModelSerializer): class Meta: model = RecipeBook fields = '__all__' read_only_fields = ['id', 'created_by'] class RecipeBookEntrySerializer(serializers.ModelSerializer): class Meta: model = RecipeBookEntry fields = '__all__' class MealTypeSerializer(serializers.ModelSerializer): class Meta: model = MealType fields = '__all__' class MealPlanSerializer(serializers.ModelSerializer): recipe_name = serializers.ReadOnlyField(source='recipe.name') meal_type_name = serializers.ReadOnlyField(source='meal_type.name') note_markdown = serializers.SerializerMethodField('get_note_markdown') servings = CustomDecimalField() def get_note_markdown(self, obj): return markdown(obj.note) class Meta: model = MealPlan fields = ( 'id', 'title', 'recipe', 'servings', 'note', 'note_markdown', 'date', 'meal_type', 'created_by', 'shared', 'recipe_name', 'meal_type_name' ) class ShoppingListRecipeSerializer(serializers.ModelSerializer): recipe_name = serializers.ReadOnlyField(source='recipe.name') servings = CustomDecimalField() class Meta: model = ShoppingListRecipe fields = ('id', 'recipe', 'recipe_name', 'servings') read_only_fields = ('id',) class ShoppingListEntrySerializer(WritableNestedModelSerializer): food = FoodSerializer(allow_null=True) unit = UnitSerializer(allow_null=True) amount = CustomDecimalField() class Meta: model = ShoppingListEntry fields = ( 'id', 'list_recipe', 'food', 'unit', 'amount', 'order', 'checked' ) class ShoppingListEntryCheckedSerializer(serializers.ModelSerializer): class Meta: model = ShoppingListEntry fields = ('id', 'checked') class ShoppingListSerializer(WritableNestedModelSerializer): recipes = ShoppingListRecipeSerializer(many=True, allow_null=True) entries = ShoppingListEntrySerializer(many=True, allow_null=True) shared = UserNameSerializer(many=True) class Meta: model = ShoppingList fields = ( 'id', 'uuid', 'note', 'recipes', 'entries', 'shared', 'finished', 'created_by', 'created_at' ) read_only_fields = ('id',) class ShoppingListAutoSyncSerializer(WritableNestedModelSerializer): entries = ShoppingListEntryCheckedSerializer(many=True, allow_null=True) class Meta: model = ShoppingList fields = ('id', 'entries',) read_only_fields = ('id',) class ShareLinkSerializer(serializers.ModelSerializer): class Meta: model = ShareLink fields = '__all__' class CookLogSerializer(serializers.ModelSerializer): def create(self, validated_data): # TODO make mixin validated_data['created_by'] = self.context['request'].user return super().create(validated_data) class Meta: model = CookLog fields = '__all__' read_only_fields = ('id', 'created_by') class ViewLogSerializer(serializers.ModelSerializer): class Meta: model = ViewLog fields = '__all__'