Welcome back! So far, you have learned how to build API endpoints that allow users to retrieve recipes, search by ingredients, and view recipe steps. These features are essential for any recipe app; however, users sometimes desire more variety or guidance. For example, what if a user cannot decide what to cook and wants a random suggestion? Or perhaps they want to see the most popular recipes based on ratings from other users.
In this lesson, you will learn how to add two new features to your cooking helper API:
- An endpoint that returns a random recipe
- An endpoint that returns the most popular recipes based on user ratings
These features make your app more interactive and helpful, providing users with new ways to discover recipes.
Before we proceed, let us briefly review how we have been working with recipes so far.
Previously, you built endpoints to perform the following tasks:
- List all
recipeswith pagination. - Retrieve a single
recipeby itsID. - Search for
recipesbyingredients.
All these endpoints follow a standard pattern: we map a URL path to a view function in urls.py and, within that function, we use QuerySets to interact with the database. You have also used helper functions like serialize_recipe to convert database objects into dictionaries, which are then returned as a JsonResponse.
Now, let us build upon these skills to add dynamic selection features.
Let us start by creating an endpoint that returns a random recipe. This feature is useful for users who want to try something new or cannot decide what to cook.
First, we must define a new route. Unlike previous lessons where we used parameters like <int:recipe_id>, this path is static. In cooking_helper/recipes/urls.py, we add:
Then, in views.py, we define the function. Every view function must accept a request object as its first argument.
To select a random recipe from the database, we use a special ordering character: the question mark "?". When we apply .order_by("?") to a QuerySet, the database shuffles the records.
Recipe.objects.order_by("?")shuffles the order randomly..prefetch_related("ingredients")ensures we efficiently load the ingredient data..first()picks the firstrecipefrom the shuffled list.
If there are no recipes in the database, the recipe variable will be None.
Now, let us check if we found a recipe. If the database is empty, we return a 404 error using a helper function like _json_error. If a recipe is found, we serialize it and return it using JsonResponse.
Here is how the route and the view function look together:
cooking_helper/recipes/urls.py
cooking_helper/recipes/views.py
Example Output:
Next, let us create an endpoint that returns the most popular recipes. We will define "popular" as recipes with the highest average user ratings.
We will add a GET endpoint at /api/recipes/popular in our urls.py configuration.
To find popular recipes, we must calculate the average rating for each recipe. We use the annotate() method combined with the Avg aggregation function to add a virtual field called avg_rating to our QuerySet.
This tells the database to look at the related reviews, calculate the average of their rating fields, and attach that value to each recipe.
We only want recipes that actually have ratings, and we want the top 10 highest-rated ones.
.filter(avg_rating__isnull=False)excludesrecipeswith no reviews..order_by("-avg_rating")sorts the results in descending order (highest rating first).[:10]limits the results to the top 10 items.
Since we are returning a list of objects, we must iterate through the recipes results, prepare the data, and use JsonResponse with safe=False.
Here is the complete implementation for the popular recipes feature:
cooking_helper/recipes/views.py
Example Output:
In this lesson, you learned how to add two new endpoints to your recipe API:
- One for serving a
random recipeusing.order_by("?")to help users discover new dishes. - One for listing the most popular recipes using
.annotate()andAvgto rank dishes by user feedback.
These features make your app more dynamic and user-friendly. You are now ready to practice building these endpoints in the exercises that follow!
