We ll create a another table called Ingredients.
It will be exactly similar to Tags model
1) Add Ingredient Model
# TEST
# sample user for the tests
def sample_user(email="test@test.com", password="testpass"):
return get_user_model().objects.create(email=email, password=password)
class IngredientModelTests(TestCase):
# test the tag usage
def test_ingredient_str(self):
# only include mandatory fields here
ingredient = models.Ingredient.objects.create(
user=sample_user(),
name="Vegan"
)
self.assertEqual(str(ingredient), ingredient.name)
# MODEL
class Ingredient(models.Model):
""" Ingredient to be used in a recipe"""
name = models.CharField(max_length=255)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
def __str__(self):
return self.name
2) Make migrations
3) Register model in admin.py
/api/recepi/ingredients should allow creation and retrieval of list of ingredients pertaining to authenticated user
The tests are eactly similar to tests of Tags API
from django.contrib.auth import get_user_model
from django.urls import reverse
from django.test import TestCase
from rest_framework import status
from rest_framework.test import APIClient
from core.models import Ingredient
from recipe.serializers import IngredientSerializer
INGREDIENT_URL = reverse("recipe:ingredient-list")
class PublicIngredientsApiTests(TestCase):
""" Test publicly available ingredients API"""
def setUp(self):
self.client = APIClient()
# test if ingredients api required authentication
def test_login_required(self):
res = self.client.get(INGREDIENT_URL)
self.assertEqual(res.status_code, status.HTTP_401_UNAUTHORIZED)
class PrivateIngredientsApiTest(TestCase):
""" Test private ingredients api"""
def setUp(self):
self.user = get_user_model().objects.create_user(
"test@test.com",
"testpass"
)
self.client = APIClient()
self.client.force_authenticate(self.user)
# test if ingredients can be retrieved
def test_retrieve_ingredients_list(self):
Ingredient.objects.create(user=self.user, name='Kale')
Ingredient.objects.create(user=self.user, name='Salt')
res = self.client.get(INGREDIENT_URL)
ingredients = Ingredient.objects.all().order_by('-name')
serializer = IngredientSerializer(ingredients, many=True)
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertEqual(res.data, serializer.data)
# test if ingredients returned are limited to the auth user
def test_ingredients_limited_to_user(self):
user2 = get_user_model().objects.create_user(
"other@test.com",
"testpass"
)
ingred = Ingredient.objects.create(user=self.user, name="auth user")
Ingredient.objects.create(user=user2, name="other user")
res = self.client.get(INGREDIENT_URL)
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertEqual(len(res.data), 1)
self.assertEqual(res.data[0]['name'], ingred.name)
# test ingredient creation api
def test_create_ingredient_successful(self):
payload = {'name': 'cucumber'}
res = self.client.post(INGREDIENT_URL, payload)
self.assertEqual(res.status_code, status.HTTP_201_CREATED)
exists = Ingredient.objects.filter(
user=self.user,
name=payload['name']
).exists()
self.assertTrue(exists)
# test if invalid ingredients are not created
def test_create_ingredient_invalid(self):
res = self.client.post(INGREDIENT_URL, {'name': ''})
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
class IngredientSerializer(serializers.ModelSerializer):
""" Serializer for ingredient object"""
class Meta:
model = Ingredient
fields = ('id', 'name')
read_only_fields = ('id',)
This view also looks similar to TagsViewSet. So we create a common base class
from rest_framework import viewsets, mixins
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from core.models import Tag, Ingredient
from recipe import serializers
# viewset is used when a separate url is mapped to this view for each user
# /api/recepi/tags/ : mapped separately for each user
class BaseRecepiAttr(viewsets.GenericViewSet,
mixins.ListModelMixin,
mixins.CreateModelMixin):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
# abstract attributes
@property
def serializer_class(self):
raise NotImplementedError
# ListModelMixin returns a list of objects when GET is called
@property
def queryset(self):
raise NotImplementedError
# This method should be overriden
# if we dont want to modify query set based on current instance attributes
def get_queryset(self):
return self.queryset.filter(user=self.request.user).order_by('-name')
# override this method for CreateModelMixin
# create operation is done here (unlike in UserModelSerializer)
# because serializer can not have user
# we pass user to serializer and save it
def perform_create(self, serializer):
serializer.save(user=self.request.user)
# This class looks almost similar to previous class.
# Its better to write a common base class
# /api/recepi/ingredients/ : mapped separately for each user
class IngredientViewSet(BaseRecepiAttr):
serializer_class = serializers.IngredientSerializer
queryset = Ingredient.objects.all()
router.register('ingredients', views.IngredientViewSet)
# reverse url is 'recepi:ingredient-list'