-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1fb6f1f
commit 600c340
Showing
15 changed files
with
370 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,6 +43,7 @@ | |
'rest_framework.authtoken', | ||
'drf_spectacular', | ||
'user', | ||
'recipe' | ||
] | ||
|
||
MIDDLEWARE = [ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Generated by Django 3.2.25 on 2024-07-18 20:12 | ||
|
||
from django.db import migrations | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('core', '0001_initial'), | ||
] | ||
|
||
operations = [ | ||
migrations.RemoveField( | ||
model_name='user', | ||
name='first_name', | ||
), | ||
migrations.RemoveField( | ||
model_name='user', | ||
name='last_name', | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Generated by Django 3.2.25 on 2024-07-19 13:37 | ||
|
||
from django.conf import settings | ||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('core', '0002_auto_20240718_2012'), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='Recipe', | ||
fields=[ | ||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('title', models.CharField(max_length=255)), | ||
('price', models.DecimalField(decimal_places=2, max_digits=5)), | ||
('description', models.TextField(blank=True)), | ||
('link', models.CharField(blank=True, max_length=255)), | ||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), | ||
], | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Generated by Django 3.2.25 on 2024-07-19 16:07 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('core', '0003_recipe'), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name='recipe', | ||
name='time_minutes', | ||
field=models.IntegerField(default=0), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class RecipeConfig(AppConfig): | ||
default_auto_field = 'django.db.models.BigAutoField' | ||
name = 'recipe' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
""" | ||
Serializers for recipe APIs | ||
""" | ||
from rest_framework import serializers | ||
|
||
from core.models import Recipe | ||
|
||
|
||
class RecipeSerializer(serializers.ModelSerializer): | ||
"""Serializer for recipes.""" | ||
|
||
class Meta: | ||
model = Recipe | ||
fields = ['id', 'title', 'time_minutes', 'price', 'link'] | ||
read_only_fields = ['id'] | ||
|
||
|
||
class RecipeDetailSerializer(RecipeSerializer): | ||
"""Serializer for recipe detail view.""" | ||
|
||
class Meta(RecipeSerializer.Meta): | ||
fields = RecipeSerializer.Meta.fields + ['description'] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
""" | ||
Tests for recipe APIs. | ||
""" | ||
from decimal import Decimal | ||
|
||
from django.contrib.auth import get_user_model | ||
from django.test import TestCase | ||
from django.urls import reverse | ||
|
||
from rest_framework import status | ||
from rest_framework.test import APIClient | ||
|
||
from core.models import Recipe | ||
|
||
from recipe.serializers import ( | ||
RecipeSerializer, | ||
RecipeDetailSerializer | ||
) | ||
|
||
|
||
RECIPES_URL = reverse('recipe:recipe-list') | ||
|
||
def detail_url(recipe_id): | ||
"""Create and return a recipe detail URL""" | ||
return reverse('recipe:recipe-detail', args=[recipe_id]) | ||
|
||
def create_recipe(user, **params): | ||
"""Create and return a sample recipe.""" | ||
defaults = { | ||
'title': 'Sample recipe title', | ||
'time_minutes': 22, | ||
'price': Decimal('5.25'), | ||
'description': 'Sample description', | ||
'link': 'http://example.com/recipe.pdf', | ||
} | ||
defaults.update(params) | ||
|
||
recipe = Recipe.objects.create(user=user, **defaults) | ||
return recipe | ||
|
||
|
||
class PublicRecipeAPITests(TestCase): | ||
"""Test unauthenticated API requests.""" | ||
|
||
def setUp(self): | ||
self.client = APIClient() | ||
self.user = create_user(email='user@example.com', password='test123') | ||
|
||
def test_auth_required(self): | ||
"""Test auth is required to call API.""" | ||
res = self.client.get(RECIPES_URL) | ||
|
||
self.assertEqual(res.status_code, status.HTTP_401_UNAUTHORIZED) | ||
|
||
def create_user(**params): | ||
"""Create and return a new user.""" | ||
return get_user_model().objects.create_user(**params) | ||
|
||
class PrivateRecipeApiTests(TestCase): | ||
"""Test authenticated API requests.""" | ||
|
||
def setUp(self): | ||
self.client = APIClient() | ||
self.user = get_user_model().objects.create_user( | ||
'user@example.com', | ||
'testpass123', | ||
) | ||
self.client.force_authenticate(self.user) | ||
|
||
def test_retrieve_recipes(self): | ||
"""Test retrieving a list of recipes.""" | ||
create_recipe(user=self.user) | ||
create_recipe(user=self.user) | ||
|
||
res = self.client.get(RECIPES_URL) | ||
|
||
recipes = Recipe.objects.all().order_by('-id') | ||
serializer = RecipeSerializer(recipes, many=True) | ||
self.assertEqual(res.status_code, status.HTTP_200_OK) | ||
self.assertEqual(res.data, serializer.data) | ||
|
||
def test_recipe_list_limited_to_user(self): | ||
"""Test list of recipes is limited to authenticated user.""" | ||
other_user = create_user(email = 'other@example.com',password = 'password123') | ||
create_recipe(user=other_user) | ||
create_recipe(user=self.user) | ||
|
||
res = self.client.get(RECIPES_URL) | ||
|
||
recipes = Recipe.objects.filter(user=self.user) | ||
serializer = RecipeSerializer(recipes, many=True) | ||
self.assertEqual(res.status_code, status.HTTP_200_OK) | ||
self.assertEqual(res.data, serializer.data) | ||
|
||
def test_get_recipe_detail(self): | ||
"""Test get recipe detail.""" | ||
recipe = create_recipe(user=self.user) | ||
|
||
url = detail_url(recipe.id) | ||
res = self.client.get(url) | ||
|
||
serializer = RecipeDetailSerializer(recipe) | ||
self.assertEqual(res.data, serializer.data) | ||
|
||
def test_create_recipe(self): | ||
"""Test create recipe.""" | ||
payload = { | ||
'title': 'Test Recipe', | ||
'time_minutes':30, | ||
'price': Decimal('5.99') | ||
} | ||
res = self.client.post(RECIPES_URL, payload) | ||
self.assertEqual(res.status_code,status.HTTP_201_CREATED) | ||
recipe = Recipe.objects.get(id=res.data['id']) | ||
for key, val in payload.items(): | ||
self.assertEqual(getattr(recipe,key),val) | ||
self.assertEqual(recipe.user,self.user) | ||
|
||
def test_partial_update(self): | ||
"""Test partial update of a recipe""" | ||
original_link = 'https://example.com/recipe.pdf' | ||
recipe = create_recipe( | ||
user = self.user, | ||
title = 'Sample recipe title', | ||
link = original_link, | ||
) | ||
|
||
payload = {'title': 'New Recipe Title'} | ||
url = detail_url(recipe.id) | ||
res = self.client.patch(url, payload) | ||
|
||
self.assertEqual(res.status_code, status.HTTP_200_OK) | ||
recipe.refresh_from_db() | ||
self.assertEqual(recipe.title, payload['title']) | ||
self.assertEqual(recipe.link, original_link) | ||
self.assertEqual(recipe.user, self.user) | ||
|
||
def test_full_update(self): | ||
"""Test full update of the recipe""" | ||
recipe = create_recipe( | ||
user = self.user, | ||
title = 'Recipe Before Changed', | ||
link = 'https://example.com/recipe.pdf', | ||
time_minutes = 30, | ||
description = 'Description before changed', | ||
price = Decimal('1.70') | ||
|
||
) | ||
url = detail_url(recipe.id) | ||
payload = { | ||
'title':'Recipe After Changed', | ||
'link': 'https://example.com/recipe_new.pdf', | ||
'time_minutes': 29, | ||
'description': 'Description after changed', | ||
'price': Decimal('2.50'), | ||
} | ||
res = self.client.put(url,payload) | ||
recipe.refresh_from_db() | ||
self.assertEqual(res.status_code, status.HTTP_200_OK) | ||
for key, val in payload.items(): | ||
self.assertEqual(getattr(recipe,key),val) | ||
self.assertEqual(recipe.user,self.user) | ||
|
||
def test_update_user_returns_error(self): | ||
"""Test changing the recipe user results in an error.""" | ||
new_user = create_user(email = 'test@example.com',password = 'password123') | ||
recipe = create_recipe( | ||
user = self.user, | ||
) | ||
payload = {'user': new_user.id} | ||
url = detail_url(recipe.id) | ||
self.client.patch(url,payload) | ||
|
||
recipe.refresh_from_db() | ||
self.assertEqual(recipe.user, self.user) | ||
|
||
def test_delete_recipe(self): | ||
"""Test deleting a recipe successful.""" | ||
recipe = create_recipe(user=self.user) | ||
|
||
url = detail_url(recipe.id) | ||
res = self.client.delete(url) | ||
|
||
self.assertEqual(res.status_code, status.HTTP_204_NO_CONTENT) | ||
self.assertFalse(Recipe.objects.filter(id=recipe.id).exists()) | ||
|
||
def test_recipe_other_users_recipe_error(self): | ||
"""Test trying to delete another users recipe gives error.""" | ||
new_user = create_user(email='user2@example.com', password='test123') | ||
recipe = create_recipe(user=new_user) | ||
|
||
url = detail_url(recipe.id) | ||
res = self.client.delete(url) | ||
|
||
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND) | ||
self.assertTrue(Recipe.objects.filter(id=recipe.id).exists()) |
Oops, something went wrong.