Skip to content

Commit

Permalink
implemented Tag model and Endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
nishu-saini committed Nov 4, 2023
1 parent 1d8ac7b commit e216fa4
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 9 deletions.
3 changes: 2 additions & 1 deletion app/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,5 @@ class UserAdmin(BaseUserAdmin):


admin.site.register(models.User, UserAdmin)
admin.site.register(models.Recipe)
admin.site.register(models.Recipe)
admin.site.register(models.Tag)
28 changes: 28 additions & 0 deletions app/core/migrations/0003_auto_20231104_1144.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 3.2.23 on 2023-11-04 11:44

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('core', '0002_recipe'),
]

operations = [
migrations.CreateModel(
name='Tag',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='recipe',
name='tags',
field=models.ManyToManyField(to='core.Tag'),
),
]
14 changes: 13 additions & 1 deletion app/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,20 @@ class Recipe(models.Model):
time_minutes = models.IntegerField()
price = models.DecimalField(max_digits=5, decimal_places=2)
link = models.CharField(max_length=255, blank=True)

tags = models.ManyToManyField('tag')

def __str__(self):
return self.title


class Tag(models.Model):
"""Tag for filtering recipe"""
name = models.CharField(max_length=255)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)

def __str__(self) -> str:
return self.name

16 changes: 12 additions & 4 deletions app/core/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
from core import models


def create_user(email='user@example.com', password='testpass123'):
"""Create and return a new user"""
return get_user_model().objects.create_user(email, password)


class ModelTests(TestCase):
"""Test models.."""

Expand All @@ -25,7 +30,6 @@ def test_create_user_with_email_successful(self):
self.assertEqual(user.email, email)
self.assertTrue(user.check_password(password))


def test_new_user_email_normalized(self):
"""Test email is normalised for new users"""
sample_emails = [
Expand All @@ -39,13 +43,11 @@ def test_new_user_email_normalized(self):
user = get_user_model().objects.create_user(email=email, password='sample123')
self.assertEqual(user.email, expected)


def test_new_user_without_email_raises_error(self):
"""Test that creating a user without an email raises a ValueError"""
with self.assertRaises(ValueError):
get_user_model().objects.create_user('', 'test123')


def test_create_superuser(self):
"""Test creating a superuser"""
user = get_user_model().objects.create_superuser(
Expand All @@ -56,7 +58,6 @@ def test_create_superuser(self):
self.assertTrue(user.is_superuser)
self.assertTrue(user.is_staff)


def test_create_recipe(self):
"""Test creating a recipe is successful"""
user = get_user_model().objects.create_user(
Expand All @@ -73,3 +74,10 @@ def test_create_recipe(self):
)

self.assertEqual(str(recipe), recipe.title)

def test_create_tag(self):
"""Test creating a tag is successful"""
user = create_user()
tag = models.Tag.objects.create(user=user, name='tag1')

self.assertEqual(str(tag), tag.name)
14 changes: 13 additions & 1 deletion app/recipe/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
"""
from rest_framework import serializers

from core.models import Recipe
from core.models import (
Recipe,
Tag
)


class RecipeSerializer(serializers.ModelSerializer):
Expand All @@ -21,3 +24,12 @@ class RecipeDetailSerializer(RecipeSerializer):
class Meta(RecipeSerializer.Meta):
fields = RecipeSerializer.Meta.fields + ['description']


class TagSerializer(serializers.ModelSerializer):
"""Serializer for tags"""

class Meta:
model = Tag
fields = ['id', 'name']
read_only_fields = ['id']

72 changes: 72 additions & 0 deletions app/recipe/tests/test_tags_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""
Tests for the 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 Tag

from recipe.serializers import TagSerializer


TAGS_URL = reverse('recipe:tag-list')


def create_user(email='user@example.com', password='testpass123'):
"""Create and return a user"""
return get_user_model().objects.create_user(email=email, password=password)


class PublicTagsApiTests(TestCase):
"""Test unauthenticated API requests"""

def setUp(self):
self.client = APIClient()

def test_auth_required(self):
"""Test auth is required for retrieving tags"""
res = self.client.get(TAGS_URL)

self.assertEqual(res.status_code, status.HTTP_401_UNAUTHORIZED)


class PrivateTagsApiTests(TestCase):
"""Test authenticated API requests"""

def setUp(self):
self.user = create_user()
self.client = APIClient()
self.client.force_authenticate(self.user)

def test_retrieve_tags(self):
"""Test retrieving a list of tags"""
Tag.objects.create(user=self.user, name='Vegan')
Tag.objects.create(user=self.user, name='Dessert')

res = self.client.get(TAGS_URL)

tags = Tag.objects.all().order_by('-name')
serializer = TagSerializer(tags, many=True)

self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertEqual(res.data, serializer.data)

def test_tags_limited_to_user(self):
"""Test list of tags is limited to authenticated user"""
user2 = create_user(email='user2@example.com')
Tag.objects.create(user=user2, name='Fruity')
tag = Tag.objects.create(user=self.user, name='Comfort Food')

res = self.client.get(TAGS_URL)

self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertEqual(len(res.data), 1)
self.assertEqual(res.data[0]['name'], tag.name)
self.assertEqual(res.data[0]['id'], tag.id)



1 change: 1 addition & 0 deletions app/recipe/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

router = DefaultRouter()
router.register('recipes', views.RecipeViewSet)
router.register('tags', views.TagViewSet)

app_name = 'recipe'

Expand Down
19 changes: 17 additions & 2 deletions app/recipe/views.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
"""
Views for the recipe APIs
"""
from rest_framework import viewsets
from rest_framework import (
viewsets,
mixins
)
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated

from core.models import Recipe
from core.models import (
Recipe,
Tag
)
from recipe import serializers


Expand Down Expand Up @@ -34,5 +40,14 @@ def perform_create(self, serializer):
serializer.save(user=self.request.user)


class TagViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
"""Manage tags in the database"""
serializer_class = serializers.TagSerializer
queryset = Tag.objects.all()
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]

def get_queryset(self):
"""Filter query to authenticated user"""
return self.queryset.filter(user=self.request.user).order_by('-name')

0 comments on commit e216fa4

Please sign in to comment.