Skip to content

Commit 657a7c5

Browse files
Merge branch 'version-3-5' into rejig-schema-generation
2 parents 8c72112 + 49ce3d6 commit 657a7c5

37 files changed

+1153
-122
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ env:
1414
- TOX_ENV=py35-django18
1515
- TOX_ENV=py34-django18
1616
- TOX_ENV=py33-django18
17-
- TOX_ENV=py32-django18
1817
- TOX_ENV=py27-django18
1918
- TOX_ENV=py27-django110
2019
- TOX_ENV=py35-django110

docs/api-guide/relations.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,8 @@ There are two keyword arguments you can use to control this behavior:
457457
- `html_cutoff` - If set this will be the maximum number of choices that will be displayed by a HTML select drop down. Set to `None` to disable any limiting. Defaults to `1000`.
458458
- `html_cutoff_text` - If set this will display a textual indicator if the maximum number of items have been cutoff in an HTML select drop down. Defaults to `"More than {count} items…"`
459459

460+
You can also control these globally using the settings `HTML_SELECT_CUTOFF` and `HTML_SELECT_CUTOFF_TEXT`.
461+
460462
In cases where the cutoff is being enforced you may want to instead use a plain input field in the HTML form. You can do so using the `style` keyword argument. For example:
461463

462464
assigned_to = serializers.SlugRelatedField(

docs/api-guide/reverse.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ There's no requirement for you to use them, but if you do then the self-describi
2323

2424
**Signature:** `reverse(viewname, *args, **kwargs)`
2525

26-
Has the same behavior as [`django.core.urlresolvers.reverse`][reverse], except that it returns a fully qualified URL, using the request to determine the host and port.
26+
Has the same behavior as [`django.urls.reverse`][reverse], except that it returns a fully qualified URL, using the request to determine the host and port.
2727

2828
You should **include the request as a keyword argument** to the function, for example:
2929

@@ -44,7 +44,7 @@ You should **include the request as a keyword argument** to the function, for ex
4444

4545
**Signature:** `reverse_lazy(viewname, *args, **kwargs)`
4646

47-
Has the same behavior as [`django.core.urlresolvers.reverse_lazy`][reverse-lazy], except that it returns a fully qualified URL, using the request to determine the host and port.
47+
Has the same behavior as [`django.urls.reverse_lazy`][reverse-lazy], except that it returns a fully qualified URL, using the request to determine the host and port.
4848

4949
As with the `reverse` function, you should **include the request as a keyword argument** to the function, for example:
5050

docs/api-guide/settings.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,22 @@ This should be a function with the following signature:
382382

383383
Default: `'rest_framework.views.get_view_description'`
384384

385+
## HTML Select Field cutoffs
386+
387+
Global settings for [select field cutoffs for rendering relational fields](relations.md#select-field-cutoffs) in the browsable API.
388+
389+
#### HTML_SELECT_CUTOFF
390+
391+
Global setting for the `html_cutoff` value. Must be an integer.
392+
393+
Default: 1000
394+
395+
#### HTML_SELECT_CUTOFF_TEXT
396+
397+
A string representing a global setting for `html_cutoff_text`.
398+
399+
Default: `"More than {count} items..."`
400+
385401
---
386402

387403
## Miscellaneous settings

docs/api-guide/testing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ REST framework includes the following test case classes, that mirror the existin
197197

198198
You can use any of REST framework's test case classes as you would for the regular Django test case classes. The `self.client` attribute will be an `APIClient` instance.
199199

200-
from django.core.urlresolvers import reverse
200+
from django.urls import reverse
201201
from rest_framework import status
202202
from rest_framework.test import APITestCase
203203
from myproject.apps.core.models import Account
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Optional packages which may be used with REST framework.
22
markdown==2.6.4
3-
django-guardian==1.4.3
4-
django-filter==0.13.0
5-
coreapi==1.32.0
3+
django-guardian==1.4.6
4+
django-filter==0.14.0
5+
coreapi==2.0.8

rest_framework/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"""
99

1010
__title__ = 'Django REST framework'
11-
__version__ = '3.4.7'
11+
__version__ = '3.5.0'
1212
__author__ = 'Tom Christie'
1313
__license__ = 'BSD 2-Clause'
1414
__copyright__ = 'Copyright 2011-2016 Tom Christie'

rest_framework/authtoken/serializers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ def validate(self, attrs):
1616
user = authenticate(username=username, password=password)
1717

1818
if user:
19+
# From Django 1.10 onwards the `authenticate` call simply
20+
# returns `None` for is_active=False users.
21+
# (Assuming the default `ModelBackend` authentication backend.)
1922
if not user.is_active:
2023
msg = _('User account is disabled.')
2124
raise serializers.ValidationError(msg)

rest_framework/compat.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@
2323
from django.utils import importlib # Will be removed in Django 1.9
2424

2525

26+
try:
27+
from django.urls import (
28+
NoReverseMatch, RegexURLPattern, RegexURLResolver, ResolverMatch, Resolver404, get_script_prefix, reverse, reverse_lazy, resolve
29+
)
30+
except ImportError:
31+
from django.core.urlresolvers import ( # Will be removed in Django 2.0
32+
NoReverseMatch, RegexURLPattern, RegexURLResolver, ResolverMatch, Resolver404, get_script_prefix, reverse, reverse_lazy, resolve
33+
)
34+
35+
2636
try:
2737
import urlparse # Python 2.x
2838
except ImportError:
@@ -128,6 +138,12 @@ def is_authenticated(user):
128138
return user.is_authenticated
129139

130140

141+
def is_anonymous(user):
142+
if django.VERSION < (1, 10):
143+
return user.is_anonymous()
144+
return user.is_anonymous
145+
146+
131147
def get_related_model(field):
132148
if django.VERSION < (1, 9):
133149
return _resolve_model(field.rel.to)
@@ -178,6 +194,13 @@ def value_from_object(field, obj):
178194
uritemplate = None
179195

180196

197+
# requests is optional
198+
try:
199+
import requests
200+
except ImportError:
201+
requests = None
202+
203+
181204
# Django-guardian is optional. Import only if guardian is in INSTALLED_APPS
182205
# Fixes (#1712). We keep the try/except for the test suite.
183206
guardian = None
@@ -200,8 +223,13 @@ def value_from_object(field, obj):
200223

201224
if markdown.version <= '2.2':
202225
HEADERID_EXT_PATH = 'headerid'
203-
else:
226+
LEVEL_PARAM = 'level'
227+
elif markdown.version < '2.6':
204228
HEADERID_EXT_PATH = 'markdown.extensions.headerid'
229+
LEVEL_PARAM = 'level'
230+
else:
231+
HEADERID_EXT_PATH = 'markdown.extensions.toc'
232+
LEVEL_PARAM = 'baselevel'
205233

206234
def apply_markdown(text):
207235
"""
@@ -211,7 +239,7 @@ def apply_markdown(text):
211239
extensions = [HEADERID_EXT_PATH]
212240
extension_configs = {
213241
HEADERID_EXT_PATH: {
214-
'level': '2'
242+
LEVEL_PARAM: '2'
215243
}
216244
}
217245
md = markdown.Markdown(
@@ -277,3 +305,11 @@ def template_render(template, context=None, request=None):
277305
# backends template, e.g. django.template.backends.django.Template
278306
else:
279307
return template.render(context, request=request)
308+
309+
310+
def set_many(instance, field, value):
311+
if django.VERSION < (1, 10):
312+
setattr(instance, field, value)
313+
else:
314+
field = getattr(instance, field)
315+
field.set(value)

rest_framework/fields.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,34 @@ class empty:
4949
pass
5050

5151

52-
def is_simple_callable(obj):
53-
"""
54-
True if the object is a callable that takes no arguments.
55-
"""
56-
function = inspect.isfunction(obj)
57-
method = inspect.ismethod(obj)
52+
if six.PY3:
53+
def is_simple_callable(obj):
54+
"""
55+
True if the object is a callable that takes no arguments.
56+
"""
57+
if not callable(obj):
58+
return False
59+
60+
sig = inspect.signature(obj)
61+
params = sig.parameters.values()
62+
return all(param.default != param.empty for param in params)
63+
64+
else:
65+
def is_simple_callable(obj):
66+
function = inspect.isfunction(obj)
67+
method = inspect.ismethod(obj)
68+
69+
if not (function or method):
70+
return False
71+
72+
if method:
73+
is_unbound = obj.im_self is None
5874

59-
if not (function or method):
60-
return False
75+
args, _, _, defaults = inspect.getargspec(obj)
6176

62-
args, _, _, defaults = inspect.getargspec(obj)
63-
len_args = len(args) if function else len(args) - 1
64-
len_defaults = len(defaults) if defaults else 0
65-
return len_args <= len_defaults
77+
len_args = len(args) if function or is_unbound else len(args) - 1
78+
len_defaults = len(defaults) if defaults else 0
79+
return len_args <= len_defaults
6680

6781

6882
def get_attribute(instance, attrs):

0 commit comments

Comments
 (0)