Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,7 @@ pip-delete-this-directory.txt
# VirtualEnv
.venv/

# Developers
*.sw*
manage.py
.DS_Store
File renamed without changes.
18 changes: 18 additions & 0 deletions example/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class TaggedItem(BaseModel):
def __str__(self):
return self.tag

class Meta:
ordering = ('id',)


@python_2_unicode_compatible
class Blog(BaseModel):
Expand All @@ -38,6 +41,9 @@ class Blog(BaseModel):
def __str__(self):
return self.name

class Meta:
ordering = ('id',)


@python_2_unicode_compatible
class Author(BaseModel):
Expand All @@ -47,6 +53,9 @@ class Author(BaseModel):
def __str__(self):
return self.name

class Meta:
ordering = ('id',)


@python_2_unicode_compatible
class AuthorBio(BaseModel):
Expand All @@ -56,6 +65,9 @@ class AuthorBio(BaseModel):
def __str__(self):
return self.author.name

class Meta:
ordering = ('id',)


@python_2_unicode_compatible
class Entry(BaseModel):
Expand All @@ -73,6 +85,9 @@ class Entry(BaseModel):
def __str__(self):
return self.headline

class Meta:
ordering = ('id',)


@python_2_unicode_compatible
class Comment(BaseModel):
Expand All @@ -87,6 +102,9 @@ class Comment(BaseModel):
def __str__(self):
return self.body

class Meta:
ordering = ('id',)


class Project(PolymorphicModel):
topic = models.CharField(max_length=30)
Expand Down
15 changes: 13 additions & 2 deletions example/settings/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
'rest_framework',
'polymorphic',
'example',
'debug_toolbar',
]

TEMPLATES = [
Expand Down Expand Up @@ -58,7 +59,11 @@

PASSWORD_HASHERS = ('django.contrib.auth.hashers.UnsaltedMD5PasswordHasher', )

MIDDLEWARE_CLASSES = ()
MIDDLEWARE_CLASSES = (
'debug_toolbar.middleware.DebugToolbarMiddleware',
)

INTERNAL_IPS = ('127.0.0.1', )

JSON_API_FORMAT_KEYS = 'camelize'
JSON_API_FORMAT_TYPES = 'camelize'
Expand All @@ -74,7 +79,13 @@
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework_json_api.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',

# If you're performance testing, you will want to use the browseable API
# without forms, as the forms can generate their own queries.
# If performance testing, enable:
'example.utils.BrowsableAPIRendererWithoutForms',
# Otherwise, to play around with the browseable API, enable:
#'rest_framework.renderers.BrowsableAPIRenderer',
),
'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata',
}
55 changes: 55 additions & 0 deletions example/tests/test_performance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from django.utils import timezone
from rest_framework.test import APITestCase

from example.factories import CommentFactory
from example.models import Author, Blog, Comment, Entry


class PerformanceTestCase(APITestCase):
def setUp(self):
self.author = Author.objects.create(name='Super powerful superhero', email='i.am@lost.com')
self.blog = Blog.objects.create(name='Some Blog', tagline="It's a blog")
self.other_blog = Blog.objects.create(name='Other blog', tagline="It's another blog")
self.first_entry = Entry.objects.create(
blog=self.blog,
headline='headline one',
body_text='body_text two',
pub_date=timezone.now(),
mod_date=timezone.now(),
n_comments=0,
n_pingbacks=0,
rating=3
)
self.second_entry = Entry.objects.create(
blog=self.blog,
headline='headline two',
body_text='body_text one',
pub_date=timezone.now(),
mod_date=timezone.now(),
n_comments=0,
n_pingbacks=0,
rating=1
)
self.comment = Comment.objects.create(entry=self.first_entry)
CommentFactory.create_batch(50)

def test_query_count_no_includes(self):
""" We expect a simple list view to issue only two queries.

1. The number of results in the set (e.g. a COUNT query), only necessary because we're using PageNumberPagination
2. The SELECT query for the set
"""
with self.assertNumQueries(2):
response = self.client.get('/comments?page_size=25')
self.assertEqual(len(response.data['results']), 25)

def test_query_count_include_author(self):
""" We expect a list view with an include have three queries:

1. Primary resource COUNT query
2. Primary resource SELECT
3. Author's prefetched
"""
with self.assertNumQueries(3):
response = self.client.get('/comments?include=author&page_size=25')
self.assertEqual(len(response.data['results']), 25)
3 changes: 1 addition & 2 deletions example/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
from django.utils import timezone
from rest_framework.reverse import reverse
from rest_framework.test import APITestCase, force_authenticate

from rest_framework_json_api.utils import format_resource_type

from . import TestBase
from .. import views
from .. import factories, views
from example.models import Author, Blog, Comment, Entry


Expand Down
8 changes: 8 additions & 0 deletions example/urls.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.conf import settings
from django.conf.urls import include, url
from rest_framework import routers

Expand All @@ -22,3 +23,10 @@
urlpatterns = [
url(r'^', include(router.urls)),
]


if settings.DEBUG:
import debug_toolbar
urlpatterns = [
url(r'^__debug__/', include(debug_toolbar.urls)),
] + urlpatterns
20 changes: 20 additions & 0 deletions example/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from rest_framework.renderers import BrowsableAPIRenderer


class BrowsableAPIRendererWithoutForms(BrowsableAPIRenderer):
"""Renders the browsable api, but excludes the forms."""

def get_context(self, *args, **kwargs):
ctx = super().get_context(*args, **kwargs)
ctx['display_edit_forms'] = False
return ctx

def show_form_for_method(self, view, method, request, obj):
"""We never want to do this! So just return False."""
return False

def get_rendered_html_form(self, data, view, method, request):
"""Why render _any_ forms at all. This method should return
rendered HTML, so let's simply return an empty string.
"""
return ""
3 changes: 1 addition & 2 deletions example/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import rest_framework.parsers
import rest_framework.renderers
from rest_framework import exceptions

import rest_framework_json_api.metadata
import rest_framework_json_api.parsers
import rest_framework_json_api.renderers
from rest_framework import exceptions
from rest_framework_json_api.utils import format_drf_errors
from rest_framework_json_api.views import ModelViewSet, RelationshipView

Expand Down
2 changes: 2 additions & 0 deletions requirements-development.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ recommonmark
Sphinx
sphinx_rtd_theme
tox
mock
django-debug-toolbar
packaging==16.8
9 changes: 7 additions & 2 deletions rest_framework_json_api/relations.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import collections
import json
from collections import OrderedDict

import six

import inflection
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import NoReverseMatch
from django.utils.translation import ugettext_lazy as _
from rest_framework.fields import MISSING_ERROR_MESSAGE
from rest_framework.relations import * # noqa: F403
from rest_framework.relations import MANY_RELATION_KWARGS, PrimaryKeyRelatedField
from rest_framework.reverse import reverse
from rest_framework.serializers import Serializer

from rest_framework_json_api.exceptions import Conflict
from rest_framework_json_api.utils import (
Hyperlink,
Expand Down
1 change: 0 additions & 1 deletion rest_framework_json_api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework.exceptions import ParseError
from rest_framework.serializers import * # noqa: F403

from rest_framework_json_api.exceptions import Conflict
from rest_framework_json_api.relations import ResourceRelatedField
from rest_framework_json_api.utils import (
Expand Down