Merge master

This commit is contained in:
Tom Christie 2015-01-19 15:16:57 +00:00
commit 6065cdbd93
27 changed files with 135 additions and 58 deletions

View File

@ -21,6 +21,10 @@ env:
- TOX_ENV=py26-django15 - TOX_ENV=py26-django15
- TOX_ENV=py27-django14 - TOX_ENV=py27-django14
- TOX_ENV=py26-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=py34-djangomaster
- TOX_ENV=py33-djangomaster - TOX_ENV=py33-djangomaster
- TOX_ENV=py32-djangomaster - TOX_ENV=py32-djangomaster
@ -29,6 +33,10 @@ env:
matrix: matrix:
fast_finish: true fast_finish: true
allow_failures: 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=py34-djangomaster
- env: TOX_ENV=py33-djangomaster - env: TOX_ENV=py33-djangomaster
- env: TOX_ENV=py32-djangomaster - env: TOX_ENV=py32-djangomaster

View File

@ -34,7 +34,7 @@ There is a live example API for testing purposes, [available here][sandbox].
# Requirements # Requirements
* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4) * 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 # Installation
@ -192,16 +192,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[index]: http://www.django-rest-framework.org/ [index]: http://www.django-rest-framework.org/
[oauth1-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth [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 [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 [serializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#serializers
[modelserializer-section]: http://www.django-rest-framework.org/api-guide/serializers.html#modelserializer [modelserializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#modelserializer
[functionview-section]: http://www.django-rest-framework.org/api-guide/views.html#function-based-views [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.html [generic-views]: http://www.django-rest-framework.org/api-guide/generic-views/
[viewsets]: http://www.django-rest-framework.org/api-guide/viewsets.html [viewsets]: http://www.django-rest-framework.org/api-guide/viewsets/
[routers]: http://www.django-rest-framework.org/api-guide/routers.html [routers]: http://www.django-rest-framework.org/api-guide/routers/
[serializers]: http://www.django-rest-framework.org/api-guide/serializers.html [serializers]: http://www.django-rest-framework.org/api-guide/serializers/
[authentication]: http://www.django-rest-framework.org/api-guide/authentication.html [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/
[rest-framework-2-announcement]: http://www.django-rest-framework.org/topics/rest-framework-2-announcement.html
[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
[image]: http://www.django-rest-framework.org/img/quickstart.png [image]: http://www.django-rest-framework.org/img/quickstart.png

View File

@ -34,7 +34,7 @@ The value of `request.user` and `request.auth` for unauthenticated requests can
## Setting the authentication scheme ## 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 = { REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ( 'DEFAULT_AUTHENTICATION_CLASSES': (
@ -126,7 +126,6 @@ To use the `TokenAuthentication` scheme you'll need to [configure the authentica
'rest_framework.authtoken' '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. **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. 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 # 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. 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.

View File

@ -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. 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: 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."} {"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 ## 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. 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.

View File

@ -316,6 +316,7 @@ Typically you'd instead control this by setting `order_by` on the initial querys
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = UserSerializer serializer_class = UserSerializer
filter_backends = (filters.OrderingFilter,) filter_backends = (filters.OrderingFilter,)
ordering_fields = ('username', 'email')
ordering = ('username',) ordering = ('username',)
The `ordering` attribute may be either a string or a list/tuple of strings. 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. 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 [cite]: https://docs.djangoproject.com/en/dev/topics/db/queries/#retrieving-specific-objects-with-filters
[django-filter]: https://github.com/alex/django-filter [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 [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 [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 [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

View File

@ -50,7 +50,7 @@ Some reasons you might want to use REST framework:
REST framework requires the following: REST framework requires the following:
* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4) * 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: The following packages are optional:

View File

@ -87,12 +87,12 @@ The resulting API changes are further detailed below.
#### The `.create()` and `.update()` methods. #### The `.create()` and `.update()` methods.
The `.restore_object()` method is now replaced with two separate methods, `.create()` and `.update()`. 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()`.
These methods also replace the optional `.save_object()` method, which no longer exists.
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. 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. 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): def restore_object(self, attrs, instance=None):

View File

@ -59,6 +59,8 @@ The following template should be used for the description of the issue, and serv
If you wish to be considered for this or a future date, please comment against this or subsequent issues. 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 #### Responsibilities of team members
Team members have the following responsibilities. Team members have the following responsibilities.
@ -109,6 +111,8 @@ The following template should be used for the description of the issue, and serv
- [ ] Make a release announcement on twitter. - [ ] Make a release announcement on twitter.
- [ ] Close the milestone on GitHub. - [ ] 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. 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. * 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 [live example][sandbox] API.
* Document ownership of the [mailing list][mailing-list] and IRC channel. * 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 [bus-factor]: http://en.wikipedia.org/wiki/Bus_factor
[un-triaged]: https://github.com/tomchristie/django-rest-framework/issues?q=is%3Aopen+no%3Alabel [un-triaged]: https://github.com/tomchristie/django-rest-framework/issues?q=is%3Aopen+no%3Alabel

View File

@ -40,6 +40,27 @@ You can determine your currently installed version using `pip freeze`:
## 3.0.x series ## 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 ### 3.0.2
**Date**: [17th December 2014][3.0.2-milestone]. **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.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
[3.0.3-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.3+Release%22
<!-- 3.0.1 --> <!-- 3.0.1 -->
[gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013 [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 [gh2290]: https://github.com/tomchristie/django-rest-framework/issues/2290
[gh2291]: https://github.com/tomchristie/django-rest-framework/issues/2291 [gh2291]: https://github.com/tomchristie/django-rest-framework/issues/2291
[gh2294]: https://github.com/tomchristie/django-rest-framework/issues/2294 [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

View File

@ -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. 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. 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 SnippetSerializer(serializers.ModelSerializer):
class Meta: class Meta:

View File

@ -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. 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 rest_framework.urlpatterns import format_suffix_patterns
from snippets import views from snippets import views

View File

@ -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. # Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user 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, permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,) IsOwnerOrReadOnly,)

View File

@ -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: 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 # API endpoints
urlpatterns = format_suffix_patterns([ urlpatterns = format_suffix_patterns([
url(r'^$', views.api_root), url(r'^$', views.api_root),

View File

@ -2,8 +2,8 @@
Django>=1.4.11 Django>=1.4.11
# Test requirements # Test requirements
pytest-django==2.6 pytest-django==2.8.0
pytest==2.5.2 pytest==2.6.4
pytest-cov==1.6 pytest-cov==1.6
flake8==2.2.2 flake8==2.2.2

View File

@ -8,7 +8,7 @@ ______ _____ _____ _____ __
""" """
__title__ = 'Django REST framework' __title__ = 'Django REST framework'
__version__ = '3.0.2' __version__ = '3.0.3'
__author__ = 'Tom Christie' __author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause' __license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2015 Tom Christie' __copyright__ = 'Copyright 2011-2015 Tom Christie'

View File

@ -8,7 +8,7 @@ from __future__ import unicode_literals
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.conf import settings from django.conf import settings
from django.utils.encoding import force_text 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 from django.utils import six
import django import django
import inspect import inspect
@ -38,10 +38,18 @@ def unicode_http_header(value):
return 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. # OrderedDict only available in Python 2.7.
# This will always be the case in Django 1.7 and above, as these versions # This will always be the case in Django 1.7 and above, as these versions
# no longer support Python 2.6. # 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: try:
from collections import OrderedDict from collections import OrderedDict
except ImportError: except ImportError:
@ -187,7 +195,7 @@ except ImportError:
class RequestFactory(DjangoRequestFactory): class RequestFactory(DjangoRequestFactory):
def generic(self, method, path, def generic(self, method, path,
data='', content_type='application/octet-stream', **extra): data='', content_type='application/octet-stream', **extra):
parsed = urlparse.urlparse(path) parsed = _urlparse(path)
data = force_bytes_or_smart_bytes(data, settings.DEFAULT_CHARSET) data = force_bytes_or_smart_bytes(data, settings.DEFAULT_CHARSET)
r = { r = {
'PATH_INFO': self._get_path(parsed), 'PATH_INFO': self._get_path(parsed),

View File

@ -179,7 +179,7 @@ class FileUploadParser(BaseParser):
for index, handler in enumerate(upload_handlers): for index, handler in enumerate(upload_handlers):
file_obj = handler.file_complete(counters[index]) file_obj = handler.file_complete(counters[index])
if file_obj: if file_obj:
return DataAndFiles(None, {'file': file_obj}) return DataAndFiles({}, {'file': file_obj})
raise ParseError("FileUpload parse error - " raise ParseError("FileUpload parse error - "
"none of upload handlers can handle the stream") "none of upload handlers can handle the stream")

View File

@ -7,6 +7,7 @@ from django.utils import six
from django.utils.encoding import smart_text from django.utils.encoding import smart_text
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_lazy as _ 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.fields import get_attribute, empty, Field
from rest_framework.reverse import reverse from rest_framework.reverse import reverse
from rest_framework.utils import html from rest_framework.utils import html
@ -103,7 +104,7 @@ class RelatedField(Field):
@property @property
def choices(self): def choices(self):
return dict([ return OrderedDict([
( (
six.text_type(self.to_representation(item)), six.text_type(self.to_representation(item)),
six.text_type(item) six.text_type(item)
@ -364,7 +365,7 @@ class ManyRelatedField(Field):
(item, self.child_relation.to_representation(item)) (item, self.child_relation.to_representation(item))
for item in iterable for item in iterable
] ]
return dict([ return OrderedDict([
( (
six.text_type(item_representation), six.text_type(item_representation),
six.text_type(item) + ' - ' + six.text_type(item_representation) six.text_type(item) + ' - ' + six.text_type(item_representation)

View File

@ -43,7 +43,7 @@ class BaseRenderer(object):
render_style = 'text' render_style = 'text'
def render(self, data, accepted_media_type=None, renderer_context=None): 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): class JSONRenderer(BaseRenderer):

View File

@ -65,13 +65,13 @@ class BaseRouter(object):
If `base_name` is not specified, attempt to automatically determine If `base_name` is not specified, attempt to automatically determine
it from the viewset. 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): def get_urls(self):
""" """
Return a list of URL patterns, given the registered viewsets. 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 @property
def urls(self): def urls(self):

View File

@ -176,7 +176,7 @@ class APISettings(object):
For example: For example:
from rest_framework.settings import api_settings 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 Any setting with string import paths will be automatically resolved
and return the class, rather than the string literal. and return the class, rather than the string literal.

View File

@ -32,10 +32,10 @@ class BaseThrottle(object):
if num_proxies == 0 or xff is None: if num_proxies == 0 or xff is None:
return remote_addr return remote_addr
addrs = xff.split(',') addrs = xff.split(',')
client_addr = addrs[-min(num_proxies, len(xff))] client_addr = addrs[-min(num_proxies, len(addrs))]
return client_addr.strip() return client_addr.strip()
return xff if xff else remote_addr return ''.join(xff.split()) if xff else remote_addr
def wait(self): def wait(self):
""" """
@ -173,12 +173,6 @@ class AnonRateThrottle(SimpleRateThrottle):
if request.user.is_authenticated(): if request.user.is_authenticated():
return None # Only throttle unauthenticated requests. 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 % { return self.cache_format % {
'scope': self.scope, 'scope': self.scope,
'ident': self.get_ident(request) 'ident': self.get_ident(request)

View File

@ -6,9 +6,11 @@ from django.db.models.query import QuerySet
from django.utils import six, timezone from django.utils import six, timezone
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.functional import Promise from django.utils.functional import Promise
from rest_framework.compat import total_seconds
import datetime import datetime
import decimal import decimal
import json import json
import uuid
class JSONEncoder(json.JSONEncoder): class JSONEncoder(json.JSONEncoder):
@ -38,10 +40,12 @@ class JSONEncoder(json.JSONEncoder):
representation = representation[:12] representation = representation[:12]
return representation return representation
elif isinstance(obj, datetime.timedelta): elif isinstance(obj, datetime.timedelta):
return six.text_type(obj.total_seconds()) return six.text_type(total_seconds(obj))
elif isinstance(obj, decimal.Decimal): elif isinstance(obj, decimal.Decimal):
# Serializers will coerce decimals to strings by default. # Serializers will coerce decimals to strings by default.
return float(obj) return float(obj)
elif isinstance(obj, uuid.UUID):
return six.text_type(obj)
elif isinstance(obj, QuerySet): elif isinstance(obj, QuerySet):
return tuple(obj) return tuple(obj)
elif hasattr(obj, 'tolist'): elif hasattr(obj, 'tolist'):

View File

@ -16,6 +16,9 @@ class ReturnDict(OrderedDict):
def copy(self): def copy(self):
return ReturnDict(self, serializer=self.serializer) return ReturnDict(self, serializer=self.serializer)
def __repr__(self):
return dict.__repr__(self)
class ReturnList(list): class ReturnList(list):
""" """
@ -27,6 +30,9 @@ class ReturnList(list):
self.serializer = kwargs.pop('serializer') self.serializer = kwargs.pop('serializer')
super(ReturnList, self).__init__(*args, **kwargs) super(ReturnList, self).__init__(*args, **kwargs)
def __repr__(self):
return list.__repr__(self)
class BoundField(object): class BoundField(object):
""" """

View File

@ -67,6 +67,7 @@ setup(
packages=get_packages('rest_framework'), packages=get_packages('rest_framework'),
package_data=get_package_data('rest_framework'), package_data=get_package_data('rest_framework'),
install_requires=[], install_requires=[],
zip_safe=False,
classifiers=[ classifiers=[
'Development Status :: 5 - Production/Stable', 'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment', 'Environment :: Web Environment',

View File

@ -48,8 +48,8 @@ class InheritedModelSerializationTests(TestCase):
Assert that a model with a onetoone field that is the primary key is Assert that a model with a onetoone field that is the primary key is
not treated like a derived model not treated like a derived model
""" """
parent = ParentModel(name1='parent name') parent = ParentModel.objects.create(name1='parent name')
associate = AssociatedModel(name='hello', ref=parent) associate = AssociatedModel.objects.create(name='hello', ref=parent)
serializer = AssociatedModelSerializer(associate) serializer = AssociatedModelSerializer(associate)
self.assertEqual(set(serializer.data.keys()), self.assertEqual(set(serializer.data.keys()),
set(['name', 'ref'])) set(['name', 'ref']))

16
tox.ini
View File

@ -3,27 +3,27 @@ envlist =
py27-{flake8,docs}, py27-{flake8,docs},
{py26,py27}-django14, {py26,py27}-django14,
{py26,py27,py32,py33,py34}-django{15,16}, {py26,py27,py32,py33,py34}-django{15,16},
{py27,py32,py33,py34}-django17, {py27,py32,py33,py34}-django{17,18alpha,master}
{py27,py32,py33,py34}-djangomaster
[testenv] [testenv]
commands = ./runtests.py --fast commands = ./runtests.py --fast
setenv = setenv =
PYTHONDONTWRITEBYTECODE=1 PYTHONDONTWRITEBYTECODE=1
deps = deps =
django14: Django==1.4.11 django14: Django==1.4.11 # Should track minimum supported
django15: Django==1.5.5 django15: Django==1.5.6 # Should track minimum supported
django16: Django==1.6.8 django16: Django==1.6.3 # Should track minimum supported
django17: Django==1.7.1 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 djangomaster: https://github.com/django/django/zipball/master
django-guardian==1.2.4 django-guardian==1.2.4
pytest-django==2.6.1 pytest-django==2.8.0
django-filter==0.9.1 django-filter==0.9.1
markdown>=2.1.0 markdown>=2.1.0
[testenv:py27-flake8] [testenv:py27-flake8]
deps = deps =
pytest==2.5.2 pytest==2.6.4
flake8==2.2.2 flake8==2.2.2
commands = ./runtests.py --lintonly commands = ./runtests.py --lintonly