diff --git a/backend/api/custom_models/public_recipe_models.py b/backend/api/custom_models/public_recipe_models.py index be179b4..dd69202 100644 --- a/backend/api/custom_models/public_recipe_models.py +++ b/backend/api/custom_models/public_recipe_models.py @@ -3,6 +3,7 @@ from django.contrib.auth.models import User from django.db import models +from .favorites_model import Favorite from ..utils.unsplash import UnsplashAPI @@ -20,6 +21,12 @@ def __str__(self): class PublicRecipe(models.Model): uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) name = models.CharField(max_length=255) + favorite = models.OneToOneField( + Favorite, + on_delete=models.CASCADE, + related_name="public_recipe", + null=True, + ) image_url = models.URLField(max_length=500, blank=True) description = models.TextField() ingredients = models.ManyToManyField(Ingredient, related_name="ingredients") diff --git a/backend/api/custom_views/recipe_views.py b/backend/api/custom_views/recipe_views.py index ec1bcbf..8b7a30f 100644 --- a/backend/api/custom_views/recipe_views.py +++ b/backend/api/custom_views/recipe_views.py @@ -1,9 +1,13 @@ +import json + from django.db.models import Avg, Value, FloatField from django.db.models.functions import Coalesce from rest_framework import status, permissions from rest_framework.views import APIView from rest_framework.response import Response -from ..custom_models.public_recipe_models import PublicRecipe, Rating + +from ..custom_models.favorites_model import Favorite +from ..custom_models.public_recipe_models import PublicRecipe, Rating, Ingredient from ..serializers.recipe_serializers import RecipeSerializer @@ -77,3 +81,95 @@ def post(self, request, uuid): {"message": str(e)}, status=status.HTTP_400_BAD_REQUEST, ) + + +class RecipeCreateView(APIView): + permission_classes = [permissions.IsAuthenticated] + + def post(self, request, pk): + user = request.user + is_shared = request.data.get("is_shared", False) + + try: + favorite = Favorite.objects.get(user=user, id=pk) + except Favorite.DoesNotExist: + return Response( + { + "success": False, + "message": "Favorite recipe not found.", + }, + status=status.HTTP_404_NOT_FOUND, + ) + + if not is_shared: + # Unshare + if hasattr(favorite, "public_recipe"): + # Delete the public recipe if it exists and is_shared is False + favorite.public_recipe.delete() + return Response( + { + "success": True, + "message": "Public recipe deleted successfully.", + }, + status=status.HTTP_200_OK, + ) + else: + return Response( + { + "success": False, + "message": "Public recipe does not exist for this favorite.", + }, + status=status.HTTP_400_BAD_REQUEST, + ) + else: + # Share + if hasattr(favorite, "public_recipe"): + return Response( + { + "success": False, + "message": "Public recipe already exists for this favorite.", + }, + status=status.HTTP_400_BAD_REQUEST, + ) + else: + # Create the public recipe if it does not exist + ingredients = favorite.ingredients + recipe_steps = favorite.recipe + + if ingredients: + ingredients_data = json.loads(ingredients) + ingredient_names = [ + ingredient["ingredient"].lower() + for ingredient in ingredients_data + ] + + # Create or get ingredients + ingredient_objects = [] + for name in ingredient_names: + ingredient, created = Ingredient.objects.get_or_create( + name=name + ) + ingredient_objects.append(ingredient) + + instructions = "" + if recipe_steps: + instructions = "\n".join(json.loads(recipe_steps)) + + # Create the public recipe + public_recipe = PublicRecipe.objects.create( + favorite=favorite, + name=favorite.name, + description=favorite.name, + instructions=instructions, + ) + + # Add ingredients to the public recipe + public_recipe.ingredients.set(ingredient_objects) + + return Response( + { + "success": True, + "message": "Public recipe created successfully.", + }, + status=status.HTTP_201_CREATED, + ) diff --git a/backend/api/serializers/favorites_serializer.py b/backend/api/serializers/favorites_serializer.py index ea98d6e..c6eead9 100644 --- a/backend/api/serializers/favorites_serializer.py +++ b/backend/api/serializers/favorites_serializer.py @@ -3,7 +3,16 @@ class FavoriteSerializer(serializers.ModelSerializer): + is_shared = serializers.SerializerMethodField() + class Meta: model = Favorite - fields = ['id', 'user', 'name', 'ingredients', 'recipe', 'added_at'] - read_only_fields = ['id', 'user', 'added_at'] \ No newline at end of file + fields = ['id', 'user', 'name', 'ingredients', 'recipe', 'added_at', + "is_shared", + ] + read_only_fields = ['id', 'user', 'added_at', + "is_shared", + ] + + def get_is_shared(self, obj): + return hasattr(obj, "public_recipe") \ No newline at end of file diff --git a/backend/api/urls.py b/backend/api/urls.py index f2e11cc..a6de19e 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -1,7 +1,7 @@ from django.urls import path, include +from .custom_views.recipe_views import RecipeListView, RecipeDetailView, RecipeRateView, RecipeCreateView from .custom_views.background_views import RandomRecipeImageView -from .custom_views.recipe_views import RecipeListView, RecipeDetailView, RecipeRateView from .custom_views.user_profile_views import UserProfileView from .custom_views.favorites_view import FavoriteListView, FavoriteDeleteView from .custom_views.shopping_list_views import ShoppingListView @@ -15,6 +15,7 @@ path("email/", include("api.custom_urls.email_request_urls"), name="email-requests"), path('favorites/', FavoriteListView.as_view(), name='favorites-list'), path('favorites//', FavoriteDeleteView.as_view(), name='favorite-delete'), + path("favorites/share//", RecipeCreateView.as_view(), name="favorite-share"), path('shopping-list/', ShoppingListView.as_view(), name='shopping-list'), path("favorites//add-to-shopping-list/", AddToShoppingListView.as_view(), name="add-to-shopping-list"), path("generate/", RecipeGeneratorView.as_view(), name="recipe_generate"), diff --git a/frontend/src/components/Favorites.js b/frontend/src/components/Favorites.js index 8225486..5ee554c 100644 --- a/frontend/src/components/Favorites.js +++ b/frontend/src/components/Favorites.js @@ -40,6 +40,31 @@ const Favorites = () => { } }; + const handleTogglePublic = async (event) => { + try { + const { target } = event || {}, + { checked, dataset } = target || {}, + { id } = dataset || {}; + const response = await axios.post(`/api/favorites/share/${id}/`, { + is_shared: checked, + }); + // Update the state to reflect the new value + setFavorites((prevFavorites) => + prevFavorites.map((favorite) => + favorite.id === parseInt(id) + ? { ...favorite, is_shared: checked } + : favorite, + ), + ); + if (sessionStorage.getItem("recipemate__recipes")) { + // Clear the session storage if the user makes a recipe public + sessionStorage.removeItem("recipemate__recipes"); + } + showAlert(response.data.message, "success"); + } catch (error) { + showAlert("Failed to make the recipe public.", "error"); + } + }; return (
@@ -75,6 +100,15 @@ const Favorites = () => { > Add Ingredients to Shopping List + ))}