diff --git a/.travis.yml b/.travis.yml index 6191e7e26..996c3ae80 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/README.md b/README.md index 6742a7b1d..a03517f47 100644 --- a/README.md +++ b/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 diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index bb7318177..ba1145136 100755 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -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. diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index 50bd14dd2..56811ec33 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -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. diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index 83977048f..e00560c7e 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -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 diff --git a/docs/index.md b/docs/index.md index 163769851..efd99a4e9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -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: diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 68d247827..5dbc5600a 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -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): diff --git a/docs/topics/project-management.md b/docs/topics/project-management.md index 7f7051966..2a54fb94e 100644 --- a/docs/topics/project-management.md +++ b/docs/topics/project-management.md @@ -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 diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index b9216e36f..c49dd62c9 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -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 [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 + +[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 diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index 60a3d9897..41ff4d073 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -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: diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md index 0a9ea3f15..abf82e495 100644 --- a/docs/tutorial/3-class-based-views.md +++ b/docs/tutorial/3-class-based-views.md @@ -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 diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index 592c77e81..887d1e56f 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -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,) diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md index c21efd7f6..2841f03e9 100644 --- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md @@ -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), diff --git a/requirements.txt b/requirements.txt index fe5edbe78..080206bc3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index dec89b3e9..fdcebb7b7 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -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' diff --git a/rest_framework/compat.py b/rest_framework/compat.py index ea3429942..bd3802ada 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -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), diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index cb23423db..dd0698402 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -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") diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 05ac3d1c6..a85edfecc 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -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) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 7af03c674..f970a3638 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -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): diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 61f3ccab0..827da0340 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -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): diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 3cce26b1c..ca5af86e3 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -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. diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 361dbddf0..0f10136d6 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -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) diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index 0bd249399..2160d18b6 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -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'): diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py index 65a04d06b..f99606039 100644 --- a/rest_framework/utils/serializer_helpers.py +++ b/rest_framework/utils/serializer_helpers.py @@ -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): """ diff --git a/setup.py b/setup.py index 1e54836c1..efe39d8d4 100755 --- a/setup.py +++ b/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', diff --git a/tests/test_multitable_inheritance.py b/tests/test_multitable_inheritance.py index e1b40cc74..15627e1dd 100644 --- a/tests/test_multitable_inheritance.py +++ b/tests/test_multitable_inheritance.py @@ -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'])) diff --git a/tox.ini b/tox.ini index 9fb8586c6..9be14bf89 100644 --- a/tox.ini +++ b/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