Commit 4cd49d5d by Tom Christie
parents c573e7b4 1c0db6dd
...@@ -127,7 +127,7 @@ This pagination style mirrors the syntax used when looking up multiple database ...@@ -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'
......
...@@ -65,7 +65,7 @@ When testing views directly using a request factory, it's often convenient to be ...@@ -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')
......
...@@ -206,4 +206,4 @@ This will either be made as a single 3.2 release, or split across two separate r ...@@ -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
...@@ -154,7 +154,7 @@ For older release notes, [please see the version 2.x documentation](old-release- ...@@ -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
......
...@@ -200,7 +200,7 @@ See the [browsable api][browsable-api] topic for more information about the brow ...@@ -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
......
...@@ -10,7 +10,7 @@ from django.core.paginator import InvalidPage, Paginator as DjangoPaginator ...@@ -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
......
...@@ -218,14 +218,15 @@ class SimpleRouter(BaseRouter): ...@@ -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
) )
......
...@@ -7,6 +7,6 @@ ...@@ -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>
...@@ -18,7 +18,6 @@ def pytest_configure(): ...@@ -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(): ...@@ -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(): ...@@ -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',
), ),
) )
......
...@@ -32,6 +32,13 @@ class NoteViewSet(viewsets.ModelViewSet): ...@@ -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): ...@@ -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 = [ ...@@ -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): ...@@ -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):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment