Skip to content

Commit

Permalink
new commit
Browse files Browse the repository at this point in the history
  • Loading branch information
DominikDrabik committed Aug 26, 2024
1 parent 33248d1 commit 7cd0f91
Show file tree
Hide file tree
Showing 13 changed files with 237 additions and 5 deletions.
1 change: 1 addition & 0 deletions app/app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
'rest_framework.authtoken',
'drf_spectacular',
'user',
'recipe',
]

MIDDLEWARE = [
Expand Down
5 changes: 4 additions & 1 deletion app/app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@
path('admin/', admin.site.urls),
path('api/schema/', SpectacularAPIView.as_view(), name='api-schema'),
path('api/docs/', SpectacularSwaggerView.as_view(url_name='api-schema'), name='api-docs'),
path('api/user/', include('user.urls'))
path('api/user/', include('user.urls')),
path('api/recipe/', include('recipe.urls')),
]

# Here this includes all the urls which we have defined in the recipe urls and other files
3 changes: 2 additions & 1 deletion app/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ class UserAdmin(BaseUserAdmin):
)


admin.site.register(models.User, UserAdmin)
admin.site.register(models.User, UserAdmin)
admin.site.register(models.Recipe)
27 changes: 27 additions & 0 deletions app/core/migrations/0002_recipe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 3.2.25 on 2024-08-24 15:09

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


class Migration(migrations.Migration):

dependencies = [
('core', '0001_initial'),
]

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)),
('description', models.TextField(blank=True)),
('time_minutes', models.IntegerField()),
('price', models.DecimalField(decimal_places=2, max_digits=5)),
('link', models.CharField(blank=True, max_length=255)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
17 changes: 15 additions & 2 deletions app/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""
Database model
"""

from django.conf import settings
from django.db import models
from django.contrib.auth.models import (
AbstractBaseUser,
Expand Down Expand Up @@ -40,4 +40,17 @@ class User(AbstractBaseUser, PermissionsMixin):
# assigning the UserManager to objects
objects = UserManager()

USERNAME_FIELD = 'email'
USERNAME_FIELD = 'email'

class Recipe(models.Model):
"""Recipe model"""
# Here we use settings.AUTH_USER_MODEL to reference the user model in case we change the user model in the future this is a best practice
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
description = models.TextField(blank=True)
time_minutes = models.IntegerField()
price = models.DecimalField(max_digits=5, decimal_places=2)
link = models.CharField(max_length=255, blank=True)

def __str__(self):
return self.title
21 changes: 20 additions & 1 deletion app/core/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
"""
Test for models
"""
from decimal import Decimal

from django.test import TestCase
from django.contrib.auth import get_user_model

from core import models

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

Expand Down Expand Up @@ -42,4 +45,20 @@ def test_create_new_superuser(self):
"""Test creating a new superuser"""
user = get_user_model().objects.create_superuser('[email protected]', 'test123')
self.assertTrue(user.is_superuser)
self.assertTrue(user.is_staff)
self.assertTrue(user.is_staff)

def test_create_recipe(self):
"""Test creating a new recipe"""
user = get_user_model().objects.create_user(
'[email protected]',
'test123')
recipe = models.Recipe.objects.create(
user=user,
title='Sample Recipe',
time_minutes=5,
price=Decimal(
'5.00'),
description='Sample Description'
)
self.assertEqual(str(recipe), recipe.title)

Empty file added app/recipe/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions app/recipe/apps.py
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'
22 changes: 22 additions & 0 deletions app/recipe/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""
Serialiers for recipe APIs
"""

from rest_framework import serializers
from core.models import Recipe

class RecipeSerializer(serializers.ModelSerializer):
"""Serializer for recipe objects"""

# Here it tells django we want to use the model Recipe and the fields we want to use
class Meta:
model = Recipe
fields = ('id', 'title', 'time_minutes', 'price', 'link')
read_only_fields = ('id',)

class RecipeDetailSerializer(RecipeSerializer):
"""Serializer for recipe detail objects"""
# Here we are extending the RecipeSerializer and adding the extra fields
class Meta(RecipeSerializer.Meta):
fields = RecipeSerializer.Meta.fields + ('ingredients', 'tags')
read_only_fields = ('id',)
Empty file added app/recipe/tests/__init__.py
Empty file.
95 changes: 95 additions & 0 deletions app/recipe/tests/test_recipe_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""
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

RECIPE_URL = reverse('recipe:recipe-list')

def detail_url(recipe_id):
"""Return 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',
'time_minutes': 10,
'price': Decimal('5.00'),
'description': 'Sample description',
'link': 'https://sample.com/recipe'
}
defaults.update(params)

return Recipe.objects.create(user=user, **defaults)


class PublicRecipeAPITests(TestCase):
"""Test unauthenticated recipe API access"""

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

def test_auth_required(self):
"""Test that authentication is required"""
res = self.client.get(reverse('recipe:recipe-list'))
self.assertEqual(res.status_code, status.HTTP_401_UNAUTHORIZED)


class PrivateRecipeApiTests(TestCase):
"""Test authenticated recipe API access"""

def setUp(self):
self.client = APIClient()
self.user = get_user_model().objects.create_user(
'[email protected]',
'testpass')
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(RECIPE_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_recipes_limited_to_user(self):
"""Test retrieving recipes for user"""
other_user = get_user_model().objects.create_user(
'[email protected]',
'password123')
create_recipe(user=other_user)
recipe = create_recipe(user=self.user)

res = self.client.get(RECIPE_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 viewing a 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)
16 changes: 16 additions & 0 deletions app/recipe/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
URL mapping for the recipe app is defined in app/recipe/urls.py. The URL mapping is then imported into the main project urls.py file.
"""

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from recipe import views

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

app_name = 'recipe'

urlpatterns = [
path('', include(router.urls))
]
29 changes: 29 additions & 0 deletions app/recipe/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""
Views for the recipe APIs
"""

from rest_framework import viewsets
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated


from core.models import Recipe
from recipe import serializers

class RecipeViewSet(viewsets.ModelViewSet):
"""Manage recipes in the database"""
serializer_class = serializers.RecipeDetailSerializer
queryset = Recipe.objects.all()
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]

def get_queryset(self):
"""Retrieve the recipes for the authenticated user"""
return self.queryset.filter(user=self.request.user).order_by('-id')

def get_serializer_class(self):
"""Return appropriate serializer class"""
if self.action == 'list':
return serializers.RecipeSerializer

return self.serializer_class

0 comments on commit 7cd0f91

Please sign in to comment.