mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-02 20:54:42 +03:00
Merge master
This commit is contained in:
commit
fbb21caaaa
19
.gitignore
vendored
19
.gitignore
vendored
|
@ -3,19 +3,14 @@
|
|||
*~
|
||||
.*
|
||||
|
||||
site/
|
||||
htmlcov/
|
||||
coverage/
|
||||
build/
|
||||
dist/
|
||||
*.egg-info/
|
||||
/site/
|
||||
/htmlcov/
|
||||
/coverage/
|
||||
/build/
|
||||
/dist/
|
||||
/*.egg-info/
|
||||
/env/
|
||||
MANIFEST
|
||||
|
||||
bin/
|
||||
include/
|
||||
lib/
|
||||
local/
|
||||
env/
|
||||
|
||||
!.gitignore
|
||||
!.travis.yml
|
||||
|
|
12
.travis.yml
12
.travis.yml
|
@ -25,18 +25,6 @@ env:
|
|||
- TOX_ENV=py33-django18alpha
|
||||
- TOX_ENV=py32-django18alpha
|
||||
- TOX_ENV=py27-django18alpha
|
||||
- TOX_ENV=py34-djangomaster
|
||||
- TOX_ENV=py33-djangomaster
|
||||
- TOX_ENV=py32-djangomaster
|
||||
- TOX_ENV=py27-djangomaster
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: TOX_ENV=py34-djangomaster
|
||||
- env: TOX_ENV=py33-djangomaster
|
||||
- env: TOX_ENV=py32-djangomaster
|
||||
- env: TOX_ENV=py27-djangomaster
|
||||
|
||||
install:
|
||||
- pip install tox
|
||||
|
|
|
@ -34,7 +34,7 @@ There is a live example API for testing purposes, [available here][sandbox].
|
|||
# Requirements
|
||||
|
||||
* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4)
|
||||
* Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7)
|
||||
* Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7, 1.8-alpha)
|
||||
|
||||
# Installation
|
||||
|
||||
|
|
|
@ -591,6 +591,10 @@ The [drf-compound-fields][drf-compound-fields] package provides "compound" seria
|
|||
|
||||
The [drf-extra-fields][drf-extra-fields] package provides extra serializer fields for REST framework, including `Base64ImageField` and `PointField` classes.
|
||||
|
||||
## djangrestframework-recursive
|
||||
|
||||
the [djangorestframework-recursive][djangorestframework-recursive] package provides a `RecursiveField` for serializing and deserializing recursive structures
|
||||
|
||||
## django-rest-framework-gis
|
||||
|
||||
The [django-rest-framework-gis][django-rest-framework-gis] package provides geographic addons for django rest framework like a `GeometryField` field and a GeoJSON serializer.
|
||||
|
@ -607,6 +611,7 @@ The [django-rest-framework-hstore][django-rest-framework-hstore] package provide
|
|||
[iso8601]: http://www.w3.org/TR/NOTE-datetime
|
||||
[drf-compound-fields]: http://drf-compound-fields.readthedocs.org
|
||||
[drf-extra-fields]: https://github.com/Hipo/drf-extra-fields
|
||||
[djangorestframework-recursive]: https://github.com/heywbj/django-rest-framework-recursive
|
||||
[django-rest-framework-gis]: https://github.com/djangonauts/django-rest-framework-gis
|
||||
[django-rest-framework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore
|
||||
[django-hstore]: https://github.com/djangonauts/django-hstore
|
||||
|
|
|
@ -304,7 +304,7 @@ The [wq.db package][wq.db] provides an advanced [Router][wq.db-router] class (an
|
|||
The [`DRF-extensions` package][drf-extensions] provides [routers][drf-extensions-routers] for creating [nested viewsets][drf-extensions-nested-viewsets], [collection level controllers][drf-extensions-collection-level-controllers] with [customizable endpoint names][drf-extensions-customizable-endpoint-names].
|
||||
|
||||
[cite]: http://guides.rubyonrails.org/routing.html
|
||||
[route-decorators]: viewsets.html#marking-extra-actions-for-routing
|
||||
[route-decorators]: viewsets.md#marking-extra-actions-for-routing
|
||||
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
|
||||
[wq.db]: http://wq.io/wq.db
|
||||
[wq.db-router]: http://wq.io/docs/app.py
|
||||
|
|
|
@ -201,7 +201,7 @@ Note that you can use any of the standard attributes or method overrides provide
|
|||
def get_queryset(self):
|
||||
return self.request.user.accounts.all()
|
||||
|
||||
Note however that upon removal of the `queryset` property from your `ViewSet`, any associated [router][routers] will be unable to derive the base_name of your Model automatically, and so you you will have to specify the `base_name` kwarg as part of your [router registration][routers].
|
||||
Note however that upon removal of the `queryset` property from your `ViewSet`, any associated [router][routers] will be unable to derive the base_name of your Model automatically, and so you will have to specify the `base_name` kwarg as part of your [router registration][routers].
|
||||
|
||||
Also note that although this class provides the complete set of create/list/retrieve/update/destroy actions by default, you can restrict the available operations by using the standard permission classes.
|
||||
|
||||
|
|
|
@ -826,7 +826,7 @@ The `style` keyword argument can be used to pass through additional information
|
|||
For example, to use a `textarea` control instead of the default `input` control, you would use the following…
|
||||
|
||||
additional_notes = serializers.CharField(
|
||||
style={'base_template': 'text_area.html'}
|
||||
style={'base_template': 'textarea.html'}
|
||||
)
|
||||
|
||||
Similarly, to use a radio button control instead of the default `select` control, you would use the following…
|
||||
|
|
|
@ -237,7 +237,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
* [django-rest-framework-proxy][django-rest-framework-proxy] - Proxy to redirect incoming request to another API server.
|
||||
* [gaiarestframework][gaiarestframework] - Utils for django-rest-framewok
|
||||
* [drf-extensions][drf-extensions] - A collection of custom extensions
|
||||
* [ember-data-django-rest-adapter][ember-data-django-rest-adapter] - An ember-data adapter
|
||||
* [ember-django-adapter][ember-django-adapter] - An adapter for working with Ember.js
|
||||
|
||||
## Other Resources
|
||||
|
||||
|
@ -309,7 +309,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
[django-rest-framework-proxy]: https://github.com/eofs/django-rest-framework-proxy
|
||||
[gaiarestframework]: https://github.com/AppsFuel/gaiarestframework
|
||||
[drf-extensions]: https://github.com/chibisov/drf-extensions
|
||||
[ember-data-django-rest-adapter]: https://github.com/toranb/ember-data-django-rest-adapter
|
||||
[ember-django-adapter]: https://github.com/dustinfarris/ember-django-adapter
|
||||
[beginners-guide-to-the-django-rest-framework]: http://code.tutsplus.com/tutorials/beginners-guide-to-the-django-rest-framework--cms-19786
|
||||
[getting-started-with-django-rest-framework-and-angularjs]: http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html
|
||||
[end-to-end-web-app-with-django-rest-framework-angularjs]: http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework
|
||||
|
|
|
@ -97,7 +97,7 @@ The first thing we need to get started on our Web API is to provide a way of ser
|
|||
class SnippetSerializer(serializers.Serializer):
|
||||
pk = serializers.IntegerField(read_only=True)
|
||||
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
|
||||
code = serializers.CharField(style={'type': 'textarea'})
|
||||
code = serializers.CharField(style={'base_template': 'textarea.html'})
|
||||
linenos = serializers.BooleanField(required=False)
|
||||
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
|
||||
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
|
||||
|
|
|
@ -96,7 +96,7 @@ Notice that we're no longer explicitly tying our requests or responses to a give
|
|||
|
||||
## Adding optional format suffixes to our URLs
|
||||
|
||||
To take advantage of the fact that our responses are no longer hardwired to a single content type let's add support for format suffixes to our API endpoints. Using format suffixes gives us URLs that explicitly refer to a given format, and means our API will be able to handle URLs such as [http://example.com/api/items/4.json][json-url].
|
||||
To take advantage of the fact that our responses are no longer hardwired to a single content type let's add support for format suffixes to our API endpoints. Using format suffixes gives us URLs that explicitly refer to a given format, and means our API will be able to handle URLs such as [http://example.com/api/items/4/.json][json-url].
|
||||
|
||||
Start by adding a `format` keyword argument to both of the views, like so.
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ After adding all those names into our URLconf, our final `snippets/urls.py` file
|
|||
|
||||
The list views for users and code snippets could end up returning quite a lot of instances, so really we'd like to make sure we paginate the results, and allow the API client to step through each of the individual pages.
|
||||
|
||||
We can change the default list style to use pagination, by modifying our `settings.py` file slightly. Add the following setting:
|
||||
We can change the default list style to use pagination, by modifying our `tutorial/settings.py` file slightly. Add the following setting:
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'PAGINATE_BY': 10
|
||||
|
|
|
@ -14,6 +14,8 @@ django-filter>=0.9.2
|
|||
|
||||
# wheel for PyPI installs
|
||||
wheel==0.24.0
|
||||
# twine for secured PyPI uploads
|
||||
twine==1.4.0
|
||||
|
||||
# MkDocs for documentation previews/deploys
|
||||
mkdocs==0.11.1
|
||||
|
|
|
@ -168,7 +168,7 @@ class TokenAuthentication(BaseAuthentication):
|
|||
|
||||
def authenticate_credentials(self, key):
|
||||
try:
|
||||
token = self.model.objects.get(key=key)
|
||||
token = self.model.objects.select_related('user').get(key=key)
|
||||
except self.model.DoesNotExist:
|
||||
raise exceptions.AuthenticationFailed(_('Invalid token.'))
|
||||
|
||||
|
|
|
@ -18,8 +18,7 @@ def api_view(http_method_names=None):
|
|||
Decorator that converts a function-based view into an APIView subclass.
|
||||
Takes a list of allowed methods for the view as an argument.
|
||||
"""
|
||||
if http_method_names is None:
|
||||
http_method_names = ['GET']
|
||||
http_method_names = ['GET'] if (http_method_names is None) else http_method_names
|
||||
|
||||
def decorator(func):
|
||||
|
||||
|
@ -109,10 +108,12 @@ def permission_classes(permission_classes):
|
|||
return decorator
|
||||
|
||||
|
||||
def detail_route(methods=['get'], **kwargs):
|
||||
def detail_route(methods=None, **kwargs):
|
||||
"""
|
||||
Used to mark a method on a ViewSet that should be routed for detail requests.
|
||||
"""
|
||||
methods = ['get'] if (methods is None) else methods
|
||||
|
||||
def decorator(func):
|
||||
func.bind_to_methods = methods
|
||||
func.detail = True
|
||||
|
@ -121,10 +122,12 @@ def detail_route(methods=['get'], **kwargs):
|
|||
return decorator
|
||||
|
||||
|
||||
def list_route(methods=['get'], **kwargs):
|
||||
def list_route(methods=None, **kwargs):
|
||||
"""
|
||||
Used to mark a method on a ViewSet that should be routed for list requests.
|
||||
"""
|
||||
methods = ['get'] if (methods is None) else methods
|
||||
|
||||
def decorator(func):
|
||||
func.bind_to_methods = methods
|
||||
func.detail = False
|
||||
|
|
|
@ -168,7 +168,9 @@ def _reverse_ordering(ordering_tuple):
|
|||
Given an order_by tuple such as `('-created', 'uuid')` reverse the
|
||||
ordering and return a new tuple, eg. `('created', '-uuid')`.
|
||||
"""
|
||||
invert = lambda x: x[1:] if (x.startswith('-')) else '-' + x
|
||||
def invert(x):
|
||||
return x[1:] if (x.startswith('-')) else '-' + x
|
||||
|
||||
return tuple([invert(item) for item in ordering_tuple])
|
||||
|
||||
|
||||
|
|
|
@ -12,12 +12,13 @@ from __future__ import unicode_literals
|
|||
from django.conf import settings
|
||||
from django.http import QueryDict
|
||||
from django.http.multipartparser import parse_header
|
||||
from django.utils import six
|
||||
from django.utils.datastructures import MultiValueDict
|
||||
from django.utils.datastructures import MergeDict as DjangoMergeDict
|
||||
from django.utils.six import BytesIO
|
||||
from rest_framework import HTTP_HEADER_ENCODING
|
||||
from rest_framework import exceptions
|
||||
from rest_framework.settings import api_settings
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
|
||||
|
@ -366,7 +367,7 @@ class Request(object):
|
|||
elif hasattr(self._request, 'read'):
|
||||
self._stream = self._request
|
||||
else:
|
||||
self._stream = BytesIO(self.raw_post_data)
|
||||
self._stream = six.BytesIO(self.raw_post_data)
|
||||
|
||||
def _perform_form_overloading(self):
|
||||
"""
|
||||
|
@ -408,7 +409,7 @@ class Request(object):
|
|||
self._CONTENTTYPE_PARAM in self._data
|
||||
):
|
||||
self._content_type = self._data[self._CONTENTTYPE_PARAM]
|
||||
self._stream = BytesIO(self._data[self._CONTENT_PARAM].encode(self.parser_context['encoding']))
|
||||
self._stream = six.BytesIO(self._data[self._CONTENT_PARAM].encode(self.parser_context['encoding']))
|
||||
self._data, self._files, self._full_data = (Empty, Empty, Empty)
|
||||
|
||||
def _parse(self):
|
||||
|
@ -489,8 +490,16 @@ class Request(object):
|
|||
else:
|
||||
self.auth = None
|
||||
|
||||
def __getattr__(self, attr):
|
||||
def __getattribute__(self, attr):
|
||||
"""
|
||||
Proxy other attributes to the underlying HttpRequest object.
|
||||
If an attribute does not exist on this instance, then we also attempt
|
||||
to proxy it to the underlying HttpRequest object.
|
||||
"""
|
||||
return getattr(self._request, attr)
|
||||
try:
|
||||
return super(Request, self).__getattribute__(attr)
|
||||
except AttributeError:
|
||||
info = sys.exc_info()
|
||||
try:
|
||||
return getattr(self._request, attr)
|
||||
except AttributeError:
|
||||
six.reraise(info[0], info[1], info[2].tb_next)
|
||||
|
|
|
@ -86,8 +86,9 @@ class Response(SimpleTemplateResponse):
|
|||
state = super(Response, self).__getstate__()
|
||||
for key in (
|
||||
'accepted_renderer', 'renderer_context', 'resolver_match',
|
||||
'client', 'request', 'wsgi_request', '_closable_objects'
|
||||
'client', 'request', 'wsgi_request'
|
||||
):
|
||||
if key in state:
|
||||
del state[key]
|
||||
state['_closable_objects'] = []
|
||||
return state
|
||||
|
|
|
@ -177,7 +177,7 @@ class BaseSerializer(Field):
|
|||
)
|
||||
|
||||
assert hasattr(self, 'initial_data'), (
|
||||
'Cannot call `.is_valid()` as no `data=` keyword argument was'
|
||||
'Cannot call `.is_valid()` as no `data=` keyword argument was '
|
||||
'passed when instantiating the serializer instance.'
|
||||
)
|
||||
|
||||
|
@ -635,11 +635,11 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data):
|
|||
If we don't do this explicitly they'd get a less helpful error when
|
||||
calling `.save()` on the serializer.
|
||||
|
||||
We don't *automatically* support these sorts of nested writes brecause
|
||||
We don't *automatically* support these sorts of nested writes because
|
||||
there are too many ambiguities to define a default behavior.
|
||||
|
||||
Eg. Suppose we have a `UserSerializer` with a nested profile. How should
|
||||
we handle the case of an update, where the `profile` realtionship does
|
||||
we handle the case of an update, where the `profile` relationship does
|
||||
not exist? Any of the following might be valid:
|
||||
|
||||
* Raise an application error.
|
||||
|
|
|
@ -143,7 +143,9 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru
|
|||
|
||||
If autoescape is True, the link text and URLs will get autoescaped.
|
||||
"""
|
||||
trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
|
||||
def trim_url(x, limit=trim_url_limit):
|
||||
return limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
|
||||
|
||||
safe_input = isinstance(text, SafeData)
|
||||
words = word_split_re.split(force_text(text))
|
||||
for i, word in enumerate(words):
|
||||
|
|
|
@ -191,7 +191,7 @@ class UserRateThrottle(SimpleRateThrottle):
|
|||
|
||||
def get_cache_key(self, request, view):
|
||||
if request.user.is_authenticated():
|
||||
ident = request.user.id
|
||||
ident = request.user.pk
|
||||
else:
|
||||
ident = self.get_ident(request)
|
||||
|
||||
|
@ -239,7 +239,7 @@ class ScopedRateThrottle(SimpleRateThrottle):
|
|||
with the '.throttle_scope` property of the view.
|
||||
"""
|
||||
if request.user.is_authenticated():
|
||||
ident = request.user.id
|
||||
ident = request.user.pk
|
||||
else:
|
||||
ident = self.get_ident(request)
|
||||
|
||||
|
|
7
setup.py
7
setup.py
|
@ -48,8 +48,11 @@ if sys.argv[-1] == 'publish':
|
|||
if os.system("pip freeze | grep wheel"):
|
||||
print("wheel not installed.\nUse `pip install wheel`.\nExiting.")
|
||||
sys.exit()
|
||||
os.system("python setup.py sdist upload")
|
||||
os.system("python setup.py bdist_wheel upload")
|
||||
if os.system("pip freeze | grep twine"):
|
||||
print("twine not installed.\nUse `pip install twine`.\nExiting.")
|
||||
sys.exit()
|
||||
os.system("python setup.py sdist bdist_wheel")
|
||||
os.system("twine upload dist/*")
|
||||
print("You probably want to also tag the version now:")
|
||||
print(" git tag -a %s -m 'version %s'" % (version, version))
|
||||
print(" git push --tags")
|
||||
|
|
|
@ -168,6 +168,15 @@ class TokenAuthTests(TestCase):
|
|||
response = self.csrf_client.post('/token/', {'example': 'example'}, format='json', HTTP_AUTHORIZATION=auth)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_post_json_makes_one_db_query(self):
|
||||
"""Ensure that authenticating a user using a token performs only one DB query"""
|
||||
auth = "Token " + self.key
|
||||
|
||||
def func_to_test():
|
||||
return self.csrf_client.post('/token/', {'example': 'example'}, format='json', HTTP_AUTHORIZATION=auth)
|
||||
|
||||
self.assertNumQueries(1, func_to_test)
|
||||
|
||||
def test_post_form_failing_token_auth(self):
|
||||
"""Ensure POSTing form over token auth without correct credentials fails"""
|
||||
response = self.csrf_client.post('/token/', {'example': 'example'})
|
||||
|
|
|
@ -12,7 +12,9 @@ factory = APIRequestFactory()
|
|||
request = factory.get('/') # Just to ensure we have a request in the serializer context
|
||||
|
||||
|
||||
dummy_view = lambda request, pk: None
|
||||
def dummy_view(request, pk):
|
||||
pass
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^dummyurl/(?P<pk>[0-9]+)/$', dummy_view, name='dummy-url'),
|
||||
|
|
|
@ -21,8 +21,13 @@ import re
|
|||
DUMMYSTATUS = status.HTTP_200_OK
|
||||
DUMMYCONTENT = 'dummycontent'
|
||||
|
||||
RENDERER_A_SERIALIZER = lambda x: ('Renderer A: %s' % x).encode('ascii')
|
||||
RENDERER_B_SERIALIZER = lambda x: ('Renderer B: %s' % x).encode('ascii')
|
||||
|
||||
def RENDERER_A_SERIALIZER(x):
|
||||
return ('Renderer A: %s' % x).encode('ascii')
|
||||
|
||||
|
||||
def RENDERER_B_SERIALIZER(x):
|
||||
return ('Renderer B: %s' % x).encode('ascii')
|
||||
|
||||
|
||||
expected_results = [
|
||||
|
|
|
@ -249,9 +249,29 @@ class TestUserSetter(TestCase):
|
|||
login(self.request, self.user)
|
||||
self.assertEqual(self.wrapped_request.user, self.user)
|
||||
|
||||
def test_calling_user_fails_when_attribute_error_is_raised(self):
|
||||
"""
|
||||
This proves that when an AttributeError is raised inside of the request.user
|
||||
property, that we can handle this and report the true, underlying error.
|
||||
"""
|
||||
class AuthRaisesAttributeError(object):
|
||||
def authenticate(self, request):
|
||||
import rest_framework
|
||||
rest_framework.MISSPELLED_NAME_THAT_DOESNT_EXIST
|
||||
|
||||
self.request = Request(factory.get('/'), authenticators=(AuthRaisesAttributeError(),))
|
||||
SessionMiddleware().process_request(self.request)
|
||||
|
||||
login(self.request, self.user)
|
||||
try:
|
||||
self.request.user
|
||||
except AttributeError as error:
|
||||
self.assertEqual(str(error), "'module' object has no attribute 'MISSPELLED_NAME_THAT_DOESNT_EXIST'")
|
||||
else:
|
||||
assert False, 'AttributeError not raised'
|
||||
|
||||
|
||||
class TestAuthSetter(TestCase):
|
||||
|
||||
def test_auth_can_be_set(self):
|
||||
request = Request(factory.get('/'))
|
||||
request.auth = 'DUMMY'
|
||||
|
|
|
@ -38,8 +38,13 @@ class MockTextMediaRenderer(BaseRenderer):
|
|||
DUMMYSTATUS = status.HTTP_200_OK
|
||||
DUMMYCONTENT = 'dummycontent'
|
||||
|
||||
RENDERER_A_SERIALIZER = lambda x: ('Renderer A: %s' % x).encode('ascii')
|
||||
RENDERER_B_SERIALIZER = lambda x: ('Renderer B: %s' % x).encode('ascii')
|
||||
|
||||
def RENDERER_A_SERIALIZER(x):
|
||||
return ('Renderer A: %s' % x).encode('ascii')
|
||||
|
||||
|
||||
def RENDERER_B_SERIALIZER(x):
|
||||
return ('Renderer B: %s' % x).encode('ascii')
|
||||
|
||||
|
||||
class RendererA(BaseRenderer):
|
||||
|
|
|
@ -188,7 +188,9 @@ class ScopedRateThrottleTests(TestCase):
|
|||
class XYScopedRateThrottle(ScopedRateThrottle):
|
||||
TIMER_SECONDS = 0
|
||||
THROTTLE_RATES = {'x': '3/min', 'y': '1/min'}
|
||||
timer = lambda self: self.TIMER_SECONDS
|
||||
|
||||
def timer(self):
|
||||
return self.TIMER_SECONDS
|
||||
|
||||
class XView(APIView):
|
||||
throttle_classes = (XYScopedRateThrottle,)
|
||||
|
@ -290,7 +292,9 @@ class XffTestingBase(TestCase):
|
|||
class Throttle(ScopedRateThrottle):
|
||||
THROTTLE_RATES = {'test_limit': '1/day'}
|
||||
TIMER_SECONDS = 0
|
||||
timer = lambda self: self.TIMER_SECONDS
|
||||
|
||||
def timer(self):
|
||||
return self.TIMER_SECONDS
|
||||
|
||||
class View(APIView):
|
||||
throttle_classes = (Throttle,)
|
||||
|
|
|
@ -32,8 +32,13 @@ class RequestInvalidVersionView(APIView):
|
|||
|
||||
factory = APIRequestFactory()
|
||||
|
||||
dummy_view = lambda request: None
|
||||
dummy_pk_view = lambda request, pk: None
|
||||
|
||||
def dummy_view(request):
|
||||
pass
|
||||
|
||||
|
||||
def dummy_pk_view(request, pk):
|
||||
pass
|
||||
|
||||
|
||||
class TestRequestVersion:
|
||||
|
|
3
tox.ini
3
tox.ini
|
@ -3,7 +3,7 @@ envlist =
|
|||
py27-{flake8,docs},
|
||||
{py26,py27}-django14,
|
||||
{py26,py27,py32,py33,py34}-django{15,16},
|
||||
{py27,py32,py33,py34}-django{17,18alpha,master}
|
||||
{py27,py32,py33,py34}-django{17,18alpha}
|
||||
|
||||
[testenv]
|
||||
commands = ./runtests.py --fast
|
||||
|
@ -15,7 +15,6 @@ deps =
|
|||
django16: Django==1.6.3 # Should track minimum supported
|
||||
django17: Django==1.7.2 # Should track maximum supported
|
||||
django18alpha: https://www.djangoproject.com/download/1.8a1/tarball/
|
||||
djangomaster: https://github.com/django/django/zipball/master
|
||||
django-guardian==1.2.4
|
||||
pytest-django==2.8.0
|
||||
django-filter==0.9.2
|
||||
|
|
Loading…
Reference in New Issue
Block a user