mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-22 09:36:49 +03:00
Merge master
This commit is contained in:
commit
6065cdbd93
|
@ -21,6 +21,10 @@ env:
|
|||
- TOX_ENV=py26-django15
|
||||
- TOX_ENV=py27-django14
|
||||
- TOX_ENV=py26-django14
|
||||
- TOX_ENV=py34-django18alpha
|
||||
- TOX_ENV=py33-django18alpha
|
||||
- TOX_ENV=py32-django18alpha
|
||||
- TOX_ENV=py27-django18alpha
|
||||
- TOX_ENV=py34-djangomaster
|
||||
- TOX_ENV=py33-djangomaster
|
||||
- TOX_ENV=py32-djangomaster
|
||||
|
@ -29,6 +33,10 @@ env:
|
|||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: TOX_ENV=py34-django18alpha
|
||||
- env: TOX_ENV=py33-django18alpha
|
||||
- env: TOX_ENV=py32-django18alpha
|
||||
- env: TOX_ENV=py27-django18alpha
|
||||
- env: TOX_ENV=py34-djangomaster
|
||||
- env: TOX_ENV=py33-djangomaster
|
||||
- env: TOX_ENV=py32-djangomaster
|
||||
|
|
21
README.md
21
README.md
|
@ -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.5+, 1.6, 1.7)
|
||||
* Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7)
|
||||
|
||||
# Installation
|
||||
|
||||
|
@ -192,16 +192,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[index]: http://www.django-rest-framework.org/
|
||||
[oauth1-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
|
||||
[oauth2-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit
|
||||
[serializer-section]: http://www.django-rest-framework.org/api-guide/serializers.html#serializers
|
||||
[modelserializer-section]: http://www.django-rest-framework.org/api-guide/serializers.html#modelserializer
|
||||
[functionview-section]: http://www.django-rest-framework.org/api-guide/views.html#function-based-views
|
||||
[generic-views]: http://www.django-rest-framework.org/api-guide/generic-views.html
|
||||
[viewsets]: http://www.django-rest-framework.org/api-guide/viewsets.html
|
||||
[routers]: http://www.django-rest-framework.org/api-guide/routers.html
|
||||
[serializers]: http://www.django-rest-framework.org/api-guide/serializers.html
|
||||
[authentication]: http://www.django-rest-framework.org/api-guide/authentication.html
|
||||
|
||||
[rest-framework-2-announcement]: http://www.django-rest-framework.org/topics/rest-framework-2-announcement.html
|
||||
[serializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#serializers
|
||||
[modelserializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#modelserializer
|
||||
[functionview-section]: http://www.django-rest-framework.org/api-guide/views/#function-based-views
|
||||
[generic-views]: http://www.django-rest-framework.org/api-guide/generic-views/
|
||||
[viewsets]: http://www.django-rest-framework.org/api-guide/viewsets/
|
||||
[routers]: http://www.django-rest-framework.org/api-guide/routers/
|
||||
[serializers]: http://www.django-rest-framework.org/api-guide/serializers/
|
||||
[authentication]: http://www.django-rest-framework.org/api-guide/authentication/
|
||||
[rest-framework-2-announcement]: http://www.django-rest-framework.org/topics/rest-framework-2-announcement/
|
||||
[2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion
|
||||
[image]: http://www.django-rest-framework.org/img/quickstart.png
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ The value of `request.user` and `request.auth` for unauthenticated requests can
|
|||
|
||||
## Setting the authentication scheme
|
||||
|
||||
The default authentication schemes may be set globally, using the `DEFAULT_AUTHENTICATION` setting. For example.
|
||||
The default authentication schemes may be set globally, using the `DEFAULT_AUTHENTICATION_CLASSES` setting. For example.
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
|
@ -126,7 +126,6 @@ To use the `TokenAuthentication` scheme you'll need to [configure the authentica
|
|||
'rest_framework.authtoken'
|
||||
)
|
||||
|
||||
|
||||
---
|
||||
|
||||
**Note:** Make sure to run `manage.py syncdb` after changing your settings. The `rest_framework.authtoken` app provides both Django (from v1.7) and South database migrations. See [Schema migrations](#schema-migrations) below.
|
||||
|
@ -249,8 +248,6 @@ Unauthenticated responses that are denied permission will result in an `HTTP 403
|
|||
|
||||
If you're using an AJAX style API with SessionAuthentication, you'll need to make sure you include a valid CSRF token for any "unsafe" HTTP method calls, such as `PUT`, `PATCH`, `POST` or `DELETE` requests. See the [Django CSRF documentation][csrf-ajax] for more details.
|
||||
|
||||
---
|
||||
|
||||
# Custom authentication
|
||||
|
||||
To implement a custom authentication scheme, subclass `BaseAuthentication` and override the `.authenticate(self, request)` method. The method should return a two-tuple of `(user, auth)` if authentication succeeds, or `None` otherwise.
|
||||
|
|
|
@ -18,7 +18,7 @@ The handled exceptions are:
|
|||
|
||||
In each case, REST framework will return a response with an appropriate status code and content-type. The body of the response will include any additional details regarding the nature of the error.
|
||||
|
||||
By default all error responses will include a key `detail` in the body of the response, but other keys may also be included.
|
||||
Most error responses will include a key `detail` in the body of the response.
|
||||
|
||||
For example, the following request:
|
||||
|
||||
|
@ -33,6 +33,16 @@ Might receive an error response indicating that the `DELETE` method is not allow
|
|||
|
||||
{"detail": "Method 'DELETE' not allowed."}
|
||||
|
||||
Validation errors are handled slightly differently, and will include the field names as the keys in the response. If the validation error was not specific to a particular field then it will use the "non_field_errors" key, or whatever string value has been set for the `NON_FIELD_ERRORS_KEY` setting.
|
||||
|
||||
Any example validation error might look like this:
|
||||
|
||||
HTTP/1.1 400 Bad Request
|
||||
Content-Type: application/json
|
||||
Content-Length: 94
|
||||
|
||||
{"amount": ["A valid integer is required."], "description": ["This field may not be blank."]}
|
||||
|
||||
## Custom exception handling
|
||||
|
||||
You can implement custom exception handling by creating a handler function that converts exceptions raised in your API views into response objects. This allows you to control the style of error responses used by your API.
|
||||
|
|
|
@ -316,6 +316,7 @@ Typically you'd instead control this by setting `order_by` on the initial querys
|
|||
queryset = User.objects.all()
|
||||
serializer_class = UserSerializer
|
||||
filter_backends = (filters.OrderingFilter,)
|
||||
ordering_fields = ('username', 'email')
|
||||
ordering = ('username',)
|
||||
|
||||
The `ordering` attribute may be either a string or a list/tuple of strings.
|
||||
|
@ -390,9 +391,9 @@ We could achieve the same behavior by overriding `get_queryset()` on the views,
|
|||
|
||||
The following third party packages provide additional filter implementations.
|
||||
|
||||
## Django REST framework chain
|
||||
## Django REST framework filters package
|
||||
|
||||
The [django-rest-framework-chain package][django-rest-framework-chain] works together with the `DjangoFilterBackend` class, and allows you to easily create filters across relationships, or create multiple filter lookup types for a given field.
|
||||
The [django-rest-framework-filters package][django-rest-framework-filters] works together with the `DjangoFilterBackend` class, and allows you to easily create filters across relationships, or create multiple filter lookup types for a given field.
|
||||
|
||||
[cite]: https://docs.djangoproject.com/en/dev/topics/db/queries/#retrieving-specific-objects-with-filters
|
||||
[django-filter]: https://github.com/alex/django-filter
|
||||
|
@ -402,4 +403,4 @@ The [django-rest-framework-chain package][django-rest-framework-chain] works tog
|
|||
[view-permissions-blogpost]: http://blog.nyaruka.com/adding-a-view-permission-to-django-models
|
||||
[nullbooleanselect]: https://github.com/django/django/blob/master/django/forms/widgets.py
|
||||
[search-django-admin]: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields
|
||||
[django-rest-framework-chain]: https://github.com/philipn/django-rest-framework-chain
|
||||
[django-rest-framework-filters]: https://github.com/philipn/django-rest-framework-filters
|
||||
|
|
|
@ -50,7 +50,7 @@ Some reasons you might want to use REST framework:
|
|||
REST framework requires the following:
|
||||
|
||||
* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4)
|
||||
* Django (1.4.11+, 1.5.5+, 1.6, 1.7)
|
||||
* Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7)
|
||||
|
||||
The following packages are optional:
|
||||
|
||||
|
|
|
@ -87,12 +87,12 @@ The resulting API changes are further detailed below.
|
|||
|
||||
#### The `.create()` and `.update()` methods.
|
||||
|
||||
The `.restore_object()` method is now replaced with two separate methods, `.create()` and `.update()`.
|
||||
|
||||
These methods also replace the optional `.save_object()` method, which no longer exists.
|
||||
The `.restore_object()` method is now removed, and we instead have two separate methods, `.create()` and `.update()`. These methods work slightly different to the previous `.restore_object()`.
|
||||
|
||||
When using the `.create()` and `.update()` methods you should both create *and save* the object instance. This is in contrast to the previous `.restore_object()` behavior that would instantiate the object but not save it.
|
||||
|
||||
These methods also replace the optional `.save_object()` method, which no longer exists.
|
||||
|
||||
The following example from the tutorial previously used `restore_object()` to handle both creating and updating object instances.
|
||||
|
||||
def restore_object(self, attrs, instance=None):
|
||||
|
|
|
@ -58,6 +58,8 @@ The following template should be used for the description of the issue, and serv
|
|||
#### New members.
|
||||
|
||||
If you wish to be considered for this or a future date, please comment against this or subsequent issues.
|
||||
|
||||
To modify this process for future maintenance cycles make a pull request to the [project management](http://www.django-rest-framework.org/topics/project-management/) documentation.
|
||||
|
||||
#### Responsibilities of team members
|
||||
|
||||
|
@ -108,6 +110,8 @@ The following template should be used for the description of the issue, and serv
|
|||
- [ ] Make a release announcement on the [discussion group](https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework).
|
||||
- [ ] Make a release announcement on twitter.
|
||||
- [ ] Close the milestone on GitHub.
|
||||
|
||||
To modify this process for future releases make a pull request to the [project management](http://www.django-rest-framework.org/topics/project-management/) documentation.
|
||||
|
||||
When pushing the release to PyPI ensure that your environment has been installed from our development `requirement.txt`, so that documentation and PyPI installs are consistently being built against a pinned set of packages.
|
||||
|
||||
|
@ -176,6 +180,7 @@ The following issues still need to be addressed:
|
|||
* Ensure `@jamie` has back-up access to the `django-rest-framework.org` domain setup and admin.
|
||||
* Document ownership of the [live example][sandbox] API.
|
||||
* Document ownership of the [mailing list][mailing-list] and IRC channel.
|
||||
* Document ownership and management of the security mailing list.
|
||||
|
||||
[bus-factor]: http://en.wikipedia.org/wiki/Bus_factor
|
||||
[un-triaged]: https://github.com/tomchristie/django-rest-framework/issues?q=is%3Aopen+no%3Alabel
|
||||
|
|
|
@ -40,6 +40,27 @@ You can determine your currently installed version using `pip freeze`:
|
|||
|
||||
## 3.0.x series
|
||||
|
||||
|
||||
### 3.0.3
|
||||
|
||||
**Date**: [8th January 2015][3.0.3-milestone].
|
||||
|
||||
* Fix `MinValueValidator` on `models.DateField`. ([#2369][gh2369])
|
||||
* Fix serializer missing context when pagination is used. ([#2355][gh2355])
|
||||
* Namespaced router URLs are now supported by the `DefaultRouter`. ([#2351][gh2351])
|
||||
* `required=False` allows omission of value for output. ([#2342][gh2342])
|
||||
* Use textarea input for `models.TextField`. ([#2340][gh2340])
|
||||
* Use custom `ListSerializer` for pagination if required. ([#2331][gh2331], [#2327][gh2327])
|
||||
* Better behavior with null and '' for blank HTML fields. ([#2330][gh2330])
|
||||
* Ensure fields in `exclude` are model fields. ([#2319][gh2319])
|
||||
* Fix `IntegerField` and `max_length` argument incompatibility. ([#2317][gh2317])
|
||||
* Fix the YAML encoder for 3.0 serializers. ([#2315][gh2315], [#2283][gh2283])
|
||||
* Fix the behavior of empty HTML fields. ([#2311][gh2311], [#1101][gh1101])
|
||||
* Fix Metaclass attribute depth ignoring fields attribute. ([#2287][gh2287])
|
||||
* Fix `format_suffix_patterns` to work with Django's `i18n_patterns`. ([#2278][gh2278])
|
||||
* Ability to customize router URLs for custom actions, using `url_path`. ([#2010][gh2010])
|
||||
* Don't install Django REST Framework as egg. ([#2386][gh2386])
|
||||
|
||||
### 3.0.2
|
||||
|
||||
**Date**: [17th December 2014][3.0.2-milestone].
|
||||
|
@ -680,6 +701,7 @@ For older release notes, [please see the GitHub repo](old-release-notes).
|
|||
|
||||
[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.3-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.3+Release%22
|
||||
|
||||
<!-- 3.0.1 -->
|
||||
[gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013
|
||||
|
@ -729,3 +751,22 @@ For older release notes, [please see the GitHub repo](old-release-notes).
|
|||
[gh2290]: https://github.com/tomchristie/django-rest-framework/issues/2290
|
||||
[gh2291]: https://github.com/tomchristie/django-rest-framework/issues/2291
|
||||
[gh2294]: https://github.com/tomchristie/django-rest-framework/issues/2294
|
||||
<!-- 3.0.3 -->
|
||||
[gh1101]: https://github.com/tomchristie/django-rest-framework/issues/1101
|
||||
[gh2010]: https://github.com/tomchristie/django-rest-framework/issues/2010
|
||||
[gh2278]: https://github.com/tomchristie/django-rest-framework/issues/2278
|
||||
[gh2283]: https://github.com/tomchristie/django-rest-framework/issues/2283
|
||||
[gh2287]: https://github.com/tomchristie/django-rest-framework/issues/2287
|
||||
[gh2311]: https://github.com/tomchristie/django-rest-framework/issues/2311
|
||||
[gh2315]: https://github.com/tomchristie/django-rest-framework/issues/2315
|
||||
[gh2317]: https://github.com/tomchristie/django-rest-framework/issues/2317
|
||||
[gh2319]: https://github.com/tomchristie/django-rest-framework/issues/2319
|
||||
[gh2327]: https://github.com/tomchristie/django-rest-framework/issues/2327
|
||||
[gh2330]: https://github.com/tomchristie/django-rest-framework/issues/2330
|
||||
[gh2331]: https://github.com/tomchristie/django-rest-framework/issues/2331
|
||||
[gh2340]: https://github.com/tomchristie/django-rest-framework/issues/2340
|
||||
[gh2342]: https://github.com/tomchristie/django-rest-framework/issues/2342
|
||||
[gh2351]: https://github.com/tomchristie/django-rest-framework/issues/2351
|
||||
[gh2355]: https://github.com/tomchristie/django-rest-framework/issues/2355
|
||||
[gh2369]: https://github.com/tomchristie/django-rest-framework/issues/2369
|
||||
[gh2386]: https://github.com/tomchristie/django-rest-framework/issues/2386
|
||||
|
|
|
@ -191,7 +191,7 @@ Our `SnippetSerializer` class is replicating a lot of information that's also co
|
|||
In the same way that Django provides both `Form` classes and `ModelForm` classes, REST framework includes both `Serializer` classes, and `ModelSerializer` classes.
|
||||
|
||||
Let's look at refactoring our serializer using the `ModelSerializer` class.
|
||||
Open the file `snippets/serializers.py` again, and edit the `SnippetSerializer` class.
|
||||
Open the file `snippets/serializers.py` again, and replace the `SnippetSerializer` class with the following.
|
||||
|
||||
class SnippetSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
|
|
|
@ -64,7 +64,7 @@ That's looking good. Again, it's still pretty similar to the function based vie
|
|||
|
||||
We'll also need to refactor our `urls.py` slightly now we're using class based views.
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from django.conf.urls import url
|
||||
from rest_framework.urlpatterns import format_suffix_patterns
|
||||
from snippets import views
|
||||
|
||||
|
|
|
@ -177,7 +177,7 @@ In the snippets app, create a new file, `permissions.py`
|
|||
# Write permissions are only allowed to the owner of the snippet.
|
||||
return obj.owner == request.user
|
||||
|
||||
Now we can add that custom permission to our snippet instance endpoint, by editing the `permission_classes` property on the `SnippetDetail` class:
|
||||
Now we can add that custom permission to our snippet instance endpoint, by editing the `permission_classes` property on the `SnippetDetail` view class:
|
||||
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
|
||||
IsOwnerOrReadOnly,)
|
||||
|
|
|
@ -106,6 +106,8 @@ If we're going to have a hyperlinked API, we need to make sure we name our URL p
|
|||
|
||||
After adding all those names into our URLconf, our final `snippets/urls.py` file should look something like this:
|
||||
|
||||
from django.conf.urls import url, include
|
||||
|
||||
# API endpoints
|
||||
urlpatterns = format_suffix_patterns([
|
||||
url(r'^$', views.api_root),
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
Django>=1.4.11
|
||||
|
||||
# Test requirements
|
||||
pytest-django==2.6
|
||||
pytest==2.5.2
|
||||
pytest-django==2.8.0
|
||||
pytest==2.6.4
|
||||
pytest-cov==1.6
|
||||
flake8==2.2.2
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ ______ _____ _____ _____ __
|
|||
"""
|
||||
|
||||
__title__ = 'Django REST framework'
|
||||
__version__ = '3.0.2'
|
||||
__version__ = '3.0.3'
|
||||
__author__ = 'Tom Christie'
|
||||
__license__ = 'BSD 2-Clause'
|
||||
__copyright__ = 'Copyright 2011-2015 Tom Christie'
|
||||
|
|
|
@ -8,7 +8,7 @@ from __future__ import unicode_literals
|
|||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.six.moves.urllib import parse as urlparse
|
||||
from django.utils.six.moves.urllib.parse import urlparse as _urlparse
|
||||
from django.utils import six
|
||||
import django
|
||||
import inspect
|
||||
|
@ -38,10 +38,18 @@ def unicode_http_header(value):
|
|||
return value
|
||||
|
||||
|
||||
def total_seconds(timedelta):
|
||||
# TimeDelta.total_seconds() is only available in Python 2.7
|
||||
if hasattr(timedelta, 'total_seconds'):
|
||||
return timedelta.total_seconds()
|
||||
else:
|
||||
return (timedelta.days * 86400.0) + float(timedelta.seconds) + (timedelta.microseconds / 1000000.0)
|
||||
|
||||
|
||||
# OrderedDict only available in Python 2.7.
|
||||
# This will always be the case in Django 1.7 and above, as these versions
|
||||
# no longer support Python 2.6.
|
||||
# For Django <= 1.6 and Python 2.6 fall back to OrderedDict.
|
||||
# For Django <= 1.6 and Python 2.6 fall back to SortedDict.
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
|
@ -187,7 +195,7 @@ except ImportError:
|
|||
class RequestFactory(DjangoRequestFactory):
|
||||
def generic(self, method, path,
|
||||
data='', content_type='application/octet-stream', **extra):
|
||||
parsed = urlparse.urlparse(path)
|
||||
parsed = _urlparse(path)
|
||||
data = force_bytes_or_smart_bytes(data, settings.DEFAULT_CHARSET)
|
||||
r = {
|
||||
'PATH_INFO': self._get_path(parsed),
|
||||
|
|
|
@ -179,7 +179,7 @@ class FileUploadParser(BaseParser):
|
|||
for index, handler in enumerate(upload_handlers):
|
||||
file_obj = handler.file_complete(counters[index])
|
||||
if file_obj:
|
||||
return DataAndFiles(None, {'file': file_obj})
|
||||
return DataAndFiles({}, {'file': file_obj})
|
||||
raise ParseError("FileUpload parse error - "
|
||||
"none of upload handlers can handle the stream")
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ from django.utils import six
|
|||
from django.utils.encoding import smart_text
|
||||
from django.utils.six.moves.urllib import parse as urlparse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework.compat import OrderedDict
|
||||
from rest_framework.fields import get_attribute, empty, Field
|
||||
from rest_framework.reverse import reverse
|
||||
from rest_framework.utils import html
|
||||
|
@ -103,7 +104,7 @@ class RelatedField(Field):
|
|||
|
||||
@property
|
||||
def choices(self):
|
||||
return dict([
|
||||
return OrderedDict([
|
||||
(
|
||||
six.text_type(self.to_representation(item)),
|
||||
six.text_type(item)
|
||||
|
@ -364,7 +365,7 @@ class ManyRelatedField(Field):
|
|||
(item, self.child_relation.to_representation(item))
|
||||
for item in iterable
|
||||
]
|
||||
return dict([
|
||||
return OrderedDict([
|
||||
(
|
||||
six.text_type(item_representation),
|
||||
six.text_type(item) + ' - ' + six.text_type(item_representation)
|
||||
|
|
|
@ -43,7 +43,7 @@ class BaseRenderer(object):
|
|||
render_style = 'text'
|
||||
|
||||
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||
raise NotImplemented('Renderer class requires .render() to be implemented')
|
||||
raise NotImplementedError('Renderer class requires .render() to be implemented')
|
||||
|
||||
|
||||
class JSONRenderer(BaseRenderer):
|
||||
|
|
|
@ -65,13 +65,13 @@ class BaseRouter(object):
|
|||
If `base_name` is not specified, attempt to automatically determine
|
||||
it from the viewset.
|
||||
"""
|
||||
raise NotImplemented('get_default_base_name must be overridden')
|
||||
raise NotImplementedError('get_default_base_name must be overridden')
|
||||
|
||||
def get_urls(self):
|
||||
"""
|
||||
Return a list of URL patterns, given the registered viewsets.
|
||||
"""
|
||||
raise NotImplemented('get_urls must be overridden')
|
||||
raise NotImplementedError('get_urls must be overridden')
|
||||
|
||||
@property
|
||||
def urls(self):
|
||||
|
|
|
@ -176,7 +176,7 @@ class APISettings(object):
|
|||
For example:
|
||||
|
||||
from rest_framework.settings import api_settings
|
||||
print api_settings.DEFAULT_RENDERER_CLASSES
|
||||
print(api_settings.DEFAULT_RENDERER_CLASSES)
|
||||
|
||||
Any setting with string import paths will be automatically resolved
|
||||
and return the class, rather than the string literal.
|
||||
|
|
|
@ -32,10 +32,10 @@ class BaseThrottle(object):
|
|||
if num_proxies == 0 or xff is None:
|
||||
return remote_addr
|
||||
addrs = xff.split(',')
|
||||
client_addr = addrs[-min(num_proxies, len(xff))]
|
||||
client_addr = addrs[-min(num_proxies, len(addrs))]
|
||||
return client_addr.strip()
|
||||
|
||||
return xff if xff else remote_addr
|
||||
return ''.join(xff.split()) if xff else remote_addr
|
||||
|
||||
def wait(self):
|
||||
"""
|
||||
|
@ -173,12 +173,6 @@ class AnonRateThrottle(SimpleRateThrottle):
|
|||
if request.user.is_authenticated():
|
||||
return None # Only throttle unauthenticated requests.
|
||||
|
||||
ident = request.META.get('HTTP_X_FORWARDED_FOR')
|
||||
if ident is None:
|
||||
ident = request.META.get('REMOTE_ADDR')
|
||||
else:
|
||||
ident = ''.join(ident.split())
|
||||
|
||||
return self.cache_format % {
|
||||
'scope': self.scope,
|
||||
'ident': self.get_ident(request)
|
||||
|
|
|
@ -6,9 +6,11 @@ from django.db.models.query import QuerySet
|
|||
from django.utils import six, timezone
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.functional import Promise
|
||||
from rest_framework.compat import total_seconds
|
||||
import datetime
|
||||
import decimal
|
||||
import json
|
||||
import uuid
|
||||
|
||||
|
||||
class JSONEncoder(json.JSONEncoder):
|
||||
|
@ -38,10 +40,12 @@ class JSONEncoder(json.JSONEncoder):
|
|||
representation = representation[:12]
|
||||
return representation
|
||||
elif isinstance(obj, datetime.timedelta):
|
||||
return six.text_type(obj.total_seconds())
|
||||
return six.text_type(total_seconds(obj))
|
||||
elif isinstance(obj, decimal.Decimal):
|
||||
# Serializers will coerce decimals to strings by default.
|
||||
return float(obj)
|
||||
elif isinstance(obj, uuid.UUID):
|
||||
return six.text_type(obj)
|
||||
elif isinstance(obj, QuerySet):
|
||||
return tuple(obj)
|
||||
elif hasattr(obj, 'tolist'):
|
||||
|
|
|
@ -16,6 +16,9 @@ class ReturnDict(OrderedDict):
|
|||
def copy(self):
|
||||
return ReturnDict(self, serializer=self.serializer)
|
||||
|
||||
def __repr__(self):
|
||||
return dict.__repr__(self)
|
||||
|
||||
|
||||
class ReturnList(list):
|
||||
"""
|
||||
|
@ -27,6 +30,9 @@ class ReturnList(list):
|
|||
self.serializer = kwargs.pop('serializer')
|
||||
super(ReturnList, self).__init__(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return list.__repr__(self)
|
||||
|
||||
|
||||
class BoundField(object):
|
||||
"""
|
||||
|
|
1
setup.py
1
setup.py
|
@ -67,6 +67,7 @@ setup(
|
|||
packages=get_packages('rest_framework'),
|
||||
package_data=get_package_data('rest_framework'),
|
||||
install_requires=[],
|
||||
zip_safe=False,
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Web Environment',
|
||||
|
|
|
@ -48,8 +48,8 @@ class InheritedModelSerializationTests(TestCase):
|
|||
Assert that a model with a onetoone field that is the primary key is
|
||||
not treated like a derived model
|
||||
"""
|
||||
parent = ParentModel(name1='parent name')
|
||||
associate = AssociatedModel(name='hello', ref=parent)
|
||||
parent = ParentModel.objects.create(name1='parent name')
|
||||
associate = AssociatedModel.objects.create(name='hello', ref=parent)
|
||||
serializer = AssociatedModelSerializer(associate)
|
||||
self.assertEqual(set(serializer.data.keys()),
|
||||
set(['name', 'ref']))
|
||||
|
|
16
tox.ini
16
tox.ini
|
@ -3,27 +3,27 @@ envlist =
|
|||
py27-{flake8,docs},
|
||||
{py26,py27}-django14,
|
||||
{py26,py27,py32,py33,py34}-django{15,16},
|
||||
{py27,py32,py33,py34}-django17,
|
||||
{py27,py32,py33,py34}-djangomaster
|
||||
{py27,py32,py33,py34}-django{17,18alpha,master}
|
||||
|
||||
[testenv]
|
||||
commands = ./runtests.py --fast
|
||||
setenv =
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
deps =
|
||||
django14: Django==1.4.11
|
||||
django15: Django==1.5.5
|
||||
django16: Django==1.6.8
|
||||
django17: Django==1.7.1
|
||||
django14: Django==1.4.11 # Should track minimum supported
|
||||
django15: Django==1.5.6 # Should track minimum supported
|
||||
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.6.1
|
||||
pytest-django==2.8.0
|
||||
django-filter==0.9.1
|
||||
markdown>=2.1.0
|
||||
|
||||
[testenv:py27-flake8]
|
||||
deps =
|
||||
pytest==2.5.2
|
||||
pytest==2.6.4
|
||||
flake8==2.2.2
|
||||
commands = ./runtests.py --lintonly
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user