This commit is contained in:
Tom Christie 2015-03-16 11:57:42 +00:00
commit 4cd49d5de3
10 changed files with 48 additions and 16 deletions

View File

@ -127,7 +127,7 @@ This pagination style mirrors the syntax used when looking up multiple database
#### Setup #### Setup
To enable the `PageNumberPagination` style globally, use the following configuration: To enable the `LimitOffsetPagination` style globally, use the following configuration:
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination' 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination'

View File

@ -65,7 +65,7 @@ When testing views directly using a request factory, it's often convenient to be
To forcibly authenticate a request, use the `force_authenticate()` method. To forcibly authenticate a request, use the `force_authenticate()` method.
from rest_framework.tests import force_authenticate from rest_framework.test import force_authenticate
factory = APIRequestFactory() factory = APIRequestFactory()
user = User.objects.get(username='olivia') user = User.objects.get(username='olivia')

View File

@ -206,4 +206,4 @@ This will either be made as a single 3.2 release, or split across two separate r
[pagination]: ../api-guide/pagination.md [pagination]: ../api-guide/pagination.md
[versioning]: ../api-guide/versioning.md [versioning]: ../api-guide/versioning.md
[internationalization]: internationalization.md [internationalization]: internationalization.md
[customizing-field-mappings]: ../api-guide/serializers.md/#customizing-field-mappings [customizing-field-mappings]: ../api-guide/serializers.md#customizing-field-mappings

View File

@ -154,7 +154,7 @@ For older release notes, [please see the version 2.x documentation](old-release-
[2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion [2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion
[ticket-582]: https://github.com/tomchristie/django-rest-framework/issues/582 [ticket-582]: https://github.com/tomchristie/django-rest-framework/issues/582
[rfc-6266]: http://tools.ietf.org/html/rfc6266#section-4.3 [rfc-6266]: http://tools.ietf.org/html/rfc6266#section-4.3
[old-release-notes]: http://tomchristie.github.io/rest-framework-2-docs/topics/release-notes#24x-series [old-release-notes]: https://github.com/tomchristie/django-rest-framework/blob/version-2.4.x/docs/topics/release-notes.md
[3.0.1-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.1+Release%22 [3.0.1-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.1+Release%22
[3.0.2-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.2+Release%22 [3.0.2-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.2+Release%22

View File

@ -200,7 +200,7 @@ See the [browsable api][browsable-api] topic for more information about the brow
In [tutorial part 3][tut-3], we'll start using class based views, and see how generic views reduce the amount of code we need to write. In [tutorial part 3][tut-3], we'll start using class based views, and see how generic views reduce the amount of code we need to write.
[json-url]: http://example.com/api/items/4.json [json-url]: http://example.com/api/items/4/.json
[devserver]: http://127.0.0.1:8000/snippets/ [devserver]: http://127.0.0.1:8000/snippets/
[browsable-api]: ../topics/browsable-api.md [browsable-api]: ../topics/browsable-api.md
[tut-1]: 1-serialization.md [tut-1]: 1-serialization.md

View File

@ -10,7 +10,7 @@ from django.core.paginator import InvalidPage, Paginator as DjangoPaginator
from django.template import Context, loader from django.template import Context, loader
from django.utils import six from django.utils import six
from django.utils.six.moves.urllib import parse as urlparse from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.compat import OrderedDict from rest_framework.compat import OrderedDict
from rest_framework.exceptions import NotFound from rest_framework.exceptions import NotFound
from rest_framework.response import Response from rest_framework.response import Response

View File

@ -218,14 +218,15 @@ class SimpleRouter(BaseRouter):
https://github.com/alanjds/drf-nested-routers https://github.com/alanjds/drf-nested-routers
""" """
base_regex = '(?P<{lookup_prefix}{lookup_field}>{lookup_value})' base_regex = '(?P<{lookup_prefix}{lookup_url_kwarg}>{lookup_value})'
# Use `pk` as default field, unset set. Default regex should not # Use `pk` as default field, unset set. Default regex should not
# consume `.json` style suffixes and should break at '/' boundaries. # consume `.json` style suffixes and should break at '/' boundaries.
lookup_field = getattr(viewset, 'lookup_field', 'pk') lookup_field = getattr(viewset, 'lookup_field', 'pk')
lookup_url_kwarg = getattr(viewset, 'lookup_url_kwarg', None) or lookup_field
lookup_value = getattr(viewset, 'lookup_value_regex', '[^/.]+') lookup_value = getattr(viewset, 'lookup_value_regex', '[^/.]+')
return base_regex.format( return base_regex.format(
lookup_prefix=lookup_prefix, lookup_prefix=lookup_prefix,
lookup_field=lookup_field, lookup_url_kwarg=lookup_url_kwarg,
lookup_value=lookup_value lookup_value=lookup_value
) )

View File

@ -7,6 +7,6 @@
{% if next_url %} {% if next_url %}
<li class="next"><a href="{{ next_url }}">Next &raquo;</a></li> <li class="next"><a href="{{ next_url }}">Next &raquo;</a></li>
{% else %} {% else %}
<li class="next disabled"><a href="#">Next &raquo;</li> <li class="next disabled"><a href="#">Next &raquo;</a></li>
{% endif %} {% endif %}
</ul> </ul>

View File

@ -18,7 +18,6 @@ def pytest_configure():
MIDDLEWARE_CLASSES=( MIDDLEWARE_CLASSES=(
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
), ),
@ -27,7 +26,6 @@ def pytest_configure():
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.sites', 'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'rest_framework', 'rest_framework',
@ -35,12 +33,7 @@ def pytest_configure():
'tests', 'tests',
), ),
PASSWORD_HASHERS=( PASSWORD_HASHERS=(
'django.contrib.auth.hashers.SHA1PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.BCryptPasswordHasher',
'django.contrib.auth.hashers.MD5PasswordHasher', 'django.contrib.auth.hashers.MD5PasswordHasher',
'django.contrib.auth.hashers.CryptPasswordHasher',
), ),
) )

View File

@ -32,6 +32,13 @@ class NoteViewSet(viewsets.ModelViewSet):
lookup_field = 'uuid' lookup_field = 'uuid'
class KWargedNoteViewSet(viewsets.ModelViewSet):
queryset = RouterTestModel.objects.all()
serializer_class = NoteSerializer
lookup_field = 'text__contains'
lookup_url_kwarg = 'text'
class MockViewSet(viewsets.ModelViewSet): class MockViewSet(viewsets.ModelViewSet):
queryset = None queryset = None
serializer_class = None serializer_class = None
@ -40,6 +47,9 @@ class MockViewSet(viewsets.ModelViewSet):
notes_router = SimpleRouter() notes_router = SimpleRouter()
notes_router.register(r'notes', NoteViewSet) notes_router.register(r'notes', NoteViewSet)
kwarged_notes_router = SimpleRouter()
kwarged_notes_router.register(r'notes', KWargedNoteViewSet)
namespaced_router = DefaultRouter() namespaced_router = DefaultRouter()
namespaced_router.register(r'example', MockViewSet, base_name='example') namespaced_router.register(r'example', MockViewSet, base_name='example')
@ -47,6 +57,7 @@ urlpatterns = [
url(r'^non-namespaced/', include(namespaced_router.urls)), url(r'^non-namespaced/', include(namespaced_router.urls)),
url(r'^namespaced/', include(namespaced_router.urls, namespace='example')), url(r'^namespaced/', include(namespaced_router.urls, namespace='example')),
url(r'^example/', include(notes_router.urls)), url(r'^example/', include(notes_router.urls)),
url(r'^example2/', include(kwarged_notes_router.urls)),
] ]
@ -177,6 +188,33 @@ class TestLookupValueRegex(TestCase):
self.assertEqual(expected[idx], self.urls[idx].regex.pattern) self.assertEqual(expected[idx], self.urls[idx].regex.pattern)
class TestLookupUrlKwargs(TestCase):
"""
Ensure the router honors lookup_url_kwarg.
Setup a deep lookup_field, but map it to a simple URL kwarg.
"""
urls = 'tests.test_routers'
def setUp(self):
RouterTestModel.objects.create(uuid='123', text='foo bar')
def test_custom_lookup_url_kwarg_route(self):
detail_route = kwarged_notes_router.urls[-1]
detail_url_pattern = detail_route.regex.pattern
self.assertIn('^notes/(?P<text>', detail_url_pattern)
def test_retrieve_lookup_url_kwarg_detail_view(self):
response = self.client.get('/example2/notes/fo/')
self.assertEqual(
response.data,
{
"url": "http://testserver/example/notes/123/",
"uuid": "123", "text": "foo bar"
}
)
class TestTrailingSlashIncluded(TestCase): class TestTrailingSlashIncluded(TestCase):
def setUp(self): def setUp(self):
class NoteViewSet(viewsets.ModelViewSet): class NoteViewSet(viewsets.ModelViewSet):