From 7b3f06c292079291100bc851f936e79167e7ce70 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Sat, 30 Nov 2024 16:02:46 -0500 Subject: [PATCH 1/8] Added RecipeCreateView --- backend/api/custom_views/recipe_views.py | 61 +++++++++++++++++++++++- backend/api/urls.py | 3 +- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/backend/api/custom_views/recipe_views.py b/backend/api/custom_views/recipe_views.py index ce11fa1..21997a4 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 @@ -65,3 +69,58 @@ 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 + + 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, + ) + + 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( + 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/urls.py b/backend/api/urls.py index 3aa4723..9d191dc 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -1,6 +1,6 @@ from django.urls import path, include -from .custom_views.recipe_views import RecipeListView, RecipeDetailView, RecipeRateView +from .custom_views.recipe_views import RecipeListView, RecipeDetailView, RecipeRateView, RecipeCreateView from .custom_views.user_profile_views import UserProfileView from .custom_views.favorites_view import FavoriteListView, FavoriteDeleteView from .custom_views.shopping_list_views import ShoppingListView @@ -14,6 +14,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"), From f25e22e01665a437b0c1f794dae1ad99e8e6c5a8 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Sat, 30 Nov 2024 16:03:35 -0500 Subject: [PATCH 2/8] Updated Favorites.js to have a checkbox for publicize a favorite --- frontend/src/components/Favorites.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/frontend/src/components/Favorites.js b/frontend/src/components/Favorites.js index 8225486..c3ece04 100644 --- a/frontend/src/components/Favorites.js +++ b/frontend/src/components/Favorites.js @@ -40,6 +40,16 @@ const Favorites = () => { } }; + const handleTogglePublic = async (id, isPublic) => { + if (isPublic) { + try { + const response = await axios.post(`/api/favorites/share/${id}/`); + showAlert(response.data.message, "success"); + } catch (error) { + showAlert("Failed to make the recipe public.", "error"); + } + } + }; return (
@@ -75,6 +85,13 @@ const Favorites = () => { > Add Ingredients to Shopping List + ))} From 0167b79dd135aa93b006eb37cb0547762634e956 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Sat, 30 Nov 2024 16:31:02 -0500 Subject: [PATCH 3/8] Updated recipe_views.py --- backend/api/custom_views/recipe_views.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/backend/api/custom_views/recipe_views.py b/backend/api/custom_views/recipe_views.py index 21997a4..4adcdd1 100644 --- a/backend/api/custom_views/recipe_views.py +++ b/backend/api/custom_views/recipe_views.py @@ -88,6 +88,15 @@ def post(self, request, pk): status=status.HTTP_404_NOT_FOUND, ) + if hasattr(favorite, "public_recipe"): + return Response( + { + "success": False, + "message": "Public recipe already exists for this favorite.", + }, + status=status.HTTP_400_BAD_REQUEST, + ) + ingredients = favorite.ingredients recipe_steps = favorite.recipe @@ -109,6 +118,7 @@ def post(self, request, pk): # Create the public recipe public_recipe = PublicRecipe.objects.create( + favorite=favorite, name=favorite.name, description=favorite.name, instructions=instructions, From 4b367605ac6328d72f8d4eb89c3eb2662418a550 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Sat, 30 Nov 2024 16:31:20 -0500 Subject: [PATCH 4/8] Added favorite as a foreign key on Public Recipe --- backend/api/custom_models/public_recipe_models.py | 7 +++++++ 1 file changed, 7 insertions(+) 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") From 9b8d530d0d5516da5c4aa8ab3efb2fd7591c700a Mon Sep 17 00:00:00 2001 From: Sean Li Date: Sat, 30 Nov 2024 16:31:40 -0500 Subject: [PATCH 5/8] Added is_shared on Favorite model --- backend/api/serializers/favorites_serializer.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) 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 From b11619de983691c2e412c6d05206d2a4e774ac68 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Sat, 30 Nov 2024 16:54:58 -0500 Subject: [PATCH 6/8] Added logic for handling adding and deleting public recipes --- backend/api/custom_views/recipe_views.py | 119 ++++++++++++++--------- 1 file changed, 73 insertions(+), 46 deletions(-) diff --git a/backend/api/custom_views/recipe_views.py b/backend/api/custom_views/recipe_views.py index 4adcdd1..45959c3 100644 --- a/backend/api/custom_views/recipe_views.py +++ b/backend/api/custom_views/recipe_views.py @@ -76,6 +76,7 @@ class RecipeCreateView(APIView): 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) @@ -88,49 +89,75 @@ def post(self, request, pk): status=status.HTTP_404_NOT_FOUND, ) - if hasattr(favorite, "public_recipe"): - return Response( - { - "success": False, - "message": "Public recipe already exists for this favorite.", - }, - status=status.HTTP_400_BAD_REQUEST, - ) - - 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, - ) + 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, + ) From b28ecf1d64f3dca7c2d5b40e753dbf7b98131140 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Sat, 30 Nov 2024 16:55:06 -0500 Subject: [PATCH 7/8] Added logic for handling adding and deleting public recipes --- frontend/src/components/Favorites.js | 31 ++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/Favorites.js b/frontend/src/components/Favorites.js index c3ece04..9cc5043 100644 --- a/frontend/src/components/Favorites.js +++ b/frontend/src/components/Favorites.js @@ -40,14 +40,25 @@ const Favorites = () => { } }; - const handleTogglePublic = async (id, isPublic) => { - if (isPublic) { - try { - const response = await axios.post(`/api/favorites/share/${id}/`); - showAlert(response.data.message, "success"); - } catch (error) { - showAlert("Failed to make the recipe public.", "error"); - } + 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, + ), + ); + showAlert(response.data.message, "success"); + } catch (error) { + showAlert("Failed to make the recipe public.", "error"); } }; @@ -88,7 +99,9 @@ const Favorites = () => { From 22c33c7380ff00a5d9d2b2ffd558c73f8a8dff51 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Sat, 30 Nov 2024 17:05:06 -0500 Subject: [PATCH 8/8] Cleared session --- frontend/src/components/Favorites.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/components/Favorites.js b/frontend/src/components/Favorites.js index 9cc5043..5ee554c 100644 --- a/frontend/src/components/Favorites.js +++ b/frontend/src/components/Favorites.js @@ -56,6 +56,10 @@ const Favorites = () => { : 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");