diff --git a/.travis.yml b/.travis.yml index a5b6d7d918..7dec96cf16 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,8 @@ env: - TOX_ENV=py2.6-django1.4 install: - - "pip install tox --download-cache $HOME/.pip-cache" +# Virtualenv < 14 is required to keep the Python 3.2 builds running. + - "pip install tox 'virtualenv<14' --download-cache $HOME/.pip-cache" script: - tox -e $TOX_ENV diff --git a/README.md b/README.md index 428fb8e9d1..fd91064168 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ **Awesome web-browseable Web APIs.** -**Note**: Full documentation for the project is available at [http://www.django-rest-framework.org][docs]. +**Note**: Full documentation for the project is available at [http://tomchristie.github.io/rest-framework-2-docs/][docs]. # Overview @@ -135,7 +135,7 @@ Or to create a new user: # Documentation & Support -Full documentation for the project is available at [http://www.django-rest-framework.org][docs]. +Full documentation for the project is available at [http://tomchristie.github.io/rest-framework-2-docs/][docs]. For questions and support, use the [REST framework discussion group][group], or `#restframework` on freenode IRC. @@ -202,7 +202,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [wlonk]: https://twitter.com/wlonk/status/261689665952833536 [laserllama]: https://twitter.com/laserllama/status/328688333750407168 -[docs]: http://www.django-rest-framework.org/ +[docs]: http://tomchristie.github.io/rest-framework-2-docs/ [urlobject]: https://github.com/zacharyvoase/urlobject [markdown]: http://pypi.python.org/pypi/Markdown/ [pyyaml]: http://pypi.python.org/pypi/PyYAML diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 11d12ae326..682d57b130 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -40,6 +40,28 @@ You can determine your currently installed version using `pip freeze`: ## 2.4.x series +### 2.4.8 + +**Date**: 18 August 2015 + +* Repackage 2.4.7 without pyc files. + + +### 2.4.7 + +**Date**: [18 August 2015](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.7+Release%22+) + +* Upgrade guardian support to 1.3 + + +### 2.4.6 + +### 2.4.5 + +**Date**: 24 March 2015 + +* **Security fix**: Escape tab switching cookie name in browsable API. [Backported from 3.1.1](http://www.django-rest-framework.org/topics/release-notes/#311). + ### 2.4.4 **Date**: [3rd November 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.4+Release%22+). diff --git a/requirements-test.txt b/requirements-test.txt index 411daeba2d..2880f5a987 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -2,7 +2,8 @@ pytest-django==2.6 pytest==2.5.2 pytest-cov==1.6 -flake8==2.2.2 +pep8==1.5.7 +flake8==2.4.0 # Optional packages markdown>=2.1.0 diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 15b12d9bea..a15ed7cd49 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -8,7 +8,7 @@ """ __title__ = 'Django REST framework' -__version__ = '2.4.4' +__version__ = '2.4.9' __author__ = 'Tom Christie' __license__ = 'BSD 2-Clause' __copyright__ = 'Copyright 2011-2014 Tom Christie' diff --git a/rest_framework/compat.py b/rest_framework/compat.py index fa0f0bfb17..b0150f3d58 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -73,6 +73,12 @@ from collections import UserDict from collections import MutableMapping as DictMixin +# http responses move in Python 3 +try: + from httplib import responses +except ImportError: + from http.client import responses + # Try to import PIL in either of the two ways it can end up installed. try: from PIL import Image @@ -265,3 +271,30 @@ def python_2_unicode_compatible(klass): klass.__unicode__ = klass.__str__ klass.__str__ = lambda self: self.__unicode__().encode('utf-8') return klass + +""" +SortedDict deprecated since django 1.8. There is collections.OrderedDict +which available since python 2.7 and python 3.1. There are no need of other +checks because of django 1.7+ requires python 2.7+ +""" +try: + from collections import OrderedDict as SortedDict +except ImportError: + from django.utils.datastructures import SortedDict + +""" +GenericForeignKey moves from generic to fields in django 1.9 and in 1.8 shows +deprecation warnings +""" +if django.VERSION >= (1, 8): + from django.contrib.contenttypes.fields import GenericForeignKey +else: + from django.contrib.contenttypes.generic import GenericForeignKey + +""" +django.utils.importlib is deprecated since django 1.8 +""" +try: + import importlib +except ImportError: + from django.utils import importlib diff --git a/rest_framework/fields.py b/rest_framework/fields.py index c0253f86b8..d310c5df36 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -10,6 +10,7 @@ import inspect import re import warnings +import collections from decimal import Decimal, DecimalException from django import forms from django.core import validators @@ -21,11 +22,10 @@ from django.utils import six, timezone from django.utils.encoding import is_protected_type from django.utils.translation import ugettext_lazy as _ -from django.utils.datastructures import SortedDict from django.utils.dateparse import parse_date, parse_datetime, parse_time from rest_framework import ISO_8601 from rest_framework.compat import ( - BytesIO, smart_text, + BytesIO, smart_text, SortedDict, force_text, is_non_str_iterable ) from rest_framework.settings import api_settings @@ -52,7 +52,7 @@ def get_component(obj, attr_name): Given an object, and an attribute name, return that attribute on the object. """ - if isinstance(obj, dict): + if isinstance(obj, collections.Mapping): val = obj.get(attr_name) else: val = getattr(obj, attr_name) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index c580f9351b..18f4862e0f 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -188,4 +188,9 @@ def filter_queryset(self, request, queryset, view): 'model_name': get_model_name(model_cls) } permission = self.perm_format % kwargs - return guardian.shortcuts.get_objects_for_user(user, permission, queryset) + if guardian.VERSION >= (1, 3): + # Maintain behavior compatibility with versions prior to 1.3 + extra = {'accept_global_perms': False} + else: + extra = {} + return guardian.shortcuts.get_objects_for_user(user, permission, queryset, **extra) diff --git a/rest_framework/response.py b/rest_framework/response.py old mode 100644 new mode 100755 index 0a7d313f40..3e7e22bac1 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -6,9 +6,9 @@ """ from __future__ import unicode_literals import django -from django.core.handlers.wsgi import STATUS_CODE_TEXT from django.template.response import SimpleTemplateResponse from django.utils import six +from rest_framework.compat import responses class Response(SimpleTemplateResponse): @@ -81,7 +81,7 @@ def status_text(self): """ # TODO: Deprecate and use a template tag instead # TODO: Status code text for RFC 6585 status codes - return STATUS_CODE_TEXT.get(self.status_code, '') + return responses.get(self.status_code, '') def __getstate__(self): """ @@ -91,4 +91,5 @@ def __getstate__(self): for key in ('accepted_renderer', 'renderer_context', 'data'): if key in state: del state[key] + state['_closable_objects'] = [] return state diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 169e6e8bc4..9e5813b398 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -20,8 +20,8 @@ from django.conf.urls import patterns, url from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import NoReverseMatch -from django.utils.datastructures import SortedDict from rest_framework import views +from rest_framework.compat import SortedDict from rest_framework.response import Response from rest_framework.reverse import reverse from rest_framework.urlpatterns import format_suffix_patterns diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 7d85894f63..67cf432e09 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -15,15 +15,17 @@ import datetime import inspect import types + +import django + from decimal import Decimal -from django.contrib.contenttypes.generic import GenericForeignKey from django.core.paginator import Page from django.db import models from django.forms import widgets from django.utils import six -from django.utils.datastructures import SortedDict from django.utils.functional import cached_property from django.core.exceptions import ObjectDoesNotExist +from rest_framework.compat import SortedDict, GenericForeignKey from rest_framework.settings import api_settings @@ -110,9 +112,17 @@ class SortedDictWithMetadata(SortedDict): """ A sorted dict-like object, that can have additional properties attached. """ + def __reduce__(self): + """ + Used by pickle (e.g., caching) if OrderedDict is used instead of SortedDict + Overriden to remove the metadata from the dict, since it shouldn't be + pickle and may in some instances be unpickleable. + """ + return self.__class__, (SortedDict(self), ) + def __getstate__(self): """ - Used by pickle (e.g., caching). + Used by pickle (e.g., caching) in SortedDict Overriden to remove the metadata from the dict, since it shouldn't be pickle and may in some instances be unpickleable. """ @@ -986,7 +996,11 @@ def restore_object(self, attrs, instance=None): m2m_data[field_name] = attrs.pop(field_name) # Forward m2m relations - for field in meta.many_to_many + meta.virtual_fields: + if issubclass(meta.many_to_many.__class__, tuple): + temp_m2m = list(meta.many_to_many) + else: + temp_m2m = meta.many_to_many + for field in temp_m2m + meta.virtual_fields: if isinstance(field, GenericForeignKey): continue if field.name in attrs: @@ -1068,6 +1082,12 @@ def save_object(self, obj, **kwargs): fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name setattr(related, fk_field, obj) self.save_object(related) + elif isinstance(related, list): + # Many to One/Many + if django.VERSION >= (1, 9): + getattr(obj, accessor_name).add(*related, bulk=False) + else: + getattr(obj, accessor_name).add(*related) else: # Reverse FK or reverse one-one setattr(obj, accessor_name, related) diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 644751f877..e77ec754fc 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -19,8 +19,9 @@ """ from __future__ import unicode_literals from django.conf import settings -from django.utils import importlib, six +from django.utils import six from rest_framework import ISO_8601 +from rest_framework.compat import importlib USER_SETTINGS = getattr(settings, 'REST_FRAMEWORK', None) diff --git a/rest_framework/static/rest_framework/js/default.js b/rest_framework/static/rest_framework/js/default.js index bcb1964dbe..f04e55696d 100644 --- a/rest_framework/static/rest_framework/js/default.js +++ b/rest_framework/static/rest_framework/js/default.js @@ -43,6 +43,10 @@ $('a[data-toggle="tab"]').click(function(){ var selectedTab = null; var selectedTabName = getCookie('tabstyle'); +if (selectedTabName) { + selectedTabName = selectedTabName.replace(/[^a-z-]/g, ''); +} + if (selectedTabName) { selectedTab = $('.form-switcher a[name=' + selectedTabName + ']'); } diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index 00ffdfbae5..813108b777 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -4,9 +4,8 @@ from __future__ import unicode_literals from django.utils import timezone from django.db.models.query import QuerySet -from django.utils.datastructures import SortedDict from django.utils.functional import Promise -from rest_framework.compat import force_text +from rest_framework.compat import force_text, SortedDict from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata import datetime import decimal diff --git a/rest_framework/views.py b/rest_framework/views.py index 38346ab799..fb30775b19 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -5,10 +5,9 @@ from django.core.exceptions import PermissionDenied from django.http import Http404 -from django.utils.datastructures import SortedDict from django.views.decorators.csrf import csrf_exempt from rest_framework import status, exceptions -from rest_framework.compat import smart_text, HttpResponseBase, View +from rest_framework.compat import smart_text, HttpResponseBase, SortedDict, View from rest_framework.request import Request from rest_framework.response import Response from rest_framework.settings import api_settings diff --git a/setup.py b/setup.py index 2c56cd758f..f9c10456bc 100755 --- a/setup.py +++ b/setup.py @@ -59,8 +59,14 @@ def get_package_data(package): if sys.argv[-1] == 'publish': - os.system("python setup.py sdist upload") - os.system("python setup.py bdist_wheel upload") + if os.system("pip freeze | grep wheel"): + print("wheel not installed.\nUse `pip install wheel`.\nExiting.") + sys.exit() + if os.system("pip freeze | grep twine"): + print("twine not installed.\nUse `pip install twine`.\nExiting.") + sys.exit() + os.system("python setup.py sdist bdist_wheel") + os.system("twine upload dist/*") print("You probably want to also tag the version now:") print(" git tag -a %s -m 'version %s'" % (version, version)) print(" git push --tags") diff --git a/tests/test_fields.py b/tests/test_fields.py index 0ddbe48b5b..058ad70355 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -10,8 +10,8 @@ from django.core import validators from django.db import models from django.test import TestCase -from django.utils.datastructures import SortedDict from rest_framework import serializers +from rest_framework.compat import SortedDict from tests.models import RESTFrameworkModel diff --git a/tox.ini b/tox.ini index b3f53cce28..0e17ca5116 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,8 @@ setenv = [testenv:flake8] basepython = python2.7 deps = pytest==2.5.2 - flake8==2.2.2 + pep8==1.5.7 + flake8==2.4.0 commands = ./runtests.py --lintonly [testenv:py3.4-django1.7]
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: