Skip to content

Commit

Permalink
Shows filter totals in browse sidebar (#137)
Browse files Browse the repository at this point in the history
* Rather than matching cuisine and course filters by title
  we're matching them by slug, which will be url-safer.
* No point in having a `?format=json` when we can just use
  the application type header
  • Loading branch information
hermzz authored and RyanNoelk committed Feb 20, 2017
1 parent 9ad8107 commit 7848432
Show file tree
Hide file tree
Showing 12 changed files with 344 additions and 63 deletions.
35 changes: 35 additions & 0 deletions api/v1/fixtures/test/course.json
@@ -0,0 +1,35 @@
[
{
"pk": 1,
"model": "recipe_groups.course",
"fields": {
"title": "Course 1",
"slug": "course-1",
"author": "1"
}
},{
"pk": 2,
"model": "recipe_groups.course",
"fields": {
"title": "Course 2",
"slug": "course-2",
"author": "1"
}
},{
"pk": 3,
"model": "recipe_groups.course",
"fields": {
"title": "Course 3",
"slug": "course-3",
"author": "1"
}
},{
"pk": 4,
"model": "recipe_groups.course",
"fields": {
"title": "Course 4",
"slug": "course-4",
"author": "1"
}
}
]
35 changes: 35 additions & 0 deletions api/v1/fixtures/test/cuisine.json
@@ -0,0 +1,35 @@
[
{
"pk": 1,
"model": "recipe_groups.cuisine",
"fields": {
"title": "Cuisine 1",
"slug": "cuisine-1",
"author": "1"
}
},{
"pk": 2,
"model": "recipe_groups.cuisine",
"fields": {
"title": "Cuisine 2",
"slug": "cuisine-2",
"author": "1"
}
},{
"pk": 3,
"model": "recipe_groups.cuisine",
"fields": {
"title": "Cuisine 3",
"slug": "cuisine-3",
"author": "1"
}
},{
"pk": 4,
"model": "recipe_groups.cuisine",
"fields": {
"title": "Cuisine 4",
"slug": "cuisine-4",
"author": "1"
}
}
]
104 changes: 104 additions & 0 deletions api/v1/fixtures/test/recipes.json
@@ -0,0 +1,104 @@
[
{
"pk": 1,
"model": "recipe.recipe",
"fields": {
"title": "Recipe 1",
"slug": "recipe-1",
"author": "1",
"cuisine": "1",
"course": "1",
"info": "Recipe info",
"prep_time": "1",
"cook_time": "1",
"servings": "1",
"pub_date": "2017-01-01 00:00:00",
"update_date": "2017-01-01 00:00:00"
}
},
{
"pk": 2,
"model": "recipe.recipe",
"fields": {
"title": "Recipe 2",
"slug": "recipe-2",
"author": "1",
"cuisine": "1",
"course": "2",
"info": "Recipe info",
"prep_time": "1",
"cook_time": "1",
"servings": "1",
"pub_date": "2017-01-01 00:00:00",
"update_date": "2017-01-01 00:00:00"
}
},
{
"pk": 3,
"model": "recipe.recipe",
"fields": {
"title": "Recipe 3",
"slug": "recipe-3",
"author": "1",
"cuisine": "2",
"course": "1",
"info": "Recipe info",
"prep_time": "1",
"cook_time": "1",
"servings": "1",
"pub_date": "2017-01-01 00:00:00",
"update_date": "2017-01-01 00:00:00"
}
},
{
"pk": 4,
"model": "recipe.recipe",
"fields": {
"title": "Recipe 4",
"slug": "recipe-4",
"author": "1",
"cuisine": "1",
"course": "3",
"info": "Recipe info",
"prep_time": "1",
"cook_time": "1",
"servings": "1",
"pub_date": "2017-01-01 00:00:00",
"update_date": "2017-01-01 00:00:00"
}
},
{
"pk": 5,
"model": "recipe.recipe",
"fields": {
"title": "Recipe 5",
"slug": "recipe-5",
"author": "1",
"cuisine": "2",
"course": "2",
"info": "Recipe info",
"prep_time": "1",
"cook_time": "1",
"servings": "1",
"pub_date": "2017-01-01 00:00:00",
"update_date": "2017-01-01 00:00:00"
}
},
{
"pk": 6,
"model": "recipe.recipe",
"fields": {
"title": "Recipe 6",
"slug": "recipe-6",
"author": "1",
"cuisine": "3",
"course": "1",
"info": "Recipe info",
"prep_time": "1",
"cook_time": "1",
"servings": "1",
"pub_date": "2017-01-01 00:00:00",
"update_date": "2017-01-01 00:00:00"
}
}
]
2 changes: 1 addition & 1 deletion api/v1/recipe/views.py
Expand Up @@ -19,7 +19,7 @@ class RecipeViewSet(viewsets.ModelViewSet):
serializer_class = serializers.RecipeSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
filter_backends = (filters.DjangoFilterBackend, filters.SearchFilter)
filter_fields = ('course__title', 'cuisine__title', 'course', 'cuisine', 'title')
filter_fields = ('course__slug', 'cuisine__slug', 'course', 'cuisine', 'title')
search_fields = ('title', 'tags__title')


Expand Down
6 changes: 0 additions & 6 deletions api/v1/recipe_groups/models.py
Expand Up @@ -26,9 +26,6 @@ class Meta:
def __unicode__(self):
return self.title

def recipe_count(self):
return self.recipe_set.filter(shared=0).count()


class Course(models.Model):
"""
Expand All @@ -48,9 +45,6 @@ class Meta:
def __unicode__(self):
return self.title

def recipe_count(self):
return self.recipe_set.filter(shared=0).count()


class Tag(models.Model):
"""
Expand Down
6 changes: 4 additions & 2 deletions api/v1/recipe_groups/serializers.py
Expand Up @@ -8,16 +8,18 @@

class CuisineSerializer(serializers.ModelSerializer):
""" Standard `rest_framework` ModelSerializer """
total = serializers.IntegerField(read_only=True)

class Meta:
model = Cuisine
exclude = ('slug',)


class CourseSerializer(serializers.ModelSerializer):
""" Standard `rest_framework` ModelSerializer """
total = serializers.IntegerField(read_only=True)

class Meta:
model = Course
exclude = ('slug',)


class TagSerializer(serializers.ModelSerializer):
Expand Down
78 changes: 78 additions & 0 deletions api/v1/recipe_groups/tests.py
@@ -0,0 +1,78 @@
#!/usr/bin/env python
# encoding: utf-8
from __future__ import unicode_literals
from django.test import TestCase
from rest_framework.test import APIRequestFactory
from v1.recipe_groups import views

class RecipeGroupsTests(TestCase):
fixtures = ['test/users.json', 'test/cuisine.json', 'test/course.json', 'test/recipes.json']

def setUp(self):
self.factory = APIRequestFactory()

def test_cuisine_all(self):
view = views.CuisineViewSet.as_view({'get': 'list'})
request = self.factory.get('/api/v1/recipe_groups/cuisine/')
response = view(request)

self.assertEqual(response.data.get('count'), 4)

results = response.data.get('results')
totals = {"cuisine-1": 3, "cuisine-2": 2, "cuisine-3": 1, "cuisine-4": 0}

for item in results:
self.assertEquals(totals[item.get('slug')], item.get('total'))

def test_course_all(self):
view = views.CourseViewSet.as_view({'get': 'list'})
request = self.factory.get('/api/v1/recipe_groups/course/')
response = view(request)

self.assertEqual(response.data.get('count'), 4)

results = response.data.get('results')
totals = {"course-1": 3, "course-2": 2, "course-3": 1, "course-4": 0}

for item in results:
self.assertEquals(totals[item.get('slug')], item.get('total'))

def test_cuisine_with_course_filter(self):
view = views.CuisineViewSet.as_view({'get': 'list'})
request = self.factory.get('/api/v1/recipe_groups/cuisine/?course=course-1')
response = view(request)

self.assertEqual(response.data.get('count'), 3)

results = response.data.get('results')
totals = {"cuisine-1": 1, "cuisine-2": 1, "cuisine-3": 1}

for item in results:
self.assertEquals(totals[item.get('slug')], item.get('total'))

def test_cuisine_with_course_filter_no_results(self):
view = views.CuisineViewSet.as_view({'get': 'list'})
request = self.factory.get('/api/v1/recipe_groups/cuisine/?course=course-4')
response = view(request)

self.assertEqual(response.data.get('count'), 0)

def test_course_with_cuisine_filter(self):
view = views.CourseViewSet.as_view({'get': 'list'})
request = self.factory.get('/api/v1/recipe_groups/course/?cuisine=cuisine-1')
response = view(request)

self.assertEqual(response.data.get('count'), 3)

results = response.data.get('results')
totals = {"course-1": 1, "course-2": 1, "course-3": 1}

for item in results:
self.assertEquals(totals[item.get('slug')], item.get('total'))

def test_cuisine_with_course_filter_no_results(self):
view = views.CourseViewSet.as_view({'get': 'list'})
request = self.factory.get('/api/v1/recipe_groups/course/?cuisine=cuisine-4')
response = view(request)

self.assertEqual(response.data.get('count'), 0)
4 changes: 2 additions & 2 deletions api/v1/recipe_groups/urls.py
Expand Up @@ -9,8 +9,8 @@

# Create a router and register our viewsets with it.
router = DefaultRouter(schema_title='recipe_groups')
router.register(r'cuisine', views.CuisineViewSet)
router.register(r'course', views.CourseViewSet)
router.register(r'cuisine', views.CuisineViewSet, base_name='cuisine')
router.register(r'course', views.CourseViewSet, base_name='course')
router.register(r'tag', views.TagViewSet)

urlpatterns = [
Expand Down
37 changes: 26 additions & 11 deletions api/v1/recipe_groups/views.py
Expand Up @@ -2,10 +2,9 @@
# encoding: utf-8
from __future__ import unicode_literals

from .models import Cuisine, Course, Tag
from .serializers import CuisineSerializer, \
CourseSerializer, \
TagSerializer
from django.db.models import Count
from v1.recipe_groups.models import Cuisine, Course, Tag
from v1.recipe_groups import serializers
from rest_framework import permissions
from rest_framework import viewsets
from v1.common.permissions import IsOwnerOrReadOnly
Expand All @@ -18,11 +17,19 @@ class CuisineViewSet(viewsets.ModelViewSet):
Uses `title` as the PK for any lookups.
"""
queryset = Cuisine.objects.all()
serializer_class = CuisineSerializer
serializer_class = serializers.CuisineSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly)
lookup_field = 'title'
lookup_field = 'slug'

def get_queryset(self):
query = Cuisine.objects

if 'course' in self.request.query_params:
course = Course.objects.get(slug=self.request.query_params.get('course'))
query = query.filter(recipe__course=course)

return query.annotate(total=Count('recipe', distinct=True))


class CourseViewSet(viewsets.ModelViewSet):
Expand All @@ -32,11 +39,19 @@ class CourseViewSet(viewsets.ModelViewSet):
Uses `title` as the PK for any lookups.
"""
queryset = Course.objects.all()
serializer_class = CourseSerializer
serializer_class = serializers.CourseSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly)
lookup_field = 'title'
lookup_field = 'slug'

def get_queryset(self):
query = Course.objects

if 'cuisine' in self.request.query_params:
cuisine = Cuisine.objects.get(slug=self.request.query_params.get('cuisine'))
query = query.filter(recipe__cuisine=cuisine)

return query.annotate(total=Count('recipe', distinct=True))


class TagViewSet(viewsets.ModelViewSet):
Expand All @@ -47,7 +62,7 @@ class TagViewSet(viewsets.ModelViewSet):
Uses `title` as the PK for any lookups.
"""
queryset = Tag.objects.all()
serializer_class = TagSerializer
serializer_class = serializers.TagSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly)
lookup_field = 'title'

0 comments on commit 7848432

Please sign in to comment.