From 3fcc01273c5efef26d911e50c02a4a43f89b34eb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 27 Jun 2013 20:29:52 +0100 Subject: [PATCH 001/225] Remove deprecated code --- rest_framework/compat.py | 5 +- rest_framework/fields.py | 7 --- rest_framework/permissions.py | 12 +---- rest_framework/relations.py | 69 ++++--------------------- rest_framework/serializers.py | 32 ++---------- rest_framework/tests/test_serializer.py | 4 +- 6 files changed, 21 insertions(+), 108 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index b748dcc51..161fffa8d 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -494,11 +494,14 @@ try: if provider_version in ('0.2.3', '0.2.4'): # 0.2.3 and 0.2.4 are supported version that do not support # timezone aware datetimes - from datetime.datetime import now as provider_now + import datetime + provider_now = datetime.datetime.now else: # Any other supported version does use timezone aware datetimes from django.utils.timezone import now as provider_now except ImportError: + import traceback + traceback.print_exc() oauth2_provider = None oauth2_provider_models = None oauth2_provider_forms = None diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 35848b4ce..2e23715de 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -224,13 +224,6 @@ class WritableField(Field): validators=[], error_messages=None, widget=None, default=None, blank=None): - # 'blank' is to be deprecated in favor of 'required' - if blank is not None: - warnings.warn('The `blank` keyword argument is deprecated. ' - 'Use the `required` keyword argument instead.', - DeprecationWarning, stacklevel=2) - required = not(blank) - super(WritableField, self).__init__(source=source, label=label, help_text=help_text) self.read_only = read_only diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index 1036663e0..0c7b02ffa 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -2,13 +2,10 @@ Provides a set of pluggable permission policies. """ from __future__ import unicode_literals -import inspect -import warnings +from rest_framework.compat import oauth2_provider_scope, oauth2_constants SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS'] -from rest_framework.compat import oauth2_provider_scope, oauth2_constants - class BasePermission(object): """ @@ -25,13 +22,6 @@ class BasePermission(object): """ Return `True` if permission is granted, `False` otherwise. """ - if len(inspect.getargspec(self.has_permission).args) == 4: - warnings.warn( - 'The `obj` argument in `has_permission` is deprecated. ' - 'Use `has_object_permission()` instead for object permissions.', - DeprecationWarning, stacklevel=2 - ) - return self.has_permission(request, view, obj) return True diff --git a/rest_framework/relations.py b/rest_framework/relations.py index edaf76d6e..ede694e31 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -40,14 +40,6 @@ class RelatedField(WritableField): many = False def __init__(self, *args, **kwargs): - - # 'null' is to be deprecated in favor of 'required' - if 'null' in kwargs: - warnings.warn('The `null` keyword argument is deprecated. ' - 'Use the `required` keyword argument instead.', - DeprecationWarning, stacklevel=2) - kwargs['required'] = not kwargs.pop('null') - queryset = kwargs.pop('queryset', None) self.many = kwargs.pop('many', self.many) if self.many: @@ -424,14 +416,11 @@ class HyperlinkedRelatedField(RelatedField): request = self.context.get('request', None) format = self.format or self.context.get('format', None) - if request is None: - msg = ( - "Using `HyperlinkedRelatedField` without including the request " - "in the serializer context is deprecated. " - "Add `context={'request': request}` when instantiating " - "the serializer." - ) - warnings.warn(msg, DeprecationWarning, stacklevel=4) + assert request is not None, ( + "`HyperlinkedRelatedField` requires the request in the serializer " + "context. Add `context={'request': request}` when instantiating " + "the serializer." + ) # If the object has not yet been saved then we cannot hyperlink to it. if getattr(obj, 'pk', None) is None: @@ -530,11 +519,11 @@ class HyperlinkedIdentityField(Field): format = self.context.get('format', None) view_name = self.view_name - if request is None: - warnings.warn("Using `HyperlinkedIdentityField` without including the " - "request in the serializer context is deprecated. " - "Add `context={'request': request}` when instantiating the serializer.", - DeprecationWarning, stacklevel=4) + assert request is not None, ( + "`HyperlinkedIdentityField` requires the request in the serializer" + " context. Add `context={'request': request}` when instantiating " + "the serializer." + ) # By default use whatever format is given for the current context # unless the target is a different type to the source. @@ -593,41 +582,3 @@ class HyperlinkedIdentityField(Field): pass raise NoReverseMatch() - - -### Old-style many classes for backwards compat - -class ManyRelatedField(RelatedField): - def __init__(self, *args, **kwargs): - warnings.warn('`ManyRelatedField()` is deprecated. ' - 'Use `RelatedField(many=True)` instead.', - DeprecationWarning, stacklevel=2) - kwargs['many'] = True - super(ManyRelatedField, self).__init__(*args, **kwargs) - - -class ManyPrimaryKeyRelatedField(PrimaryKeyRelatedField): - def __init__(self, *args, **kwargs): - warnings.warn('`ManyPrimaryKeyRelatedField()` is deprecated. ' - 'Use `PrimaryKeyRelatedField(many=True)` instead.', - DeprecationWarning, stacklevel=2) - kwargs['many'] = True - super(ManyPrimaryKeyRelatedField, self).__init__(*args, **kwargs) - - -class ManySlugRelatedField(SlugRelatedField): - def __init__(self, *args, **kwargs): - warnings.warn('`ManySlugRelatedField()` is deprecated. ' - 'Use `SlugRelatedField(many=True)` instead.', - DeprecationWarning, stacklevel=2) - kwargs['many'] = True - super(ManySlugRelatedField, self).__init__(*args, **kwargs) - - -class ManyHyperlinkedRelatedField(HyperlinkedRelatedField): - def __init__(self, *args, **kwargs): - warnings.warn('`ManyHyperlinkedRelatedField()` is deprecated. ' - 'Use `HyperlinkedRelatedField(many=True)` instead.', - DeprecationWarning, stacklevel=2) - kwargs['many'] = True - super(ManyHyperlinkedRelatedField, self).__init__(*args, **kwargs) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 023f7ccfb..ae39cce88 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -15,7 +15,6 @@ import copy import datetime import types from decimal import Decimal -from django.core.paginator import Page from django.db import models from django.forms import widgets from django.utils.datastructures import SortedDict @@ -141,7 +140,7 @@ class BaseSerializer(WritableField): _dict_class = SortedDictWithMetadata def __init__(self, instance=None, data=None, files=None, - context=None, partial=False, many=None, + context=None, partial=False, many=False, allow_add_remove=False, **kwargs): super(BaseSerializer, self).__init__(**kwargs) self.opts = self._options_class(self.Meta) @@ -348,12 +347,7 @@ class BaseSerializer(WritableField): if value is None: return None - if self.many is not None: - many = self.many - else: - many = hasattr(value, '__iter__') and not isinstance(value, (Page, dict, six.text_type)) - - if many: + if self.many: return [self.to_native(item) for item in value] return self.to_native(value) @@ -424,16 +418,7 @@ class BaseSerializer(WritableField): if self._errors is None: data, files = self.init_data, self.init_files - if self.many is not None: - many = self.many - else: - many = hasattr(data, '__iter__') and not isinstance(data, (Page, dict, six.text_type)) - if many: - warnings.warn('Implict list/queryset serialization is deprecated. ' - 'Use the `many=True` flag when instantiating the serializer.', - DeprecationWarning, stacklevel=3) - - if many: + if self.many: ret = [] errors = [] update = self.object is not None @@ -486,16 +471,7 @@ class BaseSerializer(WritableField): if self._data is None: obj = self.object - if self.many is not None: - many = self.many - else: - many = hasattr(obj, '__iter__') and not isinstance(obj, (Page, dict)) - if many: - warnings.warn('Implict list/queryset serialization is deprecated. ' - 'Use the `many=True` flag when instantiating the serializer.', - DeprecationWarning, stacklevel=2) - - if many: + if self.many: self._data = [self.to_native(item) for item in obj] else: self._data = self.to_native(obj) diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index 8b87a0847..151eb6484 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -1268,7 +1268,7 @@ class NestedSerializerContextTests(TestCase): model = Album fields = ("photo_set", "callable") - photo_set = PhotoSerializer(source="photo_set") + photo_set = PhotoSerializer(source="photo_set", many=True) callable = serializers.SerializerMethodField("_callable") def _callable(self, instance): @@ -1280,7 +1280,7 @@ class NestedSerializerContextTests(TestCase): albums = None class AlbumCollectionSerializer(serializers.Serializer): - albums = AlbumSerializer(source="albums") + albums = AlbumSerializer(source="albums", many=True) album1 = Album.objects.create(title="album 1") album2 = Album.objects.create(title="album 2") From 379ad8a82485e61b180ee823ba49799d39446aeb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 27 Jun 2013 20:36:14 +0100 Subject: [PATCH 002/225] pending deprecations -> deprecated --- rest_framework/generics.py | 32 +++++++++++++++---------------- rest_framework/mixins.py | 8 ++++---- rest_framework/relations.py | 36 +++++++++++++++++------------------ rest_framework/serializers.py | 8 ++++---- 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 99e9782e2..874a142c8 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -108,11 +108,11 @@ class GenericAPIView(views.APIView): deprecated_style = False if page_size is not None: warnings.warn('The `page_size` parameter to `paginate_queryset()` ' - 'is due to be deprecated. ' + 'is deprecated. ' 'Note that the return style of this method is also ' 'changed, and will simply return a page object ' 'when called without a `page_size` argument.', - PendingDeprecationWarning, stacklevel=2) + DeprecationWarning, stacklevel=2) deprecated_style = True else: # Determine the required page size. @@ -123,10 +123,10 @@ class GenericAPIView(views.APIView): if not self.allow_empty: warnings.warn( - 'The `allow_empty` parameter is due to be deprecated. ' + 'The `allow_empty` parameter is deprecated. ' 'To use `allow_empty=False` style behavior, You should override ' '`get_queryset()` and explicitly raise a 404 on empty querysets.', - PendingDeprecationWarning, stacklevel=2 + DeprecationWarning, stacklevel=2 ) paginator = self.paginator_class(queryset, page_size, @@ -166,10 +166,10 @@ class GenericAPIView(views.APIView): if not filter_backends and self.filter_backend: warnings.warn( 'The `filter_backend` attribute and `FILTER_BACKEND` setting ' - 'are due to be deprecated in favor of a `filter_backends` ' + 'are deprecated in favor of a `filter_backends` ' 'attribute and `DEFAULT_FILTER_BACKENDS` setting, that take ' 'a *list* of filter backend classes.', - PendingDeprecationWarning, stacklevel=2 + DeprecationWarning, stacklevel=2 ) filter_backends = [self.filter_backend] @@ -192,8 +192,8 @@ class GenericAPIView(views.APIView): """ if queryset is not None: warnings.warn('The `queryset` parameter to `get_paginate_by()` ' - 'is due to be deprecated.', - PendingDeprecationWarning, stacklevel=2) + 'is deprecated.', + DeprecationWarning, stacklevel=2) if self.paginate_by_param: query_params = self.request.QUERY_PARAMS @@ -272,16 +272,16 @@ class GenericAPIView(views.APIView): filter_kwargs = {self.lookup_field: lookup} elif pk is not None and self.lookup_field == 'pk': warnings.warn( - 'The `pk_url_kwarg` attribute is due to be deprecated. ' + 'The `pk_url_kwarg` attribute is deprecated. ' 'Use the `lookup_field` attribute instead', - PendingDeprecationWarning + DeprecationWarning ) filter_kwargs = {'pk': pk} elif slug is not None and self.lookup_field == 'pk': warnings.warn( - 'The `slug_url_kwarg` attribute is due to be deprecated. ' + 'The `slug_url_kwarg` attribute is deprecated. ' 'Use the `lookup_field` attribute instead', - PendingDeprecationWarning + DeprecationWarning ) filter_kwargs = {self.slug_field: slug} else: @@ -482,9 +482,9 @@ class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, class MultipleObjectAPIView(GenericAPIView): def __init__(self, *args, **kwargs): warnings.warn( - 'Subclassing `MultipleObjectAPIView` is due to be deprecated. ' + 'Subclassing `MultipleObjectAPIView` is deprecated. ' 'You should simply subclass `GenericAPIView` instead.', - PendingDeprecationWarning, stacklevel=2 + DeprecationWarning, stacklevel=2 ) super(MultipleObjectAPIView, self).__init__(*args, **kwargs) @@ -492,8 +492,8 @@ class MultipleObjectAPIView(GenericAPIView): class SingleObjectAPIView(GenericAPIView): def __init__(self, *args, **kwargs): warnings.warn( - 'Subclassing `SingleObjectAPIView` is due to be deprecated. ' + 'Subclassing `SingleObjectAPIView` is deprecated. ' 'You should simply subclass `GenericAPIView` instead.', - PendingDeprecationWarning, stacklevel=2 + DeprecationWarning, stacklevel=2 ) super(SingleObjectAPIView, self).__init__(*args, **kwargs) diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index f11def6d4..679dfa6c3 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -24,14 +24,14 @@ def _get_validation_exclusions(obj, pk=None, slug_field=None, lookup_field=None) include = [] if pk: - # Pending deprecation + # Deprecated pk_field = obj._meta.pk while pk_field.rel: pk_field = pk_field.rel.to._meta.pk include.append(pk_field.name) if slug_field: - # Pending deprecation + # Deprecated include.append(slug_field) if lookup_field and lookup_field != 'pk': @@ -77,10 +77,10 @@ class ListModelMixin(object): # `.allow_empty = False`, to raise 404 errors on empty querysets. if not self.allow_empty and not self.object_list: warnings.warn( - 'The `allow_empty` parameter is due to be deprecated. ' + 'The `allow_empty` parameter is deprecated. ' 'To use `allow_empty=False` style behavior, You should override ' '`get_queryset()` and explicitly raise a 404 on empty querysets.', - PendingDeprecationWarning + DeprecationWarning ) class_name = self.__class__.__name__ error_msg = self.empty_error % {'class_name': class_name} diff --git a/rest_framework/relations.py b/rest_framework/relations.py index ede694e31..f1f7dea72 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -314,7 +314,7 @@ class HyperlinkedRelatedField(RelatedField): 'incorrect_type': _('Incorrect type. Expected url string, received %s.'), } - # These are all pending deprecation + # These are all deprecated pk_url_kwarg = 'pk' slug_field = 'slug' slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden @@ -328,16 +328,16 @@ class HyperlinkedRelatedField(RelatedField): self.lookup_field = kwargs.pop('lookup_field', self.lookup_field) self.format = kwargs.pop('format', None) - # These are pending deprecation + # These are deprecated if 'pk_url_kwarg' in kwargs: - msg = 'pk_url_kwarg is pending deprecation. Use lookup_field instead.' - warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) + msg = 'pk_url_kwarg is deprecated. Use lookup_field instead.' + warnings.warn(msg, DeprecationWarning, stacklevel=2) if 'slug_url_kwarg' in kwargs: - msg = 'slug_url_kwarg is pending deprecation. Use lookup_field instead.' - warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) + msg = 'slug_url_kwarg is deprecated. Use lookup_field instead.' + warnings.warn(msg, DeprecationWarning, stacklevel=2) if 'slug_field' in kwargs: - msg = 'slug_field is pending deprecation. Use lookup_field instead.' - warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) + msg = 'slug_field is deprecated. Use lookup_field instead.' + warnings.warn(msg, DeprecationWarning, stacklevel=2) self.pk_url_kwarg = kwargs.pop('pk_url_kwarg', self.pk_url_kwarg) self.slug_field = kwargs.pop('slug_field', self.slug_field) @@ -380,9 +380,9 @@ class HyperlinkedRelatedField(RelatedField): # If the lookup succeeds using the default slug params, # then `slug_field` is being used implicitly, and we # we need to warn about the pending deprecation. - msg = 'Implicit slug field hyperlinked fields are pending deprecation.' \ + msg = 'Implicit slug field hyperlinked fields are deprecated.' \ 'You should set `lookup_field=slug` on the HyperlinkedRelatedField.' - warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) + warnings.warn(msg, DeprecationWarning, stacklevel=2) return ret except NoReverseMatch: pass @@ -480,7 +480,7 @@ class HyperlinkedIdentityField(Field): lookup_field = 'pk' read_only = True - # These are all pending deprecation + # These are all deprecated pk_url_kwarg = 'pk' slug_field = 'slug' slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden @@ -496,16 +496,16 @@ class HyperlinkedIdentityField(Field): lookup_field = kwargs.pop('lookup_field', None) self.lookup_field = lookup_field or self.lookup_field - # These are pending deprecation + # These are deprecated if 'pk_url_kwarg' in kwargs: - msg = 'pk_url_kwarg is pending deprecation. Use lookup_field instead.' - warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) + msg = 'pk_url_kwarg is deprecated. Use lookup_field instead.' + warnings.warn(msg, DeprecationWarning, stacklevel=2) if 'slug_url_kwarg' in kwargs: - msg = 'slug_url_kwarg is pending deprecation. Use lookup_field instead.' - warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) + msg = 'slug_url_kwarg is deprecated. Use lookup_field instead.' + warnings.warn(msg, DeprecationWarning, stacklevel=2) if 'slug_field' in kwargs: - msg = 'slug_field is pending deprecation. Use lookup_field instead.' - warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) + msg = 'slug_field is deprecated. Use lookup_field instead.' + warnings.warn(msg, DeprecationWarning, stacklevel=2) self.slug_field = kwargs.pop('slug_field', self.slug_field) default_slug_kwarg = self.slug_url_kwarg or self.slug_field diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index ae39cce88..dd9e14ad7 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -593,10 +593,10 @@ class ModelSerializer(Serializer): if len(inspect.getargspec(self.get_nested_field).args) == 2: warnings.warn( 'The `get_nested_field(model_field)` call signature ' - 'is due to be deprecated. ' + 'is deprecated. ' 'Use `get_nested_field(model_field, related_model, ' 'to_many) instead', - PendingDeprecationWarning + DeprecationWarning ) field = self.get_nested_field(model_field) else: @@ -605,10 +605,10 @@ class ModelSerializer(Serializer): if len(inspect.getargspec(self.get_nested_field).args) == 3: warnings.warn( 'The `get_related_field(model_field, to_many)` call ' - 'signature is due to be deprecated. ' + 'signature is deprecated. ' 'Use `get_related_field(model_field, related_model, ' 'to_many) instead', - PendingDeprecationWarning + DeprecationWarning ) field = self.get_related_field(model_field, to_many=to_many) else: From d72603bc6a16112008959c5267839f819c2bc43a Mon Sep 17 00:00:00 2001 From: Alex Burgel Date: Wed, 5 Jun 2013 17:39:14 -0400 Subject: [PATCH 003/225] Add support for collection routes to SimpleRouter --- rest_framework/decorators.py | 26 +++++++++++++++ rest_framework/routers.py | 33 +++++++++++++++++-- rest_framework/tests/test_routers.py | 48 +++++++++++++++++++++++++++- 3 files changed, 103 insertions(+), 4 deletions(-) diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index c69756a43..dacd380fb 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -113,6 +113,7 @@ def link(**kwargs): """ def decorator(func): func.bind_to_methods = ['get'] + func.collection = False func.kwargs = kwargs return func return decorator @@ -124,6 +125,31 @@ def action(methods=['post'], **kwargs): """ def decorator(func): func.bind_to_methods = methods + func.collection = False + func.kwargs = kwargs + return func + return decorator + + +def collection_link(**kwargs): + """ + Used to mark a method on a ViewSet that should be routed for GET requests. + """ + def decorator(func): + func.bind_to_methods = ['get'] + func.collection = True + func.kwargs = kwargs + return func + return decorator + + +def collection_action(methods=['post'], **kwargs): + """ + Used to mark a method on a ViewSet that should be routed for POST requests. + """ + def decorator(func): + func.bind_to_methods = methods + func.collection = True func.kwargs = kwargs return func return decorator diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 930011d39..9b859a7c7 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -88,6 +88,17 @@ class SimpleRouter(BaseRouter): name='{basename}-list', initkwargs={'suffix': 'List'} ), + # Dynamically generated collection routes. + # Generated using @collection_action or @collection_link decorators + # on methods of the viewset. + Route( + url=r'^{prefix}/{methodname}{trailing_slash}$', + mapping={ + '{httpmethod}': '{methodname}', + }, + name='{basename}-collection-{methodnamehyphen}', + initkwargs={} + ), # Detail route. Route( url=r'^{prefix}/{lookup}{trailing_slash}$', @@ -107,7 +118,7 @@ class SimpleRouter(BaseRouter): mapping={ '{httpmethod}': '{methodname}', }, - name='{basename}-{methodnamehyphen}', + name='{basename}-dynamic-{methodnamehyphen}', initkwargs={} ), ] @@ -142,20 +153,25 @@ class SimpleRouter(BaseRouter): known_actions = flatten([route.mapping.values() for route in self.routes]) # Determine any `@action` or `@link` decorated methods on the viewset + collection_routes = [] dynamic_routes = [] for methodname in dir(viewset): attr = getattr(viewset, methodname) httpmethods = getattr(attr, 'bind_to_methods', None) + collection = getattr(attr, 'collection', False) if httpmethods: if methodname in known_actions: raise ImproperlyConfigured('Cannot use @action or @link decorator on ' 'method "%s" as it is an existing route' % methodname) httpmethods = [method.lower() for method in httpmethods] - dynamic_routes.append((httpmethods, methodname)) + if collection: + collection_routes.append((httpmethods, methodname)) + else: + dynamic_routes.append((httpmethods, methodname)) ret = [] for route in self.routes: - if route.mapping == {'{httpmethod}': '{methodname}'}: + if route.name == '{basename}-dynamic-{methodnamehyphen}': # Dynamic routes (@link or @action decorator) for httpmethods, methodname in dynamic_routes: initkwargs = route.initkwargs.copy() @@ -166,6 +182,17 @@ class SimpleRouter(BaseRouter): name=replace_methodname(route.name, methodname), initkwargs=initkwargs, )) + elif route.name == '{basename}-collection-{methodnamehyphen}': + # Dynamic routes (@collection_link or @collection_action decorator) + for httpmethods, methodname in collection_routes: + initkwargs = route.initkwargs.copy() + initkwargs.update(getattr(viewset, methodname).kwargs) + ret.append(Route( + url=replace_methodname(route.url, methodname), + mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), + name=replace_methodname(route.name, methodname), + initkwargs=initkwargs, + )) else: # Standard route ret.append(route) diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py index 5fcccb741..60f150d2c 100644 --- a/rest_framework/tests/test_routers.py +++ b/rest_framework/tests/test_routers.py @@ -4,7 +4,7 @@ from django.test import TestCase from django.core.exceptions import ImproperlyConfigured from rest_framework import serializers, viewsets, permissions from rest_framework.compat import include, patterns, url -from rest_framework.decorators import link, action +from rest_framework.decorators import link, action, collection_link, collection_action from rest_framework.response import Response from rest_framework.routers import SimpleRouter, DefaultRouter from rest_framework.test import APIRequestFactory @@ -214,3 +214,49 @@ class TestActionAppliedToExistingRoute(TestCase): with self.assertRaises(ImproperlyConfigured): self.router.urls + + +class StaticAndDynamicViewSet(viewsets.ViewSet): + def list(self, request, *args, **kwargs): + return Response({'method': 'list'}) + + @collection_action() + def collection_action(self, request, *args, **kwargs): + return Response({'method': 'action1'}) + + @action() + def dynamic_action(self, request, *args, **kwargs): + return Response({'method': 'action2'}) + + @collection_link() + def collection_link(self, request, *args, **kwargs): + return Response({'method': 'link1'}) + + @link() + def dynamic_link(self, request, *args, **kwargs): + return Response({'method': 'link2'}) + + +class TestStaticAndDynamicRouter(TestCase): + def setUp(self): + self.router = SimpleRouter() + + def test_link_and_action_decorator(self): + routes = self.router.get_routes(StaticAndDynamicViewSet) + decorator_routes = [r for r in routes if not (r.name.endswith('-list') or r.name.endswith('-detail'))] + # Make sure all these endpoints exist and none have been clobbered + for i, endpoint in enumerate(['collection_action', 'collection_link', 'dynamic_action', 'dynamic_link']): + route = decorator_routes[i] + # check url listing + if endpoint.startswith('collection_'): + self.assertEqual(route.url, + '^{{prefix}}/{0}{{trailing_slash}}$'.format(endpoint)) + else: + self.assertEqual(route.url, + '^{{prefix}}/{{lookup}}/{0}{{trailing_slash}}$'.format(endpoint)) + # check method to function mapping + if endpoint.endswith('action'): + method_map = 'post' + else: + method_map = 'get' + self.assertEqual(route.mapping[method_map], endpoint) From 5b11e23f6fb35834057fba35832a597ce443cc77 Mon Sep 17 00:00:00 2001 From: Alex Burgel Date: Wed, 5 Jun 2013 17:41:29 -0400 Subject: [PATCH 004/225] Add docs for collection routes --- docs/api-guide/viewsets.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 47e59e2b2..9fa6615ba 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -92,7 +92,9 @@ The default routers included with REST framework will provide routes for a stand def destroy(self, request, pk=None): pass -If you have ad-hoc methods that you need to be routed to, you can mark them as requiring routing using the `@link` or `@action` decorators. The `@link` decorator will route `GET` requests, and the `@action` decorator will route `POST` requests. +If you have ad-hoc methods that you need to be routed to, you can mark them as requiring routing using the `@collection_link`, `@collection_action`, `@link`, or `@action` decorators. The `@collection_link` and `@link` decorator will route `GET` requests, and the `@collection_action` and `@action` decorator will route `POST` requests. + +The `@link` and `@action` decorators contain `pk` in their URL pattern and are intended for methods which require a single instance. The `@collection_link` and `@collection_action` decorators are intended for methods which operate on a collection of objects. For example: @@ -121,13 +123,20 @@ For example: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) -The `@action` and `@link` decorators can additionally take extra arguments that will be set for the routed view only. For example... + @collection_link() + def recent_users(self, request): + recent_users = User.objects.all().order('-last_login') + page = self.paginate_queryset(recent_users) + serializer = self.get_pagination_serializer(page) + return Response(serializer.data) + +The decorators can additionally take extra arguments that will be set for the routed view only. For example... @action(permission_classes=[IsAdminOrIsSelf]) def set_password(self, request, pk=None): ... -The `@action` decorator will route `POST` requests by default, but may also accept other HTTP methods, by using the `method` argument. For example: +The `@collection_action` and `@action` decorators will route `POST` requests by default, but may also accept other HTTP methods, by using the `method` argument. For example: @action(methods=['POST', 'DELETE']) def unset_password(self, request, pk=None): From 57cf8b5fa4f62f9b58912f10536a7ae5076ce54c Mon Sep 17 00:00:00 2001 From: Alex Burgel Date: Thu, 6 Jun 2013 11:51:52 -0400 Subject: [PATCH 005/225] Rework extra routes doc for better readability --- docs/api-guide/viewsets.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 9fa6615ba..e83487fb0 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -92,7 +92,7 @@ The default routers included with REST framework will provide routes for a stand def destroy(self, request, pk=None): pass -If you have ad-hoc methods that you need to be routed to, you can mark them as requiring routing using the `@collection_link`, `@collection_action`, `@link`, or `@action` decorators. The `@collection_link` and `@link` decorator will route `GET` requests, and the `@collection_action` and `@action` decorator will route `POST` requests. +If you have ad-hoc methods that you need to be routed to, you can mark them as requiring routing using the `@link`, `@action`, `@collection_link`, or `@collection_action` decorators. The `@link` and `@collection_link` decorators will route `GET` requests, and the `@action` and `@collection_action` decorators will route `POST` requests. The `@link` and `@action` decorators contain `pk` in their URL pattern and are intended for methods which require a single instance. The `@collection_link` and `@collection_action` decorators are intended for methods which operate on a collection of objects. @@ -136,7 +136,7 @@ The decorators can additionally take extra arguments that will be set for the ro def set_password(self, request, pk=None): ... -The `@collection_action` and `@action` decorators will route `POST` requests by default, but may also accept other HTTP methods, by using the `method` argument. For example: +The `@action` and `@collection_action` decorators will route `POST` requests by default, but may also accept other HTTP methods, by using the `methods` argument. For example: @action(methods=['POST', 'DELETE']) def unset_password(self, request, pk=None): From 8d521c068a254cef604df1f15690275dca986778 Mon Sep 17 00:00:00 2001 From: Alex Burgel Date: Sun, 16 Jun 2013 12:43:59 -0400 Subject: [PATCH 006/225] Revert route name change and add key to Route object to identify different route types --- rest_framework/routers.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 9b859a7c7..541df4a9d 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -25,7 +25,7 @@ from rest_framework.reverse import reverse from rest_framework.urlpatterns import format_suffix_patterns -Route = namedtuple('Route', ['url', 'mapping', 'name', 'initkwargs']) +Route = namedtuple('Route', ['key', 'url', 'mapping', 'name', 'initkwargs']) def replace_methodname(format_string, methodname): @@ -80,6 +80,7 @@ class SimpleRouter(BaseRouter): routes = [ # List route. Route( + key='list', url=r'^{prefix}{trailing_slash}$', mapping={ 'get': 'list', @@ -92,15 +93,17 @@ class SimpleRouter(BaseRouter): # Generated using @collection_action or @collection_link decorators # on methods of the viewset. Route( + key='collection', url=r'^{prefix}/{methodname}{trailing_slash}$', mapping={ '{httpmethod}': '{methodname}', }, - name='{basename}-collection-{methodnamehyphen}', + name='{basename}-{methodnamehyphen}', initkwargs={} ), # Detail route. Route( + key='detail', url=r'^{prefix}/{lookup}{trailing_slash}$', mapping={ 'get': 'retrieve', @@ -114,11 +117,12 @@ class SimpleRouter(BaseRouter): # Dynamically generated routes. # Generated using @action or @link decorators on methods of the viewset. Route( + key='dynamic', url=r'^{prefix}/{lookup}/{methodname}{trailing_slash}$', mapping={ '{httpmethod}': '{methodname}', }, - name='{basename}-dynamic-{methodnamehyphen}', + name='{basename}-{methodnamehyphen}', initkwargs={} ), ] @@ -171,23 +175,25 @@ class SimpleRouter(BaseRouter): ret = [] for route in self.routes: - if route.name == '{basename}-dynamic-{methodnamehyphen}': + if route.key == 'dynamic': # Dynamic routes (@link or @action decorator) for httpmethods, methodname in dynamic_routes: initkwargs = route.initkwargs.copy() initkwargs.update(getattr(viewset, methodname).kwargs) ret.append(Route( + key=route.key, url=replace_methodname(route.url, methodname), mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), name=replace_methodname(route.name, methodname), initkwargs=initkwargs, )) - elif route.name == '{basename}-collection-{methodnamehyphen}': + elif route.key == 'collection': # Dynamic routes (@collection_link or @collection_action decorator) for httpmethods, methodname in collection_routes: initkwargs = route.initkwargs.copy() initkwargs.update(getattr(viewset, methodname).kwargs) ret.append(Route( + key=route.key, url=replace_methodname(route.url, methodname), mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), name=replace_methodname(route.name, methodname), From f02274307826ebf98998e502fecca171bb0de696 Mon Sep 17 00:00:00 2001 From: Alex Burgel Date: Sun, 16 Jun 2013 12:51:33 -0400 Subject: [PATCH 007/225] Rename router collection test case --- rest_framework/tests/test_routers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py index 60f150d2c..e0a7e292e 100644 --- a/rest_framework/tests/test_routers.py +++ b/rest_framework/tests/test_routers.py @@ -216,7 +216,7 @@ class TestActionAppliedToExistingRoute(TestCase): self.router.urls -class StaticAndDynamicViewSet(viewsets.ViewSet): +class CollectionAndDynamicViewSet(viewsets.ViewSet): def list(self, request, *args, **kwargs): return Response({'method': 'list'}) @@ -237,12 +237,12 @@ class StaticAndDynamicViewSet(viewsets.ViewSet): return Response({'method': 'link2'}) -class TestStaticAndDynamicRouter(TestCase): +class TestCollectionAndDynamicRouter(TestCase): def setUp(self): self.router = SimpleRouter() def test_link_and_action_decorator(self): - routes = self.router.get_routes(StaticAndDynamicViewSet) + routes = self.router.get_routes(CollectionAndDynamicViewSet) decorator_routes = [r for r in routes if not (r.name.endswith('-list') or r.name.endswith('-detail'))] # Make sure all these endpoints exist and none have been clobbered for i, endpoint in enumerate(['collection_action', 'collection_link', 'dynamic_action', 'dynamic_link']): From e14cbaf6961ad9c94deaf0417d8e8ce5ec96d0ac Mon Sep 17 00:00:00 2001 From: Alex Burgel Date: Sat, 13 Jul 2013 11:11:53 -0400 Subject: [PATCH 008/225] Changed collection_* decorators to list_* --- docs/api-guide/viewsets.md | 10 ++++----- rest_framework/decorators.py | 16 +++++++------- rest_framework/routers.py | 31 ++++++++++++++-------------- rest_framework/tests/test_routers.py | 24 ++++++++++----------- 4 files changed, 41 insertions(+), 40 deletions(-) diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index e83487fb0..6d6bb1334 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -92,15 +92,15 @@ The default routers included with REST framework will provide routes for a stand def destroy(self, request, pk=None): pass -If you have ad-hoc methods that you need to be routed to, you can mark them as requiring routing using the `@link`, `@action`, `@collection_link`, or `@collection_action` decorators. The `@link` and `@collection_link` decorators will route `GET` requests, and the `@action` and `@collection_action` decorators will route `POST` requests. +If you have ad-hoc methods that you need to be routed to, you can mark them as requiring routing using the `@link`, `@action`, `@list_link`, or `@list_action` decorators. The `@link` and `@list_link` decorators will route `GET` requests, and the `@action` and `@list_action` decorators will route `POST` requests. -The `@link` and `@action` decorators contain `pk` in their URL pattern and are intended for methods which require a single instance. The `@collection_link` and `@collection_action` decorators are intended for methods which operate on a collection of objects. +The `@link` and `@action` decorators contain `pk` in their URL pattern and are intended for methods which require a single instance. The `@list_link` and `@list_action` decorators are intended for methods which operate on a list of objects. For example: from django.contrib.auth.models import User from rest_framework import viewsets - from rest_framework.decorators import action + from rest_framework.decorators import action, list_link from rest_framework.response import Response from myapp.serializers import UserSerializer, PasswordSerializer @@ -123,7 +123,7 @@ For example: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - @collection_link() + @list_link() def recent_users(self, request): recent_users = User.objects.all().order('-last_login') page = self.paginate_queryset(recent_users) @@ -136,7 +136,7 @@ The decorators can additionally take extra arguments that will be set for the ro def set_password(self, request, pk=None): ... -The `@action` and `@collection_action` decorators will route `POST` requests by default, but may also accept other HTTP methods, by using the `methods` argument. For example: +The `@action` and `@list_action` decorators will route `POST` requests by default, but may also accept other HTTP methods, by using the `methods` argument. For example: @action(methods=['POST', 'DELETE']) def unset_password(self, request, pk=None): diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index dacd380fb..92f551db7 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -109,11 +109,11 @@ def permission_classes(permission_classes): def link(**kwargs): """ - Used to mark a method on a ViewSet that should be routed for GET requests. + Used to mark a method on a ViewSet that should be routed for detail GET requests. """ def decorator(func): func.bind_to_methods = ['get'] - func.collection = False + func.detail = True func.kwargs = kwargs return func return decorator @@ -121,35 +121,35 @@ def link(**kwargs): def action(methods=['post'], **kwargs): """ - Used to mark a method on a ViewSet that should be routed for POST requests. + Used to mark a method on a ViewSet that should be routed for detail POST requests. """ def decorator(func): func.bind_to_methods = methods - func.collection = False + func.detail = True func.kwargs = kwargs return func return decorator -def collection_link(**kwargs): +def list_link(**kwargs): """ Used to mark a method on a ViewSet that should be routed for GET requests. """ def decorator(func): func.bind_to_methods = ['get'] - func.collection = True + func.detail = False func.kwargs = kwargs return func return decorator -def collection_action(methods=['post'], **kwargs): +def list_action(methods=['post'], **kwargs): """ Used to mark a method on a ViewSet that should be routed for POST requests. """ def decorator(func): func.bind_to_methods = methods - func.collection = True + func.detail = False func.kwargs = kwargs return func return decorator diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 541df4a9d..c8f711e91 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -89,8 +89,8 @@ class SimpleRouter(BaseRouter): name='{basename}-list', initkwargs={'suffix': 'List'} ), - # Dynamically generated collection routes. - # Generated using @collection_action or @collection_link decorators + # Dynamically generated list routes. + # Generated using @list_action or @list_link decorators # on methods of the viewset. Route( key='collection', @@ -114,7 +114,7 @@ class SimpleRouter(BaseRouter): name='{basename}-detail', initkwargs={'suffix': 'Instance'} ), - # Dynamically generated routes. + # Dynamically generated detail routes. # Generated using @action or @link decorators on methods of the viewset. Route( key='dynamic', @@ -157,27 +157,28 @@ class SimpleRouter(BaseRouter): known_actions = flatten([route.mapping.values() for route in self.routes]) # Determine any `@action` or `@link` decorated methods on the viewset - collection_routes = [] - dynamic_routes = [] + detail_routes = [] + list_routes = [] for methodname in dir(viewset): attr = getattr(viewset, methodname) httpmethods = getattr(attr, 'bind_to_methods', None) - collection = getattr(attr, 'collection', False) + detail = getattr(attr, 'detail', True) if httpmethods: if methodname in known_actions: - raise ImproperlyConfigured('Cannot use @action or @link decorator on ' - 'method "%s" as it is an existing route' % methodname) + raise ImproperlyConfigured('Cannot use @action, @link, @list_action ' + 'or @list_link decorator on method "%s" ' + 'as it is an existing route' % methodname) httpmethods = [method.lower() for method in httpmethods] - if collection: - collection_routes.append((httpmethods, methodname)) + if detail: + detail_routes.append((httpmethods, methodname)) else: - dynamic_routes.append((httpmethods, methodname)) + list_routes.append((httpmethods, methodname)) ret = [] for route in self.routes: if route.key == 'dynamic': - # Dynamic routes (@link or @action decorator) - for httpmethods, methodname in dynamic_routes: + # Dynamic detail routes (@link or @action decorator) + for httpmethods, methodname in detail_routes: initkwargs = route.initkwargs.copy() initkwargs.update(getattr(viewset, methodname).kwargs) ret.append(Route( @@ -188,8 +189,8 @@ class SimpleRouter(BaseRouter): initkwargs=initkwargs, )) elif route.key == 'collection': - # Dynamic routes (@collection_link or @collection_action decorator) - for httpmethods, methodname in collection_routes: + # Dynamic list routes (@list_link or @list_action decorator) + for httpmethods, methodname in list_routes: initkwargs = route.initkwargs.copy() initkwargs.update(getattr(viewset, methodname).kwargs) ret.append(Route( diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py index e0a7e292e..393101763 100644 --- a/rest_framework/tests/test_routers.py +++ b/rest_framework/tests/test_routers.py @@ -4,7 +4,7 @@ from django.test import TestCase from django.core.exceptions import ImproperlyConfigured from rest_framework import serializers, viewsets, permissions from rest_framework.compat import include, patterns, url -from rest_framework.decorators import link, action, collection_link, collection_action +from rest_framework.decorators import link, action, list_link, list_action from rest_framework.response import Response from rest_framework.routers import SimpleRouter, DefaultRouter from rest_framework.test import APIRequestFactory @@ -216,39 +216,39 @@ class TestActionAppliedToExistingRoute(TestCase): self.router.urls -class CollectionAndDynamicViewSet(viewsets.ViewSet): +class DynamicListAndDetailViewSet(viewsets.ViewSet): def list(self, request, *args, **kwargs): return Response({'method': 'list'}) - @collection_action() - def collection_action(self, request, *args, **kwargs): + @list_action() + def list_action(self, request, *args, **kwargs): return Response({'method': 'action1'}) @action() - def dynamic_action(self, request, *args, **kwargs): + def detail_action(self, request, *args, **kwargs): return Response({'method': 'action2'}) - @collection_link() - def collection_link(self, request, *args, **kwargs): + @list_link() + def list_link(self, request, *args, **kwargs): return Response({'method': 'link1'}) @link() - def dynamic_link(self, request, *args, **kwargs): + def detail_link(self, request, *args, **kwargs): return Response({'method': 'link2'}) -class TestCollectionAndDynamicRouter(TestCase): +class TestDynamicListAndDetailRouter(TestCase): def setUp(self): self.router = SimpleRouter() def test_link_and_action_decorator(self): - routes = self.router.get_routes(CollectionAndDynamicViewSet) + routes = self.router.get_routes(DynamicListAndDetailViewSet) decorator_routes = [r for r in routes if not (r.name.endswith('-list') or r.name.endswith('-detail'))] # Make sure all these endpoints exist and none have been clobbered - for i, endpoint in enumerate(['collection_action', 'collection_link', 'dynamic_action', 'dynamic_link']): + for i, endpoint in enumerate(['list_action', 'list_link', 'detail_action', 'detail_link']): route = decorator_routes[i] # check url listing - if endpoint.startswith('collection_'): + if endpoint.startswith('list_'): self.assertEqual(route.url, '^{{prefix}}/{0}{{trailing_slash}}$'.format(endpoint)) else: From ca7ba07b4e42bd1c7c6bb8088c0c5a2c434b56ee Mon Sep 17 00:00:00 2001 From: Alex Burgel Date: Sat, 13 Jul 2013 11:12:59 -0400 Subject: [PATCH 009/225] Introduce DynamicDetailRoute and DynamicListRoute to distinguish between different route types --- rest_framework/routers.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/rest_framework/routers.py b/rest_framework/routers.py index c8f711e91..b8f19b66a 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -25,7 +25,9 @@ from rest_framework.reverse import reverse from rest_framework.urlpatterns import format_suffix_patterns -Route = namedtuple('Route', ['key', 'url', 'mapping', 'name', 'initkwargs']) +Route = namedtuple('Route', ['url', 'mapping', 'name', 'initkwargs']) +DynamicDetailRoute = namedtuple('DynamicDetailRoute', ['url', 'name', 'initkwargs']) +DynamicListRoute = namedtuple('DynamicListRoute', ['url', 'name', 'initkwargs']) def replace_methodname(format_string, methodname): @@ -80,7 +82,6 @@ class SimpleRouter(BaseRouter): routes = [ # List route. Route( - key='list', url=r'^{prefix}{trailing_slash}$', mapping={ 'get': 'list', @@ -92,18 +93,13 @@ class SimpleRouter(BaseRouter): # Dynamically generated list routes. # Generated using @list_action or @list_link decorators # on methods of the viewset. - Route( - key='collection', + DynamicListRoute( url=r'^{prefix}/{methodname}{trailing_slash}$', - mapping={ - '{httpmethod}': '{methodname}', - }, name='{basename}-{methodnamehyphen}', initkwargs={} ), # Detail route. Route( - key='detail', url=r'^{prefix}/{lookup}{trailing_slash}$', mapping={ 'get': 'retrieve', @@ -116,12 +112,8 @@ class SimpleRouter(BaseRouter): ), # Dynamically generated detail routes. # Generated using @action or @link decorators on methods of the viewset. - Route( - key='dynamic', + DynamicDetailRoute( url=r'^{prefix}/{lookup}/{methodname}{trailing_slash}$', - mapping={ - '{httpmethod}': '{methodname}', - }, name='{basename}-{methodnamehyphen}', initkwargs={} ), @@ -154,7 +146,7 @@ class SimpleRouter(BaseRouter): Returns a list of the Route namedtuple. """ - known_actions = flatten([route.mapping.values() for route in self.routes]) + known_actions = flatten([route.mapping.values() for route in self.routes if isinstance(route, Route)]) # Determine any `@action` or `@link` decorated methods on the viewset detail_routes = [] @@ -176,25 +168,23 @@ class SimpleRouter(BaseRouter): ret = [] for route in self.routes: - if route.key == 'dynamic': + if isinstance(route, DynamicDetailRoute): # Dynamic detail routes (@link or @action decorator) for httpmethods, methodname in detail_routes: initkwargs = route.initkwargs.copy() initkwargs.update(getattr(viewset, methodname).kwargs) ret.append(Route( - key=route.key, url=replace_methodname(route.url, methodname), mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), name=replace_methodname(route.name, methodname), initkwargs=initkwargs, )) - elif route.key == 'collection': + elif isinstance(route, DynamicListRoute): # Dynamic list routes (@list_link or @list_action decorator) for httpmethods, methodname in list_routes: initkwargs = route.initkwargs.copy() initkwargs.update(getattr(viewset, methodname).kwargs) ret.append(Route( - key=route.key, url=replace_methodname(route.url, methodname), mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), name=replace_methodname(route.name, methodname), From eaae8fb2d973769a827214e0606a7e41028d5d34 Mon Sep 17 00:00:00 2001 From: Alex Burgel Date: Mon, 15 Jul 2013 18:35:13 -0400 Subject: [PATCH 010/225] Combined link_* and action_* decorators into detail_route and list_route, marked the originals as deprecated. --- docs/api-guide/routers.md | 16 +++++----- docs/api-guide/viewsets.md | 16 +++++----- docs/tutorial/6-viewsets-and-routers.md | 8 ++--- rest_framework/decorators.py | 19 +++++++----- rest_framework/routers.py | 14 ++++----- rest_framework/tests/test_routers.py | 40 ++++++++++++------------- 6 files changed, 59 insertions(+), 54 deletions(-) diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index 865829057..f196dc3cd 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -35,12 +35,12 @@ The example above would generate the following URL patterns: * URL pattern: `^accounts/$` Name: `'account-list'` * URL pattern: `^accounts/{pk}/$` Name: `'account-detail'` -### Extra link and actions +### Registering additional routes -Any methods on the viewset decorated with `@link` or `@action` will also be routed. +Any methods on the viewset decorated with `@detail_route` or `@list_route` will also be routed. For example, a given method like this on the `UserViewSet` class: - @action(permission_classes=[IsAdminOrIsSelf]) + @detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf]) def set_password(self, request, pk=None): ... @@ -52,7 +52,7 @@ The following URL pattern would additionally be generated: ## SimpleRouter -This router includes routes for the standard set of `list`, `create`, `retrieve`, `update`, `partial_update` and `destroy` actions. The viewset can also mark additional methods to be routed, using the `@link` or `@action` decorators. +This router includes routes for the standard set of `list`, `create`, `retrieve`, `update`, `partial_update` and `destroy` actions. The viewset can also mark additional methods to be routed, using the `@detail_route` or `@list_route` decorators. @@ -62,8 +62,8 @@ This router includes routes for the standard set of `list`, `create`, `retrieve` - - + +
URL StyleHTTP MethodActionURL Name
PUTupdate
PATCHpartial_update
DELETEdestroy
{prefix}/{lookup}/{methodname}/GET@link decorated method{basename}-{methodname}
POST@action decorated method
{prefix}/{lookup}/{methodname}/GET@detail_route decorated method{basename}-{methodname}
POST@detail_route decorated method
By default the URLs created by `SimpleRouter` are appending with a trailing slash. @@ -86,8 +86,8 @@ This router is similar to `SimpleRouter` as above, but additionally includes a d PUTupdate PATCHpartial_update DELETEdestroy - {prefix}/{lookup}/{methodname}/[.format]GET@link decorated method{basename}-{methodname} - POST@action decorated method + {prefix}/{lookup}/{methodname}/[.format]GET@detail_route decorated method{basename}-{methodname} + POST@detail_route decorated method As with `SimpleRouter` the trailing slashs on the URL routes can be removed by setting the `trailing_slash` argument to `False` when instantiating the router. diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 6d6bb1334..7a8d5979b 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -92,15 +92,15 @@ The default routers included with REST framework will provide routes for a stand def destroy(self, request, pk=None): pass -If you have ad-hoc methods that you need to be routed to, you can mark them as requiring routing using the `@link`, `@action`, `@list_link`, or `@list_action` decorators. The `@link` and `@list_link` decorators will route `GET` requests, and the `@action` and `@list_action` decorators will route `POST` requests. +If you have ad-hoc methods that you need to be routed to, you can mark them as requiring routing using the `@detail_route` or `@list_route` decorators. -The `@link` and `@action` decorators contain `pk` in their URL pattern and are intended for methods which require a single instance. The `@list_link` and `@list_action` decorators are intended for methods which operate on a list of objects. +The `@detail_route` decorator contains `pk` in its URL pattern and is intended for methods which require a single instance. The `@list_route` decorator is intended for methods which operate on a list of objects. For example: from django.contrib.auth.models import User from rest_framework import viewsets - from rest_framework.decorators import action, list_link + from rest_framework.decorators import detail_route, list_route from rest_framework.response import Response from myapp.serializers import UserSerializer, PasswordSerializer @@ -111,7 +111,7 @@ For example: queryset = User.objects.all() serializer_class = UserSerializer - @action() + @detail_route(methods=['post']) def set_password(self, request, pk=None): user = self.get_object() serializer = PasswordSerializer(data=request.DATA) @@ -123,7 +123,7 @@ For example: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - @list_link() + @list_route() def recent_users(self, request): recent_users = User.objects.all().order('-last_login') page = self.paginate_queryset(recent_users) @@ -132,13 +132,13 @@ For example: The decorators can additionally take extra arguments that will be set for the routed view only. For example... - @action(permission_classes=[IsAdminOrIsSelf]) + @detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf]) def set_password(self, request, pk=None): ... -The `@action` and `@list_action` decorators will route `POST` requests by default, but may also accept other HTTP methods, by using the `methods` argument. For example: +By default, the decorators will route `GET` requests, but may also accept other HTTP methods, by using the `methods` argument. For example: - @action(methods=['POST', 'DELETE']) + @detail_route(methods=['post', 'delete']) def unset_password(self, request, pk=None): ... --- diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index f16add39d..f126ba045 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -25,7 +25,7 @@ Here we've used `ReadOnlyModelViewSet` class to automatically provide the defaul Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighlight` view classes. We can remove the three views, and again replace them with a single class. - from rest_framework.decorators import link + from rest_framework.decorators import detail_route class SnippetViewSet(viewsets.ModelViewSet): """ @@ -39,7 +39,7 @@ Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighl permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,) - @link(renderer_classes=[renderers.StaticHTMLRenderer]) + @detail_route(renderer_classes=[renderers.StaticHTMLRenderer]) def highlight(self, request, *args, **kwargs): snippet = self.get_object() return Response(snippet.highlighted) @@ -49,9 +49,9 @@ Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighl This time we've used the `ModelViewSet` class in order to get the complete set of default read and write operations. -Notice that we've also used the `@link` decorator to create a custom action, named `highlight`. This decorator can be used to add any custom endpoints that don't fit into the standard `create`/`update`/`delete` style. +Notice that we've also used the `@detail_route` decorator to create a custom action, named `highlight`. This decorator can be used to add any custom endpoints that don't fit into the standard `create`/`update`/`delete` style. -Custom actions which use the `@link` decorator will respond to `GET` requests. We could have instead used the `@action` decorator if we wanted an action that responded to `POST` requests. +Custom actions which use the `@detail_route` decorator will respond to `GET` requests. We can use the `methods` argument if we wanted an action that responded to `POST` requests. ## Binding ViewSets to URLs explicitly diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 92f551db7..1ca176f2c 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -3,13 +3,14 @@ The most important decorator in this module is `@api_view`, which is used for writing function-based views with REST framework. There are also various decorators for setting the API policies on function -based views, as well as the `@action` and `@link` decorators, which are +based views, as well as the `@detail_route` and `@list_route` decorators, which are used to annotate methods on viewsets that should be included by routers. """ from __future__ import unicode_literals from rest_framework.compat import six from rest_framework.views import APIView import types +import warnings def api_view(http_method_names): @@ -111,6 +112,8 @@ def link(**kwargs): """ Used to mark a method on a ViewSet that should be routed for detail GET requests. """ + msg = 'link is pending deprecation. Use detail_route instead.' + warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) def decorator(func): func.bind_to_methods = ['get'] func.detail = True @@ -123,6 +126,8 @@ def action(methods=['post'], **kwargs): """ Used to mark a method on a ViewSet that should be routed for detail POST requests. """ + msg = 'action is pending deprecation. Use detail_route instead.' + warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) def decorator(func): func.bind_to_methods = methods func.detail = True @@ -131,21 +136,21 @@ def action(methods=['post'], **kwargs): return decorator -def list_link(**kwargs): +def detail_route(methods=['get'], **kwargs): """ - Used to mark a method on a ViewSet that should be routed for GET requests. + Used to mark a method on a ViewSet that should be routed for detail requests. """ def decorator(func): - func.bind_to_methods = ['get'] - func.detail = False + func.bind_to_methods = methods + func.detail = True func.kwargs = kwargs return func return decorator -def list_action(methods=['post'], **kwargs): +def list_route(methods=['get'], **kwargs): """ - Used to mark a method on a ViewSet that should be routed for POST requests. + Used to mark a method on a ViewSet that should be routed for list requests. """ def decorator(func): func.bind_to_methods = methods diff --git a/rest_framework/routers.py b/rest_framework/routers.py index b8f19b66a..b761ba9ae 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -91,7 +91,7 @@ class SimpleRouter(BaseRouter): initkwargs={'suffix': 'List'} ), # Dynamically generated list routes. - # Generated using @list_action or @list_link decorators + # Generated using @list_route decorator # on methods of the viewset. DynamicListRoute( url=r'^{prefix}/{methodname}{trailing_slash}$', @@ -111,7 +111,7 @@ class SimpleRouter(BaseRouter): initkwargs={'suffix': 'Instance'} ), # Dynamically generated detail routes. - # Generated using @action or @link decorators on methods of the viewset. + # Generated using @detail_route decorator on methods of the viewset. DynamicDetailRoute( url=r'^{prefix}/{lookup}/{methodname}{trailing_slash}$', name='{basename}-{methodnamehyphen}', @@ -148,7 +148,7 @@ class SimpleRouter(BaseRouter): known_actions = flatten([route.mapping.values() for route in self.routes if isinstance(route, Route)]) - # Determine any `@action` or `@link` decorated methods on the viewset + # Determine any `@detail_route` or `@list_route` decorated methods on the viewset detail_routes = [] list_routes = [] for methodname in dir(viewset): @@ -157,8 +157,8 @@ class SimpleRouter(BaseRouter): detail = getattr(attr, 'detail', True) if httpmethods: if methodname in known_actions: - raise ImproperlyConfigured('Cannot use @action, @link, @list_action ' - 'or @list_link decorator on method "%s" ' + raise ImproperlyConfigured('Cannot use @detail_route or @list_route ' + 'decorators on method "%s" ' 'as it is an existing route' % methodname) httpmethods = [method.lower() for method in httpmethods] if detail: @@ -169,7 +169,7 @@ class SimpleRouter(BaseRouter): ret = [] for route in self.routes: if isinstance(route, DynamicDetailRoute): - # Dynamic detail routes (@link or @action decorator) + # Dynamic detail routes (@detail_route decorator) for httpmethods, methodname in detail_routes: initkwargs = route.initkwargs.copy() initkwargs.update(getattr(viewset, methodname).kwargs) @@ -180,7 +180,7 @@ class SimpleRouter(BaseRouter): initkwargs=initkwargs, )) elif isinstance(route, DynamicListRoute): - # Dynamic list routes (@list_link or @list_action decorator) + # Dynamic list routes (@list_route decorator) for httpmethods, methodname in list_routes: initkwargs = route.initkwargs.copy() initkwargs.update(getattr(viewset, methodname).kwargs) diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py index 393101763..c3597e389 100644 --- a/rest_framework/tests/test_routers.py +++ b/rest_framework/tests/test_routers.py @@ -4,7 +4,7 @@ from django.test import TestCase from django.core.exceptions import ImproperlyConfigured from rest_framework import serializers, viewsets, permissions from rest_framework.compat import include, patterns, url -from rest_framework.decorators import link, action, list_link, list_action +from rest_framework.decorators import detail_route, list_route from rest_framework.response import Response from rest_framework.routers import SimpleRouter, DefaultRouter from rest_framework.test import APIRequestFactory @@ -18,23 +18,23 @@ class BasicViewSet(viewsets.ViewSet): def list(self, request, *args, **kwargs): return Response({'method': 'list'}) - @action() + @detail_route(methods=['post']) def action1(self, request, *args, **kwargs): return Response({'method': 'action1'}) - @action() + @detail_route(methods=['post']) def action2(self, request, *args, **kwargs): return Response({'method': 'action2'}) - @action(methods=['post', 'delete']) + @detail_route(methods=['post', 'delete']) def action3(self, request, *args, **kwargs): return Response({'method': 'action2'}) - @link() + @detail_route() def link1(self, request, *args, **kwargs): return Response({'method': 'link1'}) - @link() + @detail_route() def link2(self, request, *args, **kwargs): return Response({'method': 'link2'}) @@ -175,7 +175,7 @@ class TestActionKeywordArgs(TestCase): class TestViewSet(viewsets.ModelViewSet): permission_classes = [] - @action(permission_classes=[permissions.AllowAny]) + @detail_route(methods=['post'], permission_classes=[permissions.AllowAny]) def custom(self, request, *args, **kwargs): return Response({ 'permission_classes': self.permission_classes @@ -196,14 +196,14 @@ class TestActionKeywordArgs(TestCase): class TestActionAppliedToExistingRoute(TestCase): """ - Ensure `@action` decorator raises an except when applied + Ensure `@detail_route` decorator raises an except when applied to an existing route """ def test_exception_raised_when_action_applied_to_existing_route(self): class TestViewSet(viewsets.ModelViewSet): - @action() + @detail_route(methods=['post']) def retrieve(self, request, *args, **kwargs): return Response({ 'hello': 'world' @@ -220,20 +220,20 @@ class DynamicListAndDetailViewSet(viewsets.ViewSet): def list(self, request, *args, **kwargs): return Response({'method': 'list'}) - @list_action() - def list_action(self, request, *args, **kwargs): + @list_route(methods=['post']) + def list_route_post(self, request, *args, **kwargs): return Response({'method': 'action1'}) - @action() - def detail_action(self, request, *args, **kwargs): + @detail_route(methods=['post']) + def detail_route_post(self, request, *args, **kwargs): return Response({'method': 'action2'}) - @list_link() - def list_link(self, request, *args, **kwargs): + @list_route() + def list_route_get(self, request, *args, **kwargs): return Response({'method': 'link1'}) - @link() - def detail_link(self, request, *args, **kwargs): + @detail_route() + def detail_route_get(self, request, *args, **kwargs): return Response({'method': 'link2'}) @@ -241,11 +241,11 @@ class TestDynamicListAndDetailRouter(TestCase): def setUp(self): self.router = SimpleRouter() - def test_link_and_action_decorator(self): + def test_list_and_detail_route_decorators(self): routes = self.router.get_routes(DynamicListAndDetailViewSet) decorator_routes = [r for r in routes if not (r.name.endswith('-list') or r.name.endswith('-detail'))] # Make sure all these endpoints exist and none have been clobbered - for i, endpoint in enumerate(['list_action', 'list_link', 'detail_action', 'detail_link']): + for i, endpoint in enumerate(['list_route_get', 'list_route_post', 'detail_route_get', 'detail_route_post']): route = decorator_routes[i] # check url listing if endpoint.startswith('list_'): @@ -255,7 +255,7 @@ class TestDynamicListAndDetailRouter(TestCase): self.assertEqual(route.url, '^{{prefix}}/{{lookup}}/{0}{{trailing_slash}}$'.format(endpoint)) # check method to function mapping - if endpoint.endswith('action'): + if endpoint.endswith('_post'): method_map = 'post' else: method_map = 'get' From 4292cc18fa3e4b3f5e67c02c3780cdcbf901a0a1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 19 Aug 2013 20:53:30 +0100 Subject: [PATCH 011/225] Docs tweaking --- docs/api-guide/routers.md | 11 +++++++---- docs/api-guide/viewsets.md | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index 7884c2e94..c84654189 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -48,6 +48,8 @@ The following URL pattern would additionally be generated: * URL pattern: `^users/{pk}/set_password/$` Name: `'user-set-password'` +For more information see the viewset documentation on [marking extra actions for routing][route-decorators]. + # API Guide ## SimpleRouter @@ -58,12 +60,12 @@ This router includes routes for the standard set of `list`, `create`, `retrieve` URL StyleHTTP MethodActionURL Name {prefix}/GETlist{basename}-list POSTcreate + {prefix}/{methodname}/GET, or as specified by `methods` argument`@list_route` decorated method{basename}-{methodname} {prefix}/{lookup}/GETretrieve{basename}-detail PUTupdate PATCHpartial_update DELETEdestroy - {prefix}/{lookup}/{methodname}/GET@detail_route decorated method{basename}-{methodname} - POST@detail_route decorated method + {prefix}/{lookup}/{methodname}/GET, or as specified by `methods` argument`@detail_route` decorated method{basename}-{methodname} By default the URLs created by `SimpleRouter` are appended with a trailing slash. @@ -82,12 +84,12 @@ This router is similar to `SimpleRouter` as above, but additionally includes a d [.format]GETautomatically generated root viewapi-root {prefix}/[.format]GETlist{basename}-list POSTcreate + {prefix}/{methodname}/[.format]GET, or as specified by `methods` argument`@list_route` decorated method{basename}-{methodname} {prefix}/{lookup}/[.format]GETretrieve{basename}-detail PUTupdate PATCHpartial_update DELETEdestroy - {prefix}/{lookup}/{methodname}/[.format]GET@detail_route decorated method{basename}-{methodname} - POST@detail_route decorated method + {prefix}/{lookup}/{methodname}/[.format]GET, or as specified by `methods` argument`@detail_route` decorated method{basename}-{methodname} As with `SimpleRouter` the trailing slashes on the URL routes can be removed by setting the `trailing_slash` argument to `False` when instantiating the router. @@ -144,3 +146,4 @@ If you want to provide totally custom behavior, you can override `BaseRouter` an You may also want to override the `get_default_base_name(self, viewset)` method, or else always explicitly set the `base_name` argument when registering your viewsets with the router. [cite]: http://guides.rubyonrails.org/routing.html +[route-decorators]: viewsets.html#marking-extra-actions-for-routing \ No newline at end of file diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 95efc229f..9005e7cbc 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -61,7 +61,7 @@ There are two main advantages of using a `ViewSet` class over using a `View` cla Both of these come with a trade-off. Using regular views and URL confs is more explicit and gives you more control. ViewSets are helpful if you want to get up and running quickly, or when you have a large API and you want to enforce a consistent URL configuration throughout. -## Marking extra methods for routing +## Marking extra actions for routing The default routers included with REST framework will provide routes for a standard set of create/retrieve/update/destroy style operations, as shown below: From 8acee2e626746f3096c49b3ebb13aaf7dc882917 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 19 Aug 2013 21:02:22 +0100 Subject: [PATCH 012/225] Commenting link/action decorators as pending deprecation --- rest_framework/decorators.py | 51 ++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 1ca176f2c..18e41a18d 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -108,6 +108,31 @@ def permission_classes(permission_classes): return decorator +def detail_route(methods=['get'], **kwargs): + """ + Used to mark a method on a ViewSet that should be routed for detail requests. + """ + def decorator(func): + func.bind_to_methods = methods + func.detail = True + func.kwargs = kwargs + return func + return decorator + + +def list_route(methods=['get'], **kwargs): + """ + Used to mark a method on a ViewSet that should be routed for list requests. + """ + def decorator(func): + func.bind_to_methods = methods + func.detail = False + func.kwargs = kwargs + return func + return decorator + +# These are now pending deprecation, in favor of `detail_route` and `list_route`. + def link(**kwargs): """ Used to mark a method on a ViewSet that should be routed for detail GET requests. @@ -133,28 +158,4 @@ def action(methods=['post'], **kwargs): func.detail = True func.kwargs = kwargs return func - return decorator - - -def detail_route(methods=['get'], **kwargs): - """ - Used to mark a method on a ViewSet that should be routed for detail requests. - """ - def decorator(func): - func.bind_to_methods = methods - func.detail = True - func.kwargs = kwargs - return func - return decorator - - -def list_route(methods=['get'], **kwargs): - """ - Used to mark a method on a ViewSet that should be routed for list requests. - """ - def decorator(func): - func.bind_to_methods = methods - func.detail = False - func.kwargs = kwargs - return func - return decorator + return decorator \ No newline at end of file From 815ef50735f50c7aff5255e60f1b484e75178e87 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 21 Aug 2013 21:18:46 +0100 Subject: [PATCH 013/225] If page size query param <= 0, just use default page size. Closes #1028 --- rest_framework/generics.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 874a142c8..bcd62bf9d 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -14,6 +14,15 @@ from rest_framework.settings import api_settings import warnings +def strict_positive_int(integer_string): + """ + Cast a string to a strictly positive integer. + """ + ret = int(integer_string) + if ret <= 0: + raise ValueError() + return ret + def get_object_or_404(queryset, **filter_kwargs): """ Same as Django's standard shortcut, but make sure to raise 404 @@ -198,7 +207,7 @@ class GenericAPIView(views.APIView): if self.paginate_by_param: query_params = self.request.QUERY_PARAMS try: - return int(query_params[self.paginate_by_param]) + return strict_positive_int(query_params[self.paginate_by_param]) except (KeyError, ValueError): pass From 44ceef841543877a700c3fb4a0f84dfecbad0cbb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 21 Aug 2013 21:30:25 +0100 Subject: [PATCH 014/225] Updating 2.4.0 release notes --- .travis.yml | 1 + docs/topics/release-notes.md | 5 +- rest_framework/compat.py | 2 +- rest_framework/six.py | 389 ----------------------------------- tox.ini | 2 + 5 files changed, 8 insertions(+), 391 deletions(-) delete mode 100644 rest_framework/six.py diff --git a/.travis.yml b/.travis.yml index 6a4532411..f8640db2c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ install: - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth-plus==2.0 --use-mirrors; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth2-provider==0.2.4 --use-mirrors; fi" - "if [[ ${DJANGO::11} == 'django==1.3' ]]; then pip install django-filter==0.5.4 --use-mirrors; fi" + - "if [[ ${DJANGO::11} == 'django==1.3' ]]; then pip install six --use-mirrors; fi" - "if [[ ${DJANGO::11} != 'django==1.3' ]]; then pip install django-filter==0.6 --use-mirrors; fi" - export PYTHONPATH=. diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 52abfc08e..f3bb19c67 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -40,9 +40,12 @@ You can determine your currently installed version using `pip freeze`: ## 2.3.x series -### Master +### 2.4.0 +* `@detail_route` and `@list_route` decorators replace `@action` and `@link`. +* `six` no longer bundled. For Django <= 1.4.1, install `six` package. * Support customizable view name and description functions, using the `VIEW_NAME_FUNCTION` and `VIEW_DESCRIPTION_FUNCTION` settings. +* Bugfix: `?page_size=0` query parameter now falls back to default page size for view, instead of always turning pagination off. ### 2.3.7 diff --git a/rest_framework/compat.py b/rest_framework/compat.py index baee3a9c2..178a697f3 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -14,7 +14,7 @@ from django.conf import settings try: from django.utils import six except ImportError: - from rest_framework import six + import six # location of patterns, url, include changes in 1.4 onwards try: diff --git a/rest_framework/six.py b/rest_framework/six.py deleted file mode 100644 index 9e3823128..000000000 --- a/rest_framework/six.py +++ /dev/null @@ -1,389 +0,0 @@ -"""Utilities for writing code that runs on Python 2 and 3""" - -import operator -import sys -import types - -__author__ = "Benjamin Peterson " -__version__ = "1.2.0" - - -# True if we are running on Python 3. -PY3 = sys.version_info[0] == 3 - -if PY3: - string_types = str, - integer_types = int, - class_types = type, - text_type = str - binary_type = bytes - - MAXSIZE = sys.maxsize -else: - string_types = basestring, - integer_types = (int, long) - class_types = (type, types.ClassType) - text_type = unicode - binary_type = str - - if sys.platform == "java": - # Jython always uses 32 bits. - MAXSIZE = int((1 << 31) - 1) - else: - # It's possible to have sizeof(long) != sizeof(Py_ssize_t). - class X(object): - def __len__(self): - return 1 << 31 - try: - len(X()) - except OverflowError: - # 32-bit - MAXSIZE = int((1 << 31) - 1) - else: - # 64-bit - MAXSIZE = int((1 << 63) - 1) - del X - - -def _add_doc(func, doc): - """Add documentation to a function.""" - func.__doc__ = doc - - -def _import_module(name): - """Import module, returning the module after the last dot.""" - __import__(name) - return sys.modules[name] - - -class _LazyDescr(object): - - def __init__(self, name): - self.name = name - - def __get__(self, obj, tp): - result = self._resolve() - setattr(obj, self.name, result) - # This is a bit ugly, but it avoids running this again. - delattr(tp, self.name) - return result - - -class MovedModule(_LazyDescr): - - def __init__(self, name, old, new=None): - super(MovedModule, self).__init__(name) - if PY3: - if new is None: - new = name - self.mod = new - else: - self.mod = old - - def _resolve(self): - return _import_module(self.mod) - - -class MovedAttribute(_LazyDescr): - - def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): - super(MovedAttribute, self).__init__(name) - if PY3: - if new_mod is None: - new_mod = name - self.mod = new_mod - if new_attr is None: - if old_attr is None: - new_attr = name - else: - new_attr = old_attr - self.attr = new_attr - else: - self.mod = old_mod - if old_attr is None: - old_attr = name - self.attr = old_attr - - def _resolve(self): - module = _import_module(self.mod) - return getattr(module, self.attr) - - - -class _MovedItems(types.ModuleType): - """Lazy loading of moved objects""" - - -_moved_attributes = [ - MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), - MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), - MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), - MovedAttribute("map", "itertools", "builtins", "imap", "map"), - MovedAttribute("reload_module", "__builtin__", "imp", "reload"), - MovedAttribute("reduce", "__builtin__", "functools"), - MovedAttribute("StringIO", "StringIO", "io"), - MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), - - MovedModule("builtins", "__builtin__"), - MovedModule("configparser", "ConfigParser"), - MovedModule("copyreg", "copy_reg"), - MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), - MovedModule("http_cookies", "Cookie", "http.cookies"), - MovedModule("html_entities", "htmlentitydefs", "html.entities"), - MovedModule("html_parser", "HTMLParser", "html.parser"), - MovedModule("http_client", "httplib", "http.client"), - MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), - MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), - MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), - MovedModule("cPickle", "cPickle", "pickle"), - MovedModule("queue", "Queue"), - MovedModule("reprlib", "repr"), - MovedModule("socketserver", "SocketServer"), - MovedModule("tkinter", "Tkinter"), - MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), - MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), - MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), - MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), - MovedModule("tkinter_tix", "Tix", "tkinter.tix"), - MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), - MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), - MovedModule("tkinter_colorchooser", "tkColorChooser", - "tkinter.colorchooser"), - MovedModule("tkinter_commondialog", "tkCommonDialog", - "tkinter.commondialog"), - MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), - MovedModule("tkinter_font", "tkFont", "tkinter.font"), - MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), - MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", - "tkinter.simpledialog"), - MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), - MovedModule("winreg", "_winreg"), -] -for attr in _moved_attributes: - setattr(_MovedItems, attr.name, attr) -del attr - -moves = sys.modules["django.utils.six.moves"] = _MovedItems("moves") - - -def add_move(move): - """Add an item to six.moves.""" - setattr(_MovedItems, move.name, move) - - -def remove_move(name): - """Remove item from six.moves.""" - try: - delattr(_MovedItems, name) - except AttributeError: - try: - del moves.__dict__[name] - except KeyError: - raise AttributeError("no such move, %r" % (name,)) - - -if PY3: - _meth_func = "__func__" - _meth_self = "__self__" - - _func_code = "__code__" - _func_defaults = "__defaults__" - - _iterkeys = "keys" - _itervalues = "values" - _iteritems = "items" -else: - _meth_func = "im_func" - _meth_self = "im_self" - - _func_code = "func_code" - _func_defaults = "func_defaults" - - _iterkeys = "iterkeys" - _itervalues = "itervalues" - _iteritems = "iteritems" - - -try: - advance_iterator = next -except NameError: - def advance_iterator(it): - return it.next() -next = advance_iterator - - -if PY3: - def get_unbound_function(unbound): - return unbound - - Iterator = object - - def callable(obj): - return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) -else: - def get_unbound_function(unbound): - return unbound.im_func - - class Iterator(object): - - def next(self): - return type(self).__next__(self) - - callable = callable -_add_doc(get_unbound_function, - """Get the function out of a possibly unbound function""") - - -get_method_function = operator.attrgetter(_meth_func) -get_method_self = operator.attrgetter(_meth_self) -get_function_code = operator.attrgetter(_func_code) -get_function_defaults = operator.attrgetter(_func_defaults) - - -def iterkeys(d): - """Return an iterator over the keys of a dictionary.""" - return iter(getattr(d, _iterkeys)()) - -def itervalues(d): - """Return an iterator over the values of a dictionary.""" - return iter(getattr(d, _itervalues)()) - -def iteritems(d): - """Return an iterator over the (key, value) pairs of a dictionary.""" - return iter(getattr(d, _iteritems)()) - - -if PY3: - def b(s): - return s.encode("latin-1") - def u(s): - return s - if sys.version_info[1] <= 1: - def int2byte(i): - return bytes((i,)) - else: - # This is about 2x faster than the implementation above on 3.2+ - int2byte = operator.methodcaller("to_bytes", 1, "big") - import io - StringIO = io.StringIO - BytesIO = io.BytesIO -else: - def b(s): - return s - def u(s): - return unicode(s, "unicode_escape") - int2byte = chr - import StringIO - StringIO = BytesIO = StringIO.StringIO -_add_doc(b, """Byte literal""") -_add_doc(u, """Text literal""") - - -if PY3: - import builtins - exec_ = getattr(builtins, "exec") - - - def reraise(tp, value, tb=None): - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - - - print_ = getattr(builtins, "print") - del builtins - -else: - def exec_(code, globs=None, locs=None): - """Execute code in a namespace.""" - if globs is None: - frame = sys._getframe(1) - globs = frame.f_globals - if locs is None: - locs = frame.f_locals - del frame - elif locs is None: - locs = globs - exec("""exec code in globs, locs""") - - - exec_("""def reraise(tp, value, tb=None): - raise tp, value, tb -""") - - - def print_(*args, **kwargs): - """The new-style print function.""" - fp = kwargs.pop("file", sys.stdout) - if fp is None: - return - def write(data): - if not isinstance(data, basestring): - data = str(data) - fp.write(data) - want_unicode = False - sep = kwargs.pop("sep", None) - if sep is not None: - if isinstance(sep, unicode): - want_unicode = True - elif not isinstance(sep, str): - raise TypeError("sep must be None or a string") - end = kwargs.pop("end", None) - if end is not None: - if isinstance(end, unicode): - want_unicode = True - elif not isinstance(end, str): - raise TypeError("end must be None or a string") - if kwargs: - raise TypeError("invalid keyword arguments to print()") - if not want_unicode: - for arg in args: - if isinstance(arg, unicode): - want_unicode = True - break - if want_unicode: - newline = unicode("\n") - space = unicode(" ") - else: - newline = "\n" - space = " " - if sep is None: - sep = space - if end is None: - end = newline - for i, arg in enumerate(args): - if i: - write(sep) - write(arg) - write(end) - -_add_doc(reraise, """Reraise an exception.""") - - -def with_metaclass(meta, base=object): - """Create a base class with a metaclass.""" - return meta("NewBase", (base,), {}) - - -### Additional customizations for Django ### - -if PY3: - _iterlists = "lists" - _assertRaisesRegex = "assertRaisesRegex" -else: - _iterlists = "iterlists" - _assertRaisesRegex = "assertRaisesRegexp" - - -def iterlists(d): - """Return an iterator over the values of a MultiValueDict.""" - return getattr(d, _iterlists)() - - -def assertRaisesRegex(self, *args, **kwargs): - return getattr(self, _assertRaisesRegex)(*args, **kwargs) - - -add_move(MovedModule("_dummy_thread", "dummy_thread")) -add_move(MovedModule("_thread", "thread")) diff --git a/tox.ini b/tox.ini index aa97fd350..6ec400ddf 100644 --- a/tox.ini +++ b/tox.ini @@ -91,6 +91,7 @@ deps = django==1.3.5 django-oauth-plus==2.0 oauth2==1.5.211 django-oauth2-provider==0.2.3 + six [testenv:py2.6-django1.3] basepython = python2.6 @@ -100,3 +101,4 @@ deps = django==1.3.5 django-oauth-plus==2.0 oauth2==1.5.211 django-oauth2-provider==0.2.3 + six From f631f55f8ebdf3d4e478aa5ca435ad36e86bee0f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 21 Aug 2013 21:35:17 +0100 Subject: [PATCH 015/225] Tweak comment --- rest_framework/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 178a697f3..66be96a62 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -10,7 +10,7 @@ import django from django.core.exceptions import ImproperlyConfigured from django.conf import settings -# Try to import six from Django, fallback to included `six`. +# Try to import six from Django, fallback to external `six` package. try: from django.utils import six except ImportError: From bf07b8e616bd92e4ae3c2c09b198181d7075e6bd Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 29 Aug 2013 08:53:19 +0100 Subject: [PATCH 016/225] Better docs for customizing dynamic routes. Refs #908 --- docs/api-guide/routers.md | 81 +++++++++++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 11 deletions(-) diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index f083b3d45..730fa876a 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -123,28 +123,87 @@ The arguments to the `Route` named tuple are: **initkwargs**: A dictionary of any additional arguments that should be passed when instantiating the view. Note that the `suffix` argument is reserved for identifying the viewset type, used when generating the view name and breadcrumb links. +## Customizing dynamic routes + +You can also customize how the `@list_route` and `@detail_route` decorators are routed. +To route either or both of these decorators, include a `DynamicListRoute` and/or `DynamicDetailRoute` named tuple in the `.routes` list. + +The arguments to `DynamicListRoute` and `DynamicDetailRoute` are: + +**url**: A string representing the URL to be routed. May include the same format strings as `Route`, and additionally accepts the `{methodname}` and `{methodnamehyphen}` format strings. + +**name**: The name of the URL as used in `reverse` calls. May include the following format strings: `{basename}`, `{methodname}` and `{methodnamehyphen}`. + +**initkwargs**: A dictionary of any additional arguments that should be passed when instantiating the view. + ## Example The following example will only route to the `list` and `retrieve` actions, and does not use the trailing slash convention. - from rest_framework.routers import Route, SimpleRouter + from rest_framework.routers import Route, DynamicDetailRoute, SimpleRouter - class ReadOnlyRouter(SimpleRouter): + class CustomReadOnlyRouter(SimpleRouter): """ A router for read-only APIs, which doesn't use trailing slashes. """ routes = [ - Route(url=r'^{prefix}$', - mapping={'get': 'list'}, - name='{basename}-list', - initkwargs={'suffix': 'List'}), - Route(url=r'^{prefix}/{lookup}$', - mapping={'get': 'retrieve'}, - name='{basename}-detail', - initkwargs={'suffix': 'Detail'}) + Route( + url=r'^{prefix}$', + mapping={'get': 'list'}, + name='{basename}-list', + initkwargs={'suffix': 'List'} + ), + Route( + url=r'^{prefix}/{lookup}$', + mapping={'get': 'retrieve'}, + name='{basename}-detail', + initkwargs={'suffix': 'Detail'} + ), + DynamicDetailRoute( + url=r'^{prefix}/{lookup}/{methodnamehyphen}$', + name='{basename}-{methodnamehyphen}', + initkwargs={} + ) ] -The `SimpleRouter` class provides another example of setting the `.routes` attribute. +Let's take a look at the routes our `CustomReadOnlyRouter` would generate for a simple viewset. + +`views.py`: + + class UserViewSet(viewsets.ReadOnlyModelViewSet): + """ + A viewset that provides the standard actions + """ + queryset = User.objects.all() + serializer_class = UserSerializer + lookup_field = 'username' + + @detail_route() + def group_names(self, request): + """ + Returns a list of all the group names that the given + user belongs to. + """ + user = self.get_object() + groups = user.groups.all() + return Response([group.name for group in groups]) + +`urls.py`: + + router = CustomReadOnlyRouter() + router.register('users', UserViewSet) + urlpatterns = router.urls + +The following mappings would be generated... + + + + + + +
URLHTTP MethodActionURL Name
/usersGETlistuser-list
/users/{username}GETretrieveuser-detail
/users/{username}/group-namesGETgroup_namesuser-group-names
+ +For another example of setting the `.routes` attribute, see the source code for the `SimpleRouter` class. ## Advanced custom routers From e441f85109e64345a12e65062fc0e51c5787e67f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 25 Sep 2013 10:30:04 +0100 Subject: [PATCH 017/225] Drop 1.3 support --- .travis.yml | 12 +- rest_framework/authentication.py | 2 +- rest_framework/compat.py | 381 +----------------- rest_framework/fields.py | 4 +- rest_framework/routers.py | 2 +- rest_framework/runtests/settings.py | 5 +- rest_framework/runtests/urls.py | 2 +- rest_framework/serializers.py | 6 +- .../templates/rest_framework/base.html | 1 + .../templates/rest_framework/login_base.html | 1 + rest_framework/templatetags/rest_framework.py | 87 +--- rest_framework/tests/test_authentication.py | 2 +- rest_framework/tests/test_breadcrumbs.py | 2 +- rest_framework/tests/test_filters.py | 3 +- rest_framework/tests/test_htmlrenderer.py | 2 +- .../tests/test_hyperlinkedserializers.py | 4 +- .../tests/test_relations_hyperlink.py | 2 +- rest_framework/tests/test_renderers.py | 3 +- rest_framework/tests/test_request.py | 2 +- rest_framework/tests/test_response.py | 2 +- rest_framework/tests/test_reverse.py | 2 +- rest_framework/tests/test_routers.py | 2 +- rest_framework/tests/test_testing.py | 2 +- rest_framework/tests/test_urlpatterns.py | 2 +- rest_framework/urlpatterns.py | 2 +- rest_framework/urls.py | 2 +- rest_framework/utils/encoders.py | 3 +- tox.ini | 22 +- 28 files changed, 56 insertions(+), 506 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7ebe715a0..456f8e9c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,18 +10,15 @@ env: - DJANGO="https://www.djangoproject.com/download/1.6a1/tarball/" - DJANGO="django==1.5.1 --use-mirrors" - DJANGO="django==1.4.5 --use-mirrors" - - DJANGO="django==1.3.7 --use-mirrors" install: - pip install $DJANGO - - pip install defusedxml==0.3 + - pip install defusedxml==0.3 --use-mirrors + - pip install django-filter==0.6 --use-mirrors - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install oauth2==1.5.211 --use-mirrors; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth-plus==2.0 --use-mirrors; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth2-provider==0.2.4 --use-mirrors; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-guardian==1.1.1 --use-mirrors; fi" - - "if [[ ${DJANGO::11} == 'django==1.3' ]]; then pip install django-filter==0.5.4 --use-mirrors; fi" - - "if [[ ${DJANGO::11} == 'django==1.3' ]]; then pip install six --use-mirrors; fi" - - "if [[ ${DJANGO::11} != 'django==1.3' ]]; then pip install django-filter==0.6 --use-mirrors; fi" - export PYTHONPATH=. script: @@ -31,10 +28,5 @@ matrix: exclude: - python: "3.2" env: DJANGO="django==1.4.5 --use-mirrors" - - python: "3.2" - env: DJANGO="django==1.3.7 --use-mirrors" - python: "3.3" env: DJANGO="django==1.4.5 --use-mirrors" - - python: "3.3" - env: DJANGO="django==1.3.7 --use-mirrors" - diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index cf001a24d..db5cce40d 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -6,8 +6,8 @@ import base64 from django.contrib.auth import authenticate from django.core.exceptions import ImproperlyConfigured +from django.middleware.csrf import CsrfViewMiddleware from rest_framework import exceptions, HTTP_HEADER_ENCODING -from rest_framework.compat import CsrfViewMiddleware from rest_framework.compat import oauth, oauth_provider, oauth_provider_store from rest_framework.compat import oauth2_provider, provider_now from rest_framework.authtoken.models import Token diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 1238f043a..f048b10ad 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -5,25 +5,19 @@ versions of django/python, and compatibility wrappers around optional packages. # flake8: noqa from __future__ import unicode_literals - import django from django.core.exceptions import ImproperlyConfigured from django.conf import settings + # Try to import six from Django, fallback to external `six` package. try: from django.utils import six except ImportError: import six -# location of patterns, url, include changes in 1.4 onwards -try: - from django.conf.urls import patterns, url, include -except ImportError: - from django.conf.urls.defaults import patterns, url, include - -# Handle django.utils.encoding rename: -# smart_unicode -> smart_text +# Handle django.utils.encoding rename in 1.5 onwards. +# smart_unicode -> smart_text # force_unicode -> force_text try: from django.utils.encoding import smart_text @@ -41,13 +35,15 @@ try: except ImportError: from django.http import HttpResponse as HttpResponseBase + # django-filter is optional try: import django_filters except ImportError: django_filters = None -# guardian is optional + +# django-guardian is optional try: import guardian except ImportError: @@ -80,14 +76,6 @@ except ImportError: Image = None -def get_concrete_model(model_cls): - try: - return model_cls._meta.concrete_model - except AttributeError: - # 1.3 does not include concrete model - return model_cls - - # Django 1.5 add support for custom auth user model if django.VERSION >= (1, 5): AUTH_USER_MODEL = settings.AUTH_USER_MODEL @@ -95,46 +83,13 @@ else: AUTH_USER_MODEL = 'auth.User' +# View._allowed_methods only present from 1.5 onwards if django.VERSION >= (1, 5): from django.views.generic import View else: - from django.views.generic import View as _View - from django.utils.decorators import classonlymethod - from django.utils.functional import update_wrapper + from django.views.generic import View as DjangoView - class View(_View): - # 1.3 does not include head method in base View class - # See: https://code.djangoproject.com/ticket/15668 - @classonlymethod - def as_view(cls, **initkwargs): - """ - Main entry point for a request-response process. - """ - # sanitize keyword arguments - for key in initkwargs: - if key in cls.http_method_names: - raise TypeError("You tried to pass in the %s method name as a " - "keyword argument to %s(). Don't do that." - % (key, cls.__name__)) - if not hasattr(cls, key): - raise TypeError("%s() received an invalid keyword %r" % ( - cls.__name__, key)) - - def view(request, *args, **kwargs): - self = cls(**initkwargs) - if hasattr(self, 'get') and not hasattr(self, 'head'): - self.head = self.get - return self.dispatch(request, *args, **kwargs) - - # take name and docstring from class - update_wrapper(view, cls, updated=()) - - # and possible attributes set by decorators - # like csrf_exempt from dispatch - update_wrapper(view, cls.dispatch, assigned=()) - return view - - # _allowed_methods only present from 1.5 onwards + class View(DjangoView): def _allowed_methods(self): return [m.upper() for m in self.http_method_names if hasattr(self, m)] @@ -144,316 +99,16 @@ if 'patch' not in View.http_method_names: View.http_method_names = View.http_method_names + ['patch'] -# PUT, DELETE do not require CSRF until 1.4. They should. Make it better. -if django.VERSION >= (1, 4): - from django.middleware.csrf import CsrfViewMiddleware -else: - import hashlib - import re - import random - import logging - - from django.conf import settings - from django.core.urlresolvers import get_callable - - try: - from logging import NullHandler - except ImportError: - class NullHandler(logging.Handler): - def emit(self, record): - pass - - logger = logging.getLogger('django.request') - - if not logger.handlers: - logger.addHandler(NullHandler()) - - def same_origin(url1, url2): - """ - Checks if two URLs are 'same-origin' - """ - p1, p2 = urlparse.urlparse(url1), urlparse.urlparse(url2) - return p1[0:2] == p2[0:2] - - def constant_time_compare(val1, val2): - """ - Returns True if the two strings are equal, False otherwise. - - The time taken is independent of the number of characters that match. - """ - if len(val1) != len(val2): - return False - result = 0 - for x, y in zip(val1, val2): - result |= ord(x) ^ ord(y) - return result == 0 - - # Use the system (hardware-based) random number generator if it exists. - if hasattr(random, 'SystemRandom'): - randrange = random.SystemRandom().randrange - else: - randrange = random.randrange - - _MAX_CSRF_KEY = 18446744073709551616 # 2 << 63 - - REASON_NO_REFERER = "Referer checking failed - no Referer." - REASON_BAD_REFERER = "Referer checking failed - %s does not match %s." - REASON_NO_CSRF_COOKIE = "CSRF cookie not set." - REASON_BAD_TOKEN = "CSRF token missing or incorrect." - - def _get_failure_view(): - """ - Returns the view to be used for CSRF rejections - """ - return get_callable(settings.CSRF_FAILURE_VIEW) - - def _get_new_csrf_key(): - return hashlib.md5("%s%s" % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest() - - def get_token(request): - """ - Returns the the CSRF token required for a POST form. The token is an - alphanumeric value. - - A side effect of calling this function is to make the the csrf_protect - decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie' - header to the outgoing response. For this reason, you may need to use this - function lazily, as is done by the csrf context processor. - """ - request.META["CSRF_COOKIE_USED"] = True - return request.META.get("CSRF_COOKIE", None) - - def _sanitize_token(token): - # Allow only alphanum, and ensure we return a 'str' for the sake of the post - # processing middleware. - token = re.sub('[^a-zA-Z0-9]', '', str(token.decode('ascii', 'ignore'))) - if token == "": - # In case the cookie has been truncated to nothing at some point. - return _get_new_csrf_key() - else: - return token - - class CsrfViewMiddleware(object): - """ - Middleware that requires a present and correct csrfmiddlewaretoken - for POST requests that have a CSRF cookie, and sets an outgoing - CSRF cookie. - - This middleware should be used in conjunction with the csrf_token template - tag. - """ - # The _accept and _reject methods currently only exist for the sake of the - # requires_csrf_token decorator. - def _accept(self, request): - # Avoid checking the request twice by adding a custom attribute to - # request. This will be relevant when both decorator and middleware - # are used. - request.csrf_processing_done = True - return None - - def _reject(self, request, reason): - return _get_failure_view()(request, reason=reason) - - def process_view(self, request, callback, callback_args, callback_kwargs): - - if getattr(request, 'csrf_processing_done', False): - return None - - try: - csrf_token = _sanitize_token(request.COOKIES[settings.CSRF_COOKIE_NAME]) - # Use same token next time - request.META['CSRF_COOKIE'] = csrf_token - except KeyError: - csrf_token = None - # Generate token and store it in the request, so it's available to the view. - request.META["CSRF_COOKIE"] = _get_new_csrf_key() - - # Wait until request.META["CSRF_COOKIE"] has been manipulated before - # bailing out, so that get_token still works - if getattr(callback, 'csrf_exempt', False): - return None - - # Assume that anything not defined as 'safe' by RC2616 needs protection. - if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): - if getattr(request, '_dont_enforce_csrf_checks', False): - # Mechanism to turn off CSRF checks for test suite. It comes after - # the creation of CSRF cookies, so that everything else continues to - # work exactly the same (e.g. cookies are sent etc), but before the - # any branches that call reject() - return self._accept(request) - - if request.is_secure(): - # Suppose user visits http://example.com/ - # An active network attacker,(man-in-the-middle, MITM) sends a - # POST form which targets https://example.com/detonate-bomb/ and - # submits it via javascript. - # - # The attacker will need to provide a CSRF cookie and token, but - # that is no problem for a MITM and the session independent - # nonce we are using. So the MITM can circumvent the CSRF - # protection. This is true for any HTTP connection, but anyone - # using HTTPS expects better! For this reason, for - # https://example.com/ we need additional protection that treats - # http://example.com/ as completely untrusted. Under HTTPS, - # Barth et al. found that the Referer header is missing for - # same-domain requests in only about 0.2% of cases or less, so - # we can use strict Referer checking. - referer = request.META.get('HTTP_REFERER') - if referer is None: - logger.warning('Forbidden (%s): %s' % (REASON_NO_REFERER, request.path), - extra={ - 'status_code': 403, - 'request': request, - } - ) - return self._reject(request, REASON_NO_REFERER) - - # Note that request.get_host() includes the port - good_referer = 'https://%s/' % request.get_host() - if not same_origin(referer, good_referer): - reason = REASON_BAD_REFERER % (referer, good_referer) - logger.warning('Forbidden (%s): %s' % (reason, request.path), - extra={ - 'status_code': 403, - 'request': request, - } - ) - return self._reject(request, reason) - - if csrf_token is None: - # No CSRF cookie. For POST requests, we insist on a CSRF cookie, - # and in this way we can avoid all CSRF attacks, including login - # CSRF. - logger.warning('Forbidden (%s): %s' % (REASON_NO_CSRF_COOKIE, request.path), - extra={ - 'status_code': 403, - 'request': request, - } - ) - return self._reject(request, REASON_NO_CSRF_COOKIE) - - # check non-cookie token for match - request_csrf_token = "" - if request.method == "POST": - request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') - - if request_csrf_token == "": - # Fall back to X-CSRFToken, to make things easier for AJAX, - # and possible for PUT/DELETE - request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '') - - if not constant_time_compare(request_csrf_token, csrf_token): - logger.warning('Forbidden (%s): %s' % (REASON_BAD_TOKEN, request.path), - extra={ - 'status_code': 403, - 'request': request, - } - ) - return self._reject(request, REASON_BAD_TOKEN) - - return self._accept(request) - -# timezone support is new in Django 1.4 -try: - from django.utils import timezone -except ImportError: - timezone = None - -# dateparse is ALSO new in Django 1.4 -try: - from django.utils.dateparse import parse_date, parse_datetime, parse_time -except ImportError: - import datetime - import re - - date_re = re.compile( - r'(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})$' - ) - - datetime_re = re.compile( - r'(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})' - r'[T ](?P\d{1,2}):(?P\d{1,2})' - r'(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?' - r'(?PZ|[+-]\d{1,2}:\d{1,2})?$' - ) - - time_re = re.compile( - r'(?P\d{1,2}):(?P\d{1,2})' - r'(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?' - ) - - def parse_date(value): - match = date_re.match(value) - if match: - kw = dict((k, int(v)) for k, v in match.groupdict().iteritems()) - return datetime.date(**kw) - - def parse_time(value): - match = time_re.match(value) - if match: - kw = match.groupdict() - if kw['microsecond']: - kw['microsecond'] = kw['microsecond'].ljust(6, '0') - kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None) - return datetime.time(**kw) - - def parse_datetime(value): - """Parse datetime, but w/o the timezone awareness in 1.4""" - match = datetime_re.match(value) - if match: - kw = match.groupdict() - if kw['microsecond']: - kw['microsecond'] = kw['microsecond'].ljust(6, '0') - kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None) - return datetime.datetime(**kw) - - -# smart_urlquote is new on Django 1.4 -try: - from django.utils.html import smart_urlquote -except ImportError: - import re - from django.utils.encoding import smart_str - try: - from urllib.parse import quote, urlsplit, urlunsplit - except ImportError: # Python 2 - from urllib import quote - from urlparse import urlsplit, urlunsplit - - unquoted_percents_re = re.compile(r'%(?![0-9A-Fa-f]{2})') - - def smart_urlquote(url): - "Quotes a URL if it isn't already quoted." - # Handle IDN before quoting. - scheme, netloc, path, query, fragment = urlsplit(url) - try: - netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE - except UnicodeError: # invalid domain part - pass - else: - url = urlunsplit((scheme, netloc, path, query, fragment)) - - # An URL is considered unquoted if it contains no % characters or - # contains a % not followed by two hexadecimal digits. See #9655. - if '%' not in url or unquoted_percents_re.search(url): - # See http://bugs.python.org/issue2637 - url = quote(smart_str(url), safe=b'!*\'();:@&=+$,/?#[]~') - - return force_text(url) - - -# RequestFactory only provide `generic` from 1.5 onwards - +# RequestFactory only provides `generic` from 1.5 onwards from django.test.client import RequestFactory as DjangoRequestFactory from django.test.client import FakePayload try: # In 1.5 the test client uses force_bytes from django.utils.encoding import force_bytes_or_smart_bytes except ImportError: - # In 1.3 and 1.4 the test client just uses smart_str + # In 1.4 the test client just uses smart_str from django.utils.encoding import smart_str as force_bytes_or_smart_bytes - class RequestFactory(DjangoRequestFactory): def generic(self, method, path, data='', content_type='application/octet-stream', **extra): @@ -478,6 +133,7 @@ class RequestFactory(DjangoRequestFactory): r.update(extra) return self.request(**r) + # Markdown is optional try: import markdown @@ -492,7 +148,6 @@ try: safe_mode = False md = markdown.Markdown(extensions=extensions, safe_mode=safe_mode) return md.convert(text) - except ImportError: apply_markdown = None @@ -510,14 +165,16 @@ try: except ImportError: etree = None -# OAuth is optional + +# OAuth2 is optional try: # Note: The `oauth2` package actually provides oauth1.0a support. Urg. import oauth2 as oauth except ImportError: oauth = None -# OAuth is optional + +# OAuthProvider is optional try: import oauth_provider from oauth_provider.store import store as oauth_provider_store @@ -525,6 +182,7 @@ except (ImportError, ImproperlyConfigured): oauth_provider = None oauth_provider_store = None + # OAuth 2 support is optional try: import provider.oauth2 as oauth2_provider @@ -542,8 +200,6 @@ try: # Any other supported version does use timezone aware datetimes from django.utils.timezone import now as provider_now except ImportError: - import traceback - traceback.print_exc() oauth2_provider = None oauth2_provider_models = None oauth2_provider_forms = None @@ -551,7 +207,8 @@ except ImportError: oauth2_constants = None provider_now = None -# Handle lazy strings + +# Handle lazy strings across Py2/Py3 from django.utils.functional import Promise if six.PY3: diff --git a/rest_framework/fields.py b/rest_framework/fields.py index b3a9b0dfe..f340510d3 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -18,12 +18,14 @@ from django.conf import settings from django.db.models.fields import BLANK_CHOICE_DASH from django.http import QueryDict from django.forms import widgets +from django.utils import 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 ( - timezone, parse_date, parse_datetime, parse_time, BytesIO, six, smart_text, + BytesIO, six, smart_text, force_text, is_non_str_iterable ) from rest_framework.settings import api_settings diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 1c7a81585..790299cc3 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -17,9 +17,9 @@ from __future__ import unicode_literals import itertools from collections import namedtuple +from django.conf.urls import patterns, url from django.core.exceptions import ImproperlyConfigured from rest_framework import views -from rest_framework.compat import patterns, url 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/runtests/settings.py b/rest_framework/runtests/settings.py index be7216580..12aa73e7b 100644 --- a/rest_framework/runtests/settings.py +++ b/rest_framework/runtests/settings.py @@ -93,10 +93,7 @@ INSTALLED_APPS = ( 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', - # Uncomment the next line to enable the admin: - # 'django.contrib.admin', - # Uncomment the next line to enable admin documentation: - # 'django.contrib.admindocs', + 'django.contrib.staticfiles', 'rest_framework', 'rest_framework.authtoken', 'rest_framework.tests', diff --git a/rest_framework/runtests/urls.py b/rest_framework/runtests/urls.py index ed5baeae6..dff710115 100644 --- a/rest_framework/runtests/urls.py +++ b/rest_framework/runtests/urls.py @@ -1,7 +1,7 @@ """ Blank URLConf just to keep runtests.py happy. """ -from rest_framework.compat import patterns +from django.conf.urls import patterns urlpatterns = patterns('', ) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index f17757625..9e3881a2c 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -18,7 +18,7 @@ from decimal import Decimal from django.db import models from django.forms import widgets from django.utils.datastructures import SortedDict -from rest_framework.compat import get_concrete_model, six +from rest_framework.compat import six # Note: We do the following so that users of the framework can use this style: # @@ -575,7 +575,7 @@ class ModelSerializer(Serializer): cls = self.opts.model assert cls is not None, \ "Serializer class '%s' is missing 'model' Meta option" % self.__class__.__name__ - opts = get_concrete_model(cls)._meta + opts = cls._meta.concrete_model._meta ret = SortedDict() nested = bool(self.opts.depth) @@ -784,7 +784,7 @@ class ModelSerializer(Serializer): Return a list of field names to exclude from model validation. """ cls = self.opts.model - opts = get_concrete_model(cls)._meta + opts = cls._meta.concrete_model._meta exclusions = [field.name for field in opts.fields + opts.many_to_many] for field_name, field in self.fields.items(): diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index 2776d5500..47377d51f 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -1,4 +1,5 @@ {% load url from future %} +{% load staticfiles %} {% load rest_framework %} diff --git a/rest_framework/templates/rest_framework/login_base.html b/rest_framework/templates/rest_framework/login_base.html index be9a0072a..be83c2f53 100644 --- a/rest_framework/templates/rest_framework/login_base.html +++ b/rest_framework/templates/rest_framework/login_base.html @@ -1,4 +1,5 @@ {% load url from future %} +{% load staticfiles %} {% load rest_framework %} diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index e9c1cdd54..55f361490 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -2,97 +2,14 @@ from __future__ import unicode_literals, absolute_import from django import template from django.core.urlresolvers import reverse, NoReverseMatch from django.http import QueryDict -from django.utils.html import escape +from django.utils.html import escape, smart_urlquote from django.utils.safestring import SafeData, mark_safe -from rest_framework.compat import urlparse, force_text, six, smart_urlquote +from rest_framework.compat import urlparse, force_text, six import re, string register = template.Library() -# Note we don't use 'load staticfiles', because we need a 1.3 compatible -# version, so instead we include the `static` template tag ourselves. - -# When 1.3 becomes unsupported by REST framework, we can instead start to -# use the {% load staticfiles %} tag, remove the following code, -# and add a dependency that `django.contrib.staticfiles` must be installed. - -# Note: We can't put this into the `compat` module because the compat import -# from rest_framework.compat import ... -# conflicts with this rest_framework template tag module. - -try: # Django 1.5+ - from django.contrib.staticfiles.templatetags.staticfiles import StaticFilesNode - - @register.tag('static') - def do_static(parser, token): - return StaticFilesNode.handle_token(parser, token) - -except ImportError: - try: # Django 1.4 - from django.contrib.staticfiles.storage import staticfiles_storage - - @register.simple_tag - def static(path): - """ - A template tag that returns the URL to a file - using staticfiles' storage backend - """ - return staticfiles_storage.url(path) - - except ImportError: # Django 1.3 - from urlparse import urljoin - from django import template - from django.templatetags.static import PrefixNode - - class StaticNode(template.Node): - def __init__(self, varname=None, path=None): - if path is None: - raise template.TemplateSyntaxError( - "Static template nodes must be given a path to return.") - self.path = path - self.varname = varname - - def url(self, context): - path = self.path.resolve(context) - return self.handle_simple(path) - - def render(self, context): - url = self.url(context) - if self.varname is None: - return url - context[self.varname] = url - return '' - - @classmethod - def handle_simple(cls, path): - return urljoin(PrefixNode.handle_simple("STATIC_URL"), path) - - @classmethod - def handle_token(cls, parser, token): - """ - Class method to parse prefix node and return a Node. - """ - bits = token.split_contents() - - if len(bits) < 2: - raise template.TemplateSyntaxError( - "'%s' takes at least one argument (path to file)" % bits[0]) - - path = parser.compile_filter(bits[1]) - - if len(bits) >= 2 and bits[-2] == 'as': - varname = bits[3] - else: - varname = None - - return cls(varname, path) - - @register.tag('static') - def do_static_13(parser, token): - return StaticNode.handle_token(parser, token) - - def replace_query_param(url, key, val): """ Given a URL and a key/val pair, set or replace an item in the query diff --git a/rest_framework/tests/test_authentication.py b/rest_framework/tests/test_authentication.py index a44813b69..e9a817c0f 100644 --- a/rest_framework/tests/test_authentication.py +++ b/rest_framework/tests/test_authentication.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals +from django.conf.urls import patterns, url, include from django.contrib.auth.models import User from django.http import HttpResponse from django.test import TestCase @@ -18,7 +19,6 @@ from rest_framework.authentication import ( OAuth2Authentication ) from rest_framework.authtoken.models import Token -from rest_framework.compat import patterns, url, include from rest_framework.compat import oauth2_provider, oauth2_provider_models, oauth2_provider_scope from rest_framework.compat import oauth, oauth_provider from rest_framework.test import APIRequestFactory, APIClient diff --git a/rest_framework/tests/test_breadcrumbs.py b/rest_framework/tests/test_breadcrumbs.py index 41ddf2cea..33740cbb2 100644 --- a/rest_framework/tests/test_breadcrumbs.py +++ b/rest_framework/tests/test_breadcrumbs.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals +from django.conf.urls import patterns, url from django.test import TestCase -from rest_framework.compat import patterns, url from rest_framework.utils.breadcrumbs import get_breadcrumbs from rest_framework.views import APIView diff --git a/rest_framework/tests/test_filters.py b/rest_framework/tests/test_filters.py index 379db29d8..9697c5eef 100644 --- a/rest_framework/tests/test_filters.py +++ b/rest_framework/tests/test_filters.py @@ -1,12 +1,13 @@ from __future__ import unicode_literals import datetime from decimal import Decimal +from django.conf.urls import patterns, url from django.db import models from django.core.urlresolvers import reverse from django.test import TestCase from django.utils import unittest from rest_framework import generics, serializers, status, filters -from rest_framework.compat import django_filters, patterns, url +from rest_framework.compat import django_filters from rest_framework.test import APIRequestFactory from rest_framework.tests.models import BasicModel diff --git a/rest_framework/tests/test_htmlrenderer.py b/rest_framework/tests/test_htmlrenderer.py index 8957a43c7..6c570dfdc 100644 --- a/rest_framework/tests/test_htmlrenderer.py +++ b/rest_framework/tests/test_htmlrenderer.py @@ -1,11 +1,11 @@ from __future__ import unicode_literals from django.core.exceptions import PermissionDenied +from django.conf.urls import patterns, url from django.http import Http404 from django.test import TestCase from django.template import TemplateDoesNotExist, Template import django.template.loader from rest_framework import status -from rest_framework.compat import patterns, url from rest_framework.decorators import api_view, renderer_classes from rest_framework.renderers import TemplateHTMLRenderer from rest_framework.response import Response diff --git a/rest_framework/tests/test_hyperlinkedserializers.py b/rest_framework/tests/test_hyperlinkedserializers.py index 61e613d75..ea7f70f2f 100644 --- a/rest_framework/tests/test_hyperlinkedserializers.py +++ b/rest_framework/tests/test_hyperlinkedserializers.py @@ -1,8 +1,8 @@ from __future__ import unicode_literals import json +from django.conf.urls import patterns, url from django.test import TestCase from rest_framework import generics, status, serializers -from rest_framework.compat import patterns, url from rest_framework.test import APIRequestFactory from rest_framework.tests.models import ( Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment, @@ -24,7 +24,7 @@ class BlogPostCommentSerializer(serializers.ModelSerializer): class PhotoSerializer(serializers.Serializer): description = serializers.CharField() - album_url = serializers.HyperlinkedRelatedField(source='album', view_name='album-detail', queryset=Album.objects.all(), lookup_field='title', slug_url_kwarg='title') + album_url = serializers.HyperlinkedRelatedField(source='album', view_name='album-detail', queryset=Album.objects.all(), lookup_field='title') def restore_object(self, attrs, instance=None): return Photo(**attrs) diff --git a/rest_framework/tests/test_relations_hyperlink.py b/rest_framework/tests/test_relations_hyperlink.py index 3c4d39af6..fa6b01aca 100644 --- a/rest_framework/tests/test_relations_hyperlink.py +++ b/rest_framework/tests/test_relations_hyperlink.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals +from django.conf.urls import patterns, url from django.test import TestCase from rest_framework import serializers -from rest_framework.compat import patterns, url from rest_framework.test import APIRequestFactory from rest_framework.tests.models import ( BlogPost, diff --git a/rest_framework/tests/test_renderers.py b/rest_framework/tests/test_renderers.py index df6f4aa63..9d1dd77e5 100644 --- a/rest_framework/tests/test_renderers.py +++ b/rest_framework/tests/test_renderers.py @@ -2,12 +2,13 @@ from __future__ import unicode_literals from decimal import Decimal +from django.conf.urls import patterns, url, include from django.core.cache import cache from django.test import TestCase from django.utils import unittest from django.utils.translation import ugettext_lazy as _ from rest_framework import status, permissions -from rest_framework.compat import yaml, etree, patterns, url, include, six, StringIO +from rest_framework.compat import yaml, etree, six, StringIO from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \ diff --git a/rest_framework/tests/test_request.py b/rest_framework/tests/test_request.py index 969d8024a..d63634258 100644 --- a/rest_framework/tests/test_request.py +++ b/rest_framework/tests/test_request.py @@ -2,13 +2,13 @@ Tests for content parsing, and form-overloaded content parsing. """ from __future__ import unicode_literals +from django.conf.urls import patterns from django.contrib.auth.models import User from django.contrib.auth import authenticate, login, logout from django.contrib.sessions.middleware import SessionMiddleware from django.test import TestCase from rest_framework import status from rest_framework.authentication import SessionAuthentication -from rest_framework.compat import patterns from rest_framework.parsers import ( BaseParser, FormParser, diff --git a/rest_framework/tests/test_response.py b/rest_framework/tests/test_response.py index eea3c6418..1c4c551cf 100644 --- a/rest_framework/tests/test_response.py +++ b/rest_framework/tests/test_response.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals +from django.conf.urls import patterns, url, include from django.test import TestCase from rest_framework.tests.models import BasicModel, BasicModelSerializer -from rest_framework.compat import patterns, url, include from rest_framework.response import Response from rest_framework.views import APIView from rest_framework import generics diff --git a/rest_framework/tests/test_reverse.py b/rest_framework/tests/test_reverse.py index 690a30b11..320b125d2 100644 --- a/rest_framework/tests/test_reverse.py +++ b/rest_framework/tests/test_reverse.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals +from django.conf.urls import patterns, url from django.test import TestCase -from rest_framework.compat import patterns, url from rest_framework.reverse import reverse from rest_framework.test import APIRequestFactory diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py index 3f456fefd..1c34648f4 100644 --- a/rest_framework/tests/test_routers.py +++ b/rest_framework/tests/test_routers.py @@ -1,9 +1,9 @@ from __future__ import unicode_literals +from django.conf.urls import patterns, url, include from django.db import models from django.test import TestCase from django.core.exceptions import ImproperlyConfigured from rest_framework import serializers, viewsets, permissions -from rest_framework.compat import include, patterns, url from rest_framework.decorators import detail_route, list_route from rest_framework.response import Response from rest_framework.routers import SimpleRouter, DefaultRouter diff --git a/rest_framework/tests/test_testing.py b/rest_framework/tests/test_testing.py index 48b8956b5..c08dd4932 100644 --- a/rest_framework/tests/test_testing.py +++ b/rest_framework/tests/test_testing.py @@ -1,9 +1,9 @@ # -- coding: utf-8 -- from __future__ import unicode_literals +from django.conf.urls import patterns, url from django.contrib.auth.models import User from django.test import TestCase -from rest_framework.compat import patterns, url from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework.test import APIClient, APIRequestFactory, force_authenticate diff --git a/rest_framework/tests/test_urlpatterns.py b/rest_framework/tests/test_urlpatterns.py index 8132ec4c8..e0060e690 100644 --- a/rest_framework/tests/test_urlpatterns.py +++ b/rest_framework/tests/test_urlpatterns.py @@ -1,9 +1,9 @@ from __future__ import unicode_literals from collections import namedtuple +from django.conf.urls import patterns, url, include from django.core import urlresolvers from django.test import TestCase from rest_framework.test import APIRequestFactory -from rest_framework.compat import patterns, url, include from rest_framework.urlpatterns import format_suffix_patterns diff --git a/rest_framework/urlpatterns.py b/rest_framework/urlpatterns.py index d9143bb4c..a62530c74 100644 --- a/rest_framework/urlpatterns.py +++ b/rest_framework/urlpatterns.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals +from django.conf.urls import url, include from django.core.urlresolvers import RegexURLResolver -from rest_framework.compat import url, include from rest_framework.settings import api_settings diff --git a/rest_framework/urls.py b/rest_framework/urls.py index 9c4719f1d..87ec0f0ae 100644 --- a/rest_framework/urls.py +++ b/rest_framework/urls.py @@ -13,7 +13,7 @@ your authentication settings include `SessionAuthentication`. ) """ from __future__ import unicode_literals -from rest_framework.compat import patterns, url +from django.conf.urls import patterns, url template_name = {'template_name': 'rest_framework/login.html'} diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index 7efd5417b..13a855509 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -2,9 +2,10 @@ Helper classes for parsers. """ from __future__ import unicode_literals +from django.utils import timezone from django.utils.datastructures import SortedDict from django.utils.functional import Promise -from rest_framework.compat import timezone, force_text +from rest_framework.compat import force_text from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata import datetime import decimal diff --git a/tox.ini b/tox.ini index 6e3b8e0a8..7bd140e1c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] downloadcache = {toxworkdir}/cache/ -envlist = py3.3-django1.6,py3.2-django1.6,py2.7-django1.6,py2.6-django1.6,py3.3-django1.5,py3.2-django1.5,py2.7-django1.5,py2.6-django1.5,py2.7-django1.4,py2.6-django1.4,py2.7-django1.3,py2.6-django1.3 +envlist = py3.3-django1.6,py3.2-django1.6,py2.7-django1.6,py2.6-django1.6,py3.3-django1.5,py3.2-django1.5,py2.7-django1.5,py2.6-django1.5,py2.7-django1.4,py2.6-django1.4 [testenv] commands = {envpython} rest_framework/runtests/runtests.py @@ -88,23 +88,3 @@ deps = django==1.4.3 oauth2==1.5.211 django-oauth2-provider==0.2.3 django-guardian==1.1.1 - -[testenv:py2.7-django1.3] -basepython = python2.7 -deps = django==1.3.5 - django-filter==0.5.4 - defusedxml==0.3 - django-oauth-plus==2.0 - oauth2==1.5.211 - django-oauth2-provider==0.2.3 - django-guardian==1.1.1 - -[testenv:py2.6-django1.3] -basepython = python2.6 -deps = django==1.3.5 - django-filter==0.5.4 - defusedxml==0.3 - django-oauth-plus==2.0 - oauth2==1.5.211 - django-oauth2-provider==0.2.3 - django-guardian==1.1.1 From 1bd8fe415296739521fd2e75c0b604cbf3dd3a83 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 25 Sep 2013 10:36:08 +0100 Subject: [PATCH 018/225] Whitespace fix --- rest_framework/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index f048b10ad..efd2581fe 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -17,7 +17,7 @@ except ImportError: import six # Handle django.utils.encoding rename in 1.5 onwards. -# smart_unicode -> smart_text +# smart_unicode -> smart_text # force_unicode -> force_text try: from django.utils.encoding import smart_text From ab4be47379ba49092063f843fd446919534db776 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Thu, 3 Oct 2013 17:34:34 +0200 Subject: [PATCH 019/225] Fixed code example. --- docs/api-guide/routers.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index 730fa876a..f20a695b6 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -42,12 +42,15 @@ The example above would generate the following URL patterns: Any methods on the viewset decorated with `@detail_route` or `@list_route` will also be routed. For example, given a method like this on the `UserViewSet` class: - from myapp.permissions import IsAdminOrIsSelf + from myapp.permissions import IsAdminOrIsSelf from rest_framework.decorators import detail_route - - @detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf]) - def set_password(self, request, pk=None): + + class UserViewSet(ModelViewSet): ... + + @detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf]) + def set_password(self, request, pk=None): + ... The following URL pattern would additionally be generated: From 9ab0759e38492d9950d66299ed5c58155d39e696 Mon Sep 17 00:00:00 2001 From: kahnjw Date: Fri, 6 Dec 2013 14:21:33 -0800 Subject: [PATCH 020/225] Add tests to pass for get_ident method in BaseThrottle class. --- rest_framework/tests/test_throttling.py | 65 +++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/rest_framework/tests/test_throttling.py b/rest_framework/tests/test_throttling.py index 41bff6926..031276968 100644 --- a/rest_framework/tests/test_throttling.py +++ b/rest_framework/tests/test_throttling.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals from django.test import TestCase from django.contrib.auth.models import User from django.core.cache import cache +from rest_framework.settings import api_settings from rest_framework.test import APIRequestFactory from rest_framework.views import APIView from rest_framework.throttling import BaseThrottle, UserRateThrottle, ScopedRateThrottle @@ -275,3 +276,67 @@ class ScopedRateThrottleTests(TestCase): self.increment_timer() response = self.unscoped_view(request) self.assertEqual(200, response.status_code) + +class XffTestingBase(TestCase): + def setUp(self): + + class Throttle(ScopedRateThrottle): + THROTTLE_RATES = {'test_limit': '1/day'} + TIMER_SECONDS = 0 + timer = lambda self: self.TIMER_SECONDS + + class View(APIView): + throttle_classes = (Throttle,) + throttle_scope = 'test_limit' + + def get(self, request): + return Response('test_limit') + + cache.clear() + self.throttle = Throttle() + self.view = View.as_view() + self.request = APIRequestFactory().get('/some_uri') + self.request.META['REMOTE_ADDR'] = '3.3.3.3' + self.request.META['HTTP_X_FORWARDED_FOR'] = '0.0.0.0, 1.1.1.1, 2.2.2.2' + + def config_proxy(self, num_proxies): + setattr(api_settings, 'NUM_PROXIES', num_proxies) + + +class IdWithXffBasicTests(XffTestingBase): + def test_accepts_request_under_limit(self): + self.config_proxy(0) + self.assertEqual(200, self.view(self.request).status_code) + + def test_denies_request_over_limit(self): + self.config_proxy(0) + self.view(self.request) + self.assertEqual(429, self.view(self.request).status_code) + + +class XffSpoofingTests(XffTestingBase): + def test_xff_spoofing_doesnt_change_machine_id_with_one_app_proxy(self): + self.config_proxy(1) + self.view(self.request) + self.request.META['HTTP_X_FORWARDED_FOR'] = '4.4.4.4, 5.5.5.5, 2.2.2.2' + self.assertEqual(429, self.view(self.request).status_code) + + def test_xff_spoofing_doesnt_change_machine_id_with_two_app_proxies(self): + self.config_proxy(2) + self.view(self.request) + self.request.META['HTTP_X_FORWARDED_FOR'] = '4.4.4.4, 1.1.1.1, 2.2.2.2' + self.assertEqual(429, self.view(self.request).status_code) + + +class XffUniqueMachinesTest(XffTestingBase): + def test_unique_clients_are_counted_independently_with_one_proxy(self): + self.config_proxy(1) + self.view(self.request) + self.request.META['HTTP_X_FORWARDED_FOR'] = '0.0.0.0, 1.1.1.1, 7.7.7.7' + self.assertEqual(200, self.view(self.request).status_code) + + def test_unique_clients_are_counted_independently_with_two_proxies(self): + self.config_proxy(2) + self.view(self.request) + self.request.META['HTTP_X_FORWARDED_FOR'] = '0.0.0.0, 7.7.7.7, 2.2.2.2' + self.assertEqual(200, self.view(self.request).status_code) From 89f26c5e040febd27bc9142b0096ca119bb3fa32 Mon Sep 17 00:00:00 2001 From: kahnjw Date: Fri, 6 Dec 2013 14:21:52 -0800 Subject: [PATCH 021/225] Add get_ident method to pass new tests. --- rest_framework/settings.py | 1 + rest_framework/throttling.py | 25 ++++++++++++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 8abaf1409..383de72ea 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -63,6 +63,7 @@ DEFAULTS = { 'user': None, 'anon': None, }, + 'NUM_PROXIES': None, # Pagination 'PAGINATE_BY': None, diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index a946d837f..60e46d479 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -18,6 +18,21 @@ class BaseThrottle(object): """ raise NotImplementedError('.allow_request() must be overridden') + def get_ident(self, request): + """ + Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR + if present and number of proxies is > 0. If not use all of + HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR. + """ + xff = request.META.get('HTTP_X_FORWARDED_FOR') + remote_addr = request.META.get('REMOTE_ADDR') + num_proxies = api_settings.NUM_PROXIES + + if xff and num_proxies: + return xff.split(',')[-min(num_proxies, len(xff))].strip() + + return xff if xff else remote_addr + def wait(self): """ Optionally, return a recommended number of seconds to wait before @@ -152,13 +167,9 @@ 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') - return self.cache_format % { 'scope': self.scope, - 'ident': ident + 'ident': self.get_ident(request) } @@ -176,7 +187,7 @@ class UserRateThrottle(SimpleRateThrottle): if request.user.is_authenticated(): ident = request.user.id else: - ident = request.META.get('REMOTE_ADDR', None) + ident = self.get_ident(request) return self.cache_format % { 'scope': self.scope, @@ -224,7 +235,7 @@ class ScopedRateThrottle(SimpleRateThrottle): if request.user.is_authenticated(): ident = request.user.id else: - ident = request.META.get('REMOTE_ADDR', None) + ident = self.get_ident(request) return self.cache_format % { 'scope': self.scope, From 100a933279e3119e2627d744cd7eb472b542f6fe Mon Sep 17 00:00:00 2001 From: kahnjw Date: Fri, 6 Dec 2013 14:22:08 -0800 Subject: [PATCH 022/225] Add documentation to explain what effect these changes have. --- docs/api-guide/throttling.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index cc4692171..ee57383cd 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -35,11 +35,16 @@ The default throttling policy may be set globally, using the `DEFAULT_THROTTLE_C 'DEFAULT_THROTTLE_RATES': { 'anon': '100/day', 'user': '1000/day' - } + }, + 'NUM_PROXIES': 2, } The rate descriptions used in `DEFAULT_THROTTLE_RATES` may include `second`, `minute`, `hour` or `day` as the throttle period. +By default Django REST Framework will try to use the `HTTP_X_FORWARDED_FOR` header to uniquely identify client machines for throttling. If HTTP_X_FORWARDED_FOR is not present `REMOTE_ADDR` header value will be used. + +To help Django REST Framework identify unique clients the number of application proxies can be set using `NUM_PROXIES`. This setting will allow the throttle to correctly identify unique requests whenthere are multiple application side proxies in front of the server. `NUM_PROXIES` should be set to an integer. It is important to understand that if you configure `NUM_PROXIES > 0` all clients behind a unique [NAT'd](http://en.wikipedia.org/wiki/Network_address_translation) gateway will be treated as a single client. + You can also set the throttling policy on a per-view or per-viewset basis, using the `APIView` class based views. From 196c5952e4f610054e832aef36cb2383b8c129c0 Mon Sep 17 00:00:00 2001 From: kahnjw Date: Fri, 6 Dec 2013 14:24:16 -0800 Subject: [PATCH 023/225] Fix typo --- docs/api-guide/throttling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index ee57383cd..69b15a829 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -43,7 +43,7 @@ The rate descriptions used in `DEFAULT_THROTTLE_RATES` may include `second`, `mi By default Django REST Framework will try to use the `HTTP_X_FORWARDED_FOR` header to uniquely identify client machines for throttling. If HTTP_X_FORWARDED_FOR is not present `REMOTE_ADDR` header value will be used. -To help Django REST Framework identify unique clients the number of application proxies can be set using `NUM_PROXIES`. This setting will allow the throttle to correctly identify unique requests whenthere are multiple application side proxies in front of the server. `NUM_PROXIES` should be set to an integer. It is important to understand that if you configure `NUM_PROXIES > 0` all clients behind a unique [NAT'd](http://en.wikipedia.org/wiki/Network_address_translation) gateway will be treated as a single client. +To help Django REST Framework identify unique clients the number of application proxies can be set using `NUM_PROXIES`. This setting will allow the throttle to correctly identify unique requests when there are multiple application side proxies in front of the server. `NUM_PROXIES` should be set to an integer. It is important to understand that if you configure `NUM_PROXIES > 0` all clients behind a unique [NAT'd](http://en.wikipedia.org/wiki/Network_address_translation) gateway will be treated as a single client. You can also set the throttling policy on a per-view or per-viewset basis, using the `APIView` class based views. From 887da7f6c5a9e7b5007f5e4af32a6b93b18c70ea Mon Sep 17 00:00:00 2001 From: kahnjw Date: Fri, 6 Dec 2013 14:30:33 -0800 Subject: [PATCH 024/225] Add missing tick marks --- docs/api-guide/throttling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index 69b15a829..34418e84d 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -41,7 +41,7 @@ The default throttling policy may be set globally, using the `DEFAULT_THROTTLE_C The rate descriptions used in `DEFAULT_THROTTLE_RATES` may include `second`, `minute`, `hour` or `day` as the throttle period. -By default Django REST Framework will try to use the `HTTP_X_FORWARDED_FOR` header to uniquely identify client machines for throttling. If HTTP_X_FORWARDED_FOR is not present `REMOTE_ADDR` header value will be used. +By default Django REST Framework will try to use the `HTTP_X_FORWARDED_FOR` header to uniquely identify client machines for throttling. If `HTTP_X_FORWARDED_FOR` is not present `REMOTE_ADDR` header value will be used. To help Django REST Framework identify unique clients the number of application proxies can be set using `NUM_PROXIES`. This setting will allow the throttle to correctly identify unique requests when there are multiple application side proxies in front of the server. `NUM_PROXIES` should be set to an integer. It is important to understand that if you configure `NUM_PROXIES > 0` all clients behind a unique [NAT'd](http://en.wikipedia.org/wiki/Network_address_translation) gateway will be treated as a single client. From 23db6c98495d7b3c18a3069c6cb770d5cbc18ee1 Mon Sep 17 00:00:00 2001 From: kahnjw Date: Fri, 6 Dec 2013 14:52:39 -0800 Subject: [PATCH 025/225] PEP8 Compliance --- rest_framework/tests/test_throttling.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rest_framework/tests/test_throttling.py b/rest_framework/tests/test_throttling.py index 031276968..8c5eefe9c 100644 --- a/rest_framework/tests/test_throttling.py +++ b/rest_framework/tests/test_throttling.py @@ -277,6 +277,7 @@ class ScopedRateThrottleTests(TestCase): response = self.unscoped_view(request) self.assertEqual(200, response.status_code) + class XffTestingBase(TestCase): def setUp(self): From 83da4949c099fcf7e7636c98b9052b502e1bf74b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 13 Dec 2013 00:02:18 +0000 Subject: [PATCH 026/225] Allow NUM_PROXIES=0 and include more docs --- docs/api-guide/settings.md | 6 ++++++ docs/api-guide/throttling.md | 18 ++++++++++++------ rest_framework/throttling.py | 8 ++++++-- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 13f96f9a3..d8c878ff8 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -359,5 +359,11 @@ The name of a parameter in the URL conf that may be used to provide a format suf Default: `'format'` +#### NUM_PROXIES + +An integer of 0 or more, that may be used to specify the number of application proxies that the API runs behind. This allows throttling to more accurately identify client IP addresses. If set to `None` then less strict IP matching will be used by the throttle classes. + +Default: `None` + [cite]: http://www.python.org/dev/peps/pep-0020/ [strftime]: http://docs.python.org/2/library/time.html#time.strftime diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index 34418e84d..b2a5bb194 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -35,16 +35,11 @@ The default throttling policy may be set globally, using the `DEFAULT_THROTTLE_C 'DEFAULT_THROTTLE_RATES': { 'anon': '100/day', 'user': '1000/day' - }, - 'NUM_PROXIES': 2, + } } The rate descriptions used in `DEFAULT_THROTTLE_RATES` may include `second`, `minute`, `hour` or `day` as the throttle period. -By default Django REST Framework will try to use the `HTTP_X_FORWARDED_FOR` header to uniquely identify client machines for throttling. If `HTTP_X_FORWARDED_FOR` is not present `REMOTE_ADDR` header value will be used. - -To help Django REST Framework identify unique clients the number of application proxies can be set using `NUM_PROXIES`. This setting will allow the throttle to correctly identify unique requests when there are multiple application side proxies in front of the server. `NUM_PROXIES` should be set to an integer. It is important to understand that if you configure `NUM_PROXIES > 0` all clients behind a unique [NAT'd](http://en.wikipedia.org/wiki/Network_address_translation) gateway will be treated as a single client. - You can also set the throttling policy on a per-view or per-viewset basis, using the `APIView` class based views. @@ -71,6 +66,16 @@ Or, if you're using the `@api_view` decorator with function based views. } return Response(content) +## How clients are identified + +By default the `X-Forwarded-For` HTTP header is used to uniquely identify client machines for throttling. If the `X-Forwarded-For` header is not present, then the value of the `Remote-Addr` header will be used. + +If you need to more strictly identify unique clients, you'll need to configure the number of application proxies that the API runs behind by setting the `NUM_PROXIES` setting. This setting should be an integer of 0 or more, and will allow the throttle to identify the client IP as being the last IP address in the `X-Forwarded-For` header, once any application proxy IP addresses have first been excluded. + +It is important to understand that if you configure the `NUM_PROXIES` setting, then all clients behind a unique [NAT'd](http://en.wikipedia.org/wiki/Network_address_translation) gateway will be treated as a single client. + +Further context on how the `X-Forwarded-For` header works, and identifier a remote client IP can be [found here][identifing-clients]. + ## Setting up the cache The throttle classes provided by REST framework use Django's cache backend. You should make sure that you've set appropriate [cache settings][cache-setting]. The default value of `LocMemCache` backend should be okay for simple setups. See Django's [cache documentation][cache-docs] for more details. @@ -183,5 +188,6 @@ The following is an example of a rate throttle, that will randomly throttle 1 in [cite]: https://dev.twitter.com/docs/error-codes-responses [permissions]: permissions.md +[identifing-clients]: http://oxpedia.org/wiki/index.php?title=AppSuite:Grizzly#Multiple_Proxies_in_front_of_the_cluster [cache-setting]: https://docs.djangoproject.com/en/dev/ref/settings/#caches [cache-docs]: https://docs.djangoproject.com/en/dev/topics/cache/#setting-up-the-cache diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 60e46d479..c40f3065b 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -28,8 +28,12 @@ class BaseThrottle(object): remote_addr = request.META.get('REMOTE_ADDR') num_proxies = api_settings.NUM_PROXIES - if xff and num_proxies: - return xff.split(',')[-min(num_proxies, len(xff))].strip() + if num_proxies is not None: + if num_proxies == 0 or xff is None: + return remote_addr + addrs = xff.split(',') + client_addr = addrs[-min(num_proxies, len(xff))] + return client_addr.strip() return xff if xff else remote_addr From ed931b90ae9e72f963673e6e188b1802a5a65360 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 13 Dec 2013 00:11:59 +0000 Subject: [PATCH 027/225] Further docs tweaks --- docs/api-guide/throttling.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index b2a5bb194..536f0ab78 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -68,13 +68,13 @@ Or, if you're using the `@api_view` decorator with function based views. ## How clients are identified -By default the `X-Forwarded-For` HTTP header is used to uniquely identify client machines for throttling. If the `X-Forwarded-For` header is not present, then the value of the `Remote-Addr` header will be used. +The `X-Forwarded-For` and `Remote-Addr` HTTP headers are used to uniquely identify client IP addresses for throttling. If the `X-Forwarded-For` header is present then it will be used, otherwise the value of the `Remote-Addr` header will be used. -If you need to more strictly identify unique clients, you'll need to configure the number of application proxies that the API runs behind by setting the `NUM_PROXIES` setting. This setting should be an integer of 0 or more, and will allow the throttle to identify the client IP as being the last IP address in the `X-Forwarded-For` header, once any application proxy IP addresses have first been excluded. +If you need to strictly identify unique client IP addresses, you'll need to first configure the number of application proxies that the API runs behind by setting the `NUM_PROXIES` setting. This setting should be an integer of zero or more. If set to non-zero then the client IP will be identified as being the last IP address in the `X-Forwarded-For` header, once any application proxy IP addresses have first been excluded. If set to zero, then the `Remote-Addr` header will always be used as the identifying IP address. It is important to understand that if you configure the `NUM_PROXIES` setting, then all clients behind a unique [NAT'd](http://en.wikipedia.org/wiki/Network_address_translation) gateway will be treated as a single client. -Further context on how the `X-Forwarded-For` header works, and identifier a remote client IP can be [found here][identifing-clients]. +Further context on how the `X-Forwarded-For` header works, and identifing a remote client IP can be [found here][identifing-clients]. ## Setting up the cache From a1d7aa8f712b659f9d8302a2d2a098d2538e6c89 Mon Sep 17 00:00:00 2001 From: Paul Melnikow Date: Thu, 2 Jan 2014 17:44:47 -0500 Subject: [PATCH 028/225] Allow viewset to specify lookup value regex for routing This patch allows a viewset to define a pattern for its lookup field, which the router will honor. Without this patch, any characters are allowed in the lookup field, and overriding this behavior requires subclassing router and copying and pasting the implementation of get_lookup_regex. It's possible it would be better to remove this functionality from the routers and simply expose a parameter to get_lookup_regex which allows overriding the lookup_regex. That way the viewset config logic could be in the a subclass, which could invoke the super method directly. I'm using this now for PostgreSQL UUID fields using https://github.com/dcramer/django-uuidfield . Without this patch, that field passes the lookup string to the database driver, which raises a DataError to complain about the invalid UUID. It's possible the field ought to signal this error in a different way, which could obviate the need to specify a pattern. --- docs/api-guide/routers.md | 6 ++++++ rest_framework/routers.py | 20 ++++++++++++++------ rest_framework/tests/test_routers.py | 21 +++++++++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index 846ac9f9d..f3beabdd3 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -83,6 +83,12 @@ This behavior can be modified by setting the `trailing_slash` argument to `False Trailing slashes are conventional in Django, but are not used by default in some other frameworks such as Rails. Which style you choose to use is largely a matter of preference, although some javascript frameworks may expect a particular routing style. +With `trailing_slash` set to True, the router will match lookup values containing any characters except slashes and dots. When set to False, dots are allowed. To restrict the lookup pattern, set the `lookup_field_regex` attribute on the viewset. For example, you can limit the lookup to valid UUIDs: + + class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): + lookup_field = 'my_model_id' + lookup_value_regex = '[0-9a-f]{32}' + ## DefaultRouter This router is similar to `SimpleRouter` as above, but additionally includes a default API root view, that returns a response containing hyperlinks to all the list views. It also generates routes for optional `.json` style format suffixes. diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 740d58f0d..8766ecb2f 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -219,13 +219,21 @@ class SimpleRouter(BaseRouter): https://github.com/alanjds/drf-nested-routers """ - if self.trailing_slash: - base_regex = '(?P<{lookup_prefix}{lookup_field}>[^/]+)' - else: - # Don't consume `.json` style suffixes - base_regex = '(?P<{lookup_prefix}{lookup_field}>[^/.]+)' + base_regex = '(?P<{lookup_prefix}{lookup_field}>{lookup_value})' lookup_field = getattr(viewset, 'lookup_field', 'pk') - return base_regex.format(lookup_field=lookup_field, lookup_prefix=lookup_prefix) + try: + lookup_value = viewset.lookup_value_regex + except AttributeError: + if self.trailing_slash: + lookup_value = '[^/]+' + else: + # Don't consume `.json` style suffixes + lookup_value = '[^/.]+' + return base_regex.format( + lookup_prefix=lookup_prefix, + lookup_field=lookup_field, + lookup_value=lookup_value + ) def get_urls(self): """ diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py index 1c34648f4..0f6d62c7c 100644 --- a/rest_framework/tests/test_routers.py +++ b/rest_framework/tests/test_routers.py @@ -121,6 +121,27 @@ class TestCustomLookupFields(TestCase): ) +class TestLookupValueRegex(TestCase): + """ + Ensure the router honors lookup_value_regex when applied + to the viewset. + """ + def setUp(self): + class NoteViewSet(viewsets.ModelViewSet): + queryset = RouterTestModel.objects.all() + lookup_field = 'uuid' + lookup_value_regex = '[0-9a-f]{32}' + + self.router = SimpleRouter() + self.router.register(r'notes', NoteViewSet) + self.urls = self.router.urls + + def test_urls_limited_by_lookup_value_regex(self): + expected = ['^notes/$', '^notes/(?P[0-9a-f]{32})/$'] + for idx in range(len(expected)): + self.assertEqual(expected[idx], self.urls[idx].regex.pattern) + + class TestTrailingSlashIncluded(TestCase): def setUp(self): class NoteViewSet(viewsets.ModelViewSet): From 3cd15fb1713dfc49e1bf1fd48045ca3ae5654e18 Mon Sep 17 00:00:00 2001 From: Paul Melnikow Date: Sat, 4 Jan 2014 16:57:50 -0500 Subject: [PATCH 029/225] Router: Do not automatically adjust lookup_regex when trailing_slash is True BREAKING CHANGE When trailing_slash is set to True, the router no longer will adjust the lookup regex to allow it to include periods. To simulate the old behavior, the programmer should specify `lookup_regex = '[^/]+'` on the viewset. https://github.com/tomchristie/django-rest-framework/pull/1328#issuecomment-31517099 --- docs/api-guide/routers.md | 2 +- rest_framework/routers.py | 7 ++----- rest_framework/tests/test_routers.py | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index f3beabdd3..6b4ae6dbd 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -83,7 +83,7 @@ This behavior can be modified by setting the `trailing_slash` argument to `False Trailing slashes are conventional in Django, but are not used by default in some other frameworks such as Rails. Which style you choose to use is largely a matter of preference, although some javascript frameworks may expect a particular routing style. -With `trailing_slash` set to True, the router will match lookup values containing any characters except slashes and dots. When set to False, dots are allowed. To restrict the lookup pattern, set the `lookup_field_regex` attribute on the viewset. For example, you can limit the lookup to valid UUIDs: +The router will match lookup values containing any characters except slashes and period characters. For a more restrictive (or lenient) lookup pattern, set the `lookup_field_regex` attribute on the viewset. For example, you can limit the lookup to valid UUIDs: class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): lookup_field = 'my_model_id' diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 8766ecb2f..df1233fdd 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -224,11 +224,8 @@ class SimpleRouter(BaseRouter): try: lookup_value = viewset.lookup_value_regex except AttributeError: - if self.trailing_slash: - lookup_value = '[^/]+' - else: - # Don't consume `.json` style suffixes - lookup_value = '[^/.]+' + # Don't consume `.json` style suffixes + lookup_value = '[^/.]+' return base_regex.format( lookup_prefix=lookup_prefix, lookup_field=lookup_field, diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py index 0f6d62c7c..e41da57f5 100644 --- a/rest_framework/tests/test_routers.py +++ b/rest_framework/tests/test_routers.py @@ -152,7 +152,7 @@ class TestTrailingSlashIncluded(TestCase): self.urls = self.router.urls def test_urls_have_trailing_slash_by_default(self): - expected = ['^notes/$', '^notes/(?P[^/]+)/$'] + expected = ['^notes/$', '^notes/(?P[^/.]+)/$'] for idx in range(len(expected)): self.assertEqual(expected[idx], self.urls[idx].regex.pattern) From 899381575a6038f550a064261ed5c6ba0655211b Mon Sep 17 00:00:00 2001 From: Paul Melnikow Date: Sat, 4 Jan 2014 17:03:01 -0500 Subject: [PATCH 030/225] Fix a typo --- docs/api-guide/routers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index 6b4ae6dbd..249e99a48 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -83,7 +83,7 @@ This behavior can be modified by setting the `trailing_slash` argument to `False Trailing slashes are conventional in Django, but are not used by default in some other frameworks such as Rails. Which style you choose to use is largely a matter of preference, although some javascript frameworks may expect a particular routing style. -The router will match lookup values containing any characters except slashes and period characters. For a more restrictive (or lenient) lookup pattern, set the `lookup_field_regex` attribute on the viewset. For example, you can limit the lookup to valid UUIDs: +The router will match lookup values containing any characters except slashes and period characters. For a more restrictive (or lenient) lookup pattern, set the `lookup_value_regex` attribute on the viewset. For example, you can limit the lookup to valid UUIDs: class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): lookup_field = 'my_model_id' From 46f5c62530744017f744cdcfec91774a0566c179 Mon Sep 17 00:00:00 2001 From: Yuri Prezument Date: Sun, 5 Jan 2014 15:16:55 +0200 Subject: [PATCH 031/225] Regression test for #1330 (Coerce None to '') --- rest_framework/tests/test_serializer.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index 86f365dee..8c2c09cf8 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -1124,6 +1124,20 @@ class BlankFieldTests(TestCase): serializer = self.model_serializer_class(data={}) self.assertEqual(serializer.is_valid(), True) + def test_create_model_null_field_save(self): + """ + Regression test for #1330. + + https://github.com/tomchristie/django-rest-framework/pull/1330 + """ + serializer = self.model_serializer_class(data={'title': None}) + self.assertEqual(serializer.is_valid(), True) + + try: + serializer.save() + except Exception: + self.fail('Exception raised on save() after validation passes') + #test for issue #460 class SerializerPickleTests(TestCase): From e88e3c6ae163029f0fe564dd214235ab350dbfc9 Mon Sep 17 00:00:00 2001 From: Yuri Prezument Date: Sun, 5 Jan 2014 15:25:16 +0200 Subject: [PATCH 032/225] Possible fix for #1330 Coerce None to '' in CharField.to_native() --- rest_framework/fields.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 5ee752351..22f0120b7 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -452,7 +452,9 @@ class CharField(WritableField): self.validators.append(validators.MaxLengthValidator(max_length)) def from_native(self, value): - if isinstance(value, six.string_types) or value is None: + if value is None: + return '' + if isinstance(value, six.string_types): return value return smart_text(value) From 6e622d644c9b55b905e24497f0fb818d557fd970 Mon Sep 17 00:00:00 2001 From: Yuri Prezument Date: Sun, 5 Jan 2014 15:58:46 +0200 Subject: [PATCH 033/225] CharField - add allow_null argument --- docs/api-guide/fields.md | 7 ++++--- rest_framework/fields.py | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index e05c03061..83825350e 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -157,23 +157,24 @@ Corresponds to `django.db.models.fields.BooleanField`. ## CharField A text representation, optionally validates the text to be shorter than `max_length` and longer than `min_length`. +If `allow_none` is `False` (default), `None` values will be converted to an empty string. Corresponds to `django.db.models.fields.CharField` or `django.db.models.fields.TextField`. -**Signature:** `CharField(max_length=None, min_length=None)` +**Signature:** `CharField(max_length=None, min_length=None, allow_none=False)` ## URLField Corresponds to `django.db.models.fields.URLField`. Uses Django's `django.core.validators.URLValidator` for validation. -**Signature:** `CharField(max_length=200, min_length=None)` +**Signature:** `CharField(max_length=200, min_length=None, allow_none=False)` ## SlugField Corresponds to `django.db.models.fields.SlugField`. -**Signature:** `CharField(max_length=50, min_length=None)` +**Signature:** `CharField(max_length=50, min_length=None, allow_none=False)` ## ChoiceField diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 22f0120b7..16485b414 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -443,8 +443,9 @@ class CharField(WritableField): type_label = 'string' form_field_class = forms.CharField - def __init__(self, max_length=None, min_length=None, *args, **kwargs): + def __init__(self, max_length=None, min_length=None, allow_none=False, *args, **kwargs): self.max_length, self.min_length = max_length, min_length + self.allow_none = allow_none super(CharField, self).__init__(*args, **kwargs) if min_length is not None: self.validators.append(validators.MinLengthValidator(min_length)) @@ -452,7 +453,7 @@ class CharField(WritableField): self.validators.append(validators.MaxLengthValidator(max_length)) def from_native(self, value): - if value is None: + if value is None and not self.allow_none: return '' if isinstance(value, six.string_types): return value From e1bbe9d514c95aba596cff64292eb0f0bc7d99fa Mon Sep 17 00:00:00 2001 From: Yuri Prezument Date: Mon, 6 Jan 2014 13:56:57 +0200 Subject: [PATCH 034/225] Set `allow_none = True` for CharFields with null=True --- rest_framework/serializers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index fa9353061..0164965cd 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -821,10 +821,15 @@ class ModelSerializer(Serializer): kwargs.update({attribute: getattr(model_field, attribute)}) try: - return self.field_mapping[model_field.__class__](**kwargs) + field_class = self.field_mapping[model_field.__class__] except KeyError: return ModelField(model_field=model_field, **kwargs) + if issubclass(field_class, CharField) and model_field.null: + kwargs['allow_none'] = True + + return field_class(**kwargs) + def get_validation_exclusions(self): """ Return a list of field names to exclude from model validation. From 0fd0454a5c1ddcf8676e23b30dfaee40fa7cb0c8 Mon Sep 17 00:00:00 2001 From: Yuri Prezument Date: Mon, 6 Jan 2014 14:02:00 +0200 Subject: [PATCH 035/225] Test for setting allow_none=True for nullable CharFields --- rest_framework/tests/test_serializer.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index 8c2c09cf8..6d9b85ee0 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -1504,6 +1504,7 @@ class AttributeMappingOnAutogeneratedFieldsTests(TestCase): image_field = models.ImageField(max_length=1024, blank=True) slug_field = models.SlugField(max_length=1024, blank=True) url_field = models.URLField(max_length=1024, blank=True) + nullable_char_field = models.CharField(max_length=1024, blank=True, null=True) class AMOAFSerializer(serializers.ModelSerializer): class Meta: @@ -1536,6 +1537,10 @@ class AttributeMappingOnAutogeneratedFieldsTests(TestCase): 'url_field': [ ('max_length', 1024), ], + 'nullable_char_field': [ + ('max_length', 1024), + ('allow_none', True), + ], } def field_test(self, field): @@ -1572,6 +1577,9 @@ class AttributeMappingOnAutogeneratedFieldsTests(TestCase): def test_url_field(self): self.field_test('url_field') + def test_nullable_char_field(self): + self.field_test('nullable_char_field') + class DefaultValuesOnAutogeneratedFieldsTests(TestCase): From cd9a4194ea4f4dc0e43a34485cd8a27eba44a39a Mon Sep 17 00:00:00 2001 From: Yuri Prezument Date: Sun, 12 Jan 2014 16:30:26 +0200 Subject: [PATCH 036/225] Check the modelfield's class instead --- rest_framework/serializers.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 0164965cd..cbf73fc30 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -804,6 +804,10 @@ class ModelSerializer(Serializer): issubclass(model_field.__class__, models.PositiveSmallIntegerField): kwargs['min_value'] = 0 + if model_field.null and \ + issubclass(model_field.__class__, (models.CharField, models.TextField)): + kwargs['allow_none'] = True + attribute_dict = { models.CharField: ['max_length'], models.CommaSeparatedIntegerField: ['max_length'], @@ -821,15 +825,10 @@ class ModelSerializer(Serializer): kwargs.update({attribute: getattr(model_field, attribute)}) try: - field_class = self.field_mapping[model_field.__class__] + return self.field_mapping[model_field.__class__](**kwargs) except KeyError: return ModelField(model_field=model_field, **kwargs) - if issubclass(field_class, CharField) and model_field.null: - kwargs['allow_none'] = True - - return field_class(**kwargs) - def get_validation_exclusions(self): """ Return a list of field names to exclude from model validation. From a90796c0f0d9db1a7d9bfaca8fbdfed22435c628 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 13 Jan 2014 09:56:57 +0000 Subject: [PATCH 037/225] Track changes that need noting in 2.4 announcement --- docs/topics/2.4-accouncement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/2.4-accouncement.md b/docs/topics/2.4-accouncement.md index a5425d54e..0cf50ce90 100644 --- a/docs/topics/2.4-accouncement.md +++ b/docs/topics/2.4-accouncement.md @@ -1,4 +1,4 @@ * Writable nested serializers. * List/detail routes. * 1.3 Support dropped, install six for <=1.4.?. -* Note title ordering changed \ No newline at end of file +* `allow_none` for char fields From 2911cd64ad67ba193e3d37322ee71692cb482623 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 13 Jan 2014 15:37:52 +0000 Subject: [PATCH 038/225] Minor tweaks to 'lookup_value_regex' work --- docs/topics/2.4-accouncement.md | 1 + rest_framework/routers.py | 8 +++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/topics/2.4-accouncement.md b/docs/topics/2.4-accouncement.md index 0cf50ce90..91472b9c4 100644 --- a/docs/topics/2.4-accouncement.md +++ b/docs/topics/2.4-accouncement.md @@ -2,3 +2,4 @@ * List/detail routes. * 1.3 Support dropped, install six for <=1.4.?. * `allow_none` for char fields +* `trailing_slash = True` --> `[^/]`, `trailing_slash = False` --> `[^/.]`, becomes simply `[^/]` and `lookup_value_regex` is added. diff --git a/rest_framework/routers.py b/rest_framework/routers.py index df1233fdd..406ebcf77 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -220,12 +220,10 @@ class SimpleRouter(BaseRouter): https://github.com/alanjds/drf-nested-routers """ base_regex = '(?P<{lookup_prefix}{lookup_field}>{lookup_value})' + # Use `pk` as default field, unset set. Default regex should not + # consume `.json` style suffixes and should break at '/' boundaries. lookup_field = getattr(viewset, 'lookup_field', 'pk') - try: - lookup_value = viewset.lookup_value_regex - except AttributeError: - # Don't consume `.json` style suffixes - lookup_value = '[^/.]+' + lookup_value = getattr(viewset, 'lookup_value_regex', '[^/.]+') return base_regex.format( lookup_prefix=lookup_prefix, lookup_field=lookup_field, From d48ba1cff76ffceb1d700e9e0c6ccf518a6382da Mon Sep 17 00:00:00 2001 From: Andrey Kaygorodov Date: Wed, 5 Feb 2014 05:47:27 +0800 Subject: [PATCH 039/225] turn of pagination --- docs/api-guide/pagination.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index 0829589f8..f86e6ce11 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -102,7 +102,7 @@ You can also set the pagination style on a per-view basis, using the `ListAPIVie paginate_by_param = 'page_size' max_paginate_by = 100 -Note that using a `paginate_by` value of `None` will turn off pagination for the view. +Note that using a `paginate_by` value of `None` will turn off pagination for the view. But if you specified `PAGINATE_BY` and `PAGINATE_BY_PARAM` in your settings file then you have to set both `paginate_by` and `paginate_by_param` to a `None` value in order to turn off pagination for the view. For more complex requirements such as serialization that differs depending on the requested media type you can override the `.get_paginate_by()` and `.get_pagination_serializer_class()` methods. From 2d20512d259f51a5a5c2b71b20f98d24e0176f16 Mon Sep 17 00:00:00 2001 From: Andrey Kaygorodov Date: Wed, 5 Feb 2014 21:10:51 +0800 Subject: [PATCH 040/225] #1390, docs, turning of pagination --- docs/api-guide/pagination.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index f86e6ce11..047a09883 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -102,7 +102,8 @@ You can also set the pagination style on a per-view basis, using the `ListAPIVie paginate_by_param = 'page_size' max_paginate_by = 100 -Note that using a `paginate_by` value of `None` will turn off pagination for the view. But if you specified `PAGINATE_BY` and `PAGINATE_BY_PARAM` in your settings file then you have to set both `paginate_by` and `paginate_by_param` to a `None` value in order to turn off pagination for the view. +Note that using a `paginate_by` value of `None` will turn off pagination for the view. +Note if you use the `PAGINATE_BY_PARAM` settings, you also have to set the `paginate_by_param` attribute in your view to `None` in order to turn off pagination for those requests that contain the `paginate_by_param` parameter. For more complex requirements such as serialization that differs depending on the requested media type you can override the `.get_paginate_by()` and `.get_pagination_serializer_class()` methods. From 4d45865bd73ba16801950e3f47199aa6da0f7c19 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Sun, 9 Feb 2014 00:50:03 -0500 Subject: [PATCH 041/225] Allow filter model to be a subclass of the queryset one. --- rest_framework/filters.py | 2 +- rest_framework/tests/models.py | 12 +++++++++ rest_framework/tests/test_filters.py | 35 +++++++++++++++++++------ rest_framework/tests/test_pagination.py | 8 +----- 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index de91caedc..f7ad37baa 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -43,7 +43,7 @@ class DjangoFilterBackend(BaseFilterBackend): if filter_class: filter_model = filter_class.Meta.model - assert issubclass(filter_model, queryset.model), \ + assert issubclass(queryset.model, filter_model), \ 'FilterSet model %s does not match queryset model %s' % \ (filter_model, queryset.model) diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py index 32a726c0b..0137d45a0 100644 --- a/rest_framework/tests/models.py +++ b/rest_framework/tests/models.py @@ -60,6 +60,18 @@ class ReadOnlyManyToManyModel(RESTFrameworkModel): rel = models.ManyToManyField(Anchor) +class BaseFilterableItem(RESTFrameworkModel): + text = models.CharField(max_length=100) + + class Meta: + abstract = True + + +class FilterableItem(BaseFilterableItem): + decimal = models.DecimalField(max_digits=4, decimal_places=2) + date = models.DateField() + + # Model for regression test for #285 class Comment(RESTFrameworkModel): diff --git a/rest_framework/tests/test_filters.py b/rest_framework/tests/test_filters.py index 181881865..769d34266 100644 --- a/rest_framework/tests/test_filters.py +++ b/rest_framework/tests/test_filters.py @@ -8,17 +8,12 @@ from django.utils import unittest from rest_framework import generics, serializers, status, filters from rest_framework.compat import django_filters, patterns, url from rest_framework.test import APIRequestFactory -from rest_framework.tests.models import BasicModel +from rest_framework.tests.models import (BaseFilterableItem, BasicModel, + FilterableItem) factory = APIRequestFactory() -class FilterableItem(models.Model): - text = models.CharField(max_length=100) - decimal = models.DecimalField(max_digits=4, decimal_places=2) - date = models.DateField() - - if django_filters: # Basic filter on a list view. class FilterFieldsRootView(generics.ListCreateAPIView): @@ -59,6 +54,18 @@ if django_filters: filter_class = SeveralFieldsFilter filter_backends = (filters.DjangoFilterBackend,) + # These classes are used to test base model filter support + class BaseFilterableItemFilter(django_filters.FilterSet): + text = django_filters.CharFilter() + + class Meta: + model = BaseFilterableItem + + class BaseFilterableItemFilterRootView(generics.ListCreateAPIView): + model = FilterableItem + filter_class = BaseFilterableItemFilter + filter_backends = (filters.DjangoFilterBackend,) + # Regression test for #814 class FilterableItemSerializer(serializers.ModelSerializer): class Meta: @@ -226,6 +233,18 @@ class IntegrationTestFiltering(CommonFilteringTestCase): request = factory.get('/') self.assertRaises(AssertionError, view, request) + @unittest.skipUnless(django_filters, 'django-filter not installed') + def test_base_model_filter(self): + """ + The `get_filter_class` model checks should allow base model filters. + """ + view = BaseFilterableItemFilterRootView.as_view() + + request = factory.get('/?text=aaa') + response = view(request).render() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + @unittest.skipUnless(django_filters, 'django-filter not installed') def test_unknown_filter(self): """ @@ -612,4 +631,4 @@ class SensitiveOrderingFilterTests(TestCase): {'id': 2, username_field: 'userB'}, # PassC {'id': 3, username_field: 'userC'}, # PassA ] - ) \ No newline at end of file + ) diff --git a/rest_framework/tests/test_pagination.py b/rest_framework/tests/test_pagination.py index cadb515fa..cd2996134 100644 --- a/rest_framework/tests/test_pagination.py +++ b/rest_framework/tests/test_pagination.py @@ -8,17 +8,11 @@ from django.utils import unittest from rest_framework import generics, status, pagination, filters, serializers from rest_framework.compat import django_filters from rest_framework.test import APIRequestFactory -from rest_framework.tests.models import BasicModel +from rest_framework.tests.models import BasicModel, FilterableItem factory = APIRequestFactory() -class FilterableItem(models.Model): - text = models.CharField(max_length=100) - decimal = models.DecimalField(max_digits=4, decimal_places=2) - date = models.DateField() - - class RootView(generics.ListCreateAPIView): """ Example description for OPTIONS. From d18d32669ac47178f26409f149160dc2c0c5359c Mon Sep 17 00:00:00 2001 From: tuky Date: Wed, 12 Feb 2014 18:11:18 +0100 Subject: [PATCH 042/225] remove spaces from META['HTTP_X_FORWARDED_FOR'] as throttle key memcached cannot handle spaces in keys --- rest_framework/throttling.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index a946d837f..56023bdad 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -155,6 +155,8 @@ class AnonRateThrottle(SimpleRateThrottle): ident = request.META.get('HTTP_X_FORWARDED_FOR') if ident is None: ident = request.META.get('REMOTE_ADDR') + else: + ident = u''.join(ident.split()) return self.cache_format % { 'scope': self.scope, From 5e4336845fed97a819e69669ed7aa3b9bf443edb Mon Sep 17 00:00:00 2001 From: tuky Date: Fri, 14 Feb 2014 13:47:17 +0100 Subject: [PATCH 043/225] Update throttling.py python 3 u'' gone --- rest_framework/throttling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 56023bdad..c36b58bf2 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -156,7 +156,7 @@ class AnonRateThrottle(SimpleRateThrottle): if ident is None: ident = request.META.get('REMOTE_ADDR') else: - ident = u''.join(ident.split()) + ident = ''.join(ident.split()) return self.cache_format % { 'scope': self.scope, From 971578ca345c3d3bae7fd93b87c41d43483b6f05 Mon Sep 17 00:00:00 2001 From: Andreas Pelme Date: Sun, 2 Mar 2014 12:40:30 +0100 Subject: [PATCH 044/225] Support for running the test suite with py.test * Get rid of runtests.py * Moved test code from rest_framework/tests and rest_framework/runtests to tests * Invoke py.test from setup.py * Invoke py.test from Travis * Invoke py.test from tox * Changed setUpClass to be just plain setUp in test_permissions.py * Updated contribution guideline to show how to invoke py.test --- .travis.yml | 3 +- CONTRIBUTING.md | 2 +- conftest.py | 85 +++++++++++++++++++ docs/index.md | 16 +--- docs/topics/contributing.md | 2 +- pytest.ini | 2 + requirements.txt | 2 + rest_framework/runtests/runcoverage.py | 78 ----------------- rest_framework/runtests/runtests.py | 48 ----------- rest_framework/runtests/urls.py | 7 -- rest_framework/tests/tests.py | 16 ---- rest_framework/tests/users/__init__.py | 0 setup.py | 17 +++- .../runtests => tests}/__init__.py | 0 .../tests => tests/accounts}/__init__.py | 0 .../tests => tests}/accounts/models.py | 2 +- .../tests => tests}/accounts/serializers.py | 4 +- .../tests => tests}/description.py | 0 .../accounts => tests/extras}/__init__.py | 0 .../tests => tests}/extras/bad_import.py | 0 {rest_framework/tests => tests}/models.py | 0 .../extras => tests/records}/__init__.py | 0 .../tests => tests}/records/models.py | 0 .../tests => tests}/serializers.py | 2 +- .../runtests => tests}/settings.py | 10 +-- .../tests => tests}/test_authentication.py | 10 +-- .../tests => tests}/test_breadcrumbs.py | 2 +- .../tests => tests}/test_decorators.py | 0 .../tests => tests}/test_description.py | 4 +- .../tests => tests}/test_fields.py | 2 +- {rest_framework/tests => tests}/test_files.py | 0 .../tests => tests}/test_filters.py | 6 +- .../tests => tests}/test_genericrelations.py | 0 .../tests => tests}/test_generics.py | 2 +- .../tests => tests}/test_htmlrenderer.py | 4 +- .../test_hyperlinkedserializers.py | 18 ++-- .../test_multitable_inheritance.py | 2 +- .../tests => tests}/test_negotiation.py | 0 .../tests => tests}/test_nullable_fields.py | 8 +- .../tests => tests}/test_pagination.py | 2 +- .../tests => tests}/test_parsers.py | 0 .../tests => tests}/test_permissions.py | 15 +--- .../tests => tests}/test_relations.py | 4 +- .../test_relations_hyperlink.py | 12 +-- .../tests => tests}/test_relations_nested.py | 0 .../tests => tests}/test_relations_pk.py | 2 +- .../tests => tests}/test_relations_slug.py | 2 +- .../tests => tests}/test_renderers.py | 6 +- .../tests => tests}/test_request.py | 2 +- .../tests => tests}/test_response.py | 10 +-- .../tests => tests}/test_reverse.py | 2 +- .../tests => tests}/test_routers.py | 4 +- .../tests => tests}/test_serializer.py | 6 +- .../test_serializer_bulk_update.py | 0 .../tests => tests}/test_serializer_empty.py | 0 .../tests => tests}/test_serializer_import.py | 2 +- .../tests => tests}/test_serializer_nested.py | 0 .../tests => tests}/test_serializers.py | 2 +- .../tests => tests}/test_settings.py | 4 +- .../tests => tests}/test_status.py | 0 .../tests => tests}/test_templatetags.py | 0 .../tests => tests}/test_testing.py | 2 +- .../tests => tests}/test_throttling.py | 0 .../tests => tests}/test_urlpatterns.py | 0 .../tests => tests}/test_validation.py | 0 {rest_framework/tests => tests}/test_views.py | 0 .../tests => tests}/test_write_only_fields.py | 0 tests/urls.py | 6 ++ .../tests/records => tests/users}/__init__.py | 0 .../tests => tests}/users/models.py | 0 .../tests => tests}/users/serializers.py | 2 +- {rest_framework/tests => tests}/views.py | 4 +- tox.ini | 14 ++- 73 files changed, 206 insertions(+), 251 deletions(-) create mode 100644 conftest.py create mode 100644 pytest.ini delete mode 100755 rest_framework/runtests/runcoverage.py delete mode 100755 rest_framework/runtests/runtests.py delete mode 100644 rest_framework/runtests/urls.py delete mode 100644 rest_framework/tests/tests.py delete mode 100644 rest_framework/tests/users/__init__.py rename {rest_framework/runtests => tests}/__init__.py (100%) rename {rest_framework/tests => tests/accounts}/__init__.py (100%) rename {rest_framework/tests => tests}/accounts/models.py (81%) rename {rest_framework/tests => tests}/accounts/serializers.py (58%) rename {rest_framework/tests => tests}/description.py (100%) rename {rest_framework/tests/accounts => tests/extras}/__init__.py (100%) rename {rest_framework/tests => tests}/extras/bad_import.py (100%) rename {rest_framework/tests => tests}/models.py (100%) rename {rest_framework/tests/extras => tests/records}/__init__.py (100%) rename {rest_framework/tests => tests}/records/models.py (100%) rename {rest_framework/tests => tests}/serializers.py (71%) rename {rest_framework/runtests => tests}/settings.py (96%) rename {rest_framework/tests => tests}/test_authentication.py (99%) rename {rest_framework/tests => tests}/test_breadcrumbs.py (98%) rename {rest_framework/tests => tests}/test_decorators.py (100%) rename {rest_framework/tests => tests}/test_description.py (94%) rename {rest_framework/tests => tests}/test_fields.py (99%) rename {rest_framework/tests => tests}/test_files.py (100%) rename {rest_framework/tests => tests}/test_filters.py (99%) rename {rest_framework/tests => tests}/test_genericrelations.py (100%) rename {rest_framework/tests => tests}/test_generics.py (99%) rename {rest_framework/tests => tests}/test_htmlrenderer.py (97%) rename {rest_framework/tests => tests}/test_hyperlinkedserializers.py (95%) rename {rest_framework/tests => tests}/test_multitable_inheritance.py (97%) rename {rest_framework/tests => tests}/test_negotiation.py (100%) rename {rest_framework/tests => tests}/test_nullable_fields.py (76%) rename {rest_framework/tests => tests}/test_pagination.py (99%) rename {rest_framework/tests => tests}/test_parsers.py (100%) rename {rest_framework/tests => tests}/test_permissions.py (98%) rename {rest_framework/tests => tests}/test_relations.py (97%) rename {rest_framework/tests => tests}/test_relations_hyperlink.py (98%) rename {rest_framework/tests => tests}/test_relations_nested.py (100%) rename {rest_framework/tests => tests}/test_relations_pk.py (99%) rename {rest_framework/tests => tests}/test_relations_slug.py (99%) rename {rest_framework/tests => tests}/test_renderers.py (99%) rename {rest_framework/tests => tests}/test_request.py (99%) rename {rest_framework/tests => tests}/test_response.py (97%) rename {rest_framework/tests => tests}/test_reverse.py (93%) rename {rest_framework/tests => tests}/test_routers.py (98%) rename {rest_framework/tests => tests}/test_serializer.py (99%) rename {rest_framework/tests => tests}/test_serializer_bulk_update.py (100%) rename {rest_framework/tests => tests}/test_serializer_empty.py (100%) rename {rest_framework/tests => tests}/test_serializer_import.py (90%) rename {rest_framework/tests => tests}/test_serializer_nested.py (100%) rename {rest_framework/tests => tests}/test_serializers.py (94%) rename {rest_framework/tests => tests}/test_settings.py (83%) rename {rest_framework/tests => tests}/test_status.py (100%) rename {rest_framework/tests => tests}/test_templatetags.py (100%) rename {rest_framework/tests => tests}/test_testing.py (99%) rename {rest_framework/tests => tests}/test_throttling.py (100%) rename {rest_framework/tests => tests}/test_urlpatterns.py (100%) rename {rest_framework/tests => tests}/test_validation.py (100%) rename {rest_framework/tests => tests}/test_views.py (100%) rename {rest_framework/tests => tests}/test_write_only_fields.py (100%) create mode 100644 tests/urls.py rename {rest_framework/tests/records => tests/users}/__init__.py (100%) rename {rest_framework/tests => tests}/users/models.py (100%) rename {rest_framework/tests => tests}/users/serializers.py (71%) rename {rest_framework/tests => tests}/views.py (59%) diff --git a/.travis.yml b/.travis.yml index 2e6ed46a2..061d4c739 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ env: install: - pip install $DJANGO - pip install defusedxml==0.3 + - pip install pytest-django==2.6 - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install oauth2==1.5.211; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth-plus==2.2.1; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth2-provider==0.2.4; fi" @@ -24,7 +25,7 @@ install: - export PYTHONPATH=. script: - - python rest_framework/runtests/runtests.py + - py.test matrix: exclude: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a7aa6fc40..ff6018b82 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,7 +65,7 @@ To run the tests, clone the repository, and then: pip install -r optionals.txt # Run the tests - rest_framework/runtests/runtests.py + py.test You can also use the excellent [`tox`][tox] testing tool to run the tests against all supported versions of Python and Django. Install `tox` globally, and then simply run: diff --git a/conftest.py b/conftest.py new file mode 100644 index 000000000..7cfc77f2a --- /dev/null +++ b/conftest.py @@ -0,0 +1,85 @@ +def pytest_configure(): + from django.conf import settings + + settings.configure( + DEBUG_PROPAGATE_EXCEPTIONS=True, + DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3', + 'NAME': ':memory:'}}, + SECRET_KEY='not very secret in tests', + USE_I18N=True, + USE_L10N=True, + STATIC_URL='/static/', + ROOT_URLCONF='tests.urls', + TEMPLATE_LOADERS=( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + ), + MIDDLEWARE_CLASSES=( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + ), + INSTALLED_APPS=( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + + 'rest_framework', + 'rest_framework.authtoken', + 'tests', + 'tests.accounts', + 'tests.records', + 'tests.users', + ), + PASSWORD_HASHERS=( + 'django.contrib.auth.hashers.SHA1PasswordHasher', + 'django.contrib.auth.hashers.PBKDF2PasswordHasher', + 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', + 'django.contrib.auth.hashers.BCryptPasswordHasher', + 'django.contrib.auth.hashers.MD5PasswordHasher', + 'django.contrib.auth.hashers.CryptPasswordHasher', + ), + ) + + try: + import oauth_provider + import oauth2 + except ImportError: + pass + else: + settings.INSTALLED_APPS += ( + 'oauth_provider', + ) + + try: + import provider + except ImportError: + pass + else: + settings.INSTALLED_APPS += ( + 'provider', + 'provider.oauth2', + ) + + # guardian is optional + try: + import guardian + except ImportError: + pass + else: + settings.ANONYMOUS_USER_ID = -1 + settings.AUTHENTICATION_BACKENDS = ( + 'django.contrib.auth.backends.ModelBackend', # default + 'guardian.backends.ObjectPermissionBackend', + ) + settings.INSTALLED_APPS += ( + 'guardian', + ) + + # Force Django to load all models + from django.db.models import get_models + get_models() diff --git a/docs/index.md b/docs/index.md index 2a4ad8859..9ad647ac9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -206,19 +206,9 @@ General guides to using REST framework. ## Development -If you want to work on REST framework itself, clone the repository, then... - -Build the docs: - - ./mkdocs.py - -Run the tests: - - ./rest_framework/runtests/runtests.py - -To run the tests against all supported configurations, first install [the tox testing tool][tox] globally, using `pip install tox`, then simply run `tox`: - - tox +See the [Contribution guidelines][contributing] for information on how to clone +the repository, run the test suite and contribute changes back to REST +Framework. ## Support diff --git a/docs/topics/contributing.md b/docs/topics/contributing.md index 5a5d1a80b..09cc00b3c 100644 --- a/docs/topics/contributing.md +++ b/docs/topics/contributing.md @@ -65,7 +65,7 @@ To run the tests, clone the repository, and then: pip install -r optionals.txt # Run the tests - rest_framework/runtests/runtests.py + py.test You can also use the excellent `[tox][tox]` testing tool to run the tests against all supported versions of Python and Django. Install `tox` globally, and then simply run: diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..bbd083ac1 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = --tb=short diff --git a/requirements.txt b/requirements.txt index 730c1d07a..360acb14d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ +-e . Django>=1.3 +pytest-django==2.6 diff --git a/rest_framework/runtests/runcoverage.py b/rest_framework/runtests/runcoverage.py deleted file mode 100755 index ce11b213e..000000000 --- a/rest_framework/runtests/runcoverage.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python -""" -Useful tool to run the test suite for rest_framework and generate a coverage report. -""" - -# http://ericholscher.com/blog/2009/jun/29/enable-setuppy-test-your-django-apps/ -# http://www.travisswicegood.com/2010/01/17/django-virtualenv-pip-and-fabric/ -# http://code.djangoproject.com/svn/django/trunk/tests/runtests.py -import os -import sys - -# fix sys path so we don't need to setup PYTHONPATH -sys.path.append(os.path.join(os.path.dirname(__file__), "../..")) -os.environ['DJANGO_SETTINGS_MODULE'] = 'rest_framework.runtests.settings' - -from coverage import coverage - - -def main(): - """Run the tests for rest_framework and generate a coverage report.""" - - cov = coverage() - cov.erase() - cov.start() - - from django.conf import settings - from django.test.utils import get_runner - TestRunner = get_runner(settings) - - if hasattr(TestRunner, 'func_name'): - # Pre 1.2 test runners were just functions, - # and did not support the 'failfast' option. - import warnings - warnings.warn( - 'Function-based test runners are deprecated. Test runners should be classes with a run_tests() method.', - DeprecationWarning - ) - failures = TestRunner(['tests']) - else: - test_runner = TestRunner() - failures = test_runner.run_tests(['tests']) - cov.stop() - - # Discover the list of all modules that we should test coverage for - import rest_framework - - project_dir = os.path.dirname(rest_framework.__file__) - cov_files = [] - - for (path, dirs, files) in os.walk(project_dir): - # Drop tests and runtests directories from the test coverage report - if os.path.basename(path) in ['tests', 'runtests', 'migrations']: - continue - - # Drop the compat and six modules from coverage, since we're not interested in the coverage - # of modules which are specifically for resolving environment dependant imports. - # (Because we'll end up getting different coverage reports for it for each environment) - if 'compat.py' in files: - files.remove('compat.py') - - if 'six.py' in files: - files.remove('six.py') - - # Same applies to template tags module. - # This module has to include branching on Django versions, - # so it's never possible for it to have full coverage. - if 'rest_framework.py' in files: - files.remove('rest_framework.py') - - cov_files.extend([os.path.join(path, file) for file in files if file.endswith('.py')]) - - cov.report(cov_files) - if '--html' in sys.argv: - cov.html_report(cov_files, directory='coverage') - sys.exit(failures) - -if __name__ == '__main__': - main() diff --git a/rest_framework/runtests/runtests.py b/rest_framework/runtests/runtests.py deleted file mode 100755 index da36d23fc..000000000 --- a/rest_framework/runtests/runtests.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python - -# http://ericholscher.com/blog/2009/jun/29/enable-setuppy-test-your-django-apps/ -# http://www.travisswicegood.com/2010/01/17/django-virtualenv-pip-and-fabric/ -# http://code.djangoproject.com/svn/django/trunk/tests/runtests.py -import os -import sys - -# fix sys path so we don't need to setup PYTHONPATH -sys.path.append(os.path.join(os.path.dirname(__file__), "../..")) -os.environ['DJANGO_SETTINGS_MODULE'] = 'rest_framework.runtests.settings' - -import django -from django.conf import settings -from django.test.utils import get_runner - - -def usage(): - return """ - Usage: python runtests.py [UnitTestClass].[method] - - You can pass the Class name of the `UnitTestClass` you want to test. - - Append a method name if you only want to test a specific method of that class. - """ - - -def main(): - TestRunner = get_runner(settings) - - test_runner = TestRunner() - if len(sys.argv) == 2: - test_case = '.' + sys.argv[1] - elif len(sys.argv) == 1: - test_case = '' - else: - print(usage()) - sys.exit(1) - test_module_name = 'rest_framework.tests' - if django.VERSION[0] == 1 and django.VERSION[1] < 6: - test_module_name = 'tests' - - failures = test_runner.run_tests([test_module_name + test_case]) - - sys.exit(failures) - -if __name__ == '__main__': - main() diff --git a/rest_framework/runtests/urls.py b/rest_framework/runtests/urls.py deleted file mode 100644 index ed5baeae6..000000000 --- a/rest_framework/runtests/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Blank URLConf just to keep runtests.py happy. -""" -from rest_framework.compat import patterns - -urlpatterns = patterns('', -) diff --git a/rest_framework/tests/tests.py b/rest_framework/tests/tests.py deleted file mode 100644 index 554ebd1ad..000000000 --- a/rest_framework/tests/tests.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -Force import of all modules in this package in order to get the standard test -runner to pick up the tests. Yowzers. -""" -from __future__ import unicode_literals -import os -import django - -modules = [filename.rsplit('.', 1)[0] - for filename in os.listdir(os.path.dirname(__file__)) - if filename.endswith('.py') and not filename.startswith('_')] -__test__ = dict() - -if django.VERSION < (1, 6): - for module in modules: - exec("from rest_framework.tests.%s import *" % module) diff --git a/rest_framework/tests/users/__init__.py b/rest_framework/tests/users/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/setup.py b/setup.py index 78cdb6289..2c56cd758 100755 --- a/setup.py +++ b/setup.py @@ -2,11 +2,26 @@ # -*- coding: utf-8 -*- from setuptools import setup +from setuptools.command.test import test as TestCommand import re import os import sys +# This command has been borrowed from +# https://github.com/getsentry/sentry/blob/master/setup.py +class PyTest(TestCommand): + def finalize_options(self): + TestCommand.finalize_options(self) + self.test_args = ['tests'] + self.test_suite = True + + def run_tests(self): + import pytest + errno = pytest.main(self.test_args) + sys.exit(errno) + + def get_version(package): """ Return package version as listed in `__version__` in `init.py`. @@ -62,7 +77,7 @@ setup( author_email='tom@tomchristie.com', # SEE NOTE BELOW (*) packages=get_packages('rest_framework'), package_data=get_package_data('rest_framework'), - test_suite='rest_framework.runtests.runtests.main', + cmdclass={'test': PyTest}, install_requires=[], classifiers=[ 'Development Status :: 5 - Production/Stable', diff --git a/rest_framework/runtests/__init__.py b/tests/__init__.py similarity index 100% rename from rest_framework/runtests/__init__.py rename to tests/__init__.py diff --git a/rest_framework/tests/__init__.py b/tests/accounts/__init__.py similarity index 100% rename from rest_framework/tests/__init__.py rename to tests/accounts/__init__.py diff --git a/rest_framework/tests/accounts/models.py b/tests/accounts/models.py similarity index 81% rename from rest_framework/tests/accounts/models.py rename to tests/accounts/models.py index 525e601ba..3bf4a0c3c 100644 --- a/rest_framework/tests/accounts/models.py +++ b/tests/accounts/models.py @@ -1,6 +1,6 @@ from django.db import models -from rest_framework.tests.users.models import User +from tests.users.models import User class Account(models.Model): diff --git a/rest_framework/tests/accounts/serializers.py b/tests/accounts/serializers.py similarity index 58% rename from rest_framework/tests/accounts/serializers.py rename to tests/accounts/serializers.py index a27b9ca6f..57a91b92c 100644 --- a/rest_framework/tests/accounts/serializers.py +++ b/tests/accounts/serializers.py @@ -1,7 +1,7 @@ from rest_framework import serializers -from rest_framework.tests.accounts.models import Account -from rest_framework.tests.users.serializers import UserSerializer +from tests.accounts.models import Account +from tests.users.serializers import UserSerializer class AccountSerializer(serializers.ModelSerializer): diff --git a/rest_framework/tests/description.py b/tests/description.py similarity index 100% rename from rest_framework/tests/description.py rename to tests/description.py diff --git a/rest_framework/tests/accounts/__init__.py b/tests/extras/__init__.py similarity index 100% rename from rest_framework/tests/accounts/__init__.py rename to tests/extras/__init__.py diff --git a/rest_framework/tests/extras/bad_import.py b/tests/extras/bad_import.py similarity index 100% rename from rest_framework/tests/extras/bad_import.py rename to tests/extras/bad_import.py diff --git a/rest_framework/tests/models.py b/tests/models.py similarity index 100% rename from rest_framework/tests/models.py rename to tests/models.py diff --git a/rest_framework/tests/extras/__init__.py b/tests/records/__init__.py similarity index 100% rename from rest_framework/tests/extras/__init__.py rename to tests/records/__init__.py diff --git a/rest_framework/tests/records/models.py b/tests/records/models.py similarity index 100% rename from rest_framework/tests/records/models.py rename to tests/records/models.py diff --git a/rest_framework/tests/serializers.py b/tests/serializers.py similarity index 71% rename from rest_framework/tests/serializers.py rename to tests/serializers.py index cc943c7d0..f2f85b6ea 100644 --- a/rest_framework/tests/serializers.py +++ b/tests/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers -from rest_framework.tests.models import NullableForeignKeySource +from tests.models import NullableForeignKeySource class NullableFKSourceSerializer(serializers.ModelSerializer): diff --git a/rest_framework/runtests/settings.py b/tests/settings.py similarity index 96% rename from rest_framework/runtests/settings.py rename to tests/settings.py index 3fc0eb2f4..75f7c54b3 100644 --- a/rest_framework/runtests/settings.py +++ b/tests/settings.py @@ -79,7 +79,7 @@ MIDDLEWARE_CLASSES = ( 'django.contrib.messages.middleware.MessageMiddleware', ) -ROOT_URLCONF = 'urls' +ROOT_URLCONF = 'tests.urls' TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". @@ -99,10 +99,10 @@ INSTALLED_APPS = ( # 'django.contrib.admindocs', 'rest_framework', 'rest_framework.authtoken', - 'rest_framework.tests', - 'rest_framework.tests.accounts', - 'rest_framework.tests.records', - 'rest_framework.tests.users', + 'tests', + 'tests.accounts', + 'tests.records', + 'tests.users', ) # OAuth is optional and won't work if there is no oauth_provider & oauth2 diff --git a/rest_framework/tests/test_authentication.py b/tests/test_authentication.py similarity index 99% rename from rest_framework/tests/test_authentication.py rename to tests/test_authentication.py index f072b81b7..4ecfef44f 100644 --- a/rest_framework/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -64,7 +64,7 @@ if oauth2_provider is not None: class BasicAuthTests(TestCase): """Basic authentication""" - urls = 'rest_framework.tests.test_authentication' + urls = 'tests.test_authentication' def setUp(self): self.csrf_client = APIClient(enforce_csrf_checks=True) @@ -103,7 +103,7 @@ class BasicAuthTests(TestCase): class SessionAuthTests(TestCase): """User session authentication""" - urls = 'rest_framework.tests.test_authentication' + urls = 'tests.test_authentication' def setUp(self): self.csrf_client = APIClient(enforce_csrf_checks=True) @@ -150,7 +150,7 @@ class SessionAuthTests(TestCase): class TokenAuthTests(TestCase): """Token authentication""" - urls = 'rest_framework.tests.test_authentication' + urls = 'tests.test_authentication' def setUp(self): self.csrf_client = APIClient(enforce_csrf_checks=True) @@ -244,7 +244,7 @@ class IncorrectCredentialsTests(TestCase): class OAuthTests(TestCase): """OAuth 1.0a authentication""" - urls = 'rest_framework.tests.test_authentication' + urls = 'tests.test_authentication' def setUp(self): # these imports are here because oauth is optional and hiding them in try..except block or compat @@ -474,7 +474,7 @@ class OAuthTests(TestCase): class OAuth2Tests(TestCase): """OAuth 2.0 authentication""" - urls = 'rest_framework.tests.test_authentication' + urls = 'tests.test_authentication' def setUp(self): self.csrf_client = APIClient(enforce_csrf_checks=True) diff --git a/rest_framework/tests/test_breadcrumbs.py b/tests/test_breadcrumbs.py similarity index 98% rename from rest_framework/tests/test_breadcrumbs.py rename to tests/test_breadcrumbs.py index 41ddf2cea..78edc6032 100644 --- a/rest_framework/tests/test_breadcrumbs.py +++ b/tests/test_breadcrumbs.py @@ -36,7 +36,7 @@ urlpatterns = patterns('', class BreadcrumbTests(TestCase): """Tests the breadcrumb functionality used by the HTML renderer.""" - urls = 'rest_framework.tests.test_breadcrumbs' + urls = 'tests.test_breadcrumbs' def test_root_breadcrumbs(self): url = '/' diff --git a/rest_framework/tests/test_decorators.py b/tests/test_decorators.py similarity index 100% rename from rest_framework/tests/test_decorators.py rename to tests/test_decorators.py diff --git a/rest_framework/tests/test_description.py b/tests/test_description.py similarity index 94% rename from rest_framework/tests/test_description.py rename to tests/test_description.py index 4c03c1ded..1e481f06c 100644 --- a/rest_framework/tests/test_description.py +++ b/tests/test_description.py @@ -4,8 +4,8 @@ from __future__ import unicode_literals from django.test import TestCase from rest_framework.compat import apply_markdown, smart_text from rest_framework.views import APIView -from rest_framework.tests.description import ViewWithNonASCIICharactersInDocstring -from rest_framework.tests.description import UTF8_TEST_DOCSTRING +from .description import ViewWithNonASCIICharactersInDocstring +from .description import UTF8_TEST_DOCSTRING # We check that docstrings get nicely un-indented. DESCRIPTION = """an example docstring diff --git a/rest_framework/tests/test_fields.py b/tests/test_fields.py similarity index 99% rename from rest_framework/tests/test_fields.py rename to tests/test_fields.py index e127feef9..e65a2fb39 100644 --- a/rest_framework/tests/test_fields.py +++ b/tests/test_fields.py @@ -11,7 +11,7 @@ from django.db import models from django.test import TestCase from django.utils.datastructures import SortedDict from rest_framework import serializers -from rest_framework.tests.models import RESTFrameworkModel +from tests.models import RESTFrameworkModel class TimestampedModel(models.Model): diff --git a/rest_framework/tests/test_files.py b/tests/test_files.py similarity index 100% rename from rest_framework/tests/test_files.py rename to tests/test_files.py diff --git a/rest_framework/tests/test_filters.py b/tests/test_filters.py similarity index 99% rename from rest_framework/tests/test_filters.py rename to tests/test_filters.py index 181881865..d9d8042e2 100644 --- a/rest_framework/tests/test_filters.py +++ b/tests/test_filters.py @@ -8,7 +8,7 @@ from django.utils import unittest from rest_framework import generics, serializers, status, filters from rest_framework.compat import django_filters, patterns, url from rest_framework.test import APIRequestFactory -from rest_framework.tests.models import BasicModel +from tests.models import BasicModel factory = APIRequestFactory() @@ -243,7 +243,7 @@ class IntegrationTestDetailFiltering(CommonFilteringTestCase): """ Integration tests for filtered detail views. """ - urls = 'rest_framework.tests.test_filters' + urls = 'tests.test_filters' def _get_url(self, item): return reverse('detail-view', kwargs=dict(pk=item.pk)) @@ -612,4 +612,4 @@ class SensitiveOrderingFilterTests(TestCase): {'id': 2, username_field: 'userB'}, # PassC {'id': 3, username_field: 'userC'}, # PassA ] - ) \ No newline at end of file + ) diff --git a/rest_framework/tests/test_genericrelations.py b/tests/test_genericrelations.py similarity index 100% rename from rest_framework/tests/test_genericrelations.py rename to tests/test_genericrelations.py diff --git a/rest_framework/tests/test_generics.py b/tests/test_generics.py similarity index 99% rename from rest_framework/tests/test_generics.py rename to tests/test_generics.py index 996bd5b0e..4389994af 100644 --- a/rest_framework/tests/test_generics.py +++ b/tests/test_generics.py @@ -4,7 +4,7 @@ from django.shortcuts import get_object_or_404 from django.test import TestCase from rest_framework import generics, renderers, serializers, status from rest_framework.test import APIRequestFactory -from rest_framework.tests.models import BasicModel, Comment, SlugBasedModel +from tests.models import BasicModel, Comment, SlugBasedModel from rest_framework.compat import six factory = APIRequestFactory() diff --git a/rest_framework/tests/test_htmlrenderer.py b/tests/test_htmlrenderer.py similarity index 97% rename from rest_framework/tests/test_htmlrenderer.py rename to tests/test_htmlrenderer.py index 8957a43c7..c748fbdbb 100644 --- a/rest_framework/tests/test_htmlrenderer.py +++ b/tests/test_htmlrenderer.py @@ -42,7 +42,7 @@ urlpatterns = patterns('', class TemplateHTMLRendererTests(TestCase): - urls = 'rest_framework.tests.test_htmlrenderer' + urls = 'tests.test_htmlrenderer' def setUp(self): """ @@ -82,7 +82,7 @@ class TemplateHTMLRendererTests(TestCase): class TemplateHTMLRendererExceptionTests(TestCase): - urls = 'rest_framework.tests.test_htmlrenderer' + urls = 'tests.test_htmlrenderer' def setUp(self): """ diff --git a/rest_framework/tests/test_hyperlinkedserializers.py b/tests/test_hyperlinkedserializers.py similarity index 95% rename from rest_framework/tests/test_hyperlinkedserializers.py rename to tests/test_hyperlinkedserializers.py index 83d460435..eee179cae 100644 --- a/rest_framework/tests/test_hyperlinkedserializers.py +++ b/tests/test_hyperlinkedserializers.py @@ -5,7 +5,7 @@ from rest_framework import generics, status, serializers from rest_framework.compat import patterns, url from rest_framework.settings import api_settings from rest_framework.test import APIRequestFactory -from rest_framework.tests.models import ( +from tests.models import ( Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment, Album, Photo, OptionalRelationModel ) @@ -110,7 +110,7 @@ urlpatterns = patterns('', class TestBasicHyperlinkedView(TestCase): - urls = 'rest_framework.tests.test_hyperlinkedserializers' + urls = 'tests.test_hyperlinkedserializers' def setUp(self): """ @@ -147,7 +147,7 @@ class TestBasicHyperlinkedView(TestCase): class TestManyToManyHyperlinkedView(TestCase): - urls = 'rest_framework.tests.test_hyperlinkedserializers' + urls = 'tests.test_hyperlinkedserializers' def setUp(self): """ @@ -195,7 +195,7 @@ class TestManyToManyHyperlinkedView(TestCase): class TestHyperlinkedIdentityFieldLookup(TestCase): - urls = 'rest_framework.tests.test_hyperlinkedserializers' + urls = 'tests.test_hyperlinkedserializers' def setUp(self): """ @@ -225,7 +225,7 @@ class TestHyperlinkedIdentityFieldLookup(TestCase): class TestCreateWithForeignKeys(TestCase): - urls = 'rest_framework.tests.test_hyperlinkedserializers' + urls = 'tests.test_hyperlinkedserializers' def setUp(self): """ @@ -250,7 +250,7 @@ class TestCreateWithForeignKeys(TestCase): class TestCreateWithForeignKeysAndCustomSlug(TestCase): - urls = 'rest_framework.tests.test_hyperlinkedserializers' + urls = 'tests.test_hyperlinkedserializers' def setUp(self): """ @@ -275,7 +275,7 @@ class TestCreateWithForeignKeysAndCustomSlug(TestCase): class TestOptionalRelationHyperlinkedView(TestCase): - urls = 'rest_framework.tests.test_hyperlinkedserializers' + urls = 'tests.test_hyperlinkedserializers' def setUp(self): """ @@ -335,7 +335,7 @@ class TestOverriddenURLField(TestCase): class TestURLFieldNameBySettings(TestCase): - urls = 'rest_framework.tests.test_hyperlinkedserializers' + urls = 'tests.test_hyperlinkedserializers' def setUp(self): self.saved_url_field_name = api_settings.URL_FIELD_NAME @@ -360,7 +360,7 @@ class TestURLFieldNameBySettings(TestCase): class TestURLFieldNameByOptions(TestCase): - urls = 'rest_framework.tests.test_hyperlinkedserializers' + urls = 'tests.test_hyperlinkedserializers' def setUp(self): class Serializer(serializers.HyperlinkedModelSerializer): diff --git a/rest_framework/tests/test_multitable_inheritance.py b/tests/test_multitable_inheritance.py similarity index 97% rename from rest_framework/tests/test_multitable_inheritance.py rename to tests/test_multitable_inheritance.py index 00c153276..ce1bf3ea3 100644 --- a/rest_framework/tests/test_multitable_inheritance.py +++ b/tests/test_multitable_inheritance.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models from django.test import TestCase from rest_framework import serializers -from rest_framework.tests.models import RESTFrameworkModel +from tests.models import RESTFrameworkModel # Models diff --git a/rest_framework/tests/test_negotiation.py b/tests/test_negotiation.py similarity index 100% rename from rest_framework/tests/test_negotiation.py rename to tests/test_negotiation.py diff --git a/rest_framework/tests/test_nullable_fields.py b/tests/test_nullable_fields.py similarity index 76% rename from rest_framework/tests/test_nullable_fields.py rename to tests/test_nullable_fields.py index 6ee55c005..33a9685f3 100644 --- a/rest_framework/tests/test_nullable_fields.py +++ b/tests/test_nullable_fields.py @@ -2,9 +2,9 @@ from django.core.urlresolvers import reverse from rest_framework.compat import patterns, url from rest_framework.test import APITestCase -from rest_framework.tests.models import NullableForeignKeySource -from rest_framework.tests.serializers import NullableFKSourceSerializer -from rest_framework.tests.views import NullableFKSourceDetail +from tests.models import NullableForeignKeySource +from tests.serializers import NullableFKSourceSerializer +from tests.views import NullableFKSourceDetail urlpatterns = patterns( @@ -18,7 +18,7 @@ class NullableForeignKeyTests(APITestCase): DRF should be able to handle nullable foreign keys when a test Client POST/PUT request is made with its own serialized object. """ - urls = 'rest_framework.tests.test_nullable_fields' + urls = 'tests.test_nullable_fields' def test_updating_object_with_null_fk(self): obj = NullableForeignKeySource(name='example', target=None) diff --git a/rest_framework/tests/test_pagination.py b/tests/test_pagination.py similarity index 99% rename from rest_framework/tests/test_pagination.py rename to tests/test_pagination.py index cadb515fa..65fa9dcd1 100644 --- a/rest_framework/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -8,7 +8,7 @@ from django.utils import unittest from rest_framework import generics, status, pagination, filters, serializers from rest_framework.compat import django_filters from rest_framework.test import APIRequestFactory -from rest_framework.tests.models import BasicModel +from tests.models import BasicModel factory = APIRequestFactory() diff --git a/rest_framework/tests/test_parsers.py b/tests/test_parsers.py similarity index 100% rename from rest_framework/tests/test_parsers.py rename to tests/test_parsers.py diff --git a/rest_framework/tests/test_permissions.py b/tests/test_permissions.py similarity index 98% rename from rest_framework/tests/test_permissions.py rename to tests/test_permissions.py index 6e3a63034..a2cb0c362 100644 --- a/rest_framework/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -7,7 +7,7 @@ from rest_framework import generics, status, permissions, authentication, HTTP_H from rest_framework.compat import guardian, get_model_name from rest_framework.filters import DjangoObjectPermissionsFilter from rest_framework.test import APIRequestFactory -from rest_framework.tests.models import BasicModel +from tests.models import BasicModel import base64 factory = APIRequestFactory() @@ -187,8 +187,7 @@ class ObjectPermissionsIntegrationTests(TestCase): """ Integration tests for the object level permissions API. """ - @classmethod - def setUpClass(cls): + def setUp(self): from guardian.shortcuts import assign_perm # create users @@ -215,21 +214,13 @@ class ObjectPermissionsIntegrationTests(TestCase): assign_perm(perm, everyone) everyone.user_set.add(*users.values()) - cls.perms = perms - cls.users = users - - def setUp(self): - from guardian.shortcuts import assign_perm - perms = self.perms - users = self.users - # appropriate object level permissions readers = Group.objects.create(name='readers') writers = Group.objects.create(name='writers') deleters = Group.objects.create(name='deleters') model = BasicPermModel.objects.create(text='foo') - + assign_perm(perms['view'], readers, model) assign_perm(perms['change'], writers, model) assign_perm(perms['delete'], deleters, model) diff --git a/rest_framework/tests/test_relations.py b/tests/test_relations.py similarity index 97% rename from rest_framework/tests/test_relations.py rename to tests/test_relations.py index f52e0e1e5..bfc8d487e 100644 --- a/rest_framework/tests/test_relations.py +++ b/tests/test_relations.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals from django.db import models from django.test import TestCase from rest_framework import serializers -from rest_framework.tests.models import BlogPost +from tests.models import BlogPost class NullModel(models.Model): @@ -105,7 +105,7 @@ class RelatedFieldSourceTests(TestCase): Check that the exception message are correct if the source field doesn't exist. """ - from rest_framework.tests.models import ManyToManySource + from tests.models import ManyToManySource class Meta: model = ManyToManySource attrs = { diff --git a/rest_framework/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py similarity index 98% rename from rest_framework/tests/test_relations_hyperlink.py rename to tests/test_relations_hyperlink.py index 3c4d39af6..98f68d29f 100644 --- a/rest_framework/tests/test_relations_hyperlink.py +++ b/tests/test_relations_hyperlink.py @@ -3,7 +3,7 @@ from django.test import TestCase from rest_framework import serializers from rest_framework.compat import patterns, url from rest_framework.test import APIRequestFactory -from rest_framework.tests.models import ( +from tests.models import ( BlogPost, ManyToManyTarget, ManyToManySource, ForeignKeyTarget, ForeignKeySource, NullableForeignKeySource, OneToOneTarget, NullableOneToOneSource @@ -71,7 +71,7 @@ class NullableOneToOneTargetSerializer(serializers.HyperlinkedModelSerializer): # TODO: Add test that .data cannot be accessed prior to .is_valid class HyperlinkedManyToManyTests(TestCase): - urls = 'rest_framework.tests.test_relations_hyperlink' + urls = 'tests.test_relations_hyperlink' def setUp(self): for idx in range(1, 4): @@ -179,7 +179,7 @@ class HyperlinkedManyToManyTests(TestCase): class HyperlinkedForeignKeyTests(TestCase): - urls = 'rest_framework.tests.test_relations_hyperlink' + urls = 'tests.test_relations_hyperlink' def setUp(self): target = ForeignKeyTarget(name='target-1') @@ -307,7 +307,7 @@ class HyperlinkedForeignKeyTests(TestCase): class HyperlinkedNullableForeignKeyTests(TestCase): - urls = 'rest_framework.tests.test_relations_hyperlink' + urls = 'tests.test_relations_hyperlink' def setUp(self): target = ForeignKeyTarget(name='target-1') @@ -435,7 +435,7 @@ class HyperlinkedNullableForeignKeyTests(TestCase): class HyperlinkedNullableOneToOneTests(TestCase): - urls = 'rest_framework.tests.test_relations_hyperlink' + urls = 'tests.test_relations_hyperlink' def setUp(self): target = OneToOneTarget(name='target-1') @@ -458,7 +458,7 @@ class HyperlinkedNullableOneToOneTests(TestCase): # Regression tests for #694 (`source` attribute on related fields) class HyperlinkedRelatedFieldSourceTests(TestCase): - urls = 'rest_framework.tests.test_relations_hyperlink' + urls = 'tests.test_relations_hyperlink' def test_related_manager_source(self): """ diff --git a/rest_framework/tests/test_relations_nested.py b/tests/test_relations_nested.py similarity index 100% rename from rest_framework/tests/test_relations_nested.py rename to tests/test_relations_nested.py diff --git a/rest_framework/tests/test_relations_pk.py b/tests/test_relations_pk.py similarity index 99% rename from rest_framework/tests/test_relations_pk.py rename to tests/test_relations_pk.py index 3815afdd8..ff59b250a 100644 --- a/rest_framework/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models from django.test import TestCase from rest_framework import serializers -from rest_framework.tests.models import ( +from tests.models import ( BlogPost, ManyToManyTarget, ManyToManySource, ForeignKeyTarget, ForeignKeySource, NullableForeignKeySource, OneToOneTarget, NullableOneToOneSource, ) diff --git a/rest_framework/tests/test_relations_slug.py b/tests/test_relations_slug.py similarity index 99% rename from rest_framework/tests/test_relations_slug.py rename to tests/test_relations_slug.py index 435c821cf..97ebf23a1 100644 --- a/rest_framework/tests/test_relations_slug.py +++ b/tests/test_relations_slug.py @@ -1,6 +1,6 @@ from django.test import TestCase from rest_framework import serializers -from rest_framework.tests.models import NullableForeignKeySource, ForeignKeySource, ForeignKeyTarget +from tests.models import NullableForeignKeySource, ForeignKeySource, ForeignKeyTarget class ForeignKeyTargetSerializer(serializers.ModelSerializer): diff --git a/rest_framework/tests/test_renderers.py b/tests/test_renderers.py similarity index 99% rename from rest_framework/tests/test_renderers.py rename to tests/test_renderers.py index 0f3432c99..b41cff397 100644 --- a/rest_framework/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -152,7 +152,7 @@ class RendererEndToEndTests(TestCase): End-to-end testing of renderers using an RendererMixin on a generic view. """ - urls = 'rest_framework.tests.test_renderers' + urls = 'tests.test_renderers' def test_default_renderer_serializes_content(self): """If the Accept header is not set the default renderer should serialize the response.""" @@ -387,7 +387,7 @@ class JSONPRendererTests(TestCase): Tests specific to the JSONP Renderer """ - urls = 'rest_framework.tests.test_renderers' + urls = 'tests.test_renderers' def test_without_callback_with_json_renderer(self): """ @@ -571,7 +571,7 @@ class CacheRenderTest(TestCase): Tests specific to caching responses """ - urls = 'rest_framework.tests.test_renderers' + urls = 'tests.test_renderers' cache_key = 'just_a_cache_key' diff --git a/rest_framework/tests/test_request.py b/tests/test_request.py similarity index 99% rename from rest_framework/tests/test_request.py rename to tests/test_request.py index c0b50f330..0a9355f0b 100644 --- a/rest_framework/tests/test_request.py +++ b/tests/test_request.py @@ -278,7 +278,7 @@ urlpatterns = patterns('', class TestContentParsingWithAuthentication(TestCase): - urls = 'rest_framework.tests.test_request' + urls = 'tests.test_request' def setUp(self): self.csrf_client = APIClient(enforce_csrf_checks=True) diff --git a/rest_framework/tests/test_response.py b/tests/test_response.py similarity index 97% rename from rest_framework/tests/test_response.py rename to tests/test_response.py index eea3c6418..41c0f49d8 100644 --- a/rest_framework/tests/test_response.py +++ b/tests/test_response.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals from django.test import TestCase -from rest_framework.tests.models import BasicModel, BasicModelSerializer +from tests.models import BasicModel, BasicModelSerializer from rest_framework.compat import patterns, url, include from rest_framework.response import Response from rest_framework.views import APIView @@ -118,7 +118,7 @@ class RendererIntegrationTests(TestCase): End-to-end testing of renderers using an ResponseMixin on a generic view. """ - urls = 'rest_framework.tests.test_response' + urls = 'tests.test_response' def test_default_renderer_serializes_content(self): """If the Accept header is not set the default renderer should serialize the response.""" @@ -198,7 +198,7 @@ class Issue122Tests(TestCase): """ Tests that covers #122. """ - urls = 'rest_framework.tests.test_response' + urls = 'tests.test_response' def test_only_html_renderer(self): """ @@ -218,7 +218,7 @@ class Issue467Tests(TestCase): Tests for #467 """ - urls = 'rest_framework.tests.test_response' + urls = 'tests.test_response' def test_form_has_label_and_help_text(self): resp = self.client.get('/html_new_model') @@ -232,7 +232,7 @@ class Issue807Tests(TestCase): Covers #807 """ - urls = 'rest_framework.tests.test_response' + urls = 'tests.test_response' def test_does_not_append_charset_by_default(self): """ diff --git a/rest_framework/tests/test_reverse.py b/tests/test_reverse.py similarity index 93% rename from rest_framework/tests/test_reverse.py rename to tests/test_reverse.py index 690a30b11..3d14a28f5 100644 --- a/rest_framework/tests/test_reverse.py +++ b/tests/test_reverse.py @@ -19,7 +19,7 @@ class ReverseTests(TestCase): """ Tests for fully qualified URLs when using `reverse`. """ - urls = 'rest_framework.tests.test_reverse' + urls = 'tests.test_reverse' def test_reversed_urls_are_fully_qualified(self): request = factory.get('/view') diff --git a/rest_framework/tests/test_routers.py b/tests/test_routers.py similarity index 98% rename from rest_framework/tests/test_routers.py rename to tests/test_routers.py index e723f7d45..084c0e277 100644 --- a/rest_framework/tests/test_routers.py +++ b/tests/test_routers.py @@ -72,7 +72,7 @@ class TestCustomLookupFields(TestCase): """ Ensure that custom lookup fields are correctly routed. """ - urls = 'rest_framework.tests.test_routers' + urls = 'tests.test_routers' def setUp(self): class NoteSerializer(serializers.HyperlinkedModelSerializer): @@ -91,7 +91,7 @@ class TestCustomLookupFields(TestCase): self.router = SimpleRouter() self.router.register(r'notes', NoteViewSet) - from rest_framework.tests import test_routers + from tests import test_routers urls = getattr(test_routers, 'urlpatterns') urls += patterns('', url(r'^', include(self.router.urls)), diff --git a/rest_framework/tests/test_serializer.py b/tests/test_serializer.py similarity index 99% rename from rest_framework/tests/test_serializer.py rename to tests/test_serializer.py index 6b1e333e4..18484afeb 100644 --- a/rest_framework/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -6,10 +6,10 @@ from django.test import TestCase from django.utils.datastructures import MultiValueDict from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers, fields, relations -from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel, +from tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel, BlankFieldModel, BlogPost, BlogPostComment, Book, CallableDefaultValueModel, DefaultValueModel, ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, RESTFrameworkModel) -from rest_framework.tests.models import BasicModelSerializer +from tests.models import BasicModelSerializer import datetime import pickle @@ -1034,7 +1034,7 @@ class RelatedTraversalTest(TestCase): """ If a component of the dotted.source is None, return None for the field. """ - from rest_framework.tests.models import NullableForeignKeySource + from tests.models import NullableForeignKeySource instance = NullableForeignKeySource.objects.create(name='Source with null FK') class NullableSourceSerializer(serializers.Serializer): diff --git a/rest_framework/tests/test_serializer_bulk_update.py b/tests/test_serializer_bulk_update.py similarity index 100% rename from rest_framework/tests/test_serializer_bulk_update.py rename to tests/test_serializer_bulk_update.py diff --git a/rest_framework/tests/test_serializer_empty.py b/tests/test_serializer_empty.py similarity index 100% rename from rest_framework/tests/test_serializer_empty.py rename to tests/test_serializer_empty.py diff --git a/rest_framework/tests/test_serializer_import.py b/tests/test_serializer_import.py similarity index 90% rename from rest_framework/tests/test_serializer_import.py rename to tests/test_serializer_import.py index 9f30a7ffa..3b8ff4b3c 100644 --- a/rest_framework/tests/test_serializer_import.py +++ b/tests/test_serializer_import.py @@ -1,7 +1,7 @@ from django.test import TestCase from rest_framework import serializers -from rest_framework.tests.accounts.serializers import AccountSerializer +from tests.accounts.serializers import AccountSerializer class ImportingModelSerializerTests(TestCase): diff --git a/rest_framework/tests/test_serializer_nested.py b/tests/test_serializer_nested.py similarity index 100% rename from rest_framework/tests/test_serializer_nested.py rename to tests/test_serializer_nested.py diff --git a/rest_framework/tests/test_serializers.py b/tests/test_serializers.py similarity index 94% rename from rest_framework/tests/test_serializers.py rename to tests/test_serializers.py index 082a400ca..675477833 100644 --- a/rest_framework/tests/test_serializers.py +++ b/tests/test_serializers.py @@ -2,7 +2,7 @@ from django.db import models from django.test import TestCase from rest_framework.serializers import _resolve_model -from rest_framework.tests.models import BasicModel +from tests.models import BasicModel class ResolveModelTests(TestCase): diff --git a/rest_framework/tests/test_settings.py b/tests/test_settings.py similarity index 83% rename from rest_framework/tests/test_settings.py rename to tests/test_settings.py index 857375c21..e29fc34aa 100644 --- a/rest_framework/tests/test_settings.py +++ b/tests/test_settings.py @@ -10,13 +10,13 @@ class TestSettings(TestCase): def test_non_import_errors(self): """Make sure other errors aren't suppressed.""" - settings = APISettings({'DEFAULT_MODEL_SERIALIZER_CLASS': 'rest_framework.tests.extras.bad_import.ModelSerializer'}, DEFAULTS, IMPORT_STRINGS) + settings = APISettings({'DEFAULT_MODEL_SERIALIZER_CLASS': 'tests.extras.bad_import.ModelSerializer'}, DEFAULTS, IMPORT_STRINGS) with self.assertRaises(ValueError): settings.DEFAULT_MODEL_SERIALIZER_CLASS def test_import_error_message_maintained(self): """Make sure real import errors are captured and raised sensibly.""" - settings = APISettings({'DEFAULT_MODEL_SERIALIZER_CLASS': 'rest_framework.tests.extras.not_here.ModelSerializer'}, DEFAULTS, IMPORT_STRINGS) + settings = APISettings({'DEFAULT_MODEL_SERIALIZER_CLASS': 'tests.extras.not_here.ModelSerializer'}, DEFAULTS, IMPORT_STRINGS) with self.assertRaises(ImportError) as cm: settings.DEFAULT_MODEL_SERIALIZER_CLASS self.assertTrue('ImportError' in str(cm.exception)) diff --git a/rest_framework/tests/test_status.py b/tests/test_status.py similarity index 100% rename from rest_framework/tests/test_status.py rename to tests/test_status.py diff --git a/rest_framework/tests/test_templatetags.py b/tests/test_templatetags.py similarity index 100% rename from rest_framework/tests/test_templatetags.py rename to tests/test_templatetags.py diff --git a/rest_framework/tests/test_testing.py b/tests/test_testing.py similarity index 99% rename from rest_framework/tests/test_testing.py rename to tests/test_testing.py index 71bd8b552..8c6086a29 100644 --- a/rest_framework/tests/test_testing.py +++ b/tests/test_testing.py @@ -35,7 +35,7 @@ urlpatterns = patterns('', class TestAPITestClient(TestCase): - urls = 'rest_framework.tests.test_testing' + urls = 'tests.test_testing' def setUp(self): self.client = APIClient() diff --git a/rest_framework/tests/test_throttling.py b/tests/test_throttling.py similarity index 100% rename from rest_framework/tests/test_throttling.py rename to tests/test_throttling.py diff --git a/rest_framework/tests/test_urlpatterns.py b/tests/test_urlpatterns.py similarity index 100% rename from rest_framework/tests/test_urlpatterns.py rename to tests/test_urlpatterns.py diff --git a/rest_framework/tests/test_validation.py b/tests/test_validation.py similarity index 100% rename from rest_framework/tests/test_validation.py rename to tests/test_validation.py diff --git a/rest_framework/tests/test_views.py b/tests/test_views.py similarity index 100% rename from rest_framework/tests/test_views.py rename to tests/test_views.py diff --git a/rest_framework/tests/test_write_only_fields.py b/tests/test_write_only_fields.py similarity index 100% rename from rest_framework/tests/test_write_only_fields.py rename to tests/test_write_only_fields.py diff --git a/tests/urls.py b/tests/urls.py new file mode 100644 index 000000000..62cad3395 --- /dev/null +++ b/tests/urls.py @@ -0,0 +1,6 @@ +""" +Blank URLConf just to keep the test suite happy +""" +from rest_framework.compat import patterns + +urlpatterns = patterns('') diff --git a/rest_framework/tests/records/__init__.py b/tests/users/__init__.py similarity index 100% rename from rest_framework/tests/records/__init__.py rename to tests/users/__init__.py diff --git a/rest_framework/tests/users/models.py b/tests/users/models.py similarity index 100% rename from rest_framework/tests/users/models.py rename to tests/users/models.py diff --git a/rest_framework/tests/users/serializers.py b/tests/users/serializers.py similarity index 71% rename from rest_framework/tests/users/serializers.py rename to tests/users/serializers.py index da4965540..4893ddb34 100644 --- a/rest_framework/tests/users/serializers.py +++ b/tests/users/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers -from rest_framework.tests.users.models import User +from tests.users.models import User class UserSerializer(serializers.ModelSerializer): diff --git a/rest_framework/tests/views.py b/tests/views.py similarity index 59% rename from rest_framework/tests/views.py rename to tests/views.py index 3917b74a9..55935e924 100644 --- a/rest_framework/tests/views.py +++ b/tests/views.py @@ -1,6 +1,6 @@ from rest_framework import generics -from rest_framework.tests.models import NullableForeignKeySource -from rest_framework.tests.serializers import NullableFKSourceSerializer +from .models import NullableForeignKeySource +from .serializers import NullableFKSourceSerializer class NullableFKSourceDetail(generics.RetrieveUpdateDestroyAPIView): diff --git a/tox.ini b/tox.ini index 77766d20b..2fe39f120 100644 --- a/tox.ini +++ b/tox.ini @@ -3,19 +3,21 @@ downloadcache = {toxworkdir}/cache/ envlist = py3.3-django1.6,py3.2-django1.6,py2.7-django1.6,py2.6-django1.6,py3.3-django1.5,py3.2-django1.5,py2.7-django1.5,py2.6-django1.5,py2.7-django1.4,py2.6-django1.4,py2.7-django1.3,py2.6-django1.3 [testenv] -commands = {envpython} rest_framework/runtests/runtests.py +commands = py.test -q [testenv:py3.3-django1.6] basepython = python3.3 deps = Django==1.6 django-filter==0.6a1 defusedxml==0.3 + pytest-django==2.6 [testenv:py3.2-django1.6] basepython = python3.2 deps = Django==1.6 django-filter==0.6a1 defusedxml==0.3 + pytest-django==2.6 [testenv:py2.7-django1.6] basepython = python2.7 @@ -26,6 +28,7 @@ deps = Django==1.6 oauth2==1.5.211 django-oauth2-provider==0.2.4 django-guardian==1.1.1 + pytest-django==2.6 [testenv:py2.6-django1.6] basepython = python2.6 @@ -36,18 +39,21 @@ deps = Django==1.6 oauth2==1.5.211 django-oauth2-provider==0.2.4 django-guardian==1.1.1 + pytest-django==2.6 [testenv:py3.3-django1.5] basepython = python3.3 deps = django==1.5.5 django-filter==0.6a1 defusedxml==0.3 + pytest-django==2.6 [testenv:py3.2-django1.5] basepython = python3.2 deps = django==1.5.5 django-filter==0.6a1 defusedxml==0.3 + pytest-django==2.6 [testenv:py2.7-django1.5] basepython = python2.7 @@ -58,6 +64,7 @@ deps = django==1.5.5 oauth2==1.5.211 django-oauth2-provider==0.2.3 django-guardian==1.1.1 + pytest-django==2.6 [testenv:py2.6-django1.5] basepython = python2.6 @@ -68,6 +75,7 @@ deps = django==1.5.5 oauth2==1.5.211 django-oauth2-provider==0.2.3 django-guardian==1.1.1 + pytest-django==2.6 [testenv:py2.7-django1.4] basepython = python2.7 @@ -78,6 +86,7 @@ deps = django==1.4.10 oauth2==1.5.211 django-oauth2-provider==0.2.3 django-guardian==1.1.1 + pytest-django==2.6 [testenv:py2.6-django1.4] basepython = python2.6 @@ -88,6 +97,7 @@ deps = django==1.4.10 oauth2==1.5.211 django-oauth2-provider==0.2.3 django-guardian==1.1.1 + pytest-django==2.6 [testenv:py2.7-django1.3] basepython = python2.7 @@ -98,6 +108,7 @@ deps = django==1.3.5 oauth2==1.5.211 django-oauth2-provider==0.2.3 django-guardian==1.1.1 + pytest-django==2.6 [testenv:py2.6-django1.3] basepython = python2.6 @@ -108,3 +119,4 @@ deps = django==1.3.5 oauth2==1.5.211 django-oauth2-provider==0.2.3 django-guardian==1.1.1 + pytest-django==2.6 From 04315c12af09d9b2ee1106ab31af5891833dd2f9 Mon Sep 17 00:00:00 2001 From: Emanuele Pucciarelli Date: Mon, 24 Mar 2014 19:25:28 +0100 Subject: [PATCH 045/225] Use help_text, verbose_name, editable attributes for related fields --- rest_framework/serializers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 88972e257..46beb6ac7 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -828,6 +828,15 @@ class ModelSerializer(Serializer): if model_field: kwargs['required'] = not(model_field.null or model_field.blank) + if not model_field.editable: + kwargs['read_only'] = True + + if model_field.verbose_name is not None: + kwargs['label'] = model_field.verbose_name + + if model_field.help_text is not None: + kwargs['help_text'] = model_field.help_text + return PrimaryKeyRelatedField(**kwargs) def get_field(self, model_field): From ab5082d15c04866401c6f1bc7d77d21e695f996d Mon Sep 17 00:00:00 2001 From: Emanuele Pucciarelli Date: Fri, 28 Mar 2014 19:42:46 +0100 Subject: [PATCH 046/225] Do not check model_field's attributes if it is None --- rest_framework/serializers.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 46beb6ac7..d7941df14 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -828,14 +828,14 @@ class ModelSerializer(Serializer): if model_field: kwargs['required'] = not(model_field.null or model_field.blank) - if not model_field.editable: - kwargs['read_only'] = True + if not model_field.editable: + kwargs['read_only'] = True - if model_field.verbose_name is not None: - kwargs['label'] = model_field.verbose_name + if model_field.verbose_name is not None: + kwargs['label'] = model_field.verbose_name - if model_field.help_text is not None: - kwargs['help_text'] = model_field.help_text + if model_field.help_text is not None: + kwargs['help_text'] = model_field.help_text return PrimaryKeyRelatedField(**kwargs) From d8bf8787923080a64842a12e3e476aff27bfa8fc Mon Sep 17 00:00:00 2001 From: Emanuele Pucciarelli Date: Sun, 30 Mar 2014 11:48:17 +0200 Subject: [PATCH 047/225] Metadata for related fields -- added test case. --- rest_framework/tests/models.py | 6 ++- rest_framework/tests/test_generics.py | 73 +++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py index 6c8f2342b..355e070e4 100644 --- a/rest_framework/tests/models.py +++ b/rest_framework/tests/models.py @@ -143,14 +143,16 @@ class ForeignKeyTarget(RESTFrameworkModel): class ForeignKeySource(RESTFrameworkModel): name = models.CharField(max_length=100) - target = models.ForeignKey(ForeignKeyTarget, related_name='sources') + target = models.ForeignKey(ForeignKeyTarget, related_name='sources', + verbose_name='Target object') # Nullable ForeignKey class NullableForeignKeySource(RESTFrameworkModel): name = models.CharField(max_length=100) target = models.ForeignKey(ForeignKeyTarget, null=True, blank=True, - related_name='nullable_sources') + related_name='nullable_sources', + verbose_name='Optional target object') # OneToOne diff --git a/rest_framework/tests/test_generics.py b/rest_framework/tests/test_generics.py index 996bd5b0e..0cadc5dee 100644 --- a/rest_framework/tests/test_generics.py +++ b/rest_framework/tests/test_generics.py @@ -5,6 +5,7 @@ from django.test import TestCase from rest_framework import generics, renderers, serializers, status from rest_framework.test import APIRequestFactory from rest_framework.tests.models import BasicModel, Comment, SlugBasedModel +from rest_framework.tests.models import ForeignKeySource, ForeignKeyTarget from rest_framework.compat import six factory = APIRequestFactory() @@ -28,6 +29,13 @@ class InstanceView(generics.RetrieveUpdateDestroyAPIView): return queryset.exclude(text='filtered out') +class FKInstanceView(generics.RetrieveUpdateDestroyAPIView): + """ + FK: example description for OPTIONS. + """ + model = ForeignKeySource + + class SlugSerializer(serializers.ModelSerializer): slug = serializers.Field() # read only @@ -407,6 +415,71 @@ class TestInstanceView(TestCase): self.assertFalse(self.objects.filter(id=999).exists()) +class TestFKInstanceView(TestCase): + def setUp(self): + """ + Create 3 BasicModel instances. + """ + items = ['foo', 'bar', 'baz'] + for item in items: + t = ForeignKeyTarget(name=item) + t.save() + ForeignKeySource(name='source_' + item, target=t).save() + + self.objects = ForeignKeySource.objects + self.data = [ + {'id': obj.id, 'name': obj.name} + for obj in self.objects.all() + ] + self.view = FKInstanceView.as_view() + + def test_options_root_view(self): + """ + OPTIONS requests to ListCreateAPIView should return metadata + """ + request = factory.options('/999') + with self.assertNumQueries(1): + response = self.view(request, pk=999).render() + expected = { + 'name': 'Fk Instance', + 'description': 'FK: example description for OPTIONS.', + 'renders': [ + 'application/json', + 'text/html' + ], + 'parses': [ + 'application/json', + 'application/x-www-form-urlencoded', + 'multipart/form-data' + ], + 'actions': { + 'PUT': { + 'id': { + 'type': 'integer', + 'required': False, + 'read_only': True, + 'label': u'ID' + }, + 'name': { + 'type': 'string', + 'required': True, + 'read_only': False, + 'label': 'name', + 'max_length': 100 + }, + 'target': { + 'type': 'field', + 'required': True, + 'read_only': False, + 'label': 'Target object' + } + } + } + } + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data, expected) + + class TestOverriddenGetObject(TestCase): """ Test cases for a RetrieveUpdateDestroyAPIView that does NOT use the From 8904f179d1bc925d52001497e92b9cd509e65bd5 Mon Sep 17 00:00:00 2001 From: Emanuele Pucciarelli Date: Sun, 30 Mar 2014 12:06:03 +0200 Subject: [PATCH 048/225] Stray unicode string marker removed --- rest_framework/tests/test_generics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/tests/test_generics.py b/rest_framework/tests/test_generics.py index 0cadc5dee..b4ae20219 100644 --- a/rest_framework/tests/test_generics.py +++ b/rest_framework/tests/test_generics.py @@ -458,7 +458,7 @@ class TestFKInstanceView(TestCase): 'type': 'integer', 'required': False, 'read_only': True, - 'label': u'ID' + 'label': 'ID' }, 'name': { 'type': 'string', From af8a362d6b513b71de45109b441f79ed7d1b103c Mon Sep 17 00:00:00 2001 From: Nicolas Delaby Date: Mon, 7 Apr 2014 14:59:27 +0200 Subject: [PATCH 049/225] reset stored credentials when call client.logout() --- rest_framework/test.py | 4 ++++ rest_framework/tests/test_testing.py | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/rest_framework/test.py b/rest_framework/test.py index df5a5b3b3..79982cb05 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -154,6 +154,10 @@ class APIClient(APIRequestFactory, DjangoClient): kwargs.update(self._credentials) return super(APIClient, self).request(**kwargs) + def logout(self): + self._credentials = {} + return super(APIClient, self).logout() + class APITransactionTestCase(testcases.TransactionTestCase): client_class = APIClient diff --git a/rest_framework/tests/test_testing.py b/rest_framework/tests/test_testing.py index a55d4b225..b16d19628 100644 --- a/rest_framework/tests/test_testing.py +++ b/rest_framework/tests/test_testing.py @@ -99,6 +99,17 @@ class TestAPITestClient(TestCase): self.assertEqual(response.status_code, 403) self.assertEqual(response.data, expected) + def test_can_logout(self): + """ + `logout()` reset stored credentials + """ + self.client.credentials(HTTP_AUTHORIZATION='example') + response = self.client.get('/view/') + self.assertEqual(response.data['auth'], 'example') + self.client.logout() + response = self.client.get('/view/') + self.assertEqual(response.data['auth'], b'') + class TestAPIRequestFactory(TestCase): def test_csrf_exempt_by_default(self): From e45e52a255c0dfbecfc5048697534ffbe0e2648e Mon Sep 17 00:00:00 2001 From: Dmitry Mukhin Date: Mon, 7 Apr 2014 20:39:45 +0400 Subject: [PATCH 050/225] replace page with page_size to avoide confusion --- docs/topics/release-notes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 0010f6878..2bc8b2d6a 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -112,11 +112,11 @@ You can determine your currently installed version using `pip freeze`: * Bugfix: `client.force_authenticate(None)` should also clear session info if it exists. * Bugfix: Client sending empty string instead of file now clears `FileField`. * Bugfix: Empty values on ChoiceFields with `required=False` now consistently return `None`. -* Bugfix: Clients setting `page=0` now simply returns the default page size, instead of disabling pagination. [*] +* Bugfix: Clients setting `page_size=0` now simply returns the default page size, instead of disabling pagination. [*] --- -[*] Note that the change in `page=0` behaviour fixes what is considered to be a bug in how clients can effect the pagination size. However if you were relying on this behavior you will need to add the following mixin to your list views in order to preserve the existing behavior. +[*] Note that the change in `page_size=0` behaviour fixes what is considered to be a bug in how clients can effect the pagination size. However if you were relying on this behavior you will need to add the following mixin to your list views in order to preserve the existing behavior. class DisablePaginationMixin(object): def get_paginate_by(self, queryset=None): From 2a1571b3bf36ff153af68401f7aefa0620f80807 Mon Sep 17 00:00:00 2001 From: Mauro de Carvalho Date: Mon, 7 Apr 2014 18:27:59 -0300 Subject: [PATCH 051/225] Fixed comment. --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 68b956822..946a59545 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -164,7 +164,7 @@ class Field(object): Called to set up a field prior to field_to_native or field_from_native. parent - The parent serializer. - model_field - The model field this field corresponds to, if one exists. + field_name - The name of the field being initialized. """ self.parent = parent self.root = parent.root or parent From 3234a5dd6b0c090dd25a716e7b1c2567d8fee89b Mon Sep 17 00:00:00 2001 From: Craig Date: Tue, 8 Apr 2014 22:56:07 -0400 Subject: [PATCH 052/225] Fix python syntax in filtering docs --- docs/api-guide/filtering.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index d6c4b1c1b..6a8a267b2 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -24,7 +24,7 @@ For example: from myapp.serializers import PurchaseSerializer from rest_framework import generics - class PurchaseList(generics.ListAPIView) + class PurchaseList(generics.ListAPIView): serializer_class = PurchaseSerializer def get_queryset(self): @@ -46,7 +46,7 @@ For example if your URL config contained an entry like this: You could then write a view that returned a purchase queryset filtered by the username portion of the URL: - class PurchaseList(generics.ListAPIView) + class PurchaseList(generics.ListAPIView): serializer_class = PurchaseSerializer def get_queryset(self): @@ -63,7 +63,7 @@ A final example of filtering the initial queryset would be to determine the init We can override `.get_queryset()` to deal with URLs such as `http://example.com/api/purchases?username=denvercoder9`, and filter the queryset only if the `username` parameter is included in the URL: - class PurchaseList(generics.ListAPIView) + class PurchaseList(generics.ListAPIView): serializer_class = PurchaseSerializer def get_queryset(self): From c1ac65edce1bcfff4c87df3bb9c4df14fe8e9d6c Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 9 Apr 2014 15:51:00 +0200 Subject: [PATCH 053/225] Adds test that blank option is added when required=False on RelatedFields --- rest_framework/relations.py | 2 ++ rest_framework/tests/test_relations.py | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 308545ce9..3463954dc 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -59,6 +59,8 @@ class RelatedField(WritableField): super(RelatedField, self).__init__(*args, **kwargs) if not self.required: + # Accessed in ModelChoiceIterator django/forms/models.py:1034 + # If set adds empty choice. self.empty_label = BLANK_CHOICE_DASH[0][1] self.queryset = queryset diff --git a/rest_framework/tests/test_relations.py b/rest_framework/tests/test_relations.py index f52e0e1e5..c421096ab 100644 --- a/rest_framework/tests/test_relations.py +++ b/rest_framework/tests/test_relations.py @@ -118,3 +118,25 @@ class RelatedFieldSourceTests(TestCase): (serializers.ModelSerializer,), attrs) with self.assertRaises(AttributeError): TestSerializer(data={'name': 'foo'}) + + +class RelatedFieldChoicesTests(TestCase): + """ + Tests for #1408 "Web browseable API doesn't have blank option on drop down list box" + https://github.com/tomchristie/django-rest-framework/issues/1408 + """ + def test_blank_option_is_added_to_choice_if_required_equals_false(self): + """ + + """ + post = BlogPost(title="Checking blank option is added") + post.save() + + queryset = BlogPost.objects.all() + field = serializers.RelatedField(required=False, queryset=queryset) + + choice_count = BlogPost.objects.count() + widget_count = len(field.widget.choices) + + self.assertEqual(widget_count, choice_count + 1, 'BLANK_CHOICE_DASH option should have been added') + From a73498d7974b15a25902fbdd1024742b95a166d4 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 9 Apr 2014 19:54:13 +0200 Subject: [PATCH 054/225] Skip new test for Django < 1.6 --- rest_framework/tests/test_relations.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rest_framework/tests/test_relations.py b/rest_framework/tests/test_relations.py index c421096ab..37ac826b2 100644 --- a/rest_framework/tests/test_relations.py +++ b/rest_framework/tests/test_relations.py @@ -2,8 +2,10 @@ General tests for relational fields. """ from __future__ import unicode_literals +from django import get_version from django.db import models from django.test import TestCase +from django.utils import unittest from rest_framework import serializers from rest_framework.tests.models import BlogPost @@ -119,7 +121,7 @@ class RelatedFieldSourceTests(TestCase): with self.assertRaises(AttributeError): TestSerializer(data={'name': 'foo'}) - +@unittest.skipIf(get_version() < '1.6.0', 'Upstream behaviour changed in v1.6') class RelatedFieldChoicesTests(TestCase): """ Tests for #1408 "Web browseable API doesn't have blank option on drop down list box" From a23059b6f73aaff9709f611826bac892e56663dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Wed, 9 Apr 2014 23:35:41 +0200 Subject: [PATCH 055/225] Add more TRAILING_PUNCTUATION to work with YAML. Fixes #1517 --- rest_framework/templatetags/rest_framework.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index beb8c5b0e..dff176d62 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -180,7 +180,7 @@ def add_class(value, css_class): # Bunch of stuff cloned from urlize -TRAILING_PUNCTUATION = ['.', ',', ':', ';', '.)', '"', "'"] +TRAILING_PUNCTUATION = ['.', ',', ':', ';', '.)', '"', "']", "'}", "'"] WRAPPING_PUNCTUATION = [('(', ')'), ('<', '>'), ('[', ']'), ('<', '>'), ('"', '"'), ("'", "'")] word_split_re = re.compile(r'(\s+)') From 7ae8409370635ccec7d3c160ea87281f21c9ae11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Thu, 10 Apr 2014 01:35:45 +0200 Subject: [PATCH 056/225] Allow unicode YAML dump with UnicodeYAMLRenderer Fixes #1519 --- rest_framework/renderers.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 7a7da5610..484961add 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -193,6 +193,7 @@ class YAMLRenderer(BaseRenderer): format = 'yaml' encoder = encoders.SafeDumper charset = 'utf-8' + ensure_ascii = True def render(self, data, accepted_media_type=None, renderer_context=None): """ @@ -203,7 +204,15 @@ class YAMLRenderer(BaseRenderer): if data is None: return '' - return yaml.dump(data, stream=None, encoding=self.charset, Dumper=self.encoder) + return yaml.dump(data, stream=None, encoding=self.charset, Dumper=self.encoder, allow_unicode=not self.ensure_ascii) + + +class UnicodeYAMLRenderer(YAMLRenderer): + """ + Renderer which serializes to YAML. + Does *not* apply character escaping for non-ascii characters. + """ + ensure_ascii = False class TemplateHTMLRenderer(BaseRenderer): From f68596a7326777f971d9551ff1bfc7176389ea22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Thu, 10 Apr 2014 01:58:06 +0200 Subject: [PATCH 057/225] Document new UnicodeYAMLRenderer --- docs/api-guide/renderers.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index 7798827bc..7a3429bfd 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -138,6 +138,26 @@ Renders the request data into `YAML`. Requires the `pyyaml` package to be installed. +Note that non-ascii characters will be rendered using `\uXXXX` character escape. For example: + + unicode black star: "\u2605" + +**.media_type**: `application/yaml` + +**.format**: `'.yaml'` + +**.charset**: `utf-8` + +## UnicodeYAMLRenderer + +Renders the request data into `YAML`. + +Requires the `pyyaml` package to be installed. + +Note that non-ascii characters will not be character escaped. For example: + + unicode black star: ★ + **.media_type**: `application/yaml` **.format**: `'.yaml'` From 613df5c6501f715c0775229f34fcba9f4291c05d Mon Sep 17 00:00:00 2001 From: Ian Leith Date: Fri, 11 Apr 2014 05:49:49 +0100 Subject: [PATCH 058/225] Fix dict_keys equality test for python 3. --- rest_framework/utils/mediatypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/utils/mediatypes.py b/rest_framework/utils/mediatypes.py index c09c29338..92f99efd2 100644 --- a/rest_framework/utils/mediatypes.py +++ b/rest_framework/utils/mediatypes.py @@ -74,7 +74,7 @@ class _MediaType(object): return 0 elif self.sub_type == '*': return 1 - elif not self.params or self.params.keys() == ['q']: + elif not self.params or list(self.params.keys()) == ['q']: return 2 return 3 From 0a0e4f22e72badd1d8700a2b253cb27450a5319f Mon Sep 17 00:00:00 2001 From: Ian Foote Date: Sat, 12 Apr 2014 17:51:02 +0100 Subject: [PATCH 059/225] Set GenericForeignKey fields on object before save * A model with a required GenericForeignKey can be saved if the field is set --- rest_framework/serializers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index cb7539e0b..1d6097edd 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -16,6 +16,7 @@ import datetime import inspect import types 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 @@ -943,6 +944,8 @@ class ModelSerializer(Serializer): # Forward m2m relations for field in meta.many_to_many + meta.virtual_fields: + if isinstance(field, GenericForeignKey): + continue if field.name in attrs: m2m_data[field.name] = attrs.pop(field.name) From 853c7a16c15c7291561bc4b3dfbcad88ea262a18 Mon Sep 17 00:00:00 2001 From: Ian Foote Date: Sun, 13 Apr 2014 17:26:15 +0100 Subject: [PATCH 060/225] Use setattr for adding fields to a new instance Add test for restoring a GenericForeignKey --- rest_framework/serializers.py | 18 ++++++++---------- rest_framework/tests/test_genericrelations.py | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 1d6097edd..ea9509bf9 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -955,17 +955,15 @@ class ModelSerializer(Serializer): if isinstance(self.fields.get(field_name, None), Serializer): nested_forward_relations[field_name] = attrs[field_name] - # Update an existing instance... - if instance is not None: - for key, val in attrs.items(): - try: - setattr(instance, key, val) - except ValueError: - self._errors[key] = self.error_messages['required'] + # Create an empty instance of the model + if instance is None: + instance = self.opts.model() - # ...or create a new instance - else: - instance = self.opts.model(**attrs) + for key, val in attrs.items(): + try: + setattr(instance, key, val) + except ValueError: + self._errors[key] = self.error_messages['required'] # Any relations that cannot be set until we've # saved the model get hidden away on these diff --git a/rest_framework/tests/test_genericrelations.py b/rest_framework/tests/test_genericrelations.py index fa09c9e6c..46a2d863f 100644 --- a/rest_framework/tests/test_genericrelations.py +++ b/rest_framework/tests/test_genericrelations.py @@ -131,3 +131,21 @@ class TestGenericRelations(TestCase): } ] self.assertEqual(serializer.data, expected) + + def test_restore_object_generic_fk(self): + """ + Ensure an object with a generic foreign key can be restored. + """ + + class TagSerializer(serializers.ModelSerializer): + class Meta: + model = Tag + exclude = ('content_type', 'object_id') + + serializer = TagSerializer() + + bookmark = Bookmark(url='http://example.com') + attrs = {'tagged_item': bookmark, 'tag': 'example'} + + tag = serializer.restore_object(attrs) + self.assertEqual(tag.tagged_item, bookmark) From 4b3eb6e0b0e6412693de126ac92482a276ca9a78 Mon Sep 17 00:00:00 2001 From: Vladislav Vlastovskiy Date: Mon, 14 Apr 2014 12:21:38 +0400 Subject: [PATCH 061/225] Fixed parse file name --- rest_framework/parsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index f1b3e38d4..703cefca8 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -288,7 +288,7 @@ class FileUploadParser(BaseParser): try: meta = parser_context['request'].META - disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION']) + disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8')) return disposition[1]['filename'] except (AttributeError, KeyError): pass From 063addabfeb716f54c5784917e92ab6abb635ff5 Mon Sep 17 00:00:00 2001 From: Vladislav Vlastovskiy Date: Mon, 14 Apr 2014 12:28:41 +0400 Subject: [PATCH 062/225] Removed encode from test Django does not produce such a decoding by default, this test was not honest. --- rest_framework/tests/test_parsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/tests/test_parsers.py b/rest_framework/tests/test_parsers.py index 7699e10c9..ffd6b360f 100644 --- a/rest_framework/tests/test_parsers.py +++ b/rest_framework/tests/test_parsers.py @@ -96,7 +96,7 @@ class TestFileUploadParser(TestCase): request = MockRequest() request.upload_handlers = (MemoryFileUploadHandler(),) request.META = { - 'HTTP_CONTENT_DISPOSITION': 'Content-Disposition: inline; filename=file.txt'.encode('utf-8'), + 'HTTP_CONTENT_DISPOSITION': 'Content-Disposition: inline; filename=file.txt', 'HTTP_CONTENT_LENGTH': 14, } self.parser_context = {'request': request, 'kwargs': {}} From d474934d365291c28d5741898257cbdd5d0aa9ec Mon Sep 17 00:00:00 2001 From: Vladislav Vlastovskiy Date: Mon, 14 Apr 2014 13:01:24 +0400 Subject: [PATCH 063/225] Fixed return type From bytes to str --- rest_framework/parsers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 703cefca8..d49b17a4a 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -10,6 +10,7 @@ from django.core.files.uploadhandler import StopFutureHandlers from django.http import QueryDict from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter +from django.utils.encoding import force_str from rest_framework.compat import etree, six, yaml from rest_framework.exceptions import ParseError from rest_framework import renderers @@ -289,6 +290,6 @@ class FileUploadParser(BaseParser): try: meta = parser_context['request'].META disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8')) - return disposition[1]['filename'] + return force_str(disposition[1]['filename']) except (AttributeError, KeyError): pass From d1f4dfca2061cb552158ac7ea6f2de609989797b Mon Sep 17 00:00:00 2001 From: Vladislav Vlastovskiy Date: Mon, 14 Apr 2014 13:04:18 +0400 Subject: [PATCH 064/225] Removed decode from test filename --- rest_framework/tests/test_parsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/tests/test_parsers.py b/rest_framework/tests/test_parsers.py index ffd6b360f..8af906772 100644 --- a/rest_framework/tests/test_parsers.py +++ b/rest_framework/tests/test_parsers.py @@ -112,4 +112,4 @@ class TestFileUploadParser(TestCase): def test_get_filename(self): parser = FileUploadParser() filename = parser.get_filename(self.stream, None, self.parser_context) - self.assertEqual(filename, 'file.txt'.encode('utf-8')) + self.assertEqual(filename, 'file.txt') From 3fe038357267f947eba467f2b7714a782fa93c33 Mon Sep 17 00:00:00 2001 From: Vladislav Vlastovskiy Date: Mon, 14 Apr 2014 13:21:24 +0400 Subject: [PATCH 065/225] Fixed convert bytes to str Use compact function for convert --- rest_framework/parsers.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index d49b17a4a..4990971b8 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -10,8 +10,7 @@ from django.core.files.uploadhandler import StopFutureHandlers from django.http import QueryDict from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter -from django.utils.encoding import force_str -from rest_framework.compat import etree, six, yaml +from rest_framework.compat import etree, six, yaml, force_text from rest_framework.exceptions import ParseError from rest_framework import renderers import json @@ -290,6 +289,6 @@ class FileUploadParser(BaseParser): try: meta = parser_context['request'].META disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8')) - return force_str(disposition[1]['filename']) + return force_text(disposition[1]['filename']) except (AttributeError, KeyError): pass From 617c9825913cfc0cdeaa4405df0b885db0a9ff60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Tue, 15 Apr 2014 14:12:09 +0200 Subject: [PATCH 066/225] Add test for UnicodeYAMLRenderer --- rest_framework/tests/test_renderers.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/rest_framework/tests/test_renderers.py b/rest_framework/tests/test_renderers.py index c7bf772ef..7cb7d0f93 100644 --- a/rest_framework/tests/test_renderers.py +++ b/rest_framework/tests/test_renderers.py @@ -12,7 +12,7 @@ from rest_framework.compat import yaml, etree, patterns, url, include, six, Stri from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \ - XMLRenderer, JSONPRenderer, BrowsableAPIRenderer, UnicodeJSONRenderer + XMLRenderer, JSONPRenderer, BrowsableAPIRenderer, UnicodeJSONRenderer, UnicodeYAMLRenderer from rest_framework.parsers import YAMLParser, XMLParser from rest_framework.settings import api_settings from rest_framework.test import APIRequestFactory @@ -467,6 +467,17 @@ if yaml: self.assertTrue(string in content, '%r not in %r' % (string, content)) + class UnicodeYAMLRendererTests(TestCase): + """ + Tests specific for the Unicode YAML Renderer + """ + def test_proper_encoding(self): + obj = {'countries': ['United Kingdom', 'France', 'España']} + renderer = UnicodeYAMLRenderer() + content = renderer.render(obj, 'application/yaml') + self.assertEqual(content.strip(), 'countries: [United Kingdom, France, España]'.encode('utf-8')) + + class XMLRendererTestCase(TestCase): """ Tests specific to the XML Renderer From ef1d65282771c806f68d717d57172597184db26c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Tue, 15 Apr 2014 14:02:11 +0200 Subject: [PATCH 067/225] Introduce tests for urlize_quoted_links() function --- rest_framework/tests/test_urlizer.py | 38 ++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 rest_framework/tests/test_urlizer.py diff --git a/rest_framework/tests/test_urlizer.py b/rest_framework/tests/test_urlizer.py new file mode 100644 index 000000000..3dc8e8fe5 --- /dev/null +++ b/rest_framework/tests/test_urlizer.py @@ -0,0 +1,38 @@ +from __future__ import unicode_literals +from django.test import TestCase +from rest_framework.templatetags.rest_framework import urlize_quoted_links +import sys + + +class URLizerTests(TestCase): + """ + Test if both JSON and YAML URLs are transformed into links well + """ + def _urlize_dict_check(self, data): + """ + For all items in dict test assert that the value is urlized key + """ + for original, urlized in data.items(): + assert urlize_quoted_links(original, nofollow=False) == urlized + + def test_json_with_url(self): + """ + Test if JSON URLs are transformed into links well + """ + data = {} + data['"url": "http://api/users/1/", '] = \ + '"url": "http://api/users/1/", ' + data['"foo_set": [\n "http://api/foos/1/"\n], '] = \ + '"foo_set": [\n "http://api/foos/1/"\n], ' + self._urlize_dict_check(data) + + def test_yaml_with_url(self): + """ + Test if YAML URLs are transformed into links well + """ + data = {} + data['''{users: 'http://api/users/'}'''] = \ + '''{users: 'http://api/users/'}''' + data['''foo_set: ['http://api/foos/1/']'''] = \ + '''foo_set: ['http://api/foos/1/']''' + self._urlize_dict_check(data) From 6c108c459d8cfeda46b8e045ef750c01dd0ffcaa Mon Sep 17 00:00:00 2001 From: Ian Foote Date: Wed, 16 Apr 2014 12:32:04 +0100 Subject: [PATCH 068/225] Allow customising ChoiceField blank display value --- rest_framework/fields.py | 8 ++++++-- rest_framework/tests/test_fields.py | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 946a59545..d9521cd46 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -509,12 +509,16 @@ class ChoiceField(WritableField): 'the available choices.'), } - def __init__(self, choices=(), *args, **kwargs): + def __init__(self, choices=(), blank_display_value=None, *args, **kwargs): self.empty = kwargs.pop('empty', '') super(ChoiceField, self).__init__(*args, **kwargs) self.choices = choices if not self.required: - self.choices = BLANK_CHOICE_DASH + self.choices + if blank_display_value is None: + blank_choice = BLANK_CHOICE_DASH + else: + blank_choice = [('', blank_display_value)] + self.choices = blank_choice + self.choices def _get_choices(self): return self._choices diff --git a/rest_framework/tests/test_fields.py b/rest_framework/tests/test_fields.py index e127feef9..63dff7182 100644 --- a/rest_framework/tests/test_fields.py +++ b/rest_framework/tests/test_fields.py @@ -706,6 +706,15 @@ class ChoiceFieldTests(TestCase): f = serializers.ChoiceField(required=False, choices=SAMPLE_CHOICES) self.assertEqual(f.choices, models.fields.BLANK_CHOICE_DASH + SAMPLE_CHOICES) + def test_blank_choice_display(self): + blank = 'No Preference' + f = serializers.ChoiceField( + required=False, + choices=SAMPLE_CHOICES, + blank_display_value=blank, + ) + self.assertEqual(f.choices, [('', blank)] + SAMPLE_CHOICES) + def test_invalid_choice_model(self): s = ChoiceFieldModelSerializer(data={'choice': 'wrong_value'}) self.assertFalse(s.is_valid()) From f22ed49c648c6dc3e2cd3c1dfbda77c010189e28 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Thu, 17 Apr 2014 11:09:02 +0200 Subject: [PATCH 069/225] Upgraded to pytest-django 2.6.1 --- .travis.yml | 2 +- conftest.py | 4 ---- tox.ini | 27 +++++++++++++++------------ 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index 13dc3e289..4f4d0c307 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ env: install: - pip install $DJANGO - pip install defusedxml==0.3 Pillow==2.3.0 - - pip install pytest-django==2.6 + - pip install pytest-django==2.6.1 - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install oauth2==1.5.211; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth-plus==2.2.4; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth2-provider==0.2.4; fi" diff --git a/conftest.py b/conftest.py index 7cfc77f2a..b1691a884 100644 --- a/conftest.py +++ b/conftest.py @@ -79,7 +79,3 @@ def pytest_configure(): settings.INSTALLED_APPS += ( 'guardian', ) - - # Force Django to load all models - from django.db.models import get_models - get_models() diff --git a/tox.ini b/tox.ini index 251a40b73..2f6f16125 100644 --- a/tox.ini +++ b/tox.ini @@ -11,6 +11,7 @@ deps = https://www.djangoproject.com/download/1.7b1/tarball/ django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 + pytest-django==2.6.1 [testenv:py3.2-django1.7] basepython = python3.2 @@ -18,6 +19,7 @@ deps = https://www.djangoproject.com/download/1.7b1/tarball/ django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 + pytest-django==2.6.1 [testenv:py2.7-django1.7] basepython = python2.7 @@ -29,6 +31,7 @@ deps = https://www.djangoproject.com/download/1.7b1/tarball/ django-oauth2-provider==0.2.4 django-guardian==1.1.1 Pillow==2.3.0 + pytest-django==2.6.1 [testenv:py3.3-django1.6] basepython = python3.3 @@ -36,7 +39,7 @@ deps = Django==1.6 django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 - pytest-django==2.6 + pytest-django==2.6.1 [testenv:py3.2-django1.6] basepython = python3.2 @@ -44,7 +47,7 @@ deps = Django==1.6 django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 - pytest-django==2.6 + pytest-django==2.6.1 [testenv:py2.7-django1.6] basepython = python2.7 @@ -56,7 +59,7 @@ deps = Django==1.6 django-oauth2-provider==0.2.4 django-guardian==1.1.1 Pillow==2.3.0 - pytest-django==2.6 + pytest-django==2.6.1 [testenv:py2.6-django1.6] basepython = python2.6 @@ -68,7 +71,7 @@ deps = Django==1.6 django-oauth2-provider==0.2.4 django-guardian==1.1.1 Pillow==2.3.0 - pytest-django==2.6 + pytest-django==2.6.1 [testenv:py3.3-django1.5] basepython = python3.3 @@ -76,7 +79,7 @@ deps = django==1.5.5 django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 - pytest-django==2.6 + pytest-django==2.6.1 [testenv:py3.2-django1.5] basepython = python3.2 @@ -84,7 +87,7 @@ deps = django==1.5.5 django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 - pytest-django==2.6 + pytest-django==2.6.1 [testenv:py2.7-django1.5] basepython = python2.7 @@ -96,7 +99,7 @@ deps = django==1.5.5 django-oauth2-provider==0.2.3 django-guardian==1.1.1 Pillow==2.3.0 - pytest-django==2.6 + pytest-django==2.6.1 [testenv:py2.6-django1.5] basepython = python2.6 @@ -108,7 +111,7 @@ deps = django==1.5.5 django-oauth2-provider==0.2.3 django-guardian==1.1.1 Pillow==2.3.0 - pytest-django==2.6 + pytest-django==2.6.1 [testenv:py2.7-django1.4] basepython = python2.7 @@ -120,7 +123,7 @@ deps = django==1.4.10 django-oauth2-provider==0.2.3 django-guardian==1.1.1 Pillow==2.3.0 - pytest-django==2.6 + pytest-django==2.6.1 [testenv:py2.6-django1.4] basepython = python2.6 @@ -132,7 +135,7 @@ deps = django==1.4.10 django-oauth2-provider==0.2.3 django-guardian==1.1.1 Pillow==2.3.0 - pytest-django==2.6 + pytest-django==2.6.1 [testenv:py2.7-django1.3] basepython = python2.7 @@ -144,7 +147,7 @@ deps = django==1.3.5 django-oauth2-provider==0.2.3 django-guardian==1.1.1 Pillow==2.3.0 - pytest-django==2.6 + pytest-django==2.6.1 [testenv:py2.6-django1.3] basepython = python2.6 @@ -156,4 +159,4 @@ deps = django==1.3.5 django-oauth2-provider==0.2.3 django-guardian==1.1.1 Pillow==2.3.0 - pytest-django==2.6 + pytest-django==2.6.1 From c5f68fba0638a15fa3c802f1bafc664e890611dc Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Thu, 17 Apr 2014 14:30:33 +0200 Subject: [PATCH 070/225] Fixed the issue with django-filters / django 1.7 / pytest --- conftest.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/conftest.py b/conftest.py index b1691a884..7a49845f9 100644 --- a/conftest.py +++ b/conftest.py @@ -79,3 +79,9 @@ def pytest_configure(): settings.INSTALLED_APPS += ( 'guardian', ) + + try: + import django + django.setup() + except AttributeError: + pass From a6e525cf3a22a01a4f9924e975ea7288d80ac5ef Mon Sep 17 00:00:00 2001 From: Sergey Sinitsyn Date: Thu, 24 Apr 2014 15:58:53 +0600 Subject: [PATCH 071/225] Add help_text and verbose_name attribute mapping for related field --- rest_framework/serializers.py | 8 ++++++++ rest_framework/tests/models.py | 3 ++- rest_framework/tests/test_serializer.py | 26 ++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index ea9509bf9..9cb548a51 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -828,6 +828,10 @@ class ModelSerializer(Serializer): if model_field: kwargs['required'] = not(model_field.null or model_field.blank) + if model_field.help_text is not None: + kwargs['help_text'] = model_field.help_text + if model_field.verbose_name is not None: + kwargs['label'] = model_field.verbose_name return PrimaryKeyRelatedField(**kwargs) @@ -1088,6 +1092,10 @@ class HyperlinkedModelSerializer(ModelSerializer): if model_field: kwargs['required'] = not(model_field.null or model_field.blank) + if model_field.help_text is not None: + kwargs['help_text'] = model_field.help_text + if model_field.verbose_name is not None: + kwargs['label'] = model_field.verbose_name if self.opts.lookup_field: kwargs['lookup_field'] = self.opts.lookup_field diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py index 6c8f2342b..0256697a1 100644 --- a/rest_framework/tests/models.py +++ b/rest_framework/tests/models.py @@ -143,7 +143,8 @@ class ForeignKeyTarget(RESTFrameworkModel): class ForeignKeySource(RESTFrameworkModel): name = models.CharField(max_length=100) - target = models.ForeignKey(ForeignKeyTarget, related_name='sources') + target = models.ForeignKey(ForeignKeyTarget, related_name='sources', + help_text='Target', verbose_name='Target') # Nullable ForeignKey diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index 3ee2b38a7..e688c8239 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -9,7 +9,8 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers, fields, relations from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel, BlankFieldModel, BlogPost, BlogPostComment, Book, CallableDefaultValueModel, DefaultValueModel, - ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, RESTFrameworkModel) + ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, RESTFrameworkModel, + ForeignKeySource, ManyToManySource) from rest_framework.tests.models import BasicModelSerializer import datetime import pickle @@ -176,6 +177,16 @@ class PositiveIntegerAsChoiceSerializer(serializers.ModelSerializer): fields = ['some_integer'] +class ForeignKeySourceSerializer(serializers.ModelSerializer): + class Meta: + model = ForeignKeySource + + +class HyperlinkedForeignKeySourceSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = ForeignKeySource + + class BasicTests(TestCase): def setUp(self): self.comment = Comment( @@ -1600,6 +1611,19 @@ class ManyFieldHelpTextTest(TestCase): self.assertEqual('Some help text.', rel_field.help_text) +class AttributeMappingOnAutogeneratedRelatedFields(TestCase): + + def test_primary_key_related_field(self): + serializer = ForeignKeySourceSerializer() + self.assertEqual(serializer.fields['target'].help_text, 'Target') + self.assertEqual(serializer.fields['target'].label, 'Target') + + def test_hyperlinked_related_field(self): + serializer = HyperlinkedForeignKeySourceSerializer() + self.assertEqual(serializer.fields['target'].help_text, 'Target') + self.assertEqual(serializer.fields['target'].label, 'Target') + + @unittest.skipUnless(PIL is not None, 'PIL is not installed') class AttributeMappingOnAutogeneratedFieldsTests(TestCase): From f4a82dd5dadf95908c96c402f7f68b8e74c7de7a Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Thu, 24 Apr 2014 14:33:36 +0200 Subject: [PATCH 072/225] Updated the release notes. --- docs/topics/release-notes.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 2bc8b2d6a..335497eec 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -40,6 +40,25 @@ You can determine your currently installed version using `pip freeze`: ## 2.3.x series +### 2.3.x + +**Date**: April 2014 + +* Fix nested serializers linked through a backward foreign key relation +* Fix bad links for the `BrowsableAPIRenderer` with `YAMLRenderer` +* Add `UnicodeYAMLRenderer` that extends `YAMLRenderer` with unicode +* Fix `parse_header` argument convertion +* Fix mediatype detection under Python3 +* Web browseable API now offers blank option on dropdown when the field is not required +* `APIException` representation improved for logging purposes +* Allow source="*" within nested serializers +* Better support for custom oauth2 provider backends +* Fix field validation if it's optional and has no value +* Add `SEARCH_PARAM` and `ORDERING_PARAM` +* Fix `APIRequestFactory` to support arguments within the url string for GET +* Allow three transport modes for access tokens when accessing a protected resource +* Fix `Request`'s `QueryDict` encoding + ### 2.3.13 **Date**: 6th March 2014 From 82094554e5d267bcb550d3f7be26552befd7a1fe Mon Sep 17 00:00:00 2001 From: Kamil Niski Date: Sun, 27 Apr 2014 02:54:47 +0200 Subject: [PATCH 073/225] Minor typo --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 946a59545..8cdc55515 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -289,7 +289,7 @@ class WritableField(Field): self.validators = self.default_validators + validators self.default = default if default is not None else self.default - # Widgets are ony used for HTML forms. + # Widgets are only used for HTML forms. widget = widget or self.widget if isinstance(widget, type): widget = widget() From 4a1ef6d4b15c504881662a2667564394cb333b6b Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Sun, 27 Apr 2014 11:52:33 +0200 Subject: [PATCH 074/225] Updated Django's versions. --- .travis.yml | 16 ++++++++-------- tox.ini | 26 +++++++++++++------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index 60b48cbaf..bd6d2539a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,10 +7,10 @@ python: - "3.3" env: - - DJANGO="https://www.djangoproject.com/download/1.7b1/tarball/" - - DJANGO="django==1.6.2" - - DJANGO="django==1.5.5" - - DJANGO="django==1.4.10" + - DJANGO="https://www.djangoproject.com/download/1.7b2/tarball/" + - DJANGO="django==1.6.3" + - DJANGO="django==1.5.6" + - DJANGO="django==1.4.11" - DJANGO="django==1.3.7" install: @@ -23,7 +23,7 @@ install: - "if [[ ${DJANGO::11} == 'django==1.3' ]]; then pip install django-filter==0.5.4; fi" - "if [[ ${DJANGO::11} != 'django==1.3' ]]; then pip install django-filter==0.7; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} == '3' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi" - - "if [[ ${DJANGO} == 'https://www.djangoproject.com/download/1.7b1/tarball/' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi" + - "if [[ ${DJANGO} == 'https://www.djangoproject.com/download/1.7b2/tarball/' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi" - export PYTHONPATH=. script: @@ -32,13 +32,13 @@ script: matrix: exclude: - python: "2.6" - env: DJANGO="https://www.djangoproject.com/download/1.7b1/tarball/" + env: DJANGO="https://www.djangoproject.com/download/1.7b2/tarball/" - python: "3.2" - env: DJANGO="django==1.4.10" + env: DJANGO="django==1.4.11" - python: "3.2" env: DJANGO="django==1.3.7" - python: "3.3" - env: DJANGO="django==1.4.10" + env: DJANGO="django==1.4.11" - python: "3.3" env: DJANGO="django==1.3.7" diff --git a/tox.ini b/tox.ini index 855ab0ceb..e21210058 100644 --- a/tox.ini +++ b/tox.ini @@ -7,21 +7,21 @@ commands = {envpython} rest_framework/runtests/runtests.py [testenv:py3.3-django1.7] basepython = python3.3 -deps = https://www.djangoproject.com/download/1.7b1/tarball/ +deps = https://www.djangoproject.com/download/1.7b2/tarball/ django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 [testenv:py3.2-django1.7] basepython = python3.2 -deps = https://www.djangoproject.com/download/1.7b1/tarball/ +deps = https://www.djangoproject.com/download/1.7b2/tarball/ django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 [testenv:py2.7-django1.7] basepython = python2.7 -deps = https://www.djangoproject.com/download/1.7b1/tarball/ +deps = https://www.djangoproject.com/download/1.7b2/tarball/ django-filter==0.7 defusedxml==0.3 django-oauth-plus==2.2.1 @@ -32,21 +32,21 @@ deps = https://www.djangoproject.com/download/1.7b1/tarball/ [testenv:py3.3-django1.6] basepython = python3.3 -deps = Django==1.6 +deps = Django==1.6.3 django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 [testenv:py3.2-django1.6] basepython = python3.2 -deps = Django==1.6 +deps = Django==1.6.3 django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 [testenv:py2.7-django1.6] basepython = python2.7 -deps = Django==1.6 +deps = Django==1.6.3 django-filter==0.7 defusedxml==0.3 django-oauth-plus==2.2.1 @@ -57,7 +57,7 @@ deps = Django==1.6 [testenv:py2.6-django1.6] basepython = python2.6 -deps = Django==1.6 +deps = Django==1.6.3 django-filter==0.7 defusedxml==0.3 django-oauth-plus==2.2.1 @@ -68,21 +68,21 @@ deps = Django==1.6 [testenv:py3.3-django1.5] basepython = python3.3 -deps = django==1.5.5 +deps = django==1.5.6 django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 [testenv:py3.2-django1.5] basepython = python3.2 -deps = django==1.5.5 +deps = django==1.5.6 django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 [testenv:py2.7-django1.5] basepython = python2.7 -deps = django==1.5.5 +deps = django==1.5.6 django-filter==0.7 defusedxml==0.3 django-oauth-plus==2.2.1 @@ -93,7 +93,7 @@ deps = django==1.5.5 [testenv:py2.6-django1.5] basepython = python2.6 -deps = django==1.5.5 +deps = django==1.5.6 django-filter==0.7 defusedxml==0.3 django-oauth-plus==2.2.1 @@ -104,7 +104,7 @@ deps = django==1.5.5 [testenv:py2.7-django1.4] basepython = python2.7 -deps = django==1.4.10 +deps = django==1.4.11 django-filter==0.7 defusedxml==0.3 django-oauth-plus==2.2.1 @@ -115,7 +115,7 @@ deps = django==1.4.10 [testenv:py2.6-django1.4] basepython = python2.6 -deps = django==1.4.10 +deps = django==1.4.11 django-filter==0.7 defusedxml==0.3 django-oauth-plus==2.2.1 From 1c777ffe8b67c342bc1b27fefe67d1094a2f6b07 Mon Sep 17 00:00:00 2001 From: Max Peterson Date: Mon, 28 Apr 2014 12:35:55 +0100 Subject: [PATCH 075/225] Ensure Token.generate_key returns a string. --- rest_framework/authtoken/models.py | 2 +- rest_framework/tests/test_authentication.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index 8eac2cc49..167fa5314 100644 --- a/rest_framework/authtoken/models.py +++ b/rest_framework/authtoken/models.py @@ -34,7 +34,7 @@ class Token(models.Model): return super(Token, self).save(*args, **kwargs) def generate_key(self): - return binascii.hexlify(os.urandom(20)) + return binascii.hexlify(os.urandom(20)).decode() def __unicode__(self): return self.key diff --git a/rest_framework/tests/test_authentication.py b/rest_framework/tests/test_authentication.py index c37d2a512..8773f580b 100644 --- a/rest_framework/tests/test_authentication.py +++ b/rest_framework/tests/test_authentication.py @@ -195,6 +195,12 @@ class TokenAuthTests(TestCase): token = Token.objects.create(user=self.user) self.assertTrue(bool(token.key)) + def test_generate_key_returns_string(self): + """Ensure generate_key returns a string""" + token = Token() + key = token.generate_key() + self.assertTrue(isinstance(key, str)) + def test_token_login_json(self): """Ensure token login view using JSON POST works.""" client = APIClient(enforce_csrf_checks=True) From 170fa10ae0f2b531a8011be33cc9417b9f71e698 Mon Sep 17 00:00:00 2001 From: Max Peterson Date: Mon, 28 Apr 2014 13:10:34 +0100 Subject: [PATCH 076/225] Python < 3 compatibility. --- rest_framework/tests/test_authentication.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rest_framework/tests/test_authentication.py b/rest_framework/tests/test_authentication.py index 8773f580b..34ce1b7ac 100644 --- a/rest_framework/tests/test_authentication.py +++ b/rest_framework/tests/test_authentication.py @@ -199,7 +199,13 @@ class TokenAuthTests(TestCase): """Ensure generate_key returns a string""" token = Token() key = token.generate_key() - self.assertTrue(isinstance(key, str)) + try: + # added in Python < 3 + base = unicode + except NameError: + # added in Python >= 3 + base = str + self.assertTrue(isinstance(key, base)) def test_token_login_json(self): """Ensure token login view using JSON POST works.""" From 73597a16a2a6a388a08af923a1da8aa71d2f2848 Mon Sep 17 00:00:00 2001 From: Max Peterson Date: Mon, 28 Apr 2014 13:13:51 +0100 Subject: [PATCH 077/225] Better Python < 3 compatibility. --- rest_framework/tests/test_authentication.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/rest_framework/tests/test_authentication.py b/rest_framework/tests/test_authentication.py index 34ce1b7ac..a1c43d9ce 100644 --- a/rest_framework/tests/test_authentication.py +++ b/rest_framework/tests/test_authentication.py @@ -19,7 +19,7 @@ from rest_framework.authentication import ( OAuth2Authentication ) from rest_framework.authtoken.models import Token -from rest_framework.compat import patterns, url, include +from rest_framework.compat import patterns, url, include, six from rest_framework.compat import oauth2_provider, oauth2_provider_scope from rest_framework.compat import oauth, oauth_provider from rest_framework.test import APIRequestFactory, APIClient @@ -199,13 +199,7 @@ class TokenAuthTests(TestCase): """Ensure generate_key returns a string""" token = Token() key = token.generate_key() - try: - # added in Python < 3 - base = unicode - except NameError: - # added in Python >= 3 - base = str - self.assertTrue(isinstance(key, base)) + self.assertTrue(isinstance(key, six.string_types)) def test_token_login_json(self): """Ensure token login view using JSON POST works.""" From 5e8f05a8de410125d6df7a8e27f61e94176a8897 Mon Sep 17 00:00:00 2001 From: dpetzel Date: Mon, 28 Apr 2014 13:51:50 -0400 Subject: [PATCH 078/225] very minor typo in code example --- docs/api-guide/permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 6a0f48f44..50f669a2d 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -56,7 +56,7 @@ You can also set the authentication policy on a per-view, or per-viewset basis, using the `APIView` class based views. from rest_framework.permissions import IsAuthenticated - from rest_framework.responses import Response + from rest_framework.response import Response from rest_framework.views import APIView class ExampleView(APIView): From d8cb85ef8fb0a0804d9b2c09d909ad99f69301c8 Mon Sep 17 00:00:00 2001 From: Laurent Bristiel Date: Mon, 28 Apr 2014 22:00:36 +0200 Subject: [PATCH 079/225] typo --- docs/api-guide/generic-views.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index fb927ea8b..7d06f246c 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -70,7 +70,7 @@ The following attributes control the basic view behavior. **Shortcuts**: -* `model` - This shortcut may be used instead of setting either (or both) of the `queryset`/`serializer_class` attributes, although using the explicit style is generally preferred. If used instead of `serializer_class`, then then `DEFAULT_MODEL_SERIALIZER_CLASS` setting will determine the base serializer class. Note that `model` is only ever used for generating a default queryset or serializer class - the `queryset` and `serializer_class` attributes are always preferred if provided. +* `model` - This shortcut may be used instead of setting either (or both) of the `queryset`/`serializer_class` attributes, although using the explicit style is generally preferred. If used instead of `serializer_class`, then `DEFAULT_MODEL_SERIALIZER_CLASS` setting will determine the base serializer class. Note that `model` is only ever used for generating a default queryset or serializer class - the `queryset` and `serializer_class` attributes are always preferred if provided. **Pagination**: From fc44cd8d6a358ac6b5fd5155f69d032c7656ff4b Mon Sep 17 00:00:00 2001 From: Emanuele Pucciarelli Date: Tue, 29 Apr 2014 21:45:57 +0200 Subject: [PATCH 080/225] Sync test result w/ new label --- rest_framework/tests/test_generics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/tests/test_generics.py b/rest_framework/tests/test_generics.py index b4ae20219..4a3b67f65 100644 --- a/rest_framework/tests/test_generics.py +++ b/rest_framework/tests/test_generics.py @@ -471,7 +471,7 @@ class TestFKInstanceView(TestCase): 'type': 'field', 'required': True, 'read_only': False, - 'label': 'Target object' + 'label': 'Target' } } } From 295a4ab62d9af9ad7f74792c6543a1cf35cee2f9 Mon Sep 17 00:00:00 2001 From: Emanuele Pucciarelli Date: Tue, 29 Apr 2014 22:16:11 +0200 Subject: [PATCH 081/225] Added help_text to expected response in test --- rest_framework/tests/test_generics.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rest_framework/tests/test_generics.py b/rest_framework/tests/test_generics.py index 4a3b67f65..57d327cc6 100644 --- a/rest_framework/tests/test_generics.py +++ b/rest_framework/tests/test_generics.py @@ -471,7 +471,8 @@ class TestFKInstanceView(TestCase): 'type': 'field', 'required': True, 'read_only': False, - 'label': 'Target' + 'label': 'Target', + 'help_text': 'Target' } } } From cd93cd195ef83a443e8fe7d745b2947d2636f4ad Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Wed, 30 Apr 2014 22:32:29 +0200 Subject: [PATCH 082/225] Use url functions from Django itself. --- rest_framework/tests/test_authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/tests/test_authentication.py b/rest_framework/tests/test_authentication.py index 092030578..af292bf17 100644 --- a/rest_framework/tests/test_authentication.py +++ b/rest_framework/tests/test_authentication.py @@ -20,7 +20,7 @@ from rest_framework.authentication import ( OAuth2Authentication ) from rest_framework.authtoken.models import Token -from rest_framework.compat import patterns, url, include, six +from rest_framework.compat import six from rest_framework.compat import oauth2_provider, oauth2_provider_scope from rest_framework.compat import oauth, oauth_provider from rest_framework.test import APIRequestFactory, APIClient From 7475fceacc5bc94fde6212937685ef69ae79c751 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Thu, 1 May 2014 00:54:20 +0200 Subject: [PATCH 083/225] Added missing field for the tests. --- rest_framework/tests/test_serializer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index f0bb112d6..31bd10827 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -30,6 +30,7 @@ if PIL is not None: image_field = models.ImageField(upload_to='test', max_length=1024, blank=True) slug_field = models.SlugField(max_length=1024, blank=True) url_field = models.URLField(max_length=1024, blank=True) + nullable_char_field = models.CharField(max_length=1024, blank=True, null=True) class DVOAFModel(RESTFrameworkModel): positive_integer_field = models.PositiveIntegerField(blank=True) From 38362bb43a19c287319ccfe0538ce5524f09c633 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Thu, 1 May 2014 01:24:48 +0200 Subject: [PATCH 084/225] Fixed new default for many --- rest_framework/tests/test_genericrelations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/tests/test_genericrelations.py b/rest_framework/tests/test_genericrelations.py index 46a2d863f..3a8f3c7f1 100644 --- a/rest_framework/tests/test_genericrelations.py +++ b/rest_framework/tests/test_genericrelations.py @@ -84,7 +84,7 @@ class TestGenericRelations(TestCase): exclude = ('content_type', 'object_id') class BookmarkSerializer(serializers.ModelSerializer): - tags = TagSerializer() + tags = TagSerializer(many=True) class Meta: model = Bookmark From c9e6f31166ebccc5c3bf2f27e12a6d6c87f5cf22 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Thu, 1 May 2014 01:27:51 +0200 Subject: [PATCH 085/225] Fixed new default for many --- rest_framework/tests/test_serializer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index 31bd10827..44ef8a95c 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -661,7 +661,7 @@ class ModelValidationTests(TestCase): second_serializer = AlbumsSerializer(data={'title': 'a'}) self.assertFalse(second_serializer.is_valid()) self.assertEqual(second_serializer.errors, {'title': ['Album with this Title already exists.'],}) - third_serializer = AlbumsSerializer(data=[{'title': 'b', 'ref': '1'}, {'title': 'c'}]) + third_serializer = AlbumsSerializer(data=[{'title': 'b', 'ref': '1'}, {'title': 'c'}], many=True) self.assertFalse(third_serializer.is_valid()) self.assertEqual(third_serializer.errors, [{'ref': ['Album with this Ref already exists.']}, {}]) From eb89ed02f247d903db1cdd488d69b316323d9f60 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Thu, 1 May 2014 08:36:18 +0200 Subject: [PATCH 086/225] Added missing staticfiles app --- conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conftest.py b/conftest.py index 7a49845f9..fa5184dd8 100644 --- a/conftest.py +++ b/conftest.py @@ -27,6 +27,7 @@ def pytest_configure(): 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', + 'django.contrib.staticfiles', 'rest_framework', 'rest_framework.authtoken', From e5441d845e34f1e1bb2b7464d31aa3df7b02d0fe Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Thu, 1 May 2014 08:41:37 +0200 Subject: [PATCH 087/225] Use urls functions from django instead of compat. --- tests/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/urls.py b/tests/urls.py index 62cad3395..41f527dfd 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,6 +1,6 @@ """ Blank URLConf just to keep the test suite happy """ -from rest_framework.compat import patterns +from django.conf.urls import patterns urlpatterns = patterns('') From 15c2c58b43a00ec29af99e0478b70eea57560fce Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Thu, 1 May 2014 08:43:49 +0200 Subject: [PATCH 088/225] Updated the release-notes. --- docs/topics/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index d6256b38f..fd5c7029b 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -40,6 +40,7 @@ You can determine your currently installed version using `pip freeze`: ### 2.4.0 +* Use py.test * `@detail_route` and `@list_route` decorators replace `@action` and `@link`. * `six` no longer bundled. For Django <= 1.4.1, install `six` package. * Support customizable view name and description functions, using the `VIEW_NAME_FUNCTION` and `VIEW_DESCRIPTION_FUNCTION` settings. From c15dab903d3759578449279cc034d766d362d41f Mon Sep 17 00:00:00 2001 From: Charlie Denton Date: Thu, 1 May 2014 10:18:16 +0100 Subject: [PATCH 089/225] Mark strings in AuthTokenSerializer as translatable --- rest_framework/authtoken/serializers.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py index 60a3740e7..995f2e646 100644 --- a/rest_framework/authtoken/serializers.py +++ b/rest_framework/authtoken/serializers.py @@ -1,4 +1,6 @@ from django.contrib.auth import authenticate +from django.utils.translation import ugettext_lazy as _ + from rest_framework import serializers @@ -15,10 +17,13 @@ class AuthTokenSerializer(serializers.Serializer): if user: if not user.is_active: - raise serializers.ValidationError('User account is disabled.') + msg = _('User account is disabled.') + raise serializers.ValidationError() attrs['user'] = user return attrs else: - raise serializers.ValidationError('Unable to login with provided credentials.') + msg = _('Unable to login with provided credentials.') + raise serializers.ValidationError(msg) else: - raise serializers.ValidationError('Must include "username" and "password"') + msg = _('Must include "username" and "password"') + raise serializers.ValidationError(msg) From ccf3c508bd6750073ea3bbaefff567b92880df73 Mon Sep 17 00:00:00 2001 From: Charlie Denton Date: Fri, 2 May 2014 21:58:49 +0100 Subject: [PATCH 090/225] Fix missing message in ValidationError --- rest_framework/authtoken/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py index 995f2e646..99e99ae3d 100644 --- a/rest_framework/authtoken/serializers.py +++ b/rest_framework/authtoken/serializers.py @@ -18,7 +18,7 @@ class AuthTokenSerializer(serializers.Serializer): if user: if not user.is_active: msg = _('User account is disabled.') - raise serializers.ValidationError() + raise serializers.ValidationError(msg) attrs['user'] = user return attrs else: From 4e33ff05d9aabee0a90bfb0ef8ce58a5d274b9a2 Mon Sep 17 00:00:00 2001 From: Lucian Mocanu Date: Sun, 4 May 2014 00:12:08 +0200 Subject: [PATCH 091/225] Automatically set the field name as value for the HTML `id` attribute on the rendered widget. --- rest_framework/fields.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 8cdc55515..e67338499 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -154,7 +154,12 @@ class Field(object): def widget_html(self): if not self.widget: return '' - return self.widget.render(self._name, self._value) + + attrs = {} + if 'id' not in self.widget.attrs: + attrs['id'] = self._name + + return self.widget.render(self._name, self._value, attrs=attrs) def label_tag(self): return '' % (self._name, self.label) From cdc7d19034170e5d775166763e6df1220e131d35 Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Mon, 5 May 2014 14:41:10 +0200 Subject: [PATCH 092/225] Added missing "to" word --- docs/tutorial/1-serialization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index 979c4a3e3..dbe693ed5 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -104,7 +104,7 @@ Don't forget to sync the database for the first time. ## Creating a Serializer class -The first thing we need to get started on our Web API is provide a way of serializing and deserializing the snippet instances into representations such as `json`. We can do this by declaring serializers that work very similar to Django's forms. Create a file in the `snippets` directory named `serializers.py` and add the following. +The first thing we need to get started on our Web API is to provide a way of serializing and deserializing the snippet instances into representations such as `json`. We can do this by declaring serializers that work very similar to Django's forms. Create a file in the `snippets` directory named `serializers.py` and add the following. from django.forms import widgets from rest_framework import serializers From 05fc974dc961de6d4e11b7baf51f7b3791c06711 Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Mon, 5 May 2014 14:44:54 +0200 Subject: [PATCH 093/225] Added missing "the" word --- docs/tutorial/1-serialization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index dbe693ed5..55b194576 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -143,7 +143,7 @@ The first thing we need to get started on our Web API is to provide a way of ser # Create new instance return Snippet(**attrs) -The first part of serializer class defines the fields that get serialized/deserialized. The `restore_object` method defines how fully fledged instances get created when deserializing data. +The first part of the serializer class defines the fields that get serialized/deserialized. The `restore_object` method defines how fully fledged instances get created when deserializing data. Notice that we can also use various attributes that would typically be used on form fields, such as `widget=widgets.Textarea`. These can be used to control how the serializer should render when displayed as an HTML form. This is particularly useful for controlling how the browsable API should be displayed, as we'll see later in the tutorial. From 9e3ba939e152c2eb96d3c9b4460a3d4ce76931cd Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Mon, 5 May 2014 20:27:44 +0200 Subject: [PATCH 094/225] Removed superfluous "./"s --- docs/tutorial/4-authentication-and-permissions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index 432371f34..491df1608 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -44,11 +44,11 @@ When that's all done we'll need to update our database tables. Normally we'd create a database migration in order to do that, but for the purposes of this tutorial, let's just delete the database and start again. rm tmp.db - python ./manage.py syncdb + python manage.py syncdb You might also want to create a few different users, to use for testing the API. The quickest way to do this will be with the `createsuperuser` command. - python ./manage.py createsuperuser + python manage.py createsuperuser ## Adding endpoints for our User models From 708c7b3a816c3c2df7847695044ef852dc89e72c Mon Sep 17 00:00:00 2001 From: Lucian Mocanu Date: Tue, 6 May 2014 14:17:51 +0200 Subject: [PATCH 095/225] Added test case to check if the proper attributes are set on html widgets. --- rest_framework/tests/test_fields.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rest_framework/tests/test_fields.py b/rest_framework/tests/test_fields.py index e127feef9..03f79cf4d 100644 --- a/rest_framework/tests/test_fields.py +++ b/rest_framework/tests/test_fields.py @@ -4,6 +4,7 @@ General serializer field tests. from __future__ import unicode_literals import datetime +import re from decimal import Decimal from uuid import uuid4 from django.core import validators @@ -103,6 +104,16 @@ class BasicFieldTests(TestCase): keys = list(field.to_native(ret).keys()) self.assertEqual(keys, ['c', 'b', 'a', 'z']) + def test_widget_html_attributes(self): + """ + Make sure widget_html() renders the correct attributes + """ + r = re.compile('(\S+)=["\']?((?:.(?!["\']?\s+(?:\S+)=|[>"\']))+.)["\']?') + form = TimeFieldModelSerializer().data + attributes = r.findall(form.fields['clock'].widget_html()) + self.assertIn(('name', 'clock'), attributes) + self.assertIn(('id', 'clock'), attributes) + class DateFieldTest(TestCase): """ From 9dc5e15e5afaf806378bb52ea2134f8dec2af386 Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Tue, 6 May 2014 13:00:41 +0200 Subject: [PATCH 096/225] Added missing "the" word --- docs/tutorial/6-viewsets-and-routers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index 870632f1b..0a7c33639 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -21,7 +21,7 @@ First of all let's refactor our `UserList` and `UserDetail` views into a single queryset = User.objects.all() serializer_class = UserSerializer -Here we've used `ReadOnlyModelViewSet` class to automatically provide the default 'read-only' operations. We're still setting the `queryset` and `serializer_class` attributes exactly as we did when we were using regular views, but we no longer need to provide the same information to two separate classes. +Here we've used the `ReadOnlyModelViewSet` class to automatically provide the default 'read-only' operations. We're still setting the `queryset` and `serializer_class` attributes exactly as we did when we were using regular views, but we no longer need to provide the same information to two separate classes. Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighlight` view classes. We can remove the three views, and again replace them with a single class. From beb7253a961870a37833b7df6d1dfd3e8c1db778 Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Tue, 6 May 2014 15:06:38 +0200 Subject: [PATCH 097/225] Removed unnecessary "that" --- docs/tutorial/6-viewsets-and-routers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index 0a7c33639..dad71601a 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -85,7 +85,7 @@ In the `urls.py` file we bind our `ViewSet` classes into a set of concrete views Notice how we're creating multiple views from each `ViewSet` class, by binding the http methods to the required action for each view. -Now that we've bound our resources into concrete views, that we can register the views with the URL conf as usual. +Now that we've bound our resources into concrete views, we can register the views with the URL conf as usual. urlpatterns = format_suffix_patterns(patterns('snippets.views', url(r'^$', 'api_root'), From e033a0b9a0d655666385cd9831c7f1279573b47f Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Tue, 6 May 2014 15:07:40 +0200 Subject: [PATCH 098/225] Replaced singular "is" by plural "are" --- docs/tutorial/6-viewsets-and-routers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index dad71601a..04b42f2e7 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -138,7 +138,7 @@ You can review the final [tutorial code][repo] on GitHub, or try out a live exam ## Onwards and upwards -We've reached the end of our tutorial. If you want to get more involved in the REST framework project, here's a few places you can start: +We've reached the end of our tutorial. If you want to get more involved in the REST framework project, here are a few places you can start: * Contribute on [GitHub][github] by reviewing and submitting issues, and making pull requests. * Join the [REST framework discussion group][group], and help build the community. From 98cc8210990e3307a89d745acbbc2bcf6c665645 Mon Sep 17 00:00:00 2001 From: Serhiy Voyt Date: Tue, 6 May 2014 20:34:30 +0300 Subject: [PATCH 099/225] Extended test with case of saveing model with blank not null field. --- rest_framework/tests/test_serializer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index e688c8239..d770c6375 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -1236,6 +1236,8 @@ class BlankFieldTests(TestCase): def test_create_model_null_field(self): serializer = self.model_serializer_class(data={'title': None}) self.assertEqual(serializer.is_valid(), True) + serializer.save() + self.assertTrue(serializer.object.pk is not None) def test_create_not_blank_field(self): """ From 1ce1f387b031c368e0ad315964b78f93d6be9a19 Mon Sep 17 00:00:00 2001 From: Serhiy Voyt Date: Tue, 6 May 2014 21:57:25 +0300 Subject: [PATCH 100/225] Charfied from_native method returns default instead of None. Updated tests. --- rest_framework/fields.py | 9 ++++++++- rest_framework/tests/models.py | 4 +++- rest_framework/tests/test_serializer.py | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 8cdc55515..7858d9510 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -469,8 +469,15 @@ class CharField(WritableField): self.validators.append(validators.MaxLengthValidator(max_length)) def from_native(self, value): - if isinstance(value, six.string_types) or value is None: + if isinstance(value, six.string_types): return value + + if value is None: + if self.default: + return self.default + else: + value + return smart_text(value) diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py index e171d3bd9..fba3f8f7c 100644 --- a/rest_framework/tests/models.py +++ b/rest_framework/tests/models.py @@ -105,6 +105,7 @@ class Album(RESTFrameworkModel): title = models.CharField(max_length=100, unique=True) ref = models.CharField(max_length=10, unique=True, null=True, blank=True) + class Photo(RESTFrameworkModel): description = models.TextField() album = models.ForeignKey(Album) @@ -112,7 +113,8 @@ class Photo(RESTFrameworkModel): # Model for issue #324 class BlankFieldModel(RESTFrameworkModel): - title = models.CharField(max_length=100, blank=True, null=False) + title = models.CharField(max_length=100, blank=True, null=False, + default="title") # Model for issue #380 diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index d770c6375..82e1a89ca 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -1238,6 +1238,7 @@ class BlankFieldTests(TestCase): self.assertEqual(serializer.is_valid(), True) serializer.save() self.assertTrue(serializer.object.pk is not None) + self.assertEqual(serializer.object.title, 'title') def test_create_not_blank_field(self): """ From 27be31bd8a58fc4f37b966c36d1cebe83b80ebea Mon Sep 17 00:00:00 2001 From: Serhiy Voyt Date: Wed, 7 May 2014 18:37:08 +0300 Subject: [PATCH 101/225] In case of None value returns empty string instead of NoneType. --- rest_framework/fields.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 7858d9510..aed38d5e2 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -473,10 +473,7 @@ class CharField(WritableField): return value if value is None: - if self.default: - return self.default - else: - value + return '' return smart_text(value) From 4e6a21344fd1fda28d1d63c5aa697fac5e9f8029 Mon Sep 17 00:00:00 2001 From: Serhiy Voyt Date: Wed, 7 May 2014 18:42:02 +0300 Subject: [PATCH 102/225] Fixed test. --- rest_framework/tests/test_serializer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index 82e1a89ca..91248ce7c 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -1237,8 +1237,8 @@ class BlankFieldTests(TestCase): serializer = self.model_serializer_class(data={'title': None}) self.assertEqual(serializer.is_valid(), True) serializer.save() - self.assertTrue(serializer.object.pk is not None) - self.assertEqual(serializer.object.title, 'title') + self.assertIsNot(serializer.object.pk, None) + self.assertEqual(serializer.object.title, '') def test_create_not_blank_field(self): """ From 11115fde9cc8f70dfd85ce937893d67fd061f3c1 Mon Sep 17 00:00:00 2001 From: Elliott Date: Wed, 7 May 2014 11:37:20 -0700 Subject: [PATCH 103/225] Add colon to time zone offset in readable_datetime_formats --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 68b956822..9f53a0000 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -62,7 +62,7 @@ def get_component(obj, attr_name): def readable_datetime_formats(formats): format = ', '.join(formats).replace(ISO_8601, - 'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HHMM|-HHMM|Z]') + 'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]') return humanize_strptime(format) From 0ff474d7c4882a31f8fb133caa82d0368b0406c2 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 8 May 2014 11:20:03 +0200 Subject: [PATCH 104/225] Updated failing test from #1575 --- rest_framework/tests/test_fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/tests/test_fields.py b/rest_framework/tests/test_fields.py index e127feef9..894b5b3cf 100644 --- a/rest_framework/tests/test_fields.py +++ b/rest_framework/tests/test_fields.py @@ -312,7 +312,7 @@ class DateTimeFieldTest(TestCase): f.from_native('04:61:59') except validators.ValidationError as e: self.assertEqual(e.messages, ["Datetime has wrong format. Use one of these formats instead: " - "YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HHMM|-HHMM|Z]"]) + "YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]"]) else: self.fail("ValidationError was not properly raised") @@ -326,7 +326,7 @@ class DateTimeFieldTest(TestCase): f.from_native('04 -- 31') except validators.ValidationError as e: self.assertEqual(e.messages, ["Datetime has wrong format. Use one of these formats instead: " - "YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HHMM|-HHMM|Z]"]) + "YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]"]) else: self.fail("ValidationError was not properly raised") From 8ecb778cd23d5d561f2e9f4a3561bb1647257a89 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Sun, 11 May 2014 20:29:01 -0700 Subject: [PATCH 105/225] Enable testing on Python 3.4 --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bd6d2539a..0c9b44553 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: - "2.7" - "3.2" - "3.3" + - "3.4" env: - DJANGO="https://www.djangoproject.com/download/1.7b2/tarball/" @@ -41,4 +42,7 @@ matrix: env: DJANGO="django==1.4.11" - python: "3.3" env: DJANGO="django==1.3.7" - + - python: "3.4" + env: DJANGO="django==1.4.11" + - python: "3.4" + env: DJANGO="django==1.3.7" From 768f537dcbb5d4f7429a74556559047bfd6f3078 Mon Sep 17 00:00:00 2001 From: Giorgos Logiotatidis Date: Thu, 15 May 2014 15:34:31 +0300 Subject: [PATCH 106/225] Typo fix. --- docs/api-guide/serializers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 7ee060af4..0044f0701 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -464,7 +464,7 @@ For more specific requirements such as specifying a different lookup for each fi model = Account fields = ('url', 'account_name', 'users', 'created') -## Overiding the URL field behavior +## Overriding the URL field behavior The name of the URL field defaults to 'url'. You can override this globally, by using the `URL_FIELD_NAME` setting. @@ -478,7 +478,7 @@ You can also override this on a per-serializer basis by using the `url_field_nam **Note**: The generic view implementations normally generate a `Location` header in response to successful `POST` requests. Serializers using `url_field_name` option will not have this header automatically included by the view. If you need to do so you will ned to also override the view's `get_success_headers()` method. -You can also overide the URL field's view name and lookup field without overriding the field explicitly, by using the `view_name` and `lookup_field` options, like so: +You can also override the URL field's view name and lookup field without overriding the field explicitly, by using the `view_name` and `lookup_field` options, like so: class AccountSerializer(serializers.HyperlinkedModelSerializer): class Meta: From e5556079fc2559916d62b766dc9776b03dc4256b Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Fri, 16 May 2014 00:50:16 +0200 Subject: [PATCH 107/225] Updated tox with Python 2.4 --- tox.ini | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index e21210058..35a108e5e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,22 @@ [tox] downloadcache = {toxworkdir}/cache/ -envlist = py3.3-django1.7,py3.2-django1.7,py2.7-django1.7,py3.3-django1.6,py3.2-django1.6,py2.7-django1.6,py2.6-django1.6,py3.3-django1.5,py3.2-django1.5,py2.7-django1.5,py2.6-django1.5,py2.7-django1.4,py2.6-django1.4,py2.7-django1.3,py2.6-django1.3 +envlist = + py3.4-django1.7,py3.3-django1.7,py3.2-django1.7,py2.7-django1.7, + py3.4-django1.6,py3.3-django1.6,py3.2-django1.6,py2.7-django1.6,py2.6-django1.6, + py3.4-django1.5,py3.3-django1.5,py3.2-django1.5,py2.7-django1.5,py2.6-django1.5, + py2.7-django1.4,py2.6-django1.4, + py2.7-django1.3,py2.6-django1.3 [testenv] commands = {envpython} rest_framework/runtests/runtests.py +[testenv:py3.4-django1.7] +basepython = python3.4 +deps = https://www.djangoproject.com/download/1.7b2/tarball/ + django-filter==0.7 + defusedxml==0.3 + Pillow==2.3.0 + [testenv:py3.3-django1.7] basepython = python3.3 deps = https://www.djangoproject.com/download/1.7b2/tarball/ @@ -30,6 +42,13 @@ deps = https://www.djangoproject.com/download/1.7b2/tarball/ django-guardian==1.1.1 Pillow==2.3.0 +[testenv:py3.4-django1.6] +basepython = python3.3 +deps = Django==1.6.3 + django-filter==0.7 + defusedxml==0.3 + Pillow==2.3.0 + [testenv:py3.3-django1.6] basepython = python3.3 deps = Django==1.6.3 @@ -66,6 +85,13 @@ deps = Django==1.6.3 django-guardian==1.1.1 Pillow==2.3.0 +[testenv:py3.4-django1.5] +basepython = python3.3 +deps = django==1.5.6 + django-filter==0.7 + defusedxml==0.3 + Pillow==2.3.0 + [testenv:py3.3-django1.5] basepython = python3.3 deps = django==1.5.6 From b370fb40b6bc0fd3f597fb8c2db59f0ca57a7ccd Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Fri, 16 May 2014 01:06:34 +0200 Subject: [PATCH 108/225] Typo in the Python version. --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 35a108e5e..279f79cc4 100644 --- a/tox.ini +++ b/tox.ini @@ -43,7 +43,7 @@ deps = https://www.djangoproject.com/download/1.7b2/tarball/ Pillow==2.3.0 [testenv:py3.4-django1.6] -basepython = python3.3 +basepython = python3.4 deps = Django==1.6.3 django-filter==0.7 defusedxml==0.3 @@ -86,7 +86,7 @@ deps = Django==1.6.3 Pillow==2.3.0 [testenv:py3.4-django1.5] -basepython = python3.3 +basepython = python3.4 deps = django==1.5.6 django-filter==0.7 defusedxml==0.3 From a704d5a206238c65765c1f02eb053e461675dda2 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Fri, 16 May 2014 01:20:40 +0200 Subject: [PATCH 109/225] Fixed tests for python 3.4 --- rest_framework/tests/test_views.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/rest_framework/tests/test_views.py b/rest_framework/tests/test_views.py index 65c7e50ea..77b113ee5 100644 --- a/rest_framework/tests/test_views.py +++ b/rest_framework/tests/test_views.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import sys import copy from django.test import TestCase from rest_framework import status @@ -11,6 +12,11 @@ from rest_framework.views import APIView factory = APIRequestFactory() +if sys.version_info[:2] >= (3, 4): + JSON_ERROR = 'JSON parse error - Expecting value:' +else: + JSON_ERROR = 'JSON parse error - No JSON object could be decoded' + class BasicView(APIView): def get(self, request, *args, **kwargs): @@ -48,7 +54,7 @@ def sanitise_json_error(error_dict): of json. """ ret = copy.copy(error_dict) - chop = len('JSON parse error - No JSON object could be decoded') + chop = len(JSON_ERROR) ret['detail'] = ret['detail'][:chop] return ret @@ -61,7 +67,7 @@ class ClassBasedViewIntegrationTests(TestCase): request = factory.post('/', 'f00bar', content_type='application/json') response = self.view(request) expected = { - 'detail': 'JSON parse error - No JSON object could be decoded' + 'detail': JSON_ERROR } self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(sanitise_json_error(response.data), expected) @@ -76,7 +82,7 @@ class ClassBasedViewIntegrationTests(TestCase): request = factory.post('/', form_data) response = self.view(request) expected = { - 'detail': 'JSON parse error - No JSON object could be decoded' + 'detail': JSON_ERROR } self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(sanitise_json_error(response.data), expected) @@ -90,7 +96,7 @@ class FunctionBasedViewIntegrationTests(TestCase): request = factory.post('/', 'f00bar', content_type='application/json') response = self.view(request) expected = { - 'detail': 'JSON parse error - No JSON object could be decoded' + 'detail': JSON_ERROR } self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(sanitise_json_error(response.data), expected) @@ -105,7 +111,7 @@ class FunctionBasedViewIntegrationTests(TestCase): request = factory.post('/', form_data) response = self.view(request) expected = { - 'detail': 'JSON parse error - No JSON object could be decoded' + 'detail': JSON_ERROR } self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(sanitise_json_error(response.data), expected) From 5c12b0768166376783d62632e562f0c1301ee847 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Fri, 16 May 2014 19:40:02 +0200 Subject: [PATCH 110/225] Added missing import. --- rest_framework/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 2a0d5263e..6dd09f68b 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -21,6 +21,7 @@ from django.core.paginator import Page from django.db import models from django.forms import widgets from django.utils.datastructures import SortedDict +from django.core.exceptions import ObjectDoesNotExist from rest_framework.compat import get_concrete_model, six from rest_framework.settings import api_settings From a2e1024f8b0447a712d1f486172d38cfe56535fe Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Sun, 18 May 2014 09:27:23 +0200 Subject: [PATCH 111/225] Updated Django versions. --- .travis.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0c9b44553..638d14998 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,10 +8,10 @@ python: - "3.4" env: - - DJANGO="https://www.djangoproject.com/download/1.7b2/tarball/" - - DJANGO="django==1.6.3" - - DJANGO="django==1.5.6" - - DJANGO="django==1.4.11" + - DJANGO="https://www.djangoproject.com/download/1.7b4/tarball/" + - DJANGO="django==1.6.5" + - DJANGO="django==1.5.8" + - DJANGO="django==1.4.13" - DJANGO="django==1.3.7" install: @@ -24,7 +24,7 @@ install: - "if [[ ${DJANGO::11} == 'django==1.3' ]]; then pip install django-filter==0.5.4; fi" - "if [[ ${DJANGO::11} != 'django==1.3' ]]; then pip install django-filter==0.7; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} == '3' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi" - - "if [[ ${DJANGO} == 'https://www.djangoproject.com/download/1.7b2/tarball/' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi" + - "if [[ ${DJANGO} == 'https://www.djangoproject.com/download/1.7b4/tarball/' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi" - export PYTHONPATH=. script: @@ -33,16 +33,16 @@ script: matrix: exclude: - python: "2.6" - env: DJANGO="https://www.djangoproject.com/download/1.7b2/tarball/" + env: DJANGO="https://www.djangoproject.com/download/1.7b4/tarball/" - python: "3.2" - env: DJANGO="django==1.4.11" + env: DJANGO="django==1.4.13" - python: "3.2" env: DJANGO="django==1.3.7" - python: "3.3" - env: DJANGO="django==1.4.11" + env: DJANGO="django==1.4.13" - python: "3.3" env: DJANGO="django==1.3.7" - python: "3.4" - env: DJANGO="django==1.4.11" + env: DJANGO="django==1.4.13" - python: "3.4" env: DJANGO="django==1.3.7" From af1ee3e63175d2b1fd30ab18091bed1019ac5de6 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Sun, 18 May 2014 09:38:46 +0200 Subject: [PATCH 112/225] Fixed a small change in the 1.7 beta url. --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 638d14998..b2da9e816 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ python: - "3.4" env: - - DJANGO="https://www.djangoproject.com/download/1.7b4/tarball/" + - DJANGO="https://www.djangoproject.com/download/1.7.b4/tarball/" - DJANGO="django==1.6.5" - DJANGO="django==1.5.8" - DJANGO="django==1.4.13" @@ -24,7 +24,7 @@ install: - "if [[ ${DJANGO::11} == 'django==1.3' ]]; then pip install django-filter==0.5.4; fi" - "if [[ ${DJANGO::11} != 'django==1.3' ]]; then pip install django-filter==0.7; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} == '3' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi" - - "if [[ ${DJANGO} == 'https://www.djangoproject.com/download/1.7b4/tarball/' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi" + - "if [[ ${DJANGO} == 'https://www.djangoproject.com/download/1.7.b4/tarball/' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi" - export PYTHONPATH=. script: @@ -33,7 +33,7 @@ script: matrix: exclude: - python: "2.6" - env: DJANGO="https://www.djangoproject.com/download/1.7b4/tarball/" + env: DJANGO="https://www.djangoproject.com/download/1.7.b4/tarball/" - python: "3.2" env: DJANGO="django==1.4.13" - python: "3.2" From a1a3ad763996b9ab5535bc5d442c2d6fab10b7cc Mon Sep 17 00:00:00 2001 From: allenhu Date: Sat, 17 May 2014 06:05:33 +0800 Subject: [PATCH 113/225] fix pep8 --- rest_framework/serializers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 6dd09f68b..87d20cfce 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -33,8 +33,8 @@ from rest_framework.settings import api_settings # This helps keep the separation between model fields, form fields, and # serializer fields more explicit. -from rest_framework.relations import * -from rest_framework.fields import * +from rest_framework.relations import * # NOQA +from rest_framework.fields import * # NOQA def _resolve_model(obj): @@ -345,7 +345,7 @@ class BaseSerializer(WritableField): for field_name, field in self.fields.items(): if field.read_only and obj is None: - continue + continue field.initialize(parent=self, field_name=field_name) key = self.get_field_key(field_name) value = field.field_to_native(obj, field_name) From 1e7b5fd2c04e587e30cf29e15ca3074b8d33b92e Mon Sep 17 00:00:00 2001 From: Ian Foote Date: Tue, 20 May 2014 14:55:00 +0100 Subject: [PATCH 114/225] Document ChoiceField blank_display_value parameter --- docs/api-guide/fields.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 67fa65d2d..58dbf977e 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -184,7 +184,9 @@ Corresponds to `django.db.models.fields.SlugField`. ## ChoiceField -A field that can accept a value out of a limited set of choices. +A field that can accept a value out of a limited set of choices. Optionally takes a `blank_display_value` parameter that customizes the display value of an empty choice. + +**Signature:** `ChoiceField(choices=(), blank_display_value=None)` ## EmailField From 04c820b8e5e4ae153eacd1cbf19b39286c374e87 Mon Sep 17 00:00:00 2001 From: John Spray Date: Thu, 22 May 2014 15:24:35 +0100 Subject: [PATCH 115/225] fields: allow help_text on SerializerMethodField ...by passing through any extra *args and **kwargs to the parent constructor. Previously one couldn't assign help_text to a SerializerMethodField during construction. --- rest_framework/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 2da895500..4ac5285e8 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1027,9 +1027,9 @@ class SerializerMethodField(Field): A field that gets its value by calling a method on the serializer it's attached to. """ - def __init__(self, method_name): + def __init__(self, method_name, *args, **kwargs): self.method_name = method_name - super(SerializerMethodField, self).__init__() + super(SerializerMethodField, self).__init__(*args, **kwargs) def field_to_native(self, obj, field_name): value = getattr(self.parent, self.method_name)(obj) From 807f7a6bb9e36321f3487b5ac31ef5fdc8f4b3fb Mon Sep 17 00:00:00 2001 From: Piper Merriam Date: Thu, 22 May 2014 13:51:20 -0600 Subject: [PATCH 116/225] Fix _resolve_model to work with unicode strings --- rest_framework/serializers.py | 14 +++++++------- rest_framework/tests/test_serializers.py | 5 +++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 87d20cfce..c2b414d7a 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -49,7 +49,7 @@ def _resolve_model(obj): String representations should have the format: 'appname.ModelName' """ - if type(obj) == str and len(obj.split('.')) == 2: + if isinstance(obj, six.string_types) and len(obj.split('.')) == 2: app_name, model_name = obj.split('.') return models.get_model(app_name, model_name) elif inspect.isclass(obj) and issubclass(obj, models.Model): @@ -759,9 +759,9 @@ class ModelSerializer(Serializer): field.read_only = True ret[accessor_name] = field - + # Ensure that 'read_only_fields' is an iterable - assert isinstance(self.opts.read_only_fields, (list, tuple)), '`read_only_fields` must be a list or tuple' + assert isinstance(self.opts.read_only_fields, (list, tuple)), '`read_only_fields` must be a list or tuple' # Add the `read_only` flag to any fields that have been specified # in the `read_only_fields` option @@ -776,10 +776,10 @@ class ModelSerializer(Serializer): "on serializer '%s'." % (field_name, self.__class__.__name__)) ret[field_name].read_only = True - + # Ensure that 'write_only_fields' is an iterable - assert isinstance(self.opts.write_only_fields, (list, tuple)), '`write_only_fields` must be a list or tuple' - + assert isinstance(self.opts.write_only_fields, (list, tuple)), '`write_only_fields` must be a list or tuple' + for field_name in self.opts.write_only_fields: assert field_name not in self.base_fields.keys(), ( "field '%s' on serializer '%s' specified in " @@ -790,7 +790,7 @@ class ModelSerializer(Serializer): "Non-existant field '%s' specified in `write_only_fields` " "on serializer '%s'." % (field_name, self.__class__.__name__)) - ret[field_name].write_only = True + ret[field_name].write_only = True return ret diff --git a/rest_framework/tests/test_serializers.py b/rest_framework/tests/test_serializers.py index 082a400ca..120510ace 100644 --- a/rest_framework/tests/test_serializers.py +++ b/rest_framework/tests/test_serializers.py @@ -3,6 +3,7 @@ from django.test import TestCase from rest_framework.serializers import _resolve_model from rest_framework.tests.models import BasicModel +from rest_framework.compat import six class ResolveModelTests(TestCase): @@ -19,6 +20,10 @@ class ResolveModelTests(TestCase): resolved_model = _resolve_model('tests.BasicModel') self.assertEqual(resolved_model, BasicModel) + def test_resolve_unicode_representation(self): + resolved_model = _resolve_model(six.text_type('tests.BasicModel')) + self.assertEqual(resolved_model, BasicModel) + def test_resolve_non_django_model(self): with self.assertRaises(ValueError): _resolve_model(TestCase) From eab5933070d5df9078a6b88e85ee933cbfa28955 Mon Sep 17 00:00:00 2001 From: khamaileon Date: Mon, 26 May 2014 18:43:50 +0200 Subject: [PATCH 117/225] Add the allow_add_remove parameter to the get_serializer method --- docs/api-guide/generic-views.md | 2 +- rest_framework/generics.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 7d06f246c..bb748981e 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -187,7 +187,7 @@ Remember that the `pre_save()` method is not called by `GenericAPIView` itself, You won't typically need to override the following methods, although you might need to call into them if you're writing custom views using `GenericAPIView`. * `get_serializer_context(self)` - Returns a dictionary containing any extra context that should be supplied to the serializer. Defaults to including `'request'`, `'view'` and `'format'` keys. -* `get_serializer(self, instance=None, data=None, files=None, many=False, partial=False)` - Returns a serializer instance. +* `get_serializer(self, instance=None, data=None, files=None, many=False, partial=False, allow_add_remove=False)` - Returns a serializer instance. * `get_pagination_serializer(self, page)` - Returns a serializer instance to use with paginated data. * `paginate_queryset(self, queryset)` - Paginate a queryset if required, either returning a page object, or `None` if pagination is not configured for this view. * `filter_queryset(self, queryset)` - Given a queryset, filter it with whichever filter backends are in use, returning a new queryset. diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 7bac510f7..7fc9db364 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -90,8 +90,8 @@ class GenericAPIView(views.APIView): 'view': self } - def get_serializer(self, instance=None, data=None, - files=None, many=False, partial=False): + def get_serializer(self, instance=None, data=None, files=None, many=False, + partial=False, allow_add_remove=False): """ Return the serializer instance that should be used for validating and deserializing input, and for serializing output. @@ -99,7 +99,9 @@ class GenericAPIView(views.APIView): serializer_class = self.get_serializer_class() context = self.get_serializer_context() return serializer_class(instance, data=data, files=files, - many=many, partial=partial, context=context) + many=many, partial=partial, + allow_add_remove=allow_add_remove, + context=context) def get_pagination_serializer(self, page): """ From a7ff51118f8c8d696219ea7723b283a0ee680457 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 29 May 2014 14:33:16 +0100 Subject: [PATCH 118/225] Note on configuring TokenAuthentication --- docs/api-guide/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 88a7a0119..1cb37d67f 100755 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -119,7 +119,7 @@ Unauthenticated responses that are denied permission will result in an `HTTP 401 This authentication scheme uses a simple token-based HTTP Authentication scheme. Token authentication is appropriate for client-server setups, such as native desktop and mobile clients. -To use the `TokenAuthentication` scheme, include `rest_framework.authtoken` in your `INSTALLED_APPS` setting: +To use the `TokenAuthentication` scheme you'll need to [configure the authentication classes](#setting-the-authentication-scheme) to include `TokenAuthentication`, and additionally include `rest_framework.authtoken` in your `INSTALLED_APPS` setting: INSTALLED_APPS = ( ... From 6cb6bfae1b83c8682fa3c3d208c732c8ea49606e Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Fri, 30 May 2014 17:53:26 +0200 Subject: [PATCH 119/225] Always use specified content type in APIRequestFactory If `content_type` is specified in the `APIRequestFactory`, always include it in the request, even if data is empty. --- rest_framework/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/test.py b/rest_framework/test.py index df5a5b3b3..284bcee07 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -36,7 +36,7 @@ class APIRequestFactory(DjangoRequestFactory): """ if not data: - return ('', None) + return ('', content_type) assert format is None or content_type is None, ( 'You may not set both `format` and `content_type`.' From 31f63e1e5502d45f414df400679c238346137b10 Mon Sep 17 00:00:00 2001 From: Rodolfo Carvalho Date: Mon, 2 Jun 2014 11:06:03 +0200 Subject: [PATCH 120/225] Fix typo in docs --- docs/api-guide/viewsets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 23b16575f..b3085f75c 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -137,7 +137,7 @@ The `@action` and `@link` decorators can additionally take extra arguments that def set_password(self, request, pk=None): ... -The `@action` decorator will route `POST` requests by default, but may also accept other HTTP methods, by using the `method` argument. For example: +The `@action` decorator will route `POST` requests by default, but may also accept other HTTP methods, by using the `methods` argument. For example: @action(methods=['POST', 'DELETE']) def unset_password(self, request, pk=None): From 08c4594145a7219a14fafc87db0b9d61483d70d0 Mon Sep 17 00:00:00 2001 From: khamaileon Date: Thu, 5 Jun 2014 12:49:02 +0200 Subject: [PATCH 121/225] Replace ChoiceField type_label --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 4ac5285e8..86e8fd9df 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -506,7 +506,7 @@ class SlugField(CharField): class ChoiceField(WritableField): type_name = 'ChoiceField' - type_label = 'multiple choice' + type_label = 'choice' form_field_class = forms.ChoiceField widget = widgets.Select default_error_messages = { From e8ec81f5e985f9cc9f524f77ec23013be918b990 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Sun, 8 Jun 2014 09:03:21 +0200 Subject: [PATCH 122/225] Fixed #1624 (thanks @abraithwaite) --- rest_framework/compat.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index d155f5542..fdf12448a 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -51,6 +51,7 @@ except ImportError: # guardian is optional try: import guardian + import guardian.shortcuts # Fixes #1624 except ImportError: guardian = None From be84f71bc906c926c9955a4cf47630b24461067d Mon Sep 17 00:00:00 2001 From: Greg Barker Date: Tue, 10 Jun 2014 15:20:45 -0700 Subject: [PATCH 123/225] Fix #1614 - Corrected reference to serializers.CharField --- docs/api-guide/serializers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 0044f0701..cedf1ff7b 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -73,8 +73,8 @@ Sometimes when serializing objects, you may not want to represent everything exa If you need to customize the serialized value of a particular field, you can do this by creating a `transform_` method. For example if you needed to render some markdown from a text field: - description = serializers.TextField() - description_html = serializers.TextField(source='description', read_only=True) + description = serializers.CharField() + description_html = serializers.CharField(source='description', read_only=True) def transform_description_html(self, obj, value): from django.contrib.markup.templatetags.markup import markdown From 1386767013d044d337b8e08dd2f9b0197197cccf Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 12 Jun 2014 11:47:26 +0100 Subject: [PATCH 124/225] Version 2.3.14 --- docs/api-guide/content-negotiation.md | 2 -- docs/topics/release-notes.md | 36 ++++++++++--------- rest_framework/__init__.py | 2 +- rest_framework/templatetags/rest_framework.py | 4 +-- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/docs/api-guide/content-negotiation.md b/docs/api-guide/content-negotiation.md index 94dd59cac..58b2a2ce0 100644 --- a/docs/api-guide/content-negotiation.md +++ b/docs/api-guide/content-negotiation.md @@ -1,5 +1,3 @@ - - # Content negotiation > HTTP has provisions for several mechanisms for "content negotiation" - the process of selecting the best representation for a given response when there are multiple representations available. diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 335497eec..ea4c912c9 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -40,24 +40,28 @@ You can determine your currently installed version using `pip freeze`: ## 2.3.x series -### 2.3.x +### 2.3.14 -**Date**: April 2014 +**Date**: 12th June 2014 -* Fix nested serializers linked through a backward foreign key relation -* Fix bad links for the `BrowsableAPIRenderer` with `YAMLRenderer` -* Add `UnicodeYAMLRenderer` that extends `YAMLRenderer` with unicode -* Fix `parse_header` argument convertion -* Fix mediatype detection under Python3 -* Web browseable API now offers blank option on dropdown when the field is not required -* `APIException` representation improved for logging purposes -* Allow source="*" within nested serializers -* Better support for custom oauth2 provider backends -* Fix field validation if it's optional and has no value -* Add `SEARCH_PARAM` and `ORDERING_PARAM` -* Fix `APIRequestFactory` to support arguments within the url string for GET -* Allow three transport modes for access tokens when accessing a protected resource -* Fix `Request`'s `QueryDict` encoding +* **Security fix**: Escape request path when it is include as part of the login and logout links in the browsable API. +* `help_text` and `verbose_name` automatically set for related fields on `ModelSerializer`. +* Fix nested serializers linked through a backward foreign key relation. +* Fix bad links for the `BrowsableAPIRenderer` with `YAMLRenderer`. +* Add `UnicodeYAMLRenderer` that extends `YAMLRenderer` with unicode. +* Fix `parse_header` argument convertion. +* Fix mediatype detection under Python 3. +* Web browseable API now offers blank option on dropdown when the field is not required. +* `APIException` representation improved for logging purposes. +* Allow source="*" within nested serializers. +* Better support for custom oauth2 provider backends. +* Fix field validation if it's optional and has no value. +* Add `SEARCH_PARAM` and `ORDERING_PARAM`. +* Fix `APIRequestFactory` to support arguments within the url string for GET. +* Allow three transport modes for access tokens when accessing a protected resource. +* Fix `QueryDict` encoding on request objects. +* Ensure throttle keys do not contain spaces, as those are invalid if using `memcached`. +* Support `blank_display_value` on `ChoiceField`. ### 2.3.13 diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 2d76b55d5..01036cefa 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -8,7 +8,7 @@ ______ _____ _____ _____ __ _ """ __title__ = 'Django REST framework' -__version__ = '2.3.13' +__version__ = '2.3.14' __author__ = 'Tom Christie' __license__ = 'BSD 2-Clause' __copyright__ = 'Copyright 2011-2014 Tom Christie' diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index dff176d62..a155d8d25 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -122,7 +122,7 @@ def optional_login(request): except NoReverseMatch: return '' - snippet = "Log in" % (login_url, request.path) + snippet = "Log in" % (login_url, escape(request.path)) return snippet @@ -136,7 +136,7 @@ def optional_logout(request): except NoReverseMatch: return '' - snippet = "Log out" % (logout_url, request.path) + snippet = "Log out" % (logout_url, escape(request.path)) return snippet From 82659873c9d3e3058b7e7ea63e4c4b190c7fc19c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 12 Jun 2014 11:48:58 +0100 Subject: [PATCH 125/225] Fix accidental docs change --- docs/api-guide/content-negotiation.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api-guide/content-negotiation.md b/docs/api-guide/content-negotiation.md index 58b2a2ce0..94dd59cac 100644 --- a/docs/api-guide/content-negotiation.md +++ b/docs/api-guide/content-negotiation.md @@ -1,3 +1,5 @@ + + # Content negotiation > HTTP has provisions for several mechanisms for "content negotiation" - the process of selecting the best representation for a given response when there are multiple representations available. From b4c7717cb80cb13a2f13aae8855e226685306880 Mon Sep 17 00:00:00 2001 From: Walt Javins Date: Fri, 13 Jun 2014 22:26:00 -0700 Subject: [PATCH 126/225] Refactor login template to extend base. While experimenting with extending DRF, I found that the login page 1) had no title, and 2) duplicated info from base.html. This change adds a new {% block body %} to the base.html template which allows override of the entire html . login_base.html has its duplicated head info stripped, and now extends base.html to share common html templating. As part of this change, pretify.css is unnecessarily added to login_base.html. If this is deemed a problem, it will be easy to block that css out, and have login_base.html override the block. Ideally, I would have liked to create a new api_base.html that extends base.html, move the api specific logic into that template, and leave base.html content agnostic, to truely be a unifying base for all DRF pages. But this change would break current apps that override api.html and expect base.html to be the immediate super template. :/ This change is benificial because it: - removes duplication of header declarations (mostly css includes) - adds a html title to the login page - standardizes html header info across all DRF pages Docs are updated to reflect the new structure. --- docs/topics/browsable-api.md | 1 + rest_framework/templates/rest_framework/base.html | 2 ++ .../templates/rest_framework/login_base.html | 15 +++------------ 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/docs/topics/browsable-api.md b/docs/topics/browsable-api.md index e32db6958..ffa0b07dd 100644 --- a/docs/topics/browsable-api.md +++ b/docs/topics/browsable-api.md @@ -69,6 +69,7 @@ For more specific CSS tweaks than simply overriding the default bootstrap theme All of the blocks available in the browsable API base template that can be used in your `api.html`. +* `body` - The entire html ``. * `bodyclass` - Class attribute for the `` tag, empty by default. * `bootstrap_theme` - CSS for the Bootstrap theme. * `bootstrap_navbar_variant` - CSS class for the navbar. diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index 7067ee2f0..1f3def8f3 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -24,6 +24,7 @@ {% endblock %} + {% block body %}
@@ -230,4 +231,5 @@ {% endblock %} + {% endblock %} diff --git a/rest_framework/templates/rest_framework/login_base.html b/rest_framework/templates/rest_framework/login_base.html index be9a0072a..312a1138c 100644 --- a/rest_framework/templates/rest_framework/login_base.html +++ b/rest_framework/templates/rest_framework/login_base.html @@ -1,17 +1,8 @@ +{% extends "rest_framework/base.html" %} {% load url from future %} {% load rest_framework %} - - - - {% block style %} - {% block bootstrap_theme %} - - - {% endblock %} - - {% endblock %} - + {% block body %}
@@ -50,4 +41,4 @@
- + {% endblock %} From 544183f64ff115398ae62c916d58f8369263d47c Mon Sep 17 00:00:00 2001 From: TankorSmash Date: Mon, 16 Jun 2014 19:13:02 -0400 Subject: [PATCH 127/225] typo in the docs --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 86e8fd9df..d5c8134b0 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -187,7 +187,7 @@ class Field(object): def field_to_native(self, obj, field_name): """ - Given and object and a field name, returns the value that should be + Given an object and a field name, returns the value that should be serialized for that field. """ if obj is None: From f34011f8010a5a3358eead9f60be1fb4db4e834a Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 23 Jun 2014 14:52:18 +0200 Subject: [PATCH 128/225] Allow use of native migrations in 1.7 --- .../authtoken/migrations/0001_initial.py | 84 +++++-------------- .../south_migrations/0001_initial.py | 67 +++++++++++++++ .../authtoken/south_migrations/__init__.py | 0 rest_framework/settings.py | 7 ++ 4 files changed, 96 insertions(+), 62 deletions(-) create mode 100644 rest_framework/authtoken/south_migrations/0001_initial.py create mode 100644 rest_framework/authtoken/south_migrations/__init__.py diff --git a/rest_framework/authtoken/migrations/0001_initial.py b/rest_framework/authtoken/migrations/0001_initial.py index d5965e404..2e5d6b47e 100644 --- a/rest_framework/authtoken/migrations/0001_initial.py +++ b/rest_framework/authtoken/migrations/0001_initial.py @@ -1,67 +1,27 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models +# encoding: utf8 +from __future__ import unicode_literals -from rest_framework.settings import api_settings +from django.db import models, migrations +from django.conf import settings -try: - from django.contrib.auth import get_user_model -except ImportError: # django < 1.5 - from django.contrib.auth.models import User -else: - User = get_user_model() +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'Token' - db.create_table('authtoken_token', ( - ('key', self.gf('django.db.models.fields.CharField')(max_length=40, primary_key=True)), - ('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='auth_token', unique=True, to=orm['%s.%s' % (User._meta.app_label, User._meta.object_name)])), - ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - )) - db.send_create_signal('authtoken', ['Token']) - - - def backwards(self, orm): - # Deleting model 'Token' - db.delete_table('authtoken_token') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - "%s.%s" % (User._meta.app_label, User._meta.module_name): { - 'Meta': {'object_name': User._meta.module_name}, - }, - 'authtoken.token': { - 'Meta': {'object_name': 'Token'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'primary_key': 'True'}), - 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'auth_token'", 'unique': 'True', 'to': "orm['%s.%s']" % (User._meta.app_label, User._meta.object_name)}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - } - } - - complete_apps = ['authtoken'] + operations = [ + migrations.CreateModel( + name='Token', + fields=[ + ('key', models.CharField(max_length=40, serialize=False, primary_key=True)), + ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, to_field='id')), + ('created', models.DateTimeField(auto_now_add=True)), + ], + options={ + 'abstract': False, + }, + bases=(models.Model,), + ), + ] diff --git a/rest_framework/authtoken/south_migrations/0001_initial.py b/rest_framework/authtoken/south_migrations/0001_initial.py new file mode 100644 index 000000000..d5965e404 --- /dev/null +++ b/rest_framework/authtoken/south_migrations/0001_initial.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +from rest_framework.settings import api_settings + + +try: + from django.contrib.auth import get_user_model +except ImportError: # django < 1.5 + from django.contrib.auth.models import User +else: + User = get_user_model() + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'Token' + db.create_table('authtoken_token', ( + ('key', self.gf('django.db.models.fields.CharField')(max_length=40, primary_key=True)), + ('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='auth_token', unique=True, to=orm['%s.%s' % (User._meta.app_label, User._meta.object_name)])), + ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + )) + db.send_create_signal('authtoken', ['Token']) + + + def backwards(self, orm): + # Deleting model 'Token' + db.delete_table('authtoken_token') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + "%s.%s" % (User._meta.app_label, User._meta.module_name): { + 'Meta': {'object_name': User._meta.module_name}, + }, + 'authtoken.token': { + 'Meta': {'object_name': 'Token'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'auth_token'", 'unique': 'True', 'to': "orm['%s.%s']" % (User._meta.app_label, User._meta.object_name)}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['authtoken'] diff --git a/rest_framework/authtoken/south_migrations/__init__.py b/rest_framework/authtoken/south_migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 38753c968..332d5e4f9 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -119,6 +119,13 @@ DEFAULTS = { # Pending deprecation 'FILTER_BACKEND': None, + + + # 1.7 Migration Compatibility + + 'SOUTH_MIGRATION_MODULES': { + 'authtoken': 'rest_framework.authtoken.south_migrations', + } } From 3f727ce738776838d8420450ce28485954fbb097 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 24 Jun 2014 09:02:44 +0200 Subject: [PATCH 129/225] Added (first pass) notes to docs & release notes. Backed out `SOUTH_MIGRATION_MODULES` setting from `rest_framework.settings` --- docs/api-guide/authentication.md | 24 ++++++++++++++++++++++-- docs/topics/release-notes.md | 16 ++++++++++++---- rest_framework/settings.py | 6 ------ 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 88a7a0119..0bddd0d03 100755 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -126,7 +126,13 @@ To use the `TokenAuthentication` scheme, include `rest_framework.authtoken` in y 'rest_framework.authtoken' ) -Make sure to run `manage.py syncdb` after changing your settings. The `authtoken` database tables are managed by south (see [Schema migrations](#schema-migrations) below). + +--- + +**Note:** Make sure to run `manage.py syncdb` after changing your settings. Both Django native (from v1.7) and South migrations for the `authtoken` database tables are provided. See [Schema migrations](#schema-migrations) below. + +--- + You'll also need to create tokens for your users. @@ -198,7 +204,21 @@ Note that the default `obtain_auth_token` view explicitly uses JSON requests and #### Schema migrations -The `rest_framework.authtoken` app includes a south migration that will create the authtoken table. +The `rest_framework.authtoken` app includes both a Django native migration (for Django versions >1.7) and a south migration that will create the authtoken table. + +---- + +**Note** By default both Django (>1.7) and South will look for a module named `migrations`. To avoid a collision here, in order to use South you **must** provide the `SOUTH_MIGRATION_MODULES` option in your `settings.py`: + + + SOUTH_MIGRATION_MODULES = { + 'authtoken': 'rest_framework.authtoken.south_migrations', + } + +This tells South to look in the `south_migrations` module for the `authtoken` app. + +---- + If you're using a [custom user model][custom-user-model] you'll need to make sure that any initial migration that creates the user table runs before the authtoken table is created. diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 335497eec..5722d45be 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -43,6 +43,14 @@ You can determine your currently installed version using `pip freeze`: ### 2.3.x **Date**: April 2014 +* Added compatibility with Django 1.7's native migrations. + + **IMPORTANT**: In order to continue to use south with Django <1.7 you **must** provide + the `SOUTH_MIGRATION_MODULES` option in your `settings.py`: + + SOUTH_MIGRATION_MODULES = { + 'authtoken': 'rest_framework.authtoken.south_migrations', + } * Fix nested serializers linked through a backward foreign key relation * Fix bad links for the `BrowsableAPIRenderer` with `YAMLRenderer` @@ -165,9 +173,9 @@ You can determine your currently installed version using `pip freeze`: * Added `trailing_slash` option to routers. * Include support for `HttpStreamingResponse`. * Support wider range of default serializer validation when used with custom model fields. -* UTF-8 Support for browsable API descriptions. +* UTF-8 Support for browsable API descriptions. * OAuth2 provider uses timezone aware datetimes when supported. -* Bugfix: Return error correctly when OAuth non-existent consumer occurs. +* Bugfix: Return error correctly when OAuth non-existent consumer occurs. * Bugfix: Allow `FileUploadParser` to correctly filename if provided as URL kwarg. * Bugfix: Fix `ScopedRateThrottle`. @@ -208,7 +216,7 @@ You can determine your currently installed version using `pip freeze`: * Added SearchFilter * Added OrderingFilter * Added GenericViewSet -* Bugfix: Multiple `@action` and `@link` methods now allowed on viewsets. +* Bugfix: Multiple `@action` and `@link` methods now allowed on viewsets. * Bugfix: Fix API Root view issue with DjangoModelPermissions ### 2.3.2 @@ -261,7 +269,7 @@ You can determine your currently installed version using `pip freeze`: * Long HTTP headers in browsable API are broken in multiple lines when possible. * Bugfix: Fix regression with DjangoFilterBackend not worthing correctly with single object views. * Bugfix: OAuth should fail hard when invalid token used. -* Bugfix: Fix serializer potentially returning `None` object for models that define `__bool__` or `__len__`. +* Bugfix: Fix serializer potentially returning `None` object for models that define `__bool__` or `__len__`. ### 2.2.5 diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 332d5e4f9..fbef6e021 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -120,12 +120,6 @@ DEFAULTS = { # Pending deprecation 'FILTER_BACKEND': None, - - # 1.7 Migration Compatibility - - 'SOUTH_MIGRATION_MODULES': { - 'authtoken': 'rest_framework.authtoken.south_migrations', - } } From c1426d1078a40de521aeec6c4099538efa6b24c7 Mon Sep 17 00:00:00 2001 From: Chibisov Gennady Date: Thu, 26 Jun 2014 23:29:00 +0400 Subject: [PATCH 130/225] Fixes #1651. Add link to drf-extensions nested routers to docs --- docs/api-guide/routers.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index 7efc140a5..64f05af39 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -179,7 +179,16 @@ The [wq.db package][wq.db] provides an advanced [Router][wq.db-router] class (an app.router.register_model(MyModel) +## DRF-extensions + +The [`DRF-extensions` package][drf-extensions] provides [routers][drf-extensions-routers] for creating [nested viewsets][drf-extensions-nested-viewsets], [collection level controllers][drf-extensions-collection-level-controllers] with [customizable endpoint names][drf-extensions-customizable-endpoint-names]. + [cite]: http://guides.rubyonrails.org/routing.html [drf-nested-routers]: https://github.com/alanjds/drf-nested-routers [wq.db]: http://wq.io/wq.db [wq.db-router]: http://wq.io/docs/app.py +[drf-extensions]: http://chibisov.github.io/drf-extensions/docs/ +[drf-extensions-routers]: http://chibisov.github.io/drf-extensions/docs/#routers +[drf-extensions-nested-viewsets]: http://chibisov.github.io/drf-extensions/docs/#nested-routes +[drf-extensions-collection-level-controllers]: http://chibisov.github.io/drf-extensions/docs/#collection-level-controllers +[drf-extensions-customizable-endpoint-names]: http://chibisov.github.io/drf-extensions/docs/#controller-endpoint-name \ No newline at end of file From 91eabd54bbc42e8a2540db2ff070097db7a0f4a0 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 1 Jul 2014 14:34:23 +0100 Subject: [PATCH 131/225] Docs tweak --- docs/api-guide/filtering.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index 6a8a267b2..ec5ab61fe 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -199,8 +199,7 @@ This enables us to make queries like: http://example.com/api/products?manufacturer__name=foo -This is nice, but it shows underlying model structure in REST API, which may -be undesired, but you can use: +This is nice, but it exposes the Django's double underscore convention as part of the API. If you instead want to explicitly name the filter argument you can instead explicitly include it on the `FilterSet` class: import django_filters from myapp.models import Product @@ -208,7 +207,6 @@ be undesired, but you can use: from rest_framework import generics class ProductFilter(django_filters.FilterSet): - manufacturer = django_filters.CharFilter(name="manufacturer__name") class Meta: From a5e628bf8b4cf26227d4ee0cbef45049aa0632d5 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 7 Jul 2014 09:42:50 +0100 Subject: [PATCH 132/225] Kick travis --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 73e4b13fc..da5f27aef 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + [build-status-image]: https://secure.travis-ci.org/tomchristie/django-rest-framework.png?branch=master [travis]: http://travis-ci.org/tomchristie/django-rest-framework?branch=master [twitter]: https://twitter.com/_tomchristie From b51901812596aa478cc8cb1046e42049214bc9ff Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 7 Jul 2014 09:51:23 +0100 Subject: [PATCH 133/225] Docs on object level permissions and filters. Closes #1683 --- docs/api-guide/permissions.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 50f669a2d..c44b22de8 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -36,6 +36,12 @@ For example: self.check_object_permissions(self.request, obj) return obj +#### Limitations of object level permissions + +For performance reasons the generic views will not automatically apply object level permissions to each instance in a queryset when returning a list of objects. + +Often when you're using object level permissions you'll also want to [filter the queryset][filtering] appropriately, to ensure that users only have visibility onto instances that they are permitted to view. + ## Setting the permission policy The default permission policy may be set globally, using the `DEFAULT_PERMISSION_CLASSES` setting. For example. @@ -237,6 +243,7 @@ The [REST Condition][rest-condition] package is another extension for building c [cite]: https://developer.apple.com/library/mac/#documentation/security/Conceptual/AuthenticationAndAuthorizationGuide/Authorization/Authorization.html [authentication]: authentication.md [throttling]: throttling.md +[filtering]: filtering.md [contribauth]: https://docs.djangoproject.com/en/1.0/topics/auth/#permissions [objectpermissions]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#handling-object-permissions [guardian]: https://github.com/lukaszb/django-guardian From 18eab53e892f2f579fd0bb4e1ca3cb47a074accc Mon Sep 17 00:00:00 2001 From: Emmanouil Date: Wed, 9 Jul 2014 15:53:31 +0100 Subject: [PATCH 134/225] Updated quick start project set up order --- docs/tutorial/quickstart.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 8bf8c7f5c..04792c692 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -6,8 +6,8 @@ We're going to create a simple API to allow admin users to view and edit the use Create a new Django project named `tutorial`, then start a new app called `quickstart`. - # Set up a new project - django-admin.py startproject tutorial + # Create the project directory + mkdir tutorial cd tutorial # Create a virtualenv to isolate our package dependencies locally @@ -18,6 +18,9 @@ Create a new Django project named `tutorial`, then start a new app called `quick pip install django pip install djangorestframework + # Set up a new project + django-admin.py startproject tutorial + # Create a new app python manage.py startapp quickstart From dd2e950cde5fc7078303925fa936e59ea4fe363b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 15 Jul 2014 15:02:09 +0100 Subject: [PATCH 135/225] Fusion ads --- docs/template.html | 24 +++++++++++++++++++----- mkdocs.py | 4 ++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/template.html b/docs/template.html index a397d067b..6009b1510 100644 --- a/docs/template.html +++ b/docs/template.html @@ -33,6 +33,21 @@ })(); + @@ -169,11 +184,9 @@
@@ -199,6 +212,7 @@ + """) else: output = output.replace('{{ ad_block }}', '') From 680fabe9dd2307014862beb1c2d77625e808788d Mon Sep 17 00:00:00 2001 From: Dave King Date: Thu, 17 Jul 2014 11:46:59 +0100 Subject: [PATCH 136/225] Update fields.md obj.__class__ will return the actual Class object, we want to serialise a string (accessed with obj.__class__.__name__) --- docs/api-guide/fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 58dbf977e..6d1adcb03 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -347,7 +347,7 @@ As an example, let's create a field that can be used represent the class name of """ Serialize the object's class name. """ - return obj.__class__ + return obj.__class__.__name__ # Third party packages From 6bf0f81b0b911eb3d2bf3dd3fbaaf44570a58b28 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 17 Jul 2014 13:41:19 +0100 Subject: [PATCH 137/225] Kickstarter note on front page --- docs/index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/index.md b/docs/index.md index 2a4ad8859..a870a8280 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,6 +9,10 @@ --- +**Project announcement:** We are currently running a Kickstart campaign to help fund the development of Django REST framework 3. If you want to help make sustainable open source development please [check out the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3), and consider funding us! + +--- +

2.0 Announcement
  • 2.2 Announcement
  • 2.3 Announcement
  • +
  • Kickstarter Announcement
  • Release Notes
  • Credits
  • diff --git a/docs/topics/kickstarter-announcement.md b/docs/topics/kickstarter-announcement.md new file mode 100644 index 000000000..98cf12e3b --- /dev/null +++ b/docs/topics/kickstarter-announcement.md @@ -0,0 +1,31 @@ +# Kickstarting Django REST framework 3 + +--- + + + +--- + +In order to continue to drive the project forward, I'm launching a Kickstarter campaign to help fund the development of a major new release - Django REST framework 3. + +## Project details + +This new release will allow us to comprehensively address some of the shortcomings of the framework, and will aim to include the following: + +* Faster, simpler and easier-to-use serializers. +* An alternative admin-style interface for the browsable API. +* Search and filtering controls made accessible in the browsable API. +* Alternative API pagination styles. +* Documentation around API versioning. +* Triage of outstanding tickets. +* Improving the ongoing quality and maintainability of the project. + +Full details are available now on the [project page](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3). + +If you're interested in helping make sustainable open source development a reality please [visit the Kickstarter page](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3) and consider funding the project. + +I can't wait to see where this takes us! + +Many thanks to everyone for your support so far, + + Tom Christie :) From 550243fd2649195f6433bf07354880d86bfedde9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 18 Jul 2014 11:51:48 +0100 Subject: [PATCH 139/225] Beef up the kickstarter announcement --- docs/index.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index c1f60097e..c07a56457 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,7 +9,11 @@ --- -**Project announcement:** We are currently running a Kickstarter campaign to help fund the development of Django REST framework 3. If you want to help make sustainable open source development please [check out the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3), and consider funding us! +#### Django REST framework 3 - Kickstarter announcement! + +We are currently running a Kickstarter campaign to help fund the development of Django REST framework 3. + +If you want to help make sustainable open source development **please [check out the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3) and consider funding us.** --- From ab34ddcf2f734ca5f1a6e43ff1280283b9a171e5 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 18 Jul 2014 11:53:23 +0100 Subject: [PATCH 140/225] Typo --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index c07a56457..5b5a62eba 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,7 +13,7 @@ We are currently running a Kickstarter campaign to help fund the development of Django REST framework 3. -If you want to help make sustainable open source development **please [check out the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3) and consider funding us.** +If you want to help make sustainable open-source development a reality **please [check out the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3) and consider funding us.** --- From 4324f504375f7f118297fa9694bd6cee56bc050e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 18 Jul 2014 11:54:51 +0100 Subject: [PATCH 141/225] Phrasing tweak --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 5b5a62eba..d9c686c4a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,7 +13,7 @@ We are currently running a Kickstarter campaign to help fund the development of Django REST framework 3. -If you want to help make sustainable open-source development a reality **please [check out the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3) and consider funding us.** +If you want to help drive sustainable open-source development **please [check out the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3) and consider funding us.** --- From 0782640415e665caef7b76b349a7d1e59dec25df Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 18 Jul 2014 13:19:28 +0100 Subject: [PATCH 142/225] Kickstarter announcement --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index da5f27aef..0f81f3ae4 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,16 @@ **Note**: Full documentation for the project is available at [http://www.django-rest-framework.org][docs]. +--- + +#### Django REST framework 3 - Kickstarter announcement! + +We are currently running a Kickstarter campaign to help fund the development of Django REST framework 3. + +If you want to help drive sustainable open-source development **please [check out the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3) and consider funding us.** + +--- + # Overview Django REST framework is a powerful and flexible toolkit that makes it easy to build Web APIs. From d059b6d6c391099ded2abd8fa0cf06821f971a5e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 18 Jul 2014 13:20:03 +0100 Subject: [PATCH 143/225] Change positioning of announcement. --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0f81f3ae4..1d7eefcff 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,3 @@ -# Django REST framework - -[![build-status-image]][travis] - -**Awesome web-browseable Web APIs.** - -**Note**: Full documentation for the project is available at [http://www.django-rest-framework.org][docs]. - --- #### Django REST framework 3 - Kickstarter announcement! @@ -16,6 +8,14 @@ If you want to help drive sustainable open-source development **please [check ou --- +# Django REST framework + +[![build-status-image]][travis] + +**Awesome web-browseable Web APIs.** + +**Note**: Full documentation for the project is available at [http://www.django-rest-framework.org][docs]. + # Overview Django REST framework is a powerful and flexible toolkit that makes it easy to build Web APIs. From 30bf55bf8da2be8ae0228c8d0dee3a523ae5eba5 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 18 Jul 2014 13:20:55 +0100 Subject: [PATCH 144/225] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d7eefcff..e58ec8ae5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ We are currently running a Kickstarter campaign to help fund the development of Django REST framework 3. -If you want to help drive sustainable open-source development **please [check out the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3) and consider funding us.** +If you want to help drive sustainable open-source development forward **please [cconsider funding the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3).** --- From 591e67f5405d6a8f59b2e52b1050752faa906c52 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 18 Jul 2014 13:21:03 +0100 Subject: [PATCH 145/225] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e58ec8ae5..a1bfa91df 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ We are currently running a Kickstarter campaign to help fund the development of Django REST framework 3. -If you want to help drive sustainable open-source development forward **please [cconsider funding the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3).** +If you want to help drive sustainable open-source development forward **please [consider funding the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3).** --- From bf7371fbc5a05b359cee03b8cbadd8229e563070 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 18 Jul 2014 13:21:19 +0100 Subject: [PATCH 146/225] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a1bfa91df..e0d421c00 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ We are currently running a Kickstarter campaign to help fund the development of Django REST framework 3. -If you want to help drive sustainable open-source development forward **please [consider funding the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3).** +If you want to help drive sustainable open-source development forward **please consider funding [the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3).** --- From 08772799d466907a16f02aec990396736be3c022 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 18 Jul 2014 13:22:29 +0100 Subject: [PATCH 147/225] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0d421c00..eea002b4a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ We are currently running a Kickstarter campaign to help fund the development of Django REST framework 3. -If you want to help drive sustainable open-source development forward **please consider funding [the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3).** +If you want to help drive sustainable open-source development forward, then **please check out [the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3) and consider funding us.** --- From 81d15aa9be72ac8b805fc728bd86f930ff1b17e7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sun, 20 Jul 2014 15:45:45 +0100 Subject: [PATCH 148/225] Add link to drf-extra-fields. Closes #1698 --- docs/api-guide/fields.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 58dbf977e..cebfaac92 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -357,9 +357,16 @@ The following third party packages are also available. The [drf-compound-fields][drf-compound-fields] package provides "compound" serializer fields, such as lists of simple values, which can be described by other fields rather than serializers with the `many=True` option. Also provided are fields for typed dictionaries and values that can be either a specific type or a list of items of that type. +## DRF Extra Fields + +The [drf-extra-fields][drf-extra-fields] package provides extra serializer fields for REST framework, including `Base64ImageField` and `PointField` classes. + + + [cite]: https://docs.djangoproject.com/en/dev/ref/forms/api/#django.forms.Form.cleaned_data [FILE_UPLOAD_HANDLERS]: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FILE_UPLOAD_HANDLERS [ecma262]: http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 [strftime]: http://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior [iso8601]: http://www.w3.org/TR/NOTE-datetime [drf-compound-fields]: http://drf-compound-fields.readthedocs.org +[drf-extra-fields]: https://github.com/Hipo/drf-extra-fields \ No newline at end of file From 05882cc5999088ac232788ae62717c061e74ad12 Mon Sep 17 00:00:00 2001 From: Ron Cohen Date: Fri, 25 Jul 2014 10:55:53 +0000 Subject: [PATCH 149/225] Sending "Bearer" and "Bearer " resulted in a 500. --- rest_framework/authentication.py | 14 +++++++------- rest_framework/tests/test_authentication.py | 9 +++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index da9ca510e..887ef5d73 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -310,6 +310,13 @@ class OAuth2Authentication(BaseAuthentication): auth = get_authorization_header(request).split() + if len(auth) == 1: + msg = 'Invalid bearer header. No credentials provided.' + raise exceptions.AuthenticationFailed(msg) + elif len(auth) > 2: + msg = 'Invalid bearer header. Token string should not contain spaces.' + raise exceptions.AuthenticationFailed(msg) + if auth and auth[0].lower() == b'bearer': access_token = auth[1] elif 'access_token' in request.POST: @@ -319,13 +326,6 @@ class OAuth2Authentication(BaseAuthentication): else: return None - if len(auth) == 1: - msg = 'Invalid bearer header. No credentials provided.' - raise exceptions.AuthenticationFailed(msg) - elif len(auth) > 2: - msg = 'Invalid bearer header. Token string should not contain spaces.' - raise exceptions.AuthenticationFailed(msg) - return self.authenticate_credentials(request, access_token) def authenticate_credentials(self, request, access_token): diff --git a/rest_framework/tests/test_authentication.py b/rest_framework/tests/test_authentication.py index a1c43d9ce..cf8415ed6 100644 --- a/rest_framework/tests/test_authentication.py +++ b/rest_framework/tests/test_authentication.py @@ -549,6 +549,15 @@ class OAuth2Tests(TestCase): response = self.csrf_client.get('/oauth2-test/', HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 401) + @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') + def test_get_form_with_wrong_authorization_header_token_missing(self): + """Ensure that a wrong token lead to the correct HTTP error status code""" + auth = "Bearer" + response = self.csrf_client.get('/oauth2-test/', {}, HTTP_AUTHORIZATION=auth) + self.assertEqual(response.status_code, 401) + response = self.csrf_client.get('/oauth2-test/', HTTP_AUTHORIZATION=auth) + self.assertEqual(response.status_code, 401) + @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') def test_get_form_passing_auth(self): """Ensure GETing form over OAuth with correct client credentials succeed""" From e3aff6a5678d48a2e328c9bb44b7c3de81caffd5 Mon Sep 17 00:00:00 2001 From: Ron Cohen Date: Fri, 25 Jul 2014 13:38:42 +0000 Subject: [PATCH 150/225] Updated test docstring related to missing bearer token. --- rest_framework/tests/test_authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/tests/test_authentication.py b/rest_framework/tests/test_authentication.py index cf8415ed6..34bf29101 100644 --- a/rest_framework/tests/test_authentication.py +++ b/rest_framework/tests/test_authentication.py @@ -551,7 +551,7 @@ class OAuth2Tests(TestCase): @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') def test_get_form_with_wrong_authorization_header_token_missing(self): - """Ensure that a wrong token lead to the correct HTTP error status code""" + """Ensure that a missing token lead to the correct HTTP error status code""" auth = "Bearer" response = self.csrf_client.get('/oauth2-test/', {}, HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 401) From 48b02f016a827bc254aba2aedb81b472189c2165 Mon Sep 17 00:00:00 2001 From: Kyle Valade Date: Sun, 27 Jul 2014 14:01:43 -0700 Subject: [PATCH 151/225] Issue #1707: Add documentation to api-docs.viewsets notifying users that they should use the get_queryset() method when overriding a ModelViewSet method, such as list(). Otherwise, since queryset is a static property, the value will be cached for every instance of that ViewSet. --- docs/api-guide/viewsets.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 23b16575f..774e11b76 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -70,6 +70,21 @@ There are two main advantages of using a `ViewSet` class over using a `View` cla Both of these come with a trade-off. Using regular views and URL confs is more explicit and gives you more control. ViewSets are helpful if you want to get up and running quickly, or when you have a large API and you want to enforce a consistent URL configuration throughout. +## Overriding ModelViewSet Methods + +Overriding the ModelViewSet is the same as overriding anything else, except you will need to remember to clone `self.queryset` before you use it, which you can do by using the built-in `get_queryset` method. For example: + + class UserViewSet(viewsets.ModelViewSet): + """ + A viewset for viewing and editing user instances. + """ + queryset = User.objects.all() + + def list(self, request): + queryset = self.get_queryset() + serializer = UserSerializer(queryset, many=True) + return Response(serializer.data) + ## Marking extra methods for routing The default routers included with REST framework will provide routes for a standard set of create/retrieve/update/destroy style operations, as shown below: @@ -142,7 +157,7 @@ The `@action` decorator will route `POST` requests by default, but may also acce @action(methods=['POST', 'DELETE']) def unset_password(self, request, pk=None): ... - + The two new actions will then be available at the urls `^users/{pk}/set_password/$` and `^users/{pk}/unset_password/$` From fe048dc4fbf064b11d7247061c931bb1038cc774 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Mon, 28 Jul 2014 07:37:30 +0200 Subject: [PATCH 152/225] Fix #1712 (issue when django-guardian is installed but not configured/used) --- rest_framework/compat.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index fdf12448a..9ad8b0d28 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -48,12 +48,15 @@ try: except ImportError: django_filters = None -# guardian is optional -try: - import guardian - import guardian.shortcuts # Fixes #1624 -except ImportError: - guardian = None +# Django-guardian is optional. Import only if guardian is in INSTALLED_APPS +# Fixes (#1712). We keep the try/except for the test suite. +guardian = None +if 'guardian' in settings.INSTALLED_APPS: + try: + import guardian + import guardian.shortcuts # Fixes #1624 + except ImportError: + pass # cStringIO only if it's available, otherwise StringIO From 5429c2800e263d27094ffa814589674b1b02d4f6 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Mon, 28 Jul 2014 08:00:50 +0200 Subject: [PATCH 153/225] Django-guardian version cleanup. --- .travis.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index b2da9e816..7f1fda837 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ python: - "3.4" env: - - DJANGO="https://www.djangoproject.com/download/1.7.b4/tarball/" + - DJANGO="https://www.djangoproject.com/download/1.7c2/tarball/" - DJANGO="django==1.6.5" - DJANGO="django==1.5.8" - DJANGO="django==1.4.13" @@ -16,15 +16,13 @@ env: install: - pip install $DJANGO - - pip install defusedxml==0.3 Pillow==2.3.0 + - pip install defusedxml==0.3 Pillow==2.3.0 django-guardian==1.2.3 - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install oauth2==1.5.211; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth-plus==2.2.4; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth2-provider==0.2.4; fi" - - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-guardian==1.1.1; fi" - "if [[ ${DJANGO::11} == 'django==1.3' ]]; then pip install django-filter==0.5.4; fi" - "if [[ ${DJANGO::11} != 'django==1.3' ]]; then pip install django-filter==0.7; fi" - - "if [[ ${TRAVIS_PYTHON_VERSION::1} == '3' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi" - - "if [[ ${DJANGO} == 'https://www.djangoproject.com/download/1.7.b4/tarball/' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi" + - "if [[ ${DJANGO} == 'https://www.djangoproject.com/download/1.7c2/tarball/' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi" - export PYTHONPATH=. script: @@ -33,7 +31,7 @@ script: matrix: exclude: - python: "2.6" - env: DJANGO="https://www.djangoproject.com/download/1.7.b4/tarball/" + env: DJANGO="https://www.djangoproject.com/download/1.7c2/tarball/" - python: "3.2" env: DJANGO="django==1.4.13" - python: "3.2" From 0cbdbd24e57fbe58b90d77e454594a49a5f357b8 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Mon, 28 Jul 2014 13:54:43 +0200 Subject: [PATCH 154/225] Updated the tox file. --- tox.ini | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tox.ini b/tox.ini index 279f79cc4..72d156f9b 100644 --- a/tox.ini +++ b/tox.ini @@ -12,34 +12,34 @@ commands = {envpython} rest_framework/runtests/runtests.py [testenv:py3.4-django1.7] basepython = python3.4 -deps = https://www.djangoproject.com/download/1.7b2/tarball/ +deps = https://www.djangoproject.com/download/1.7c2/tarball/ django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 [testenv:py3.3-django1.7] basepython = python3.3 -deps = https://www.djangoproject.com/download/1.7b2/tarball/ +deps = https://www.djangoproject.com/download/1.7c2/tarball/ django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 [testenv:py3.2-django1.7] basepython = python3.2 -deps = https://www.djangoproject.com/download/1.7b2/tarball/ +deps = https://www.djangoproject.com/download/1.7c2/tarball/ django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 [testenv:py2.7-django1.7] basepython = python2.7 -deps = https://www.djangoproject.com/download/1.7b2/tarball/ +deps = https://www.djangoproject.com/download/1.7c2/tarball/ django-filter==0.7 defusedxml==0.3 - django-oauth-plus==2.2.1 - oauth2==1.5.211 - django-oauth2-provider==0.2.4 - django-guardian==1.1.1 + # django-oauth-plus==2.2.1 + # oauth2==1.5.211 + # django-oauth2-provider==0.2.4 + django-guardian==1.2.3 Pillow==2.3.0 [testenv:py3.4-django1.6] @@ -71,7 +71,7 @@ deps = Django==1.6.3 django-oauth-plus==2.2.1 oauth2==1.5.211 django-oauth2-provider==0.2.4 - django-guardian==1.1.1 + django-guardian==1.2.3 Pillow==2.3.0 [testenv:py2.6-django1.6] @@ -82,7 +82,7 @@ deps = Django==1.6.3 django-oauth-plus==2.2.1 oauth2==1.5.211 django-oauth2-provider==0.2.4 - django-guardian==1.1.1 + django-guardian==1.2.3 Pillow==2.3.0 [testenv:py3.4-django1.5] @@ -114,7 +114,7 @@ deps = django==1.5.6 django-oauth-plus==2.2.1 oauth2==1.5.211 django-oauth2-provider==0.2.3 - django-guardian==1.1.1 + django-guardian==1.2.3 Pillow==2.3.0 [testenv:py2.6-django1.5] @@ -125,7 +125,7 @@ deps = django==1.5.6 django-oauth-plus==2.2.1 oauth2==1.5.211 django-oauth2-provider==0.2.3 - django-guardian==1.1.1 + django-guardian==1.2.3 Pillow==2.3.0 [testenv:py2.7-django1.4] @@ -136,7 +136,7 @@ deps = django==1.4.11 django-oauth-plus==2.2.1 oauth2==1.5.211 django-oauth2-provider==0.2.3 - django-guardian==1.1.1 + django-guardian==1.2.3 Pillow==2.3.0 [testenv:py2.6-django1.4] @@ -147,7 +147,7 @@ deps = django==1.4.11 django-oauth-plus==2.2.1 oauth2==1.5.211 django-oauth2-provider==0.2.3 - django-guardian==1.1.1 + django-guardian==1.2.3 Pillow==2.3.0 [testenv:py2.7-django1.3] @@ -158,7 +158,7 @@ deps = django==1.3.5 django-oauth-plus==2.2.1 oauth2==1.5.211 django-oauth2-provider==0.2.3 - django-guardian==1.1.1 + django-guardian==1.2.3 Pillow==2.3.0 [testenv:py2.6-django1.3] @@ -169,5 +169,5 @@ deps = django==1.3.5 django-oauth-plus==2.2.1 oauth2==1.5.211 django-oauth2-provider==0.2.3 - django-guardian==1.1.1 + django-guardian==1.2.3 Pillow==2.3.0 From e40ffd60d44d736d7e27ff454cba1905f0becc26 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 28 Jul 2014 10:11:40 -0700 Subject: [PATCH 155/225] Issue #1707 - Add documentation about the caching of `GenericAPIView.queryset` to the `queryset` property, `get_queryset()`, and do generic-views.md; remove changes to the viewsets.md documentation from my last commit. --- docs/api-guide/generic-views.md | 8 +++++++- docs/api-guide/viewsets.md | 15 --------------- rest_framework/generics.py | 8 ++++++++ 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 7d06f246c..b76c18cf5 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -43,6 +43,12 @@ For more complex cases you might also want to override various methods on the vi return 20 return 100 + def list(self, request): + # Note the use of `get_queryset()` instead of `self.queryset` + queryset = self.get_queryset() + serializer = UserSerializer(queryset, many=True) + return Response(serializer.data) + For very simple cases you might want to pass through any class attributes using the `.as_view()` method. For example, your URLconf might include something the following entry. url(r'^/users/', ListCreateAPIView.as_view(model=User), name='user-list') @@ -63,7 +69,7 @@ Each of the concrete generic views provided is built by combining `GenericAPIVie The following attributes control the basic view behavior. -* `queryset` - The queryset that should be used for returning objects from this view. Typically, you must either set this attribute, or override the `get_queryset()` method. +* `queryset` - The queryset that should be used for returning objects from this view. Typically, you must either set this attribute, or override the `get_queryset()` method. If you are overriding a view method, it is important that you call `get_queryset()` instead of accessing this property directly, as `queryset` will get evaluated once, and those results will be cached for all subsequent requests. * `serializer_class` - The serializer class that should be used for validating and deserializing input, and for serializing output. Typically, you must either set this attribute, or override the `get_serializer_class()` method. * `lookup_field` - The model field that should be used to for performing object lookup of individual model instances. Defaults to `'pk'`. Note that when using hyperlinked APIs you'll need to ensure that *both* the API views *and* the serializer classes set the lookup fields if you need to use a custom value. * `lookup_url_kwarg` - The URL keyword argument that should be used for object lookup. The URL conf should include a keyword argument corresponding to this value. If unset this defaults to using the same value as `lookup_field`. diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 774e11b76..4f345abba 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -70,21 +70,6 @@ There are two main advantages of using a `ViewSet` class over using a `View` cla Both of these come with a trade-off. Using regular views and URL confs is more explicit and gives you more control. ViewSets are helpful if you want to get up and running quickly, or when you have a large API and you want to enforce a consistent URL configuration throughout. -## Overriding ModelViewSet Methods - -Overriding the ModelViewSet is the same as overriding anything else, except you will need to remember to clone `self.queryset` before you use it, which you can do by using the built-in `get_queryset` method. For example: - - class UserViewSet(viewsets.ModelViewSet): - """ - A viewset for viewing and editing user instances. - """ - queryset = User.objects.all() - - def list(self, request): - queryset = self.get_queryset() - serializer = UserSerializer(queryset, many=True) - return Response(serializer.data) - ## Marking extra methods for routing The default routers included with REST framework will provide routes for a standard set of create/retrieve/update/destroy style operations, as shown below: diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 7bac510f7..65ccd9521 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -43,6 +43,10 @@ class GenericAPIView(views.APIView): # You'll need to either set these attributes, # or override `get_queryset()`/`get_serializer_class()`. + # If you are overriding a view method, it is important that you call + # `get_queryset()` instead of accessing the `queryset` property directly, + # as `queryset` will get evaluated only once, and those results are cached + # for all subsequent requests. queryset = None serializer_class = None @@ -256,6 +260,10 @@ class GenericAPIView(views.APIView): This must be an iterable, and may be a queryset. Defaults to using `self.queryset`. + This method should always be used rather than accessing `self.queryset` + directly, as `self.queryset` gets evaluated only once, and those results + are cached for all subsequent requests. + You may want to override this if you need to provide different querysets depending on the incoming request. From fc8eb76c2259ea64a19876f040db4d93e834d39d Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 28 Jul 2014 10:19:42 -0700 Subject: [PATCH 156/225] Issue #1707 - Add info about queryset property caching to get_queryset() docs. Add documentation to the get_queryset() method of generic-views.md regarding the caching of the queryset property. --- docs/api-guide/generic-views.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index b76c18cf5..43c5782f4 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -99,6 +99,8 @@ The following attributes are used to control pagination when used with list view Returns the queryset that should be used for list views, and that should be used as the base for lookups in detail views. Defaults to returning the queryset specified by the `queryset` attribute, or the default queryset for the model if the `model` shortcut is being used. +This method should always be used rather than accessing `self.queryset` directly, as `self.queryset` gets evaluated only once, and those results are cached for all subsequent requests. + May be overridden to provide dynamic behavior such as returning a queryset that is specific to the user making the request. For example: From 4210fedd211eaff80d139a4967577621282f520b Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Tue, 29 Jul 2014 08:35:00 +0200 Subject: [PATCH 157/225] Fixed the cache issue with Django 1.7 rc* --- rest_framework/response.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rest_framework/response.py b/rest_framework/response.py index 1dc6abcf6..77cbb07cd 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -15,6 +15,7 @@ class Response(SimpleTemplateResponse): An HttpResponse that allows its data to be rendered into arbitrary media types. """ + rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_closable_objects'] def __init__(self, data=None, status=200, template_name=None, headers=None, From 59d0a0387d907260ef8f91bbbca618831abd75a3 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Tue, 29 Jul 2014 10:20:10 +0200 Subject: [PATCH 158/225] Fixed the Django 1.3 compat --- rest_framework/response.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rest_framework/response.py b/rest_framework/response.py index 77cbb07cd..3928ca911 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -5,6 +5,7 @@ it is initialized with unrendered data, instead of a pre-rendered string. The appropriate renderer is called during Django's template response rendering. """ from __future__ import unicode_literals +import django from django.core.handlers.wsgi import STATUS_CODE_TEXT from django.template.response import SimpleTemplateResponse from rest_framework.compat import six @@ -15,7 +16,9 @@ class Response(SimpleTemplateResponse): An HttpResponse that allows its data to be rendered into arbitrary media types. """ - rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_closable_objects'] + # TODO: remove that once Django 1.3 isn't supported + if django.VERSION > (1, 3): + rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_closable_objects'] def __init__(self, data=None, status=200, template_name=None, headers=None, From 5e02f015b81bd743f6ee78f8414eca4e93eaab82 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Tue, 29 Jul 2014 10:30:08 +0200 Subject: [PATCH 159/225] Better fix for the Django 1.3 compat --- rest_framework/response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/response.py b/rest_framework/response.py index 3928ca911..5c02ea508 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -17,7 +17,7 @@ class Response(SimpleTemplateResponse): arbitrary media types. """ # TODO: remove that once Django 1.3 isn't supported - if django.VERSION > (1, 3): + if django.VERSION >= (1, 4): rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_closable_objects'] def __init__(self, data=None, status=200, From 57d6e04ff5512f11594e29bf49c47b610594666c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 30 Jul 2014 12:50:36 +0100 Subject: [PATCH 160/225] Add django-rest-framework-mongoengine link. Closes #1722 Closes #1562 Closes #1545 --- docs/api-guide/serializers.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index cedf1ff7b..72568e53d 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -580,7 +580,16 @@ The following custom model serializer could be used as a base class for model se def get_pk_field(self, model_field): return None +--- +# Third party packages + +The following third party packages are also available. + +## MongoengineModelSerializer + +The [django-rest-framework-mongoengine][mongoengine] package provides a `MongoEngineModelSerializer` serializer class that supports using MongoDB as the storage layer for Django REST framework. [cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion [relations]: relations.md +[mongoengine]: https://github.com/umutbozkurt/django-rest-framework-mongoengine From 80444165230f019b09d7f19d31c49fbdd68b744d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 31 Jul 2014 20:19:28 +0100 Subject: [PATCH 161/225] Add platinum sponsors --- docs/css/default.css | 31 ++++++++++++++++++ docs/img/sponsors/0-eventbrite.png | Bin 0 -> 22429 bytes docs/img/sponsors/1-cyan.png | Bin 0 -> 6121 bytes docs/img/sponsors/1-divio.png | Bin 0 -> 4864 bytes docs/img/sponsors/1-kuwaitnet.png | Bin 0 -> 15489 bytes docs/img/sponsors/1-lulu.png | Bin 0 -> 18013 bytes docs/img/sponsors/1-potato.png | Bin 0 -> 12190 bytes docs/img/sponsors/1-purplebit.png | Bin 0 -> 9161 bytes docs/img/sponsors/1-runscope.png | Bin 0 -> 10913 bytes docs/img/sponsors/1-simple-energy.png | Bin 0 -> 54455 bytes docs/img/sponsors/1-vokal_interactive.png | Bin 0 -> 12532 bytes docs/img/sponsors/1-wiredrive.png | Bin 0 -> 8082 bytes docs/img/sponsors/2-byte.png | Bin 0 -> 13690 bytes docs/img/sponsors/2-crate.png | Bin 0 -> 8257 bytes docs/img/sponsors/2-heroku.png | Bin 0 -> 7337 bytes docs/img/sponsors/2-hipflask.png | Bin 0 -> 6016 bytes docs/img/sponsors/2-hipo.png | Bin 0 -> 8111 bytes docs/img/sponsors/2-koordinates.png | Bin 0 -> 1934 bytes docs/img/sponsors/2-laterpay.png | Bin 0 -> 2003 bytes docs/img/sponsors/2-lightning_kite.png | Bin 0 -> 6715 bytes docs/img/sponsors/2-mirus_research.png | Bin 0 -> 12414 bytes docs/img/sponsors/2-opbeat.png | Bin 0 -> 11603 bytes docs/img/sponsors/2-schuberg_philis.png | Bin 0 -> 21870 bytes docs/img/sponsors/2-vinta.png | Bin 0 -> 9918 bytes docs/img/sponsors/3-blimp.png | Bin 0 -> 6241 bytes docs/img/sponsors/3-gizmag.png | Bin 0 -> 5370 bytes docs/img/sponsors/3-imt_computer_services.png | Bin 0 -> 70397 bytes docs/img/sponsors/3-infinite_code.png | Bin 0 -> 21786 bytes docs/img/sponsors/3-life_the_game.png | Bin 0 -> 5485 bytes docs/img/sponsors/3-pkgfarm.png | Bin 0 -> 2275 bytes docs/img/sponsors/3-providenz.png | Bin 0 -> 12580 bytes docs/img/sponsors/3-shippo.png | Bin 0 -> 7345 bytes docs/img/sponsors/3-thermondo-gmbh.png | Bin 0 -> 20046 bytes docs/img/sponsors/3-tivix.png | Bin 0 -> 4091 bytes docs/img/sponsors/3-triggered_messaging.png | Bin 0 -> 10509 bytes docs/img/sponsors/3-wildfish.png | Bin 0 -> 4137 bytes docs/topics/kickstarter-announcement.md | 29 ++++++++++++++++ 37 files changed, 60 insertions(+) create mode 100644 docs/img/sponsors/0-eventbrite.png create mode 100644 docs/img/sponsors/1-cyan.png create mode 100644 docs/img/sponsors/1-divio.png create mode 100644 docs/img/sponsors/1-kuwaitnet.png create mode 100644 docs/img/sponsors/1-lulu.png create mode 100644 docs/img/sponsors/1-potato.png create mode 100644 docs/img/sponsors/1-purplebit.png create mode 100644 docs/img/sponsors/1-runscope.png create mode 100644 docs/img/sponsors/1-simple-energy.png create mode 100644 docs/img/sponsors/1-vokal_interactive.png create mode 100644 docs/img/sponsors/1-wiredrive.png create mode 100644 docs/img/sponsors/2-byte.png create mode 100644 docs/img/sponsors/2-crate.png create mode 100644 docs/img/sponsors/2-heroku.png create mode 100644 docs/img/sponsors/2-hipflask.png create mode 100644 docs/img/sponsors/2-hipo.png create mode 100644 docs/img/sponsors/2-koordinates.png create mode 100644 docs/img/sponsors/2-laterpay.png create mode 100644 docs/img/sponsors/2-lightning_kite.png create mode 100644 docs/img/sponsors/2-mirus_research.png create mode 100644 docs/img/sponsors/2-opbeat.png create mode 100644 docs/img/sponsors/2-schuberg_philis.png create mode 100644 docs/img/sponsors/2-vinta.png create mode 100644 docs/img/sponsors/3-blimp.png create mode 100644 docs/img/sponsors/3-gizmag.png create mode 100644 docs/img/sponsors/3-imt_computer_services.png create mode 100644 docs/img/sponsors/3-infinite_code.png create mode 100644 docs/img/sponsors/3-life_the_game.png create mode 100644 docs/img/sponsors/3-pkgfarm.png create mode 100644 docs/img/sponsors/3-providenz.png create mode 100644 docs/img/sponsors/3-shippo.png create mode 100644 docs/img/sponsors/3-thermondo-gmbh.png create mode 100644 docs/img/sponsors/3-tivix.png create mode 100644 docs/img/sponsors/3-triggered_messaging.png create mode 100644 docs/img/sponsors/3-wildfish.png diff --git a/docs/css/default.css b/docs/css/default.css index af6a9cc03..090d42a62 100644 --- a/docs/css/default.css +++ b/docs/css/default.css @@ -307,3 +307,34 @@ table { .side-nav { overflow-y: scroll; } + + +ul.sponsor.diamond li a { + float: left; + width: 600px; + height: 20px; + text-align: center; + margin: 10px 40px; + padding: 300px 0 0 0; + background-position: 0 50%; + background-size: 600px auto; + background-repeat: no-repeat; + font-size: 150%; +} + +ul.sponsor.platinum li a { + float: left; + width: 300px; + height: 20px; + text-align: center; + margin: 10px 40px; + padding: 300px 0 0 0; + background-position: 0 50%; + background-size: 280px auto; + background-repeat: no-repeat; + font-size: 150%; +} + +ul.sponsor { + list-style: none; +} diff --git a/docs/img/sponsors/0-eventbrite.png b/docs/img/sponsors/0-eventbrite.png new file mode 100644 index 0000000000000000000000000000000000000000..6c7392936e88fcc292012c91bf5dccfe0cc38cdc GIT binary patch literal 22429 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/1-kuwaitnet.png b/docs/img/sponsors/1-kuwaitnet.png new file mode 100644 index 0000000000000000000000000000000000000000..8b2d0550a5cb17513986fbf424ad2542d02c04f7 GIT binary patch literal 15489 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/1-lulu.png b/docs/img/sponsors/1-lulu.png new file mode 100644 index 0000000000000000000000000000000000000000..8a28bfa9f42e452539e42d685e5867ba9e57bbd9 GIT binary patch literal 18013 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 |W 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 P2flc^`njxgN@xNA`=#+J literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/1-potato.png b/docs/img/sponsors/1-potato.png new file mode 100644 index 0000000000000000000000000000000000000000..ad38abdd2c3c56c0fc369951fcf6f7894ee0e0d0 GIT binary patch literal 12190 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/1-runscope.png b/docs/img/sponsors/1-runscope.png new file mode 100644 index 0000000000000000000000000000000000000000..d80a4b85b8d48c50f15113b5b869972e5e43bc7e GIT binary patch literal 10913 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/1-simple-energy.png b/docs/img/sponsors/1-simple-energy.png new file mode 100644 index 0000000000000000000000000000000000000000..f59f7374c3573d1225018d7a542df9b60752382f GIT binary patch literal 54455 zcmV)eK&HQmP)BDP^A%M=00hhL2td-^qM?NfUHZCKD?b+yP(Yy$ zCNMyuibfFuH8c?mMWdKEn75b(Gz-jjnun+HxCza4F`Z9O7ws&Bb}>JlE@mMt+R(Jx z0t%XO>9Li0{6?CRAV8svs!`=eRaLL}74JutAFWL`$8Nkb+8Ea>8!MZuvhFeR@D=KYS500D%k0RkihMpd(js_Nb%K!wHpc4?O1w-8nOAqI;}O-0Vk77a^>9Ogg- z0C>Owgt@`(P-Zo~ZUvZzs%c7r!Pzei@g-rxA#v%L4o*qyqAU$Tw@j>Kv+i^oT$ZXz zl0v{K%m4!orZ9t9jFl59izFffW@bhhVnmqREV60ZX&al<`SJ05e>Ojz&rkD*IB&2B ze$ikaEwnMhS~Ue!EkuiI(KMQ9y6rL(QepX7C_owz0XR6)IXZ#!^p4KE2?K~);40J( zRV{VxM{ASy^~vT+wZ2-dt*>m3>lNoJC!7zfv$J&mguxei?GAr3 zoX-m`k>q6luRq632>@zP&DSskS}cOKYM~8n(0Qwi({MZw$MbgDg!$?G_;_}>Sj^^& z*>rw9T}+$U1~le1N-(IJlFB685Fs$=Kw1n(nuN9t4|OlZ5TF_mp^OlMg1NOY6R3!a zzWH3QbI_<*aU%3XJUwR27VbV-G;YJR%Af^?Z7P00-X@5;?*Pumm z8URzFWofAfgMly+@371SYT7iX&XFV>=CEK<5zU%YJD4^{)5YQOZ0~sXc;1{IO&{Oi zxf9}CEd&cHNv?|X&IDPj3kE6(GOM$ML5z6_7Bwq=w!F-=EOaw-B!Qw)56>_dG?FF` zPUsvff);4e%s`^i*jON>lG^)fWwdr_^~$BSYpdh+%j?%RCYM(H`ifhdU~R+|M=z`@ z)G+``s5mG2)k?Z$dZVJMSyB!3`i_PNzR^NF`wsdw@Z!mpmxBovFa;!85g?bg#Q?;? z6w4VVG$0H$)Vz=qZ|42DZ6k#uqO_ zo&ZS^(V#FAAcSy9c{QVv<^ka%HjF~JN=Du!wocU57_a!Xjg?DVE0@;ltt)G9Y_49~ zSlL>iT&XY;IGPhW7zql*5X%PUBPrAv2>rlQl9bbIo);S{K?Nby&OzvNe^T; z%lpY8Y`}pANUvKC^w40Ew5kr-AuL^zAYf(y4KziJwotTzrw6mWliB_}Opn@w$J3pY z=J>QZolTG1*tEI`VZI16Lx3oq!I?pg23Tad38;DO@Nn$rf@Wrb8I6gPIGCpVQHK9w z`Votri2hQ5v14$xA}a+3%eIw3oH-b&(K6GT6VL)_^jcS}YBwJFQO(u0(bno@V{Njz zwR&}JyfLcA=oMLUl%xVjdhIH(m6#jLS5>0<;nuQVd_Hvn@WplO3otOtN3$s{XP z3%gl)xS=etZnvtk)fiN) z60N--k47t-E0;IcFRe^gFK=DFI=->tu8z1mlJ#0@q9Pp4Re7wm(9jL`xxwHEOenaB z375s36*WMHKkladg`<~%2{Et-GTuETo;k6nVki_GdP2b>8U7Xw@LmEN1xz7)8t*scJEr_cdK_7pa+<86pZZ^;%TA ze__G|#`e4a1x(nj%<{uz77G(=Y6dMqsu|5`6d*mtU{pXfbB$06 z>vLeEnI+526m&c*46Kau%a>KcU=ujZHXd=O@#+w>N*Vf4cM9?3dbEp6> z=oC{~dXBH)pR*HrmR>aWOY~AoLhE1;gCr9PxM+s@M_&NZq-m|4#?yJ69?W+h9^H9( z@ZjO${k!|OZPci1%|}CPR03j%ZV{Y(&rNf&QB2tXsWR2lK?i2F<~-^Q7y!fKo9ZG zA}v8oCZK~c#ThZdqXsdD;2B-5_2soUUc2svqg!PWOR{pDK5P9`-+f{GAL zn^xj**Yjt$Z7iYOxIi6!0mUF*4kpZCL=P6)5*VYd*kJ(jJS8_=nXg5}ETU;KeLTH; z^U)`}M-R6je|p&L1uoPDctj`?K$aYltws6DiJ~cu{ibv7at0+kur=>coerBv-}y~T@K7g=dr6fz4#Hk()Z`nqq$}3R zq_rTls$x~M9?ROhH-2_x7{%WlWf` zP^y8wpzN`n!Fl-+(w*{NQTWS{B&iJsUanF8=(i34vILuGvZxgXI71G3Fr!ceu7*M|WGB!bO=Vp%6_RRU`wm zt(BB7vRv~?8?&AJ06Ra}Zp@jaXq9Ic-B)avwmg`N#?)OlKSahyi&fc= z`18dRk}rcK)DjV#@K7KpD=}xMWhwN65eq>P-Ccq_Mf@@o|iSM6kO*A-*o!w(8)RcjAdzs z4rcA{)v4#byz7=}5r~OzBwd?E$|iTsG!2B zpfRV_#IL-*_2zqT{p{Mt8<$tFt;y99Hw7bsCtUVtm^co&jAzDTC7q%>EH9iSd>NQ9 zD1yPc{Q9%SQ&12Mjd*Xg1`EIzm`~&3NB4jG+5JBr%yv$~{%JU9N3D9Wa%`iv0crx7 z_=+Ud+R)NTvUOtC=8|ut56Gg~Qk+V^M!SCk7g{sDM(3>@>+^g z%7xEt7oCZz?4B-RQdvzWOv>I=^jpc&3duTjjv%7|)Ib=hPT;&$m9NHJ|H0-@UcdbI z>sxQVvGIcn6Tku9qjsnSWC=h3mb46mm3%_Ji1~ysgH6~?dzqkYouoel<2;_s8ql|8~z%Ekqvy1R9^=sPAnH`L^#C`xU2c_O)C(wqpT zz_@FyLAG3bStghNt9E9ssQs)&s> z3q8Jna&!0S-Y2(z_h|pKhR5m`T17h2d8H2_)Kb&fhNjiv94UZEv8HW!avi-3Kae4s|tWAVgAFdRB%d?}uup772<}UPkG4YGBNc4LFD_YOnQ}D_6H} z{OAWiySo1R57yqRWuwN}!3!!kjQ%Z;hrwHW5qa??U_wQVh?*3ZXfarTAflPZ<2%PU zKe_Y4_U@9CSP{eQ^jDkbI_<|2BnFSUpf^>fWnnC{+s{Ne4#AD=Y4tu3N7 z@Jbp;1w}kE^Jo!ET9O_hiK$Z3LtZY+TW+y)Qf{Cj*^{t-@ppPAko7%c=@RKITh?*$ z?H5vkN6sC@(w(1tdGo;G`Jn420boLu_R=H&9F_19OhNz{bvLVsxep9O>NwyTR2i*82Fyo_fj zOkY?g?8!`$w$nsturSxz&g}lpoey`99&GP@ayWm)afDNqKp=6U8ax~wDTpC3r(bkc zDiZhQ|4Elu`vmGG4Tp?G42-8G&dV#?tUu+c%)FiZaDJp;q4Exl-=ajHwYWO8lih16 zR_56Ymmpb|4XyBMi9BGxQxVVz6~aKN8N+-QoB951HangzPOfad{!Se(S5|Y)bL`;c z0&nJ5Z8Og$Js0Yz4T7_lNdZX#&eDaY#U!c9tCo;Vc}2Ux>7&!TclK_6c=IIqAy>M z`l45zX;>aAv*7tFEgd>D{PPShXFV%ewq}nMH&a%^cI6_THyo{Qmyl?VaQMv#_VtOc^4Zh!j_vUTsFOnSlo_ zNPVYel>znO=PUX?Ni0X7f{sR*#;9=cj(%)wm2JMh@q@RnzW;ac{Er*B>fr=#gmE!5 z*+Ptx@Fg)Y8lu4uQZ6eFm{3c4*t2B9=Sm&TiEl$CC+kny7u&x*g(_l1YX}GP-JR+E zTYDdGKltQ$u|E&TLFUHBM2%gwCMmGW0M^S_KgV$2%`|&O-+__>D@fM187LIMOf4>S zs?m0ibY0^X_IXFU`*IrH+k4}zvr$>8Ljj-_^CpNC7rkBKoX)T$l zs?jJpOkD#+K>RUl?D|#?wSf0PO(t5Et&&; z{M!%zoqBw;FqP=)NEZ7pT_fE|&bA@OMTmlSq z;sgd$jlol#IS(&k#6^8=^X<2<{p25h@-M6Mn#07Q_QClC0Ey-*A~2!v{iZ#O6{|2) zC=1a&0ipOy(Q_dQ%kDz}0x8whDd}C%AQ3U7sH_MWng`SEos$QD-u~Ug!!H)$sC6@` zgVLh4L4!fXxwNsxXj0N0F1Yi>sNO4jHk6(Ih(iP243Qw=TopoNgwZI$*YWZE?)2{T zaCY?m+y8KR?X_#8w<(}y6!%4` zbWWovQmC$h5fap9G2rh07o)Y+d7O^6Ms9#L`tod7PH6A&BrHq z{_w?r+`RYu#!byByaa^EprWJED8vwh22}@v?@&4TTAW2q zRa~EJ6F##V-p>gW4ud_4dzEI4Ei?iPZMTm<|M=d&?;PKLaQIn}V{@&+R0(mBF+dGU zdJ+m!NpUIp#VpcdPO&RYXnjTzE#2x_J@_jcP+<}^OFljrjFi-6&fs8Dnl=eU&7$Rk zK`O@DM&tSBJ6Hea+U5^_{Q5s$U3;hE*rD`0?HS&ktq~r*=}gpwxtWA=wIGYbFH6IiUiQz538pbsku%Tm;$@TEk~< z7`!6nxiLTtF{l1_5u!AzT2?4aCRD0WZWc8fV9rhB$-~2YP1M!#rP{Bo*Ow-4%}@>c zj68R<{9G`h0a3M&1yqF-BO7q4v)czBe){NlA3ys2QM04cR;y@(nkqr>L^YCh;*gxO zMhg_eV)E(d%7UN{0`C(DH%OmbO4xZt&y4y+S*}tSL1k8GsOAO{hZGUQ8jJOs zJ)S){c)YiFymJ~4-@W|P8*4ut%T`Q=i6boJz}M*defXKWik}O0)Rw{(QqkzxB201e zt55#x_Q9VXo_u;39;lCSl!{HGVsPMyJ|;gdldmtQBot?V3J{GlXpLrIGB{!>Ndth0 zOqoWn=$VpsEn|AOo&teT=8}p9@HF3b=LHpv3EQizcBn+155A6TZ(aV$k6!=9-~ZsB zJyr=vlR8g&%AzWT-4v8;sL1!8oc~-%LXAOXO4DCKaZG;_AcbQ1+!`m&(JuqGwHmwB2mGvvVZG5(C_q zBq&vK#AuNU3(BYr5%Iyv&2WEF`T9rKe=&CJjw3ZGd$i^?(CyI7KI89MC_Z~8Oc6$O zDcaT=jga;v9PCVQ|KW@OcK`I#mNWAWg-HEOv_$8cMYK^(x+X@6v-<3r6a!cKTLa5R z;43WV`J^JLfRaW%fHYH5l%}5GEc0kEXH>w{)DUTKPR!Z<;^EQk&?uX0moHClta>U& zB{wvt^5IDbN;O-bNn^EVu8!uQ?O8h$KQf}h>~XvO@txm(eET;$vp=@(7&TbA5Hu>y z2yvB-z_gRKOjUEu&~0Xk2A$6aWpl;ye^mHXmJeAS)mQ51Gbinv-Z*3?G$4Rs;J6Z~ zqxqzoT2^}^s4xpuCl0h2g9B?B*BHeU|Hj%+|Ms1K{Mp;T+{ASsg+>AByrxA%uOi8L zRb5;b=r2gmmR>?`8Z72^`fzspKR*1wb`HNdY94Vsk8ZB1I*EBxPjlXRibV}- z8lfZa&D3HPs1|i1JsJ9sEc2QS##B};8TFDX7rn9xpEF99nHAnE)l<(X)8G7W*C%g{Wm3U6ZLGZ`#UQQLB!zv->Gwv@ zTpcwa8X7}5X?Gq^AO2zc*AMnSo7sW-g)+ocJ_1f4LPrS9pfSf)06}A}0}6IKXSs6r zMdWGAGgko7oYWMTOpsUfyilp}b2cY8+3>}sl*2B{hHF%q6K7zwKtfHUwvMWPcG&E+ zd+pZU%kGBTnp|0Po29&l0WggsLVn+#dOQbAsD`L)7ka!q-M;(y*2lMgJLN7$%G!cO zQwW5lz^4~v8X>_-=(Q@q6!vY)QGo2=7t0W+3@VbQ>Jp{Y?JHmX^G1DH(FDD4>7PK6 zeoF^wP*`ej3=_~y%`8TkRn0uc`E>sAy${yc)}yq3T-R=_9Yn4XrB&Sb@`OD-V|6rQ zp;*jq?~}a`e)q}0@0~n4;bCx#Mw);CikjsVX&7TnJ)`MB?;sLMGfSOF08{oGTTdop zl3J*z=$I4*V5tnk5U_d0iqDz``7HTEDr9Q8z_JumKY|G*Cv){!2O}tzbBlJNX0DQ0 zGI?v~n|WKKt~idz?zPQ#uW!Efi+BI0cQ=0~s0mkF<%OzhYR-?o=cj(ElJE>WU%aGq z*h(rZsF+RT!R_M@e|Pi0-#xfB4^vE10mG8@oNJt!nTn9k(KHaMDOHR{$r%%A0H)`h z?rxyW^y4hyY6B1cEBbCUe1qqsar=@6rAq0!-**)uwNqr$l}focrR6DO2|dhssY$9y zxrjl$7a~Rl90{Q{VL>rvh_c{XwL80e+8j5b)%WbJ%^ywNy3!?dR2+xx=$=4l3ch!b zi~O zE}%5QxoiTK1mMz#SM;qY^ULyU?Aj3zP65u?3)cN(aIG^x(ymigvE)%&p!J-PexPQK zlCYevYBx+trcTUs5;&hmdw8&Y@8I+G(I&^X>NX9@ApAP~b7t|J|LU93Q(!`avNS#z zMuvgfc|wUSyPUJ2!PqL>){eJNKHT2>{69YWf11(M#=&UHXh^{>W{AV`1KCPY>uRkt z2d06&d*XZusCGjt+}aJQER0u*=yxWftIQ1`b+#DvXB^B*VY^`~rF^(AaJmTXpj@6mWi_ZFf^gLWN2l%X z?&{EVnHT%7mg8E>uJ1Jq@AJs@9|VUI?+ewKD$c+Sd5>6>bGM4-Y~d)q2^?)M_z{ zh(QCaa>qf;=`*tlJAHJZMGQ!D*icITG>msXV!!Z z0Gl~5Ckqp#%ol+`T~`{_k1U48p3TBd$jXm)Fs(v5C%L<(Ox?B$-@JT;s{1Hi>d}Q zM_~+%yC>UmdvQG7-~8p)Dz190IMlhzDF8Dvixz`iTnXdLCFPUpskhFu-7n4Hhs4O&aLGok@zGY z>ondAw)5f7V$`at%uF?qVpN4GLp82X;@*RO+_47ve{%ab$ITsQZ~2qUv}w25dfWj5JXrqQ2#KOwS*LR=|WNaSQ^0~X*E(~s`|?hjx5*N3Nnv`J8>(LmOq z2TY-7yb-VH#iZoxW6HTMatz&aRh-{%OT*)Av&Hi#3_Xgy9(YC)bzXxfl9rl7XcsLd zml9SDB%QaDcvw|+?c$%d|G%}h^&h@Azj3KrMMc&*V$K|r2hwE>l39_ix{-c6dOChW zl(#q>h5;=!BF=RBo16dbvz=exJN~3~M}9nlT5Qd%@qQ$f+;8a>z1XD1MKDN&&s8dx z(u2<7HYL-YTGYMb|6bB9zf#%^WGra8AWa9FDnOysbroXJXaF5O7?DD_7$cg7TCI&f zxcRGtWlHCxdU_1xL4u|nUI^I@aw&cB+U9w-l;BqDtVYh z>d+1=3hRX`YoZjRn7O)Wq!B8miW@;m<%Vg-bbCbgao(Qnp4{_0qw8DOUt9g*NY*Qv zSnqb}jKdsbiJ@cVo6}Q~gqDbbW2^ZidB{r>*przi2Ty5K6W8e!r| z6$jW^6ZaLpphWcEi2QHq`iv&FD;cwDWy3Bdw^Jx~muGFr5yocBt$P6smrCEHez<&k z^U~0_8ZJ0(c3Z@RwSZ7fSx-V5;F5Pgnz9au%}(5JxA$LL8Es1KDyg*<#}1b%zDHF! zBQQjCzbP|56&J-YOi(SffVxT+Xzm~1x^wu+XODh&sP|C?mnt>2Mo>&#+InbqPO876 z7m=9zA7=)G0n0{U6IwISi{$1YW){t&WzQZfftsnNi#S3mffA-#81PG0c)Dq>4or4H zLoaD*P%3kkTM>eAfLSj3WB{X>uQfK)<7V;u&;Iq&>J_)8Cbm&sfjSrv4D=~G)j4O- z)c=VP4C2baK0O_DG=$tE08|q*Sj1vKKK%3jf4{r``Cg*+<-v53kzaeRI>T3tUEJlqfvW)^)6=O!Bsns={D>h{emhzWCIwxJX$us(=Egi{& zF5rYy^J5K|kX0dD^ui|B1d@?etpv87OJEgI00{{_vK`PjcXb75? z06~FIjm^z0Mgt(;dG8IXs%`Ax(+jSiDM742DJol{xq3F~pm-MrHF5nWR*sS zdZpT*-EH=!JlVNJ4}!e)2D;r@(}&5SQY!b%BMUIh;TK z?cIO>^xp4vbyoYhShULIlXnhI#59Jet-*Ziq>ESj=u1Gdq=hC!gW91rAnM9zKvc;T z$Y~H%k|L$eMe`{WPt81|!7OT{L7_;MMoZH|*qMzxNhcLceJl%bQquLMp!nJ`W0Hp) zUO@>6U2IyUMl@{e6`6&j-*5kFef^DJyzws^qiX@IW#qCDr$b6Y1R2WU_>Gjir@(~H zAzD<#$e;)hXWRDg=5X8Zh=7JBFXW>I zIHj7Vd?u$NRtZQrL_$-+1*Ssm(G)a#F9aB)HX>03&Pf}?0LHs0PK?@>xmLfe;-I9$(dDa}(G|?}`M6+}9;KAYDqv@WFEp@IU=pYIMB21LgXfV);+1U;46}_;O+Cg@LCV}343Fe%H zV1Sa*2|VE(DuH*-J34W`MGV@s8iIzDE1$Z2me9-#S4wx>imI6t+lOdPN-$uqqMyQ8 zvunF}$ut+%x2^(PG`NruM;8xgdykIp)P8iSz9y)cN=xA>EZ^_Og#Atw1KK&}`*H7& z+rPWNf2*lx-Z4Y~tgA}EU}_eOh-JU<-p96unR~^CPokkMlj!+P-D*G?t)0E zggGb~J>$q@#j~#c%4)T-I@wrTSzp^++c~^HZH}kS^kjZCZ)Q7U(H{Ti<9~g4_{B8tyE-UTd_|Z> zQ!zsnQK_m(4Qi%{(F_hW&2QkZp3X-=5(z0tZnUTHl4utx|6T6Oce;a7Ed!&Gx$w)d zkPJm@#>$7$W_|6(wYM*ATz&KU+gozQaV+8>*1UGA7J@eW*#F}07kh_0_jhhDW=B>f zOq~s*A=Ozc8G{56dp~{#V*sjv$xAUv=uP9yZz4&*?w4tl4lW{Q28|je739~sOs##y zV|;SwgYlay>+YJD3b_HFnnsFCX?c*ZX%Oa@LcsA>B_Dn?f{l3)pfg22EWU;o$VDLlsrmh1{Zn2?Da7B8S$ z>WB&{U1qh5cTaj%uH(RqF1^fi1NDEPtt~ClZ}&jPR7yE1do(kc=4xCe`4;`++fOGhjFm+Q-)-u+V(52H1WGyqhT z)P%5fPnSJ*6w3-{>hNN;m>^pgKVd>j*^Rnm!Ez`v8A++2EwdU-HK)<0)3Eo&bPAX5_Y$ghfTp+RtFBO1_r@F?WH70}vGgIE%0r^ZnVw zJNus=9Np7;O4lk{kkLR0rFcZCssRKcB3)o#cgMdrI`hcZ7jNwTB}kEzn)(ngVG>0T zDJACyT0Q&ItYC@k?ikB!?>O*jFd=9VOFM1hLYky1XjE~movy4`m*2VkH-G!)KVDsV zqn1&r?&Om^Fw|+htKin=wfWlV)_Cjq;PCM@&h4a)t$HeGq&Fzc(}*spl*^@GvRAr1 zYB|T2N8P=Jt_V$%GgLr@i96NA08`)0^!WbXt=n6l)^2URzAUU901`PO^~}rs8m|p; zsE%GtB8Zw%URTtX5Y&4Cqp^*MkD9xmKl;;0H-D$qTwEJ%F>hy+mCM$unnj~9NA8Iw zvsbMY1Nx?;ekqB7uPTdbYB8#%!o~oINChd*Mb)SQ22v_lQ3Np6C}vIw;F)KGL9jxIJIYA!Z2>v6KPO`2Y(o3H~#={O3>TsXIrDvf9a5188OvjH-eKjy~P~ zVB*#;ZCu%ei>M^mERAX@S0ih%$XnV)RjPTI9#k?DUyD4pn9xw)I zYwanIKH2`m7Y{$%o9$L>O<;_Gc;AMg=FhR%0B7g|7yb&V+k%s2W44$zLLr@qCq%`} ztTlrM2uOxV1Ybf{gG`z{&GG^oN=BUpk&83M#RN7SfZTYd9y-ud~;-pc+b`vP^T zn;9uam<&=c#D=Tu+f!oQu7ND_E3_Q!7PZ+sefY)w4@VgN!`uHWvEd;uC*UWmzSl03 zX52;OlnZvB2+%Jd!a1J7nQ6;K$YE;Hpb_oP^xnPwFHYJ6*4mpeBpd~0JvD~>1=`TWE2o) z767{3DVZR|P>DxywEE2(?_OEGzE*8ok$V8t+7zrN!qnlDxt~iOsKAl#+S(g!XxCS^ zX1Ze%0XQpNcKa$dXBXJFeh~BtAL#stSsMfmk-j;YKHT2@_?_2(bfq2>-pr~mOUkn+ zRM#gUK7Sbeb@s;vPh%Ec2AEPKqB68Pzq^0y(b1iGJn~~swM|W}L|l_290o6?+mmx0 zO1xsjOoVeH;>;Lhw3u2ehzB)7=)@#iM98jcUwM&~dJ`0Sk*&B)oeFgE7f*u;3qI+R zTRe3}QaE^1=Q)1!wI8n6mn%%voLQC{st_>|m%=zw^=m0Lm5P}sTjQ(kYI|w*+T)|W z7-57cX*7^xC2)Lb=zgc&BsbEdgJIYoDrtmZWNoL5y}P^m@c4_3OK*@hObpT&-X4G| zg#oM&I_<>}02xTaVX>v9Pbs*a5HA9wD0HDMMdz6Ag`Gd$`t?cJjWUNs6N`>UvuMsc zQ&St1nq&r&iuT((IllEPd*R8LP*I~gi9nKm(WO?h>a6iJf)uSLehg=z#@MQa)UAk! z4WgxDL@Y22aE9lIGeG^2o{lw{j5Q%rKUOoS1r2r0@%rfUn;Y+q{iestSXOB+O}-P) z>|Ik*oFHcv3StC6@zn(DE!?=;eC^6xw~uZHn*r*IyFF{BT{VsGLLGLMq7F=yDD`qo zEXUlaL_^ccB82%LKL3wvzx>fw?P>}z>R;s2ShSRBE5b0dL=Lq(=dAC4++~wksiPV@ z(@{l%`J=--pYHr&_hdWN)5umC>BJH)W>A>RlD%xSxuu6oX785-LHxB*wu^)U@8F@X z)=4d+_0iVlD_7Q5)>bAfbv-_vpPWvQkEcg_Cy!5?qktw#Yfg!-q6D5O48ISeb zCR4tbFNv5Lh{*0L<);Rl>vV6r^XTwyYcpeO5*6xl<8PAGXqHfQA{QXBr2+nR*Ew9O zkS8!@c5yo$kNx^aeRZ{3yE?gcW#j7F%IeBwGO8!Xvy+q2@o{~$>i70%52oSxXz_R! zBb^o{?1>y$IAieF)3yZ&Afw-%=0-L1zMhO%9lgK{+*y#JQ;V$R9+k_b-JEa&?>HLQ z>okh-!pD4~#{G@S2CJPk>0{iGp`VB-A|^VUo<2Idv$b;V>e}lbBRBMsT%Z_VJUhN$ z3GB|Z0@yO8b(Me$gGQw&%@7|>w{PuzbnDT_7>6JYoC!s9fIR6O+aN9qQ*&G$ydf6r z_bI%v=bg?wgVW*ek9>-Zs%kzpi`7UjzjytYKY8;PS68pUHhIHmRl*#6s7yCtK#S(~ z@y8F2w*PqZR}W4;6AD~xtzF*NZ0T;LsP4dJ+?gf?hyTt~Vn}_by&>fQ5`-65yDIlQ zx4w;$!Bo|YBMB$gw5%&GH#9N>O)9R81smf_&b(Mo)z#uoNIle_Difl(86}s?Hw9x_ zv?P=gfrhG8bL-)s*C%UNE?;(1SK^s=86d~*(7K}F{VU3&&g2KRgmuubLk0}&`wmfF z0-A_8kF!5~_Uk)`pDpAla-kMzB0_B~C04Q3%2&_`3w?XD9KoAR8TLvl&sMPet0F?E z3`csYzP`2o+WR;D@uxTbdCOg`F|JU1ZJBKdI*Ni3B=qgIzq_{j_SMVR|9tb`KD+n9 z>Eg((RBE9&Y`)&T^OpZq9)tv-6fK?W%p&QUW-&dTR#x(1Ek{ui1bwt95JFg0s*%a( zM#NwMs;Y{nW+3ajS|J=vataa^#$4o`xuU5;zvEsa2BpF@#iJFT6vOYTLF| zH9DC+zO(c3$c?t%em%&F104pH$IXKQ1qiwDO?T>OGW_CFfeZrxHHdg1mC`UEEaJ)i z<4^DHe}3HVnr}dJbO0@q6l#KTA*u-qplMM;&nWIPxbk zw49z(wq8?l4qfC{Zl$14BNTOsa{VTuW_S_Kfet~N+2XW86A*>wq(JR}LBNuZ07}9> zQe>IS+lf##Lf|VItxwkGi_t>oaH3}B01*gtvmxNi+yq~j(ijwJN6Xpw1OXKz)JX9r z5zwH~Rg0aIdwLJ=T>IJUEA^Ob;7F{Xssf97s}tvjuY9S7>d=93F*6tw3an5=+r*Pc z$M>e~VQX{bR*eD^s)#wl(L{_iV+M5q#GyD|dRm;}%$J?cXoDopIaC{?%hxyFyuSYC zR(+*H?cih(;@JJi(ihLcRT!;u#H}mCRfFL`F;tl$I?amYyG8OdkWtvN%jy# zi)~m0w1(J2;ujC2x1-Kfv>+&Sbv5!X`_-&;rcGXjr&lhlj3H$tcUVT@M$7IP=45=) zMVy|@4t7uPH}O=_4#U<94l=mHIT`{p#VOehq`;`DHm~#2V7{=!gX#T`Z~fcv?-nnF^$p?|7g||8(A8WY!-u0ik%HO|vr`5F3NufxRHEIUacoAbh<@c|kfnt)| zAJNQIV5;pRPFtOsYk(T?ec!Sr7j~aTV$f{C7W0{aH$NGzxJ7Lt*DN%eLQBzae$#B) zi-_9!1!zD6s{x_0M57mAGsVn_8Crw1IQ{U}@2*X5_;o)5y{x8dM1^x=1{yS467O7Y z)aaK-oo+KQ2niS@6?y{|xOaT>_WnnY4nL38A{AB>xW2VON7f?%xiZyDE$y@~{*w^x zbB{}dDGa1DOCc~6xHekFYJI%*-i@DL9=|c+x_|+O6kbaJxf)qAp^}%!0ZtS!gAn64 zF8wfwuHOIEJWgSrG(#+6Q}wG%%lawhIg$>fv=&W^ z`ThM{_YZCbnX*!ds+I79vr(YE<}+})ET3RmiFoS9$#Y1ZHi=XKFsD6;sd?welhyUr z%e9+0R4|dVrp-m#WtlHkSG$#!dVPImtKvxDnAkTOvUNBF!ny&mAJ$WOKo&P}`k1sy zGm>U8gvBDx%mNS%3Z{JdvP%0+YiU2|Mn^zEN14=<%GFuNW5#>cti-xK6_UY#Iwh{e zf2JGdZW{)(R5meMXfbBTO?TCeJ7&+P+8X# zuV}B`FPHHvb_$tTRk_Lf>LusAFh!k6idab;f05nIW1tQAuzXjPQ39|OF<^>dwLUz& zcYptGqtl2*M5727GT|BWgYVtaiJjAxqOgbU( zqyS2(kD;L|Haz*S#VjnES_-Mmn-)QsdGE)Qm5LLCQ;5Myiv22yg2jG=WRP8>Q& zLp%v(BW}F8aakUDLNJrnU7Nv3CB+#Kw4JxJXstpG zefA7tQi4bdgrB=7o!M{*2;9nOQn?YLH-r*?l09(*R4LzSEfJum#CVgW0%nxcQj&gp zcS^(@x~i63E#}Nsx^sNj6x+ui|7hcvLN!zl6;*n+wxVMWNP0O7F%S zMUv`dMl~}v&H1*@fT)Oy&RI=B)Ev}7J)%R9QY8~b^FZF1;hbBYtkRrm3bA6w7jvIz zHPwsserE0KDLrFOps_DlEqA;wqokcoF#oDN1dO>Y3thK_VCZ2|6!rW zf*=SH4V}>ykA0axLIs+(j2@UN!012^<0TZL24{r+4>GAGA6%D#fJ^JeGrkwYVA2 zgcJ?lZT@%O?$XG!Vlp`MC8ne<d+H3xcz=Glbxt5?LgeMFhxAjg2v?k9xAW-Ife@m-AXMdrScv`TN@=}SWQz77@h+YEG6O|=0aR@{c2P2W3^EcM@piL=()$A=I1r#sc^ zcqA?*6`g-a!_U&0{5Cv;1D=IOw6k#h=;-$GVz0H}(E!E6n><|bmK<{!{ z*#4z=DA3E#e~D6wYU`Bn{6fskV#vXh3CKd0bOTGC07Y`Kc-fFd!g0I%=w$n|`+sg^N>5N0p>YtQ5LD_wpXh<5VU}CE;9mm@`Xn+lRcN#r zQ*dp;_GpUGYHP6MYE7vSNZ2pQz5re5ooEiz2;Tnsupl-fDee~e??t|(5rfM9FV1>$Z1vTq^AVl8^%Tm~J z>=cWf^jV58*uWNi3DPOJ41S*X8jFaj0&rfU#k5?afu2FbteKt0lNGt*QJEqaE&~A3 zT-c}7aU$tt7)nEca1}=D*c#dY&)%CgM~)b#zxB(_6DWBh#|; zup;vx_7Csl%E~^?_N+`#Pao4&HCbJG#vLS`?he3Absx+O4kw+A$RM+hOjk+CaKhn$ z!C-27f4UTSgGLOFMDAyVjQ&!=|Ew^9L1*kxF`Peq21^XBTYvWEfBx0_-!7WPY4eD+x(iZ@C;^-K*0{@If(P(seHm*0yThaQ*DFFt>9@nuAB0!J}r8gtn* zDKo&i19MOg-B3h{hfDSQqo7+Bhyj{3j$^+a$I&M**=0Z3A@6l@fdI~PE@g%AZ@L5A2nZ(ojhYLs!2*21D7#t!m2A?R++BhKm?y!1enBDo(6Y8qxT+3yVdw zj02n$IGB?2Q2}l5{WOPO)s)6v|Gaq;%SK@8#sS+eU#Xwp9smF!07*naRQ@)k0A)a$ zzvicBf9`N9bRbf#keJlce77vX6QPsy1V!K~pwaqgmtTDG^3xY@z7(2M)IFdSdhqv- zv-d|rwduybvXB!#H1*@S-CQIcfHcqiF#9IEJ9*ljw7#M4$N2Y1_CHGvQ$0i>3u#gU z6q&2U;6wi?Cd_ON=UTg`aEBCi{c1Y$$MW>Di0V zzj*$e^Zr%DV8y;j{-*sc*tLe5Tk2{Fw8(Ltu0DJD>o?oyy{^2t?8q}FIyy&+?InyK z=`K3F#7U~^FmBhwCRwtXkv&r&-$?{0l*QsCgf1g&74J;Gw|z5q{%|sw)*`?BH<@#I z#x!h)bw92PKBr|NaPtDwGfPDzLM@V@LufilW5-vEPqD+!73-eSa$RQ!F)9OwC z{PQ=z?qwxS@=#~Qgbq#AMAkou?dK1lO6ITG6k*DTRW*(=j$6}M=D+Upa|F))YaKY` z=uelYt#4rvNKVFiuYuVe>gzY8%<;n7iltzF(IA4&u-Ob7%Q;_r(QepvbyVipoDK;j zxTXzDX6OA>_|99W>UT;8m{~G|nvWa3{PgR8IghWmSSO6Rk-^PT?x_R$Wn{}RVm#lx zdG_}6^X+SuE!~i8@DMS;ENW7X29xYA|9wCE9}DHJjx{=K*+)t!#`Um?Hs+jbJtPwE zq-=k2xjYS_%?)RN_nxn;am~Yk1hV}CD!to`>-BJzVDs?cwY9@Lqm~4{3vFnGjU~W@ zbW-*0hbyG`a3bhPMM9GX**v@a;@QQQm;D>XKqz!d$=GQi*^50-naa*l?XiBddHMDE z*8}?`DWh)G%E_8@Wpyh$O2YS`96G-i&YsHPhGC;R8lufn2xhXZY@tPh9N77UzHPeT z8fFw)+sm!FLEpl9Eo*C+S;veoWCm3shPdrx*3IS|$ZJLPcao6Kxe$EFPAB{(~>?gsrcL( zIY&wO4l0IO{VVHE#i`ERE~k(EtkS1C+RYAaaR+8R8wZ_Vco%5SO?-%hX6Cn2KxR81 zt0CuqLu(78N7FJ1kz0>)sYU-K~1R& zIsd*!;V5|Gtwf=i{f*Qeu)NzI^?g z7gwKO;f)rtH$7<~0e5dGVGX8K(};dgr(m13=E|Lh!C> ze2eO?=j>j$T<^i&VjU8%AI|mZVx#s$+NOd>cJsdP90>vE7`$(TZ%HSV+2&I8xZm%p zWY&5dO`&QL$$Y#TFJ4_fHzbALRK*1qpTi0hYQng+)z$E(*DG~tx^k(C$YHLXBj-r( zMRzDmnL)-hBps8Lj;v)d&6L=AqSSt1XhI2b0tV-s&zMHT!VQr=jFMkKRn2@*HM<(tbFiouv}=C}&z z47o%&E8}Uf&GU<|Uay|7hYRB%1~JJUY9_bcF--rEL^GuIcHIx_G4=q`^<+Ssn-GWA zXK|Yf+SV_Y?NaDjw8OnObsNLofLypY9pG+_x*c@0!6qXVPaD(p`CVxPBqT6&vRE_= zLNcZU$(%#-pbzlDM1h!*CbFclFerAj!rM^E`R9G%lAuHINRY2SCSyj>pP+p1T-d;8*v4qr*>tWa^_5cs& zQHmKpr|fz2O)?e(;e6=YcBxuokfn^R2#PX}L$B*0Hq$Bp?eLXUMMsS5?WI|w7{zwd z_&P4?bj|%6C}4DSUDLT-{Hzel!)_$^Ax*1R`# zuh!jV(Jq2-DDnrRh3=6TPfO5I&72@Xw`qGhzJ4)&b)^>-t1-7*Qz4yZO@S~Bmz&qm z-h9^URYsE&no;2rN`KU87nh?X{079?|952bZ|T_&+wHJT`}kbEJu--W*33Z6J~Umo zph;n}vH`G`-hGQfU7Jj|TXtomjW(_}t2lX{%)VhIqYxr=UAGKAOvwQE9r}Y2i=fIb zBhw7n1`34Rw0idD^UL*l3CtJKW>SG#6|jVHGpsH*=gImCeZaXKoas^3jDD9t>qxhe zUHeQMH4Z7~aDVWe($hDt5pxqpg6+Mh`uc3r8~4MF53rtd3`=&9dzc22Ow%~_-*D&N zomqAhGPuUM;NUQ?o4hd$>}FuDaF+}m+xHwj3=uFY6lOvQs5!UXVbp%LdAl9gb--mr z;sL6fs#4e3oDXl_uAU9pP%?sdNzdPV$P$skk@-G=Zt7i@SEq|(+Kg$FFjifqq;%ah zB`dnQ+>mP$r0LvAK&Nn8QI;vC(uZ0*K=4{K3Zg2^yUM0-VkcHC=h?4wHLumY0Wnb3 z#(rF@Rh);J%6fZ2)$VY93V}ZfCkt74x{4`o<=c8<)rfaE)00J=0r+#X+M;Ug?HBi< z{M<+>3g94(h$a<*^S;-s*O$*%{dvS_)TyY+`x0T%9`<7W>ec4O#qhdarj$95)QqH@ zXVPjae*x;6_L3vLFHO2rMF%oNBvEUNZiRaYO7_l&v*x2t9$o6o6i0e>E-0Ba zx;inWOSRpAA!PtbFat0alS&}SXyMu6Qv}z&-!P+d)Z8`8vGLx3F%7W_D5PP$9 z-2|AUMeJ|&`L#S;nXWZALx^rT2dc_@Kw%SO~>c3IsAB1{b` z0uc%X8MQj=6-L3Xv->@1hm>B7JyTJYv>mQi+l$q3kua#GT-PR0IEW)7;%d8E_p32& zGVW#OLo4~UH|K@lpO#ABE)|~8q%p-YY1&_Wb%y8m+hH<-9W+~?o2_Q7vWegxE`aO_TDY}ILma|1mi!AMj^?JKn zZ7$Seu0w{=gQ6T0VzkZ0<;%_HGNt52s3e4x{q1Vc8}|^yJ<^VbVc7QlnEKj|a?&*4 zMFs#acP$56y2s0insK!`DtXQ%CZQiYk`>xa?p-chEEi_1(fYK}h zzNEm&u6ng7O_k4RDv7p9o8-rUfQ-tvgXDDFnUNU@Sm!|4S}S)OG|o3hACstJG-t(! zktSHjQn5}7ro?BXS<<8izz32dFsPcQqK`h5SSeRds;0T71cq(DzPNn#_U*Iu=ogR$ zL0G}swuJG@-kz^t4cfaVSn3TV9Vk_6C_O{>Ui*!H*kLE9X15^b$60lvm}MZ1QgR70 zXH7pBP;h9l%Wp6 zfnqRPPI5OwjOIN{NWk7JDtnOX1E?zVhT@9Rp80*GuD6$OE?z{tGMpMba(_aDCL8;> zy4t>tsTbi`vMaK0^%}$3Zt`!;){#Dp2#{1TsSa^#mTK3%ntHFcu(_C=jA94ACQDb zOP2a^lc26RyZ0w~auVKp73RvArs+tr;`_t&){L}SQd~95vPeoqy55|?_BpWnPOjfJ zUq^-1{m`4G+DHt5Gm6rnL%>fSL)jud;gLZ`i{-wzqn#{Gn-@;Jp`##ov^>He_BC zmJrT_%voHbrdH745M$~!!=yS|E>wxDvX%FiMWk3&!H;wwvb=W+6VhsXK4NST0RkSJ@I!BrfK^y3VDNyE zA%tZcmRnt`ZOx+Oom368wzyKmZ&Dzk4f*}f4aj_l=@pkK;*Di$0GP}HOrSFsgl(!} z*E!D=Mnv1JHm^p#Qk*G3aoKNS0OxI)u3vwr1q-0yS$lHgy9UmZXKwpw04NBAcj&|d z3e3JEMd(C)*PQg@HpcBRo?F}~H!3l5h@{a8ox8t6%8aukFy^wq3@~Cypep(yUY)OB zjc20?NO*%OQcV43dkLiKaxP#!8FYI#2YdWx#TR>I!UrI=oW-F-?Co%S*Sly66Bq<= zPJ+-o?qxge-c)uKo>QiMop@=zI49zy@oa=%#COeUKW>M( zx!OLz9GZVqk#eh&tQTy$cM}mRSq(Z3aX56X`3O(4TAybxX_0wLD7eWLsNZY!`n^zTsFjb%@ zGgp;Yh0UD*wNKW(+v{{s?%uaf=TAgPM%6CZ;!Jhb7;Op;0NHoLQR6VKqiq$VVv&>E z0H~R}lMN~diopp&=boJSAD+1%w|wN92I4?>c5>Q=MR46|_h{LjE!wVY7t7`80*^cz zK`=N)>eJPW%TNE|)4%=v#s9cU7nH5c~18& zker%h5TBv7p(!VV>Ey`-C~(0yA|A0@4`Vb-z*r1U1I#nbE}H?bZj?BgkmAu0{CM$~ z?a!b5`6vHj6l<4fjqf@v9AfSm?%<`C^%)%C4c_1kPJmOF9zXffCx7#&fBVb-`-_*K z{`U2+9YCK9BQ1V-hyp;?mbK;>LR1Z6FcYI9Ng$oRc>C-so}b}|fu~*(x8v1zSdF?> zj7Eh3jyl;7#d4^2w}CY+*sgifj&vW&c=(pKX|u&zabPKEl?K-*elp@LktVr?Skr{9 zLl0?$bVerCneUQQcrc4EGqt2saV;Hbg;7%ud?4pu?z%=;g*4927*hjGAYzjH(V8XG zCbzy7xPC}Bsth_V(=et{(MbS2GtegQD6_KNgBk!h_=SJcx|64iPa+5x04Hz($<;)G zxwk?QvSu!p(=L7MoO9_HKmHFV?b+34brIjFZs6|N>)wN^`?~y}vo|$q7^YFAb@HdB8%KAmN+{Bxwo(8it&ACC?@C=L5bDVgy!;CNJn^~2%TpzXM}hz9xa}F zX#|oH;tp-(H;?UEp(H&6=zx$wh08qVAg+WAS!csgmAb48} zd;lsfcabtREqSOi6%1*;5)6cx^Qn;}Oiq)BQ9$RUZBN^<=$hpsEKj?$*0;-W_Q?JC z;W4ucpxYLw4rb4H^?7B!;8+-UTa zr_D!kx&7$zM_;Tyg(T;Y?k8TZ39afM&4U&NMTErmYI`+YZsW?Z^@{#-bFtc7Dq_k3 z9?3dKx~Y-N)PKWJ5iO5a;lofCeQIflSDWEV{jQ^}P0p~Qo?N#Bte#gDIs(#!*3s(- zYmh7{R6<6_7Z{U4h+=NS#7Gmfolt~kxj{kBA|{N8q?)Oj!W3e3&U@#Z&^8htpFQpT zq6?=di?gO(woTVIUGQCSU695xw9*8GjtkFa>rR~oncTpi~YFSj_ZWp zD~3(K+6-%m5Td1%Pf3KY-q7-Kzii^jd=E$^ss@^h_QOUyo#f!5cC3dKkn--tK*9%U zg#qvYy+lP+Rjcs=G1eG%4gG3Hiwem4r8y`Zyg(eilMvW6u5EqOx<&9I_@vz@ z30*_KXqG{m#xGmH@V<4v@y?5QPv_uC=RgPX2sAqL%(**7LS$CS5lhat8M*$D)50rC zXGY738CPQ09&HM^uoQb_##K|HBZVTQS9p`qHp>G4@eo1py&R=Fiuw(e1ekzrT=((H zFnGnV9oGG@ft%`;Sh|W%mSr_7u3vNaphvnd)$%t5bUSYJMz!6w(*azYdD|I6BJd$B zoHS4`2C^ZX>QJ;wql2UZK>?c5JP`yfT`P1#apnciTjS^(wn4gOI6Y}j7VXKC#nVN1 zvS^nl?P=R|UVLzkK=OYMp<<{_a#>_oDr|<q!jnzJUoyfZKOy9N1`fPY)CT#Hy?o zV|KbMFk8|kR+$jXu*3_z!9haPEa)1uwWWLG=+5H_T$4MAgsa^f2N4h6j##8pTzFMWn3I5$bnF|VnnlxJ! zK6DFr(qO?c^yi~%Jm@?f#5=SCCv>?#n*c#5^xfi7w|Lz6)6?Z?+q9?4)06J;X?NC2 zcZ#zH3kRRuT?icEnYZoIFwY5?J1dd`gghkG>Wkeu6AU`a#5hFmF$5|sW&;lZp>{BP zhNV6aAojhf22&jS5pEG4G51ea2ed`vRTG-6?MG-QkFdfKF51^ zg56-r0EgA~;&StL#KsTUuKLw_*jQk)k_Ue+Z#Wk$m;Gn*JRRvFiO#`N5lolsRZN5J z`(o;%wcDatG&pfc`FCOIpM1Rd@lPLJ66zhrIq#dc>ALWw@ypZh@yX(3(X`9ZE!%d1 zj&vX$L*Y+~%lz*>TqC(gu$6p6`FJAhha#XY^rA^CcSE=t=*s+*3X>txPAJwkN&LnT^C%gK=-a` z+#Ii^V!H4S8?kA42KK6l!{9cn)zZG|-&qf-oLhF{eH|B;;bu+_^gin%` zOk{F#GpI&G0uyYii<^G5$Amj?cxl>B5CZq8{YXA?KREl*>FKgXBk%(65QwfSlP)VZ z0d7}=i@9@xyCu!t*mqb}`%aL&S~4qh{*d(yQp`*>Imw(u$}anEALx;Lcb-3~VP^jn z2Yy|n#{J2;M#a`FUA%KRUxdDqwsDQ419;0$`QF5eyh`L*HE2aNNHsHoG!Cin<0hf^ zui~>9wrCY%9#AttHK!Js>6B9hRAoIszfKzd_nxxJ<1&tti1w)$=bdZDA*CEv_Ab}e z0p=1|vvLhEjHW=e6qBTe>Mh~*_U&faju-{ev0*ixY7!x52G(pG+4Ei;B622APueqU zb*hK5BE(!xF~wx4>>dTPRX=~k`lOf($i44{dnezVBmf}kr2(|MYUY-o+ntP?AL}>| z8AEm8tUwm!juWuPbQeQVOMnU^yg}6gr|n7WmJSV~&|#ia`;BRyX0h9X%%~tl4JI`2 zAgbf2{eZ2%7%n$yGionZ078tKBTg)N7BD1$Ft^+C=5PP#l|e2Q1!!h5h^eKRMu1)# z8m)&Q2t<@VS{hZ6-6yW?+7Nu`!fANa%1MyL!ar{L_|f8tz$=s`Qn$ZB@1|UJzM6N( z#iIq9`HyB`8OW?OivvFhGWYvm34~W zmRW!cS#?5%z==AVXrz~>bzShj^9>tk=1A8x&2rfV-!!2)Z60@S8Khf=$DSvPa3;mq zf8$>7I5N>riG7cgzcX}giQcVT=7b?SI_DbFX{yPZAFPyCFVtI#nfJ4GUQ9n`vgoZBn+xLuD4fyvt1h!3l8d(B2P1= z{Nr>EVMzx?Sh{?~u=H;?3L!*0o?;}Y88jF5OYtmUpPYzAO-48e5lx!JQcwmL+dui?U;X6CpZ@iq{BMt&j~gsKd<#zv zW|IAKrP-IM_f1CBq>z9YLAFnJO~mFeWCGTH01mwA?ffGp%Q%iu?AmqA{sFMl;3$?s z*Af7sbZys#MH@O;f8TXIDU-#ejPj-o**Z)HM^elf`(f>e*c%j7LoUSJIh0U5Ju~NY z^(Eh}^?uK&;0O{R1gg$*jhqX9`NKy)`{eP@KU)6KVHwakI4QjW1yfKDxoWm#h~{#^ z9Xs4=81VQ44pDX_TV0^MK24LBXwybU0G`e}AH+xWcSSkADH+MB0&`L-%P@@=)98m` zql^$U(aH5f&?ZpiUDW8_X?5RSQcHw0Fs0Ong+(tKo}Pa4S3muCKY8@?4v$e2zH+X{ zt}P}>dN=prDXeyaG=o_J6jZ6DJ2M$bV86@fH{{Acq|Sa1LQ}EDtaZ*LIWifMvRrY1 zX3&CMPvRV%XT1L)49M=$oS%@30;g9)kR}H6rY>#L#&6>mG;u6dAh~SQEQ6?$aK{M5 zJIDZYEQRH2(NPq1l%M?QlhfwRqm6(^0GhHae03IBGK)33TQ!!Xq=3GZcEV*;3P5N1 z&DD{94-Uj1zy5iRs&+0YYG=(r*bCImKB$kiQ6?*8pX^&0;vYQ~^NuFs0Zp3JQqu(yA;f1zG*jK{GTV_3 z^w|fxD_WHtOrBE7=KAMxTYPWY1NFbLXqmkvF=4yai*^5YGp+{fOZ(1maZsi??OaSm z711Y)g)eQ|ov8|U{Y@Wv?@r3)4;hM`$lVT79Mwuu!=yp7wvkQ%CdVfnh1I1e0O@_% zFx`CK1+dyyv)s6TIEE_#R2+Q(R20ZFfj}8EP*pN7j(|mrhGE3!VtoBiFaQ46&;If4 z_PI1s@4L-iZ)`oKlucRdB%_YrH}Hry`l<`v_~ntl8Rb5z%(=QoOGw-8+Ek?`YVPYn zrRuD_7z5~L8IC(|%e=qIKH2{F4->BVW2YPiK-S+~nRTO%7%aob)C4wT>aqIl^3$&` zKKt*V{_WS7U%N%@7TtDxUwDi6NuDsOX-pA)f8}RU_=(=N4HOgTs1!{!2NFasP*tzI?Ns#N&Kh;hUwqE8sMX`51vY3%#e z%hl`8&;RxH`o-(*YYhg%^Z@{7@NT8i03@hcWTZ#UsQCuJKGGf$ASfAJA!iI5)y&r0 zjj0Jpo9|dwxu>LjS7zL6wsJcYVxMEDp7lp21W=~J45~^m1|;YvtuEq=-d4R`T@Dxh zxE_c8V)gdx7hgSFz1Zk_^jo*^$#fi|r@Q|uzdKx-ljd;U5rFlHr&tOcpTWCCl@TewNC{dLpreq=)+Mx_&y&U=L_(l%{>_2n=B zzfb?Kx7%l1y%3kw27{_uw73zq)_ezwq$trKybFZ*L8#Q+SZRjm_jXvjt`y~?<==gF zodoESi$MhvsE~|9O#zs&-d+tUX87to2g-cP_KpL$n-jYibDZ$o$vMedhl0tHQWct9 z|NQEUtMt;s#yM3T%v1<-&IA$G59VM(2G_#wn;m`q#|JvjS#G8cgPHkxg3t7$yRUie z=yCrAfSt!ua~D4o8{?2H>Rm0C=Gja~dH~wf`%BMkbAhAvNe1yT$zY6tnZj~w8N#CG zdUuzbn%##9Z@$d^BXg~VHFsnB^0*W}oR|}nEXRM9q)~^s9kh=&<}>C6C^;}MC3wtg z%AlT!LrH>1=jVHnmX$3PhQN`Fla5YBOj_B-q45yF62{b92;!t`9edD>Nxx0q-md>; z0B%LqlfAQ!QNp{)#F8T*5H$zOEXEl7O~2U;{Yh)CbfPXXdIc3ipedP5^Q)m^b=hp+ z%m(BxBXahO!md1W>7He!o=_kW&IRvVO)`#A=rySUNGfn?OcqnJUH}gYMJe}L>Nd4= zsjqK(D-*~NKxp}cfVauBb_aauEj=8M)?gtc>9co~2CI5dRO@v;=sIB_MD`SZqPTRh zS2IyAh?;?%W`}Zm@19Mm(3y+oeXD!Fvs8AuY-~Xa7MlysFBd19ZIf&*aH>YY`QS{F zjDXTf$gSK|QNgkva7=R|a>FRItSpn>@*(V6B2Ua_$uannV=}XFLS^{SAjug2j0|p5+)? zMH*M+#8Fac&GJ=LZSGi5Wt zCYhP0-`#sn3lMK8E9FK8g{F~4Gm;|WYv)LtcVL?BtvUJF%Ced4#&>B^-ybT|o1r1i zK&YvJG*hHaf3@kafM^&}7i#U;P2H9Cboghp16u zBCuq->902ZN--$5EWH+L)siX137E`G)ruZf`kYKGr-!z?Z)v@JCyAhg>zdX%KdJMD zGe~B+_&by+Bt;xd#pvl5ZRZ`Gni1xNYT0k9LQzX*2HqV#{NBq!0LYeFGJsNy05gNB zr*8W7Wq*Ew*B@gsVh|)}PQ0HL>8cYtVazR!KvR&uq`VAm{yYf#!A?TDb{`t^!7bAP(#CsRW6 z?wr|*6eo^B;GG4J#<#9X@J%KM+W+w*Ym(5Oo|<-;djq?j%S<*U=I= zK-HkgxIDtV0b2wHh%?2j%P;=<%m233_0y9d7|hcPGTd@f44b&!4x4`4f8u_6(wsR4 zGbJ6WZZf;ik=~mohX@ErvEODDGHzRA$$NpmQFR#9E5(pSHr9q{4=0#h(T>SZ;{x45YpAtx_u6E<0 z2ulsP#>=3jkRobCg~KnsODzdCbj^uxWg+H~#31xekqk*y=sb)`;dkj9^BWvj#WgCe)N^}C zf@Y%7U3MSUgr-WQ0%?KV%urzv!~`33J?PdZFvK0ry`1fP0cZ__#=ee*&ZJ-W&!+FPCF4+#jJ zTP(st4H1?s18`+*pPY7RHjgzuz&NIE-P|20FGoaYTC2px48Qhz5L#*sdR2nd{)5PX~2 zG6%n>V$LiRzqK{J3lh#OQNl>N#&!P5$y09uNd!LSUfTp&wd3Lz&)GiN@1U3=*E~eZ z`5!-Jb-gAkO$kc`VUAFnCf`a*cJ*~~x1~AV46F69N*LyLNj3(w_EM{~tKU6($R^rf zi8@6fBs)l5*M$X4Y8NJ|>EygA4XW-h32$u5vr(1ARWoQ}pfn6icjA}+lne%kf|5d0 zF|%=qq$3>;nyL@tM*6Oh?FDrda0DbX8Wj*VEj((u{h&_2BqW0oiMQ>rUiYh^HYPaI zhf&`0CO9(yS>^<3T3L7>dcZ6vurV_qMNIW!oQzrERbvw~y|J>pkY2#xyT&gYEF_={ z3?2?jnB``|;hjP;b@n#MH_ zPCP>Jt#=I)EGO;PoE`}FaZr!%_+3=(aGT!$UMr25F2lCOqWp zp8c2qo-q23F9?{g{Rw3xOBNRz-}n#;?-5yFv5EN}ND|f#c=@kGrx;*peA9%MaN^+3 zP9B|}oEfqMFP|j>50hCZSHrE>JW}yaDxkS|dv{P2&fpW7Pl$pj7R92&C>RAJG1AOg zKdiU?I%!PEphNaBd-L~<4x)f~_{O)3?xblJ86+P$Pt$J6%X~=M>jq?ImXZP8V!2qH z1T?Ic0!%)f;|K$gYJ_6`;|TI8>P- zsaYB`|2ef{QqTlZ(~t(W(J&Uj4#lARip{lf(T<94RS)kwZ0@xSA4JGdbA2Ln z@)Qk15GJIG0i=0xDV#K?r{SzY=NojV?W09=GH5EsivgO;ztt=!L1wT$52#I1;_1yD zJv38{xKOUtPLLg9$V}_GHeELPO+^H-b$^M)2>Sd64bw04>L#vVW&*pg;ziqM10G(r z*qyXv^J`(2+urOA{y21QbrH`_nw~>bMHb}ze9Sn|a?XMPtb$7>$MZG4Z13Jv<%!jA zvggc8Sh!`&MwBpvvt%*zD}kINaev>a6L&W~O7hf~isK4vqKr7-R&>Y&*<(Xa^+* zsMbKH(C%`|+N$WVzuW*sy?cC{@amcbH?&V|;W zw*J(i^?-EUV$m&wcpzq1tx!~>0Nm9RT1ou2>$fcQ{g#9eLKG;{)n=WL93nvnKh=Q0 zTkj`uu%(bzfdU0Fv4&>cjL8L_+a-e8&Nl+k85hZcPu%}^cjK6kIP7Gd88X7WLL8@k zoEhcMw4A6&hLOz>xh2F9;F=*N0>Qaj=W~t0weB5rxSeY|1qIF{1lKw7Vq%&xCaXL9 zZrT6-lqZjvneIAGPELt2W+C>vOz#9xOYXZ9|{> zvcVMUh-rCquVy>zvDdvfH)V|#5TmK^e-e?b1~LpKe~&IBOz*T%Gc`m&`*iW_@{1Rj zpRfBDX&k(3o;>>L&wudmJO9+7N$@0GapDrYAU1blfLcaazNjVuDTEXFHY^+)!Y3Q{ z=hb$<`EZ}V<%ce{??u2)K3W0-r9d7%dVJcQc41-J1=p}W38zobesJ;T4MinbvJH1w zz;VAxQ|0hUf*7?nl6;el^hA?MwF_KYvN z6;ThhnhlKAO-;Zsb$K#JkSc=a>FLKy_o%@_;5^_Qn$|6!p8epruReuEIyHp|Yt7nJ z;(N&7c4Whcpo0EIF|&2Qj@Tw>Hm5k>=6659A*|8NwhEX)XQ2$){c6wb*~iA5B=2;B z)oT`=^;*FcuYIT&63ngG@+tRCE$Mspe2v%=DPp^h=l}4V|M%A~{_ZNi9&8=k&FCl; za`oAp?c>jWbotTwU;pHP2bL%R%DZtGakHCQaYnJK0|r2Z?xcOx$kL&OZOkT-r{p`} zUEiO~6a*|6>Jz1NB?zSkiQ?cMFP;SH2nWDteBvPOvORO~I^TAS0c_WFn)1JVgZbni z>4RuKiDouxA2Hgl>mc#oQ#@mf>1ECJ&&m+DUSR$WZ+w>9&)Gf1E#{yV z{U*MBv;OMym%myKuLiEwjmf1%B^l^PUA;K}qVp&ElYggZq=aPd&=_qRz_n)}rENOz zgNO$>DT)tGlTJLldY&5vO}sQG?Wq%=Uv6Q-)^$%#KVI;}Y*g(9Or4ZwFV9He> z0AaKKM7rNpTCh{R-w3A)mkUtRq2w=e$bU!MJ!*pBdO zLDkzBM}p>rtv-A8KZBzGxBqd07F+_X{cWp+n0g1;o`OJ|FHj2oqB#qG;UpkB`t&d! z(fR$5Z+W;^q?rZRoQ20vPkt1f2aE=<3KQqpochOYSPbJjX*BOu2Tt*gqD5T0;1Oo; zA&Df!OwwSRF>Oa1J(HN_I+neC?#80o9k{M-=0lp8a;1RoejxxV_;hn9*+BO|H1fZ$fBn@z{QBA7fA;cU>?E3JqD4zFMhOmvtIc@om#%dP z>8gM8sQqOBo>oJk)03Co=epjUWfR#p%iuaE?d*I`$TU{-*X~2PPO6SckhN2onK_fz zEzi1-9(O-zq{(}mH>FU5gtPAPqs5a!S66XOPbc13J!3rlDvlEHL8+7@sne@|m6`+x z=H^ZZmc_xj&y8kL!Mg=8BQn5#v~iuTwqtJwp|qjveCyE&8li*bo=|SGKg~?IyS14W z6wm4Q*B3=C2&x_qD|RUXKQCNYgSj-|>MEXp_U2!H^ZcKmUwm;DFS#@WlSzW3b1=)T zJ5{|YE#n@Q5ndAj?^za4f$QADvlaMR%0XeihZ2cm<=Hc)!Ze2@G+E;pXWf&9oH+zS z0P`>q;u%iD*^|Z7%k8Vn{xwJ`5PnnQ0gA)y$a&umG4l(>sF(dJouu4GEoW@qNT<4; z33H}~5fGy)QbgRQt4&(HTz`4huTo4-{MqvH)5S+if7;5aAh0xQm&saFOWb9@(V%<2 z&;yyU<&b0+F3y+)xSg1?%vq-FSMl|$^;f@s_V>Si^{Z7nAAJ&`no}iAL_{?~Arde+ z)c`bJPI$Nn$4td8W4htT%J9~ZPL`gHz~^|KWsyzgti8j1h#+ZE;j&IHCF4Od^3-U} zqCIV&v{-uhe9(sw5+DdI7iZ1mMix*JxL6b@WZ1$A9*d zkN@l`pj;A-AWVj!h%-bhY1D9ZW;xC$WkkEQ@r)#4lDnK5QZiC0#Z)uEXgM5d(1@#7 z@w0#Y^gn<3`ZvFN_V=;v*-8lQdRTksd~il=hba&kBLjIwC~(?DAzgtH4X0D+-#Z8Ku#oYeYzm5rZ8f12?A3qq&w`dZ?ElS)TvP`IHcvpj@+QX-tP`F*C}-hBa>Lv+vHHZ)*QN$ zOAuq7Ovgb}!|b>C;5^=(__sW&sg57)e|s=}KGGxfN{<LT-T6M#^w*4XoKBZcx*FSi&nqfS=&x@!{9|qsQMo``v@1Kh5+c zu!l2?m}zpL6s80dgK!3Ao6!3=Ke#rywN`8~@;B0ZXNAzJDT@fE85yOCFSBktd31q3N*HY>XojKRlGQ4bw zC_YHXvys2}NV-byL*?^;S;&>E6MUi9hl@;i9annW-{MPFV%-t8#KWs;oR6 zjmG{IE(~QScU>V7hzAKAgBeUz(>0!>oKxy2HL8RnFF*)Tk8-BdX*GHgo_+c3pPnB- zdUEvj(d@bFC^=9js3Ny*!hsn?MC{F}BXy#^PwxI?v$xabzQDcIYdc^2Egex`U&Hf4 zHdpm?mXHfb+5&4FxcNVg5_Pg}uUP37eTFprZVqO`E_%}aVmRz?3|H3o_8&RPl!C%% zbvjg-XYPf~s02@FEZ)V?U`* zJ(X1$O&=dWI{K#98EggYJzoF-AOJ~3K~xQI?QY%~_I3)_%VkxN2{MU*9wl9;b!eOb zg?Y>rKERLt^mKZBJUV>x;+tnD-<^~Plj?A$C&)^s5GaIT=|Y8(2qsJjRjEXdk!59e zZ|4ANK$X9fo9p*KyYc5eSr_CCp0_E+lFrg5X!0gQMPLG_+;U49VMHL}NIL%(&+L4O z_9GjS856^T!9*nAT-#mk4g~@=PWcA)52Qf96nVGTTV>`J6#-#Exz5jYWtILLh#5>Z zRO-EXyzyoiCvr;*5CJ)7UDuH`eXUjo>6zBpP7DwRZzW7;RXIMH6w}^FClgGDvx82y zvXX6d@@`HYM+Fl(mc$SZGYw{{rfOlTqe{z}pB~RnPR7T_qr<0%-ye>ilr{=BLFU0R zZlPp=MdlTlh1lPbLBSa6go@Sv`i+hI*Vo=#>ufs|1fhAA>>CW?f*IJn5N!=ju3ro3 zXdQgfDc$rLX-?d14#N3LZ&2jjCM`RP0|4XXrh5#grq0^_0Hy6XLvBny{AwGVlFr%mjPk{ zX1;`r^g$K^bP1dCrWo3J9QJP&Q*C2Zl)Ec#rg9L?8$|f)uneEOvf+ z=Py3K_Svx5%CRa|m?|Q=u~f5b_3y0=nU-r;SMjBJs|F3Iy!xOaQtU3y;Fpi?+et&# zl+rB-M#Op6+g`u6QVh7RwvS8R#fp0X#D$>h%i3msW00@UMwKDOivdK|l9+;IxLSRe zF9wOz%1qUK>Bp5HhrA+yjL)F((Q=y8SPr zBpM?R)B0gjX#oTXnR(?v1_5y{KFmgTIy!mr;OI*PHf8l8lRV2@<}wgw<<$G&)n@|F z6<8@VDugIxq!oPH-Jvj2HALveVlEef#Brt;f?24fuj}`AKYIWAk3Zh}^$Kq~6l@s+ zTDPB!MWQ8T*OW50U#xd6YW~)VjmmHu{8-hCG(i`bDa;He7h8voVomCn73LYaShmXR zo4C=#0EotQ5~o%%O9E2W<Av9!W8p( zp<>3Qlj`WSI;=3wr2~Un%PIjSHt&=P9hHEwg4z|>20$G5uC0FXn~(MB;rIXi_1~RL zUQ|?>7!?RGA9Llz2n;jTKtwDllS6|-0xX)cfgKZAjMb(ksEp5F05KPn!z0$h2&l}= z5*NK0ECmFJ)nEu@#Oxx+rUnB^23c`yzy07pUmM=u=v*7}P>>VY%&4|Q4QR@*mfXJ< zS_q95^YuK9(RXh#W}~C={;WFmIuZwO3d_yNOqrQ*(f))nQ?x@)~n2d9ypxG`?){0Jj%n%G>ezsnGs176V)H!Z`g z4ATJr602ir;2Xb@+HVq=k#*g0dvKdM+dp{z_>hjPgP;|u88Mq$i;bV_M3}^@7aws9 zPZ$)+_0FqxX(2m8q#}aCjB3TxfVa)%QOcxj67iV8P$5O0ce};P_3rJP8}Dxv+dUaN z4H z<>{n62`Cla2ujS!@u~hm`_D!*%%K?&NRV|gpiFw}8*9JV{_mgv&DT%=Fe;C$augU$ z1PK6q2uv(27A#oM)E|$WFfl+Dd{v($PO6xZ#sM)x80m|P zsy88K6;g-N<-EGnzj1T--Y;(dW`NBOdU?uCOSExl^5Yi5)!G6M>pPyWdm{verr0YX z*0-xxhbMcJ^03m8qST~MYf`0%*zkoj*9cBrgT?7a z6WCg|j%3umxq5G{v*E|1u`kUDks|U6*BcY1m@QpOg!4*Jr#XWl#Ktgk50|9}c z0v2l`Ly`=~$5sZBglbUCdnRJcd198vtO2SPQq4H!tCj*aV>4lKfxH4! zEuS7enfP&lH;KWDXe~2^06=03$#;E-DcEAF3=N)$GS1Aqt_;`v*ETvkOww!{DWx2l zo96-5%a9-)?r*Gajs3x7d0|R<_r`qSz{OivyxEWm*K|X=CHeccm31twR@|b zt)5$xP{^D$hU$FvbH?h-th4zXwxz>}z##;PRyrG1r$?j1nau(RBaT(ZF$I=uZ{UIx z+7gA2a>Hv{Wh=vt-OXDEo ze7kp4oX!Zwm1-c3v>K7snt@Pzo6NPLY-(qcoEt|dPAdU}m>|Srs-{8;0h2<^^>}5w zck{h#pKPt(xV!Objv=GV$b*8|631UtX#3LfS{}Z|0jL8t0F2bsL?_|&c(#8$J*>1; z);dEbrjf=c&@$*RfX*0^S;}3bK&Z0S&erbw9ZKa*jU-MW*UrZz3(A%>x0dfWREc!Dc{taQgI{XI~u6Ud*Iar(g=6 z1OcXuXmX=D%{xBG1XmIO$4p8#qm^uPclF+_jgJ6G+70I$nMQ1@z9B=(dxAo?vKu?y z+nc=ykERpjSzYA}NlP!O)vNV)DMA2~Sxn9(CZ>|66C5AoMHlNfCn!tDwrHT{|69XH z0#a3O5&ChR{!|5@3j?{qDb%4`xWUhF?R;|m7tbaSo=m>m8$CTbJe*A@qv`Q@dg`?_ zQg&b=jh|{V`d0T^*Y!JYP*4O3 znNz3R=zlf!rEZkpv z+dpF!EWG97K!DU^ws-t+@AT2hY(GeVm~pfpgrG_5GLhl}pv?RU#5fF;*jz`tyKA>r zdmD_Lo3U>h&4?FT>-yT30SU3981y!G)^Fy|iVEX;i;WL9;#P+CH(klaWbPCIAPQE_ z{OD-(!gqo-&#pe!=1mBTFsY6~g(zbynPl_Pk%4RaTB> z$1jebACC@Z$rSBw)KuQQKns@WT_S*H;&Kko}x~pZ)<(j)qZ<)fBg&1zFzjcf6 zb-wIZIITd&q*lcQ2tbKij8a!}VKFl$GeCn7p$fBcd2)FCY`Q+_$f{7jz+za7CICeN zGxNk!YsmId6bbOjLJem|URp?<-eyK#b2Ud!bF3KYN@s1mxQQ)q@THB%VeG98@C5T( zdhde|0J1z|5n(YiCz+$HM}0w;kb|;}`ixwnzEhIMs$^f^3sA2;ruqC_W?do$%+#or z?NxVNX$wQce4RQ3s!E(5gr{FV``4!@4`wtnR*ji43To&;01!fqzg!TQA;g$oX5B8Y zY!(j3kl-=^Y2%xF6bwc%5QB_4%=4cXfsOR3^zryn zS;7<@#qPo}8JZ1j&f9O!J5dqHT=RNGWx!+-j{orRA0HonGr_SGD4}E^BLX;KSp--x zh2fln<@27^tJbKQYRIV28Rx~y{ac@`y44&m*`gtU1hkL|>|SO~5?J7J?raRUi~Y{H z92qRA)qNM~!q)SZ)Iv*-HKlKO4ddDAs66evNybH*#Z-411JFFY==i;U_g(^67vG~s z1;mNTmZpbMLM{{0Q*9#>z_CuD-3W%0zxt|koq7G-_}tl7-ej*&x1mslpV{QJI(W4A zZK-2*zG1~80;_S;`=!nBbOBcGbk9l*c2;!V>gHf4<1F&^yyQa`M>(``-g-EU0q3}T zW9$C<<3E5wI^=X=Sb@9l$GRW%RDRLYd5*K$XJuwxyGpt=^bLNbTf7;x&Te_?^ zt>zM!#~BY$>);+GZNE&*%CRUyOPiKB-J3mpaQKJMzx_uy1cWNBz|o~Pf-(T(*qJ(Q zYLRjNHTvZhY7hi+#9@q`-p;kPJG;X>!UZ?i7!zh%LkWaduzo%SjrqVi71uj=Zw}wn z5Wd|%a$PVvkimiy2*!1D$1Ay@5Q)WYEkg)_$tiSH9)I=l_bbxfTp0lU-1Q^A63n7c zo_hIGySVP$Z;x0SmS*|lCuq?XYud}8g<=@IcCp1%2u#hQ13Cf6mX$CMLM!umg zerJgz=+sT8w6k;P=KB3L-f>o>oc0uAh%8WG(ul#hy4wL-9hDnTl>{!w>dlQ00_^dN z?d6d_R5NLs)*- z7Je+ec=n6pQfwL=on0>tvwqt^Zm7B&zpb_X)53vG{p;%~t!xfnP%_MkEK(G!!IVs) zMpbE}{polA@YVnOVE>CT9jTP57zD96Q(N~*>C$6~??tCG!`RfVX2Waiw|0g%7`Y>f z>Fg4NleM`|=>zj1sI^Rjxj|15K{n`a40~IhtWVmc$1wA&6N|3oZKUNuS)+VNO>E*% zjwX9A#!mw(g=#DgSmwXB#V7dcGoAa?Uj4zgkJtWp_Dd4?_ACFr?E7Caoz8zZ;uDrC zd(_|+Rf)-|KYV!j&7=Kqj%P2-dE=&0Zv90@ATlFs0X$gv`EJ56fp>%6`k=enD~6HJ z2F;3Fz7Yb*wYH}^E`tXAjcHH$M*o_(^7_WjFP_xivHwg6n0b`@{^8@JZ~y*Z z|9XG?K(i9LL7+ZR$~)5fVw~2NZR2a!JTLDktVEv07k6)eusggy=&X^(g0U3rwEAwF za}W~mXtW1(-u9Jhz|AP5qCjtuZ{E4~(N~YZ2`pP1`9MQ-d54E5qqG}+u z%%IiDZ2!S?{r$s#`st0o=!g?!fUy98+Hz9xT-sLOg6)@1*gCMqV{@*&Y*8${&W&Zf zxR&aPcXsJEFBs7D!IO$}O#-vjas2 zCMjHZYjt<9x^duVipo?qrVEms$z!;ZcM(FGw^36V1rQV!M13g7{`kAcU)@>zaHE@b zU02()(&n^j?M>FsY`f_ctrOP5bzRaj8u{i<5)rtrccl8t8dXSWr?s5LWDN69&GDbb zYiS=JPZRMK7&KT}>GWu}_oru{KRNv7Xu2QV%qS2Ok%ZtW#xxVaL`GDTE~eIFJV$>o zk`YarE7rSP8{O@Ui`Wn@)y5F!eSsI(%km)&Z5*T3N|1HQ@<{hJL3tfJ+$ zBt%9~Q&aQgB+JM#M#l&L<@3KDe=yD8@2=8zfM;+XDT6!#i(|YnqUUvuUDA2;C6H{$ zmJvw+H%08{LI4ew{KA(nDm;DZ1=exGwsv1qnyvjLdQBc)Q392sDslSd)89YZ`|fvN z{r$u}3*D)?U}U8RRS;307g|l)1w^lqNQY5-uJdjcwQU_;%VrpiEy6?0CRr#}Wq5D* z!wwBuWAf@el+JZ5Z)v(a!nD?D^NI=rKt8d-aZI;&8TCKe`o+Vi-)Y5%<%57zGc|@8 zGdaMZY7}CQH(9dr4Wx-JF>SYv!;oMtB=hUFqw^nIG=NM1Rwx1qjM-qIplaTmai8~2 zAO6e3zdJ1VfBwmDH~B_^UVw1uP@~BjA4)-I^C({J+EHw#>l@>+ofOpu_!g-@fpazA z8PoH8r!zTb^&+fq!J|x|VqifXN@p1P0QFE01eAx>v*)7+fB5bn9`65XTpd-tQ_E(? z!N^n*tFH`D9~iF2of87W@Jfh~3>h0G8()cyG(|0J!G})9If+*fBIZbj!2&1d!icVK z-n+B;;YY(CcWA{?mimy^x9#Tl9ABsA^|0tQpM5^@kuW1KWMy~lR;AO^C(i?o;WXi3 zHIQS*vD*{D%+w5qn6h}Td2?mNF33E|GHp_GMW9e{Ww{+!dyfve|6rAOWms%6m>`bk zol+ux_41ghrmuc9ZN%sDWZ{CW-U4d0MgcPl0X}xQIQFMc4j=A~9y~kw?%~0o zj{P%FQ)Hf~X0cDTnT@G<)>e;jO|$*Cf{=4hwYCMKW{H6f7_r2>8v-mC88Mtgc76TE z_4S(t_a#xPz19%`>egtM%#X$mZyqQal!zH1hpa??ZT+@K`R&s`R(7HYv6jCzVn&g& zh+^se*?vw}@@k0_Bcd1tsMT}^YJqZ^gj0X2{C&38+c2^$a{vo>2$JFgw%om9-sJEl zk7D5=CB209Qa;yxTzKoOLwEj-z3h$UH!Z~*i8#U*!Qca?rA==61iCx|3y;I0&9>HRj@+uYOhxrXk|Vpma+?ib4?q9@@DLP;gEy}hV&k5e_(%Y= zkOjo#kF;Ynqf;yPxeIZ%Pyy1#RIj!jX8;r`LTXx7v;C9DfAZ9I1Q%gCpw2_oGmd8KV(WkZ7GeYgm+*)sSSSTE z!(^;`FUAj^9Y1_>@ZhV*e>$y>lqLZ00IX%pYXCc;&PsRV{+%D~kDiYG zzWND?Z@TDNNd4Y4C~-`0B#Ui#8q{pT^0^WK3!pePa6JG>%}5Fmdkf--glFaGpw#KX zD11>)?i}1)zt_(OT^@2m*WxFnCZ(Kj;>|bnBE&*9976vSpAI2}*koG+W`;6Ar#L=1 zc{U#JP0Qo`(SxVQU!TtQ$K}z;pCSv8XrqPz7+6>qGSc4hD&i=S85^ZSsn{q20APlI z5Wq~tnK>%Cpzg4<_2KQ$2E}%U-rHtVyVvh%^}?~_8M<7$TPxQF#d^^3#Lrkb8pQ|= z7}NeJ8vO5!AJLv1KU`@WPl@xKTD1bk=~X5RYEUH)hNsyi*ptKW2dn)ob8FpgE^-hv zlu|99)d^FHJ8((K(m90<|MN`}MF?7+%=RaGQdv0((-$XCj!&OYW+#WIPxeL+N*j61 zR026;s)_C+@^^8QY8fy-!*MHC}u#q~G z0|3Z~4FG9dJrRtpvXYD>%d?!k_j{*@({BzAPoC}!Z+~*@H*5KJ!5v4LMJ_ZrPEjZW zBByU#)1}}TC3KP%0!rbt`a|>UYhF7iPBGW77ZP!p>Fj8>_pcBB_R;9uadkSL9iNuR zWqIPYDy>u($blRai&sykAQfaJVuoOWjNf@DY2aTZkfRBPH>0KogIOj7fX#Bss>*Gw z-MqK^9li5D!Zb&mehVmnXCWVeCM+E8)i{ z?w&-l!f}C$CnZL%2r9vX!AM1XFi=duOcY0PIw~xL;H!$?qLt`5XGd-L#n84Q#9M27 zn#PI%7>RO*m5*-!{Lbc&hS|*w-B>~Yw&y>G^6<=s&A+o=H7sx)>JB?wTf^)5(_TOs ztWpbLZRH3On(C>A+eE$eto5~5(w5o>Pu6L@lf|8lj5HWCF^s(ui3_Gy(df9EO~T3X z^kAwp%K5Z28oJY7v0k{o<2<7dA+x5bOF9v11sjW`*b@8fn>>~`2u-}*Z5xAxon&lm zY6@qDU`%G*;FqB$Y2k+V+b(GI597RnEQeKU#kJ5Duf}G9nyVaOM&8H3GX2@dzxkWr{U4Pt9aRC| zCli#2MC0?VJHnnh&vGsQ4_T5GRPX4DrJ4pfkop+Fbw(ODP}NA68C8T!ty?lc%2Hp1 zaCCb5@cHQ%?wg`;osGf9N@s1bvR)LumF`NGcbFW(S)5`-40hVt=w9n`zXKv=#MBU| zS^(Aj>`kh0%TBV(c#2F}KnNsx)+e{3)nqcAxU2_`z19dG%*JGZvUwm7doeROGm9*Y z?<|(mR@4kDFvuhT03ZNKL_t&%w>7YjolgO9isU+We&##AznWe9?ESx3$u^wjFc6a> zwsv}HdD*$*&WzsNKAA(=IP*S;NC;U@{p+iDcLq0~AD5@oiA(8p@n)z_!{ItPj%vhw zQ5l{O3jHAE?4xL-TInw{6HXQfk#Rx}2o`gxOqDvi&_-{&C^|7Yvc@K;zJ?~if!D{k-co|Xh?rbabYq8Tq1;rV z9MN5+WEc_xkusD_BQb>mMs{)Fk3~YNn1(?uC_pZE9+5-C7!1vZ-K`sg_lBLFj0#B( z>N}3b*+qy&aA0wi7u-W4IHAr)=lZS9d*$r-^kh$p3?PLCZL-j5;Y${hLYg4OrEH`B z;bdV*nvew&>=h$p%JFxLP0~|olO=jWhnTb$D_+ZTbPR_vsZsE;As+)m>~hL&1`cw4 z_14zldN1o)u>p29k6R35P-_w5(pMl5<5?t-JnzC_W{$JcW>L`#afO(MWekl?tm>!> z)xPsZg@r>11`DPH3T9x=SbV6UF}IC!tZb~_xV`g1-)&?m*xp>mTZ=Q)ov3Uf>U$y(zM+3-D}K8LGl%q-+y+ee=Eh*M9o`^2)5d>GYd+F3{o77o*$3)hQ-=iwhiT! zj0#l{z}8eXq_5QW-hA<`)H!$<%p6>|(=W>ItUO^BmIxJ=^|oONyINdq^9-PO7Bf?g zu{aPzjM$mMDqnO9R3sr^$##Bp_t&@AK3c($kpoI(AXNmsr8k)F`X#S7EOh7jC@mpS z24UoD#n#Qi{q5lw&qohuVUp)G2@@xA3$DLv@c{yhZ5gj*QQE_O#xiZco!mBpf)H0% z{UK@sK@>?ZDWMDin$Dq#dj%S-H04q&A6GRCr6TkOt$+1pUR3HPFb0CKW4I6?lKL*H zda9GsDc!OyZ!{N>7z1EX(3&_)#fJc8k)j}}s!&kx_U8N7R_?84>x8WF(y>Hs|2E=Y zg1fY|>Ky)R;iZHIy>k#H>AN-V(VeZ2k0+;PrH;hHsQ&mI>aK$>bs=wqIsCxI>PLz6 zORKDAd6a6CCE+Dzph%QL5tpf-lr_MO0h27s8kruw`b zck9(c0MwY2Q)xS36qLkjd_ECEsUa2JM&Per1sC>Fqvy$71tkJ8j1YWaP~keFK*&}* z!}qp7+Ui{I@*r_98<04OTWnNsBW)}8JQ9R-ZEd3RWe=E43d#e>GWywvzulj_2rqCj zdhB|cYH4$Z_U0-z?qqWspet#D&T)9DU3HYuNya%2j~Nhq%%)tkg=k~^g;}hQeVsR)Sc~AYESMfHNNS%`^GoWDyAjD*aWEUw^h$N$|C-~^@&pNC9|Mtaxo`-Q5 zs|9Aa=(*R;+2^D7Ltl%DM!^6i{;=fw%(*(}S$_5Y9}71V?<9dtYlo!S+=xJ=2{2ySh+R}apyRH&9CQ-=ps``or9m!eGQJf+xd;o znwx(3;{W>l2dNK|qGt7_X_Xq(y+ju4P4Vj9Bmd5X+Kx?pWp0a}M+iN) zQGv!nq!_tq21(P@wPsvn`#t7Z<%GJ;peaJ^ze!+Xlr}AG98h{_maZ=u!?tu4DUmv@ zjn+n~XWf>Ui9rKOj}j=ojrYb6zB~Bx>&Jhb+6WvpXhRy72FM60Nyv<}MdxV*1BIGy zrJfbZrm$wCNPHo!q&H==0nX4Xvfa-%|7`E}gD;={Zd{(^T?F!hR8774 z!sQ_ZuOVX&h*kIT)Vc`4S?g-*AQ=palq(K;066WA|9&|#(pIarosgDS6n;scftu94 zIndMsXiiwx@WeM20Cl*q@hni!>G+0#gaj{6pY*cc3HEZVs%8wzSuCVa5ObQ$W>6Qu zAV^90?{~M z$u>T{_3Mu}f8LV~M(!k^KZWMLW7ZeHOPJ>6=IFwdei`f&2U@i@YDl5M2r|qzfAQXL z%d&iW{P1YHM@1kQkqS%YD`w8JOue4FXEeC;A~i#^Ei7@BUc?UiUqcAV&6m`Eq^X*Da@j1LoL2h><&%DXt;+?01A+*ts;WA2q4Gq+(et5ZbDZxR z?FimHl8DwLoTGtO>g@5!_YY3Kcr^Or;o%=oriYVqWSP0l5eUHq#2Sya+DJ&+&z5J0 z#caNIk$}zrN;|oQ5RYME;@GH65X&ox0*u9xnpWn@Y-i=xwY58+-TP%BD}sXRCDZi1 zX^r7n~ z@APfmK=m2ppXU(d{JWI%?EiPVt8*6In|_a(SrBqT%Sm5wAR;NXa-)wB1|@FbS}tTmKmf1 zR_MluH-6RcZ5&Sy_NFs}k)skND0r>BQNJ?`<;+yMZFJE*(TXhkevF2+56#}+3?&lQ zdLI6UfhQCucEJ zjY&J{yadzn_+)SI$)jiAcQ#0JC(KN&re>z5lDdn5xfMKZT$lFzC)PyQ62mcJ?mH(2 zVIpnJX%7;Z86lX48fanjIWa6F%?F(uAK&@S^|d=4tS2tdNY|3Yk^z3Pb$Fpg7*ERq zEa@_WdQQRBd=_l0ce{}8*dP4;?@woX0t@D6B+P1NHkpkXBE&Q=z)zdYV#1^$tx{@M zn?YinnSr{e$5oHE3yH9>vDZMvmCjxqKl+>h{=eV5@#C$*&dtqR4h6#zBmg}cI5+{T zFGYk>OZx@J?=&~sCrA|63r|W_3vsz~c`n^vo@EGxU|Ly4Mnsg0Q?sDqwMqU4m{DZf zHnL^~Hn3j|*HF}z5o|K`FjG?^fQXoh8PKs6)*JM${p^Fk+*$pg$Lop?TdL|plXH}A zk>^+NPAlUe+jHJ(oQjt5VUHoN%>A+x}28WDk?jxZ7we?UEke-|Tuv+7D>v?Lf4tSbol%b=j7*>gZ}Vi!v1G0Y&1G?0JFlCdFIM6A|ofo>FINkj4VH$hEqM9 z*+@~f(jIB9`Cjob>O`{!%wt9XoO5|mXizFR6K5={UNxwumGm~f+07PAs5&Z`u8f#t zbOoAdI30ljR&VOnDp1w^?ET-ozx(q$8$TATWXS5a@?7VarxUb$JT0>^XjJ(u%L6gg@_u*ft1@_nDm3-o zR9IMS{PRlj!8;KG6EPq~(On%3vn-p1Qq7x$XeBa%z(k~GW?Fjh=zJQNl$JndJm*Xy zGqMEgP%I2012`bW<;0ZIZnO#la_Drk)q9&C|LX348g_PbbO}xpy%u~`IhXxgcs`f0 z2svU;Kuif1P!kCy;Y36k%Zq1@#~^vS3C;WvBoaSA>*qZ|>!OMt0qoh2&#+LVkYY=C5f&V8J^(FBtz8DgVeioOv7=TdZKbuHWe^yV*D zip`AsjI4c6DWv$Cv!|24@~~yO4CXAN1_#zUkQ6P<_|%w$!493itbKIjv)$D@ec9lu z7acb~XS=yYB1eUcE%((Hz|v1Bk?N#w09EOtr)nWGR+RB>dXMXq*3UDf6@rK_@FT z7zIFM|6DO5B{T8V!RlJRdwp>KU#bctc>ngA@?x5>8Id|w zz_qh>QdGw@R3c2qKrx{0KmYVUfAQ#FzIgb%M<-uMkIebfMlNSnH6tSuajK?4LkL-x z5tK}ol;IFN^#LgpgX&Z}v)ApBSMn-Rf|Wi1;+wqEAJ~^+Z}MFgCY^3?>L<=+29(~1Fmo=;@~oT&HBA{1 zbrTDo7u(fQAj3lT)|I@YBwMjAK5Xz#j@;|9^hr_8X2+F3Rm^}o8ot~V@g=K;1OT`5 zx(y&<&YX0F&Lmz^jd;A7b|K>?w}uQt%nXig!D>+&aXSPAwrK@_WXj-vvBbS7ZCV5%3vVNQf0y^AX?`(yk-&wnLS{`XI=wu$B zFc{PT@78&k>)EMcv;enXS4+s$(oCsUWzCAFjFl7AJ^Y`qyOqfv#3JA zLRlsSk)*Ofyu%}Hb^xZjI)te9Elq9ij~Yb;$Cy+y>-4j=8|!zjt=$^r>kfIVrxLaP zf7#t#!rZ&iB8=Bk7jJ1QY-^NKQ*C51&=6o=2n=44LDtKEa`)F|Sw1`d{>Av)PA{v< zU}Twy!c;>jy*Ss>@KT?$N&7a~CGbpM34oTrrlgD%cjMT71QwsA5mZ>Ja;jm*2!M}) zK4NKwDB7XCmlpfR<70tw=8A6KCCZQrO5Tk#iU6>QTI8Fql1~}fBBLoA$eTh$$bsVy_67RK%lw4ammXCS!!Hy`MoIKP@3kZwJcvq zOiFM7mDLQ4Op3e1PxO6zbnw+bemxpZ_wp=)a(-r|IA#_fJU|Lg&R^P+qkTFL{u~sy~aBn{QOk0`@aO^TyT*7{89AbeZkwMHt zh^ZuM6nmx$`%pS%4vcAcyMO!FKl$Ho4DW8bn_cwST$)`#vIS_4JuN2UG{qsQqB_En z?=}xFBX_jcg*AJBvoPxI%xZ(wBv{B@%*Q7&zBRbNTC6fx|NPb89?yXh^W+k$UG&BYZ}vGh zP|y%GSOPyUz#L2Sbw)tDdi0GWZY_{9u5gPb9hg^Lfk81i$S|;Gf^Odpudm;^KD^U+ z>pA*w(`B9-Q+4r5-|TwNlS|1RJ!>?dfvxKflt84ff&e*K5&ANGZ|!6L2=1%@_Wbb4 z{>dX^1$iSSF+;rq$uto(M&{|5>Ub3-yfmSNG|!#GWcE`e)sLKF07wZ+2(g=cz4gDD zEqX>#B=bpI&c)JYEq~%ww*W}wVKXRMFt9KYs}TYy5C`Uv?X29szJB}9KK-jLcRRy? z;b;L}Z?O)XZ(}cpCl>3L-$5>+iehnKz0J4K+9|L92nH7MCNPC*7JG|=9IxJ9{kZeV z%HzZD|LgDn@4d<6qEmFc*-7~#c+W(fqNbXvwo^q(f@O?Q_m{qsca(&I#x^36BLfuk zl0Yzoz-%UD#6rw4QXgpnO$j8tl-kMG-iw>Fr0g=K1?0sWs(_M{Z)Ho{xkY;N|ahqpG|tqyuzPtkcnE3wVZ>8AP+b(T+z zhjDEn=zI~*A<4HJ{7W@Q=eW|cXzU$2FMc2*HVrYZN}zz1_0BG>$wzk&_4_dL2i0Vh zOHpFlSQ~~^2185an#-4Q-gV7q#H$hKAF{+2=T_HBx7;2ABIiC^@IDvL>}d<1SR1moC^ z6k=4f!M$=3o|PJRZAiobutRuryRHTVA}e81g=)2>WCV#5PJzJve09CEy}Nq%(_23u z$X14qLr1mdWNZGt&3M)SUv&OlQrcSjgBD+#mteZAb)~h5N|HtU+swMs3n&D=o7u|h z&(^+r{P`ch|EGWb{=XJ$J*7|~1gjuSEKK67=#_xYL@1c5nbw4C2~udqtQZ_!oSJ@X zA(91(?$8f2*gf9#4Ro zF)<(t^s#w==d+FB&Tu&Vhd=#YH62mzi*Du-Of6W|q{exM&l+zxh$llU?bL|(7|}Py}PpiVE5B;Z!hD4 zAWMvlEuQ;x;Me~*@@^<0#rk=out#MXgt<6a&yH`Mxfhw9`1A4^ixPWwUMV&h4*e1= z@d>x zJy?JA!S085Hhx{8&u|P!ZOn5C1iIl$Xz(5)-0uBs&PV~*NA|nh|8Q$_|J##;fBgKv zomStOP}a|9)x3cju}I;)b_|zT#HpP{H^q7g#Ed2$HH2bhTDtg#OY7CQg-D5~g_c5i z=DT|oQ3@k2au<`+eF?R&=zWl?C9>)U(8iZ#H3NBKWe&0SMOqsh5c#u1GPZgGlS7`F zSyKn<@l059mrLoT?q?XR_6|RJ@b?F6k9LN497Y6YSb}g8jGyrOZjAEHca44rl#o)o zQ*h!8!cZfF6Qdrl?F?v;k5+qY|MJ!Ucscq0@b_r?p zmhz93%%%F26qEWz1EUaPqpcJ-k2a%Nf^CqzcQYf9uxlsrYa$5ksm@TRh+w?X%6fiw zaXR-phkZ+Bzm)}y=sVSn|* zzFRM7;E=^+JfM?H$IHg_(hmG4!T(IWPHeO`x<}c4HeK*lsM)08Q)&U1Niz`o*x&PtEeU8Pd&DYbRyQNKPnN)Efo~i%8auFDy&D|IyW> zboo-PeN|R7uN5uDIoi^-;93%(8ZwDP5)49KBLo?T?A6At7VCTC+gszqTjM*c*@mD` zkWPJ6*YSA$H-B^TZYW`pcBmu30zhi7!o7@gIDGctKOapWWMB6F^!+~?N|5(fNtOjK zg=}F&fs)Ncu_`ATS+uB*TORD!1vzhw1iOKaYQP6L#;DDaAixYtDk4Sh289gT#24x~ zg$fpnWlun-xIX^P-uC9*KYsbYj;GIaHzjVo)QZMf$zt4x&B#K6TZ7n(l#~S~ z0gN#Uaf^^yq1RCl-#`g0uu2+8417wF4|}AB#smZuGVHmPqF5ogWKO)IqP(;5SV**3 zI&KPo;iu=-!gidzhMo;JEDYvmLOysDm){yOqy4UeboV#397`kLz&{FDX3wv~1>#4zD9@V5eJD;D-%ZZU1Nnmxe6enS5XnGbhDu|$_ zAMrcX#8?$A_P7uN>^JJQ&J0TLzG+vT#g5x0)nXDlOr_-ACrPwGh7>N_bfZ zT1US2@FxB;OZL`tR$NEd9+fj-QIk7u<^lqe`lDj339-tSKNKNS3Nc2 z^S(WK>q@AI9jT&ljPd~hi6U@L8lf>30ugJ^Gi~K}#`)Ifc=L51SKId6{!-B>FVTe%P*^C$d$Wp6WYX8+lQWy@rHNw8Pu7ttf@8k+zi)n|j zdLqzZQ&To%1v9ywy&>l^>Sd#EfB5`#`fO4jiEofGGYnKgi5RK$A~ZP=Sk@*HH?h3! z#;YMLfHTAZ0&z&NAYeqs2Ee56Mr*^3UNKzin7PQVNlWE&{a6r6ZkfV+G&RbZO-?Re zmVV9xZz>X2OG?UZ-m8PLGAWUFLtR}A8HSGLSLx{xao z;NWJOHAZ!V`J_2HogJ0U1qmy>5G%|Ocw!i)T6+LaMO362ZubuN*FU(u_3+{TXMKzr z86dt*Du5h=OpIpst#>s49YA*u8>}HUT0Wc$;WpO9{u)%KWq$c4P~Q;u{Bv}V8|R0HuI{) z#w`{bvC&uAnQfw0c=;{O6yL-+VRsE$&Qi<_2>lExjgeWWPLYvuY(3c^Gpw#<{lVJ) z_NNd3^5Rd=Prv)>$v+>T|A2l2XJAueejS8Sm09|w_Tx3$2wn!?Qb0myRsNf>IyGCoh&0di`el~b9{l2O% zOgu9brm5p=h}ZjHBK1HF!vVwsRWtQ4CT3c0DsJ zDT5hoR!vSXj!&j9JvU5hYEWhpGKRt$&C&0<;nv#iyE`8rj6YuOZLZ`yP75JP$I`PlP6NJD7?4Ob_+k+^XdjjKunm0$Qqcb*2D#j&0yN7$Dp^e zH9lDFZ84uO2*199mjc+{sC$H&tb=k+N>jZ76}Y~}=xRZ#1=zqLNvd35;c zgIk}BXxE`H=+mNVUk#zlC$~-4JMhp?P{JjDu)~0KGnH_OZo)X_Scg+_Od5^ddYM?E zEG;2JSh!7`qu@lU^`ua64%f@_&BL90JC9zRKl%LIfBO3SKh>D2vupql^P%O1YnvN;K-Eah)ibbUgM97Y;r&O4pKJ_wcZ%Bu4GBGh5X>AI(8M~?keN-S z%1OOJSWux}eV>Fy@DPZhFQkar;+i)qZ(Rvnx^ASW=PTg={{$*bh80ioB*TUWxLDpzdw&H-Y?j#%C6!&zbE zJzB|Jk*#EuaW2D*ho*8?p48ejwf9_uy)AeYBRhBs>SCcu)XnY+IVEBB4ME8ElBI~J z89+<5=jfhY{Qjz0^QyUW?e$9w$d#C@bq~fsUTjom?c=t-xT%ps%=KkuXZ6;`U~}k( z4CY0}-D|#^C91PnzhHpE8|ud9N0aZT<#E-_*qMXNdDP2S$NARXjfXpHdz&k}Tg9y$ zgH9fHr{wfUytf}!t=EAyZ$;j^5{6Ri<$f|(7?>^@_>4dV48kHQKE&puBvl~;Qw}&n zk5KIQ#@oGzA8t<`pMCkoH~;eD^x4Vu=)xvdPgS7gkqRn=h!K((1CK*U>7vB<+q_y1*(Uzys0!Zxgi*--DZpNetYUkohym1|hR*MAM3&<y4Aarp~xVV`~{|17t0d+$AsGwJjJ|R1{@#;c%Y`b z!E9PT|Kk_`eOf*dz9NzA;h)WiB%_ufCp_cJq6Ej2$vE*ZFA6W6;roh1nqKzi>ziI-8$={`h}?`|OD~-|vm@9^Suy z_{rAb-fFg=@u-I$L-^IT?d>$Ue8tbuqPLcw_9|J}QUCwS0uH$9O$HhZ1ps0+WNM9~ zu5rQSYb~p0IxSCsn0|YDarE@~`=>AdI;$rhm1T`(s^pE-lR`wzynzDkWf(ZCLmH0| zst61a2R{Blg_N*+GiwWFmWO+>L9(?P8uzVq>e8gK%Rek)H)sRXhPJLNDWNT~44}=e zBpBFJ6MQ!|$|z?Ou5vmrKEC_A{f!TPz5nk*$Mxx#Fl^V`f#z&EP)Ba z$QUkX$@6@W4_%(G_fPxiyGo8`Pv?F%$9e59K+uSviJ4l|=2&x~27)IdX5nVRJw_CW zHIsX0Q0Nt(Fo>ov`LmY7*IeB7nw9~kc5l3L$|9Vo1ZA*zVa=#1R(d--<9j=+cLr`P z*oUv(M!Ipqmi)IB1+-~u^)|>?*ESA!w{}MT(KuT-5F-~jj&8@h<*>a0dCzu2T{-{& z1akn);5`74fEfxk)CzC#1ZAk9sW3gdc=_b`o1@9|ub+MS{QUd5UU;f`fEwOIH8`SV z!F>`!L{?*q#MJFQB&ret@K7^T_~2tMf8`~Zffv91=uUWPH?{7+3m-7xhtV93pGwqn zt%gw2Ko8U~-$Q2QfSRJ`*Z=;L|MJQHXRE!93AbRX+8jSU|NiOm{Ry#h+i)C*{m0}05ynyNTn@-1(Syl3WU8>^zu zZbN1o{IyPiA%!4}U=my@hGpcO6a|{x^&R&|+1l1Dl9Hd3sTwPwSH!6_E-| zW5X^cVlxE7Tw+2h1!y}D5wWEzwT#ap@JX+(#DvHmV>w*V}JQst{wvsSeO~1ku}AFp5rb1CQM{_mcszxAaFinLB2+c{%LltK54a%4G^RJ)&`RMG~ zcQ3y_EuWQsR$E!oITv18l?;djZX9Whz-&w=WCBR;`&rW)^6Ibd{Qh@${xI%u6Z$z;B_tz6^CI2h6-DTsm}h$| zCG*Bp0)%)^yqT)8q%H`maISOlO_BFi39*bB#NJOa@V;Dt>U{$zAxB&Q4zxEgXzJ8Q zq3&Llv_x6^S!3nQpT4Z0JUMzixj23~{o%BFe$mXTW`0r50V5U`b}_fa%v3E=Xc#Co zExG&ziv!VzyyiiEx^#-=79OdZS*SW7BSy|9Y{Hsnet385(Zjt@KHT~3z^w}vK#?O4 zv{>GIah&efN$$P0`CT)WE-4|I!F%sqZsyZrgDKP?5&@bUriJg3IeOWnSO(}9(;)nR zVXUUCk8bt^UDniy-ARsIgblsI_3~^m!e&{|Ce_hN`E*uKX4U!W5r_;7ACuk!f!I>_;bfk{f+L3P$Vo7HEt^6cpJ z`O)OrY<@bOPbTH*ygBozJl86w+*k-_1U@*Bf{28O*@zpTN^;CwAa67Syw!worUQP4 zo5ZgwM3lZ#+OFU@a`LnrVECQkiNP5PtH@c=cf-BS+rR(lA9nhOtFkQ^B@N_66Ka6( z{BiD@qg~5%;ox>(4Iw4gSm*qu0z^$fYGMbyN%9^kVOKh6{Z%~-hdV(Ut zgv+;SBXsI9RjU<^x3aW(snd(*>}2}lc=~)^&o1i8564fY<)p03X*q37iHRJGa|*zW z%ragO7At&6N(aHrhZ?RLO@LTJBcx7$KhhARiz>sU!Xcd5LjK`38^Ke~Ih!NzsLT&` z4)5+i`1tTQtFl#K#bGGcUykOqoyodT7hhWM!uOW8yPzct%hGxlD7}|@A5ns8(Ag?) zqB;5=C}Gr)R1JV&+}P3;*L*55k%UQdof5Y5?Al3TShN5pYnm$bZ!ihltM_QCrmTGB z&DU1W>dCA*ubTO6em1S9Wj!zJi|Kqay_lBGy!P|P&uUZ=3bKYB6N3beuL9`=GpVYX z25>d(nnWRoKn)-Xt}l}bd)7u1>Y7ERH4@6S;+%`^(cXiDU)>rX?yT$$i;W%zfi1tu~EPfPEN%ph`+u#34fGouKK&O?Wx|v&DsPmih&GZ+t>aIs0_o&Umd* zXPW$N@4n+$CNnn7bpQ_PWHfMQum*nIFH3*XXgRN@v--TOW*61@pCkwNZFgEvtD|mkrQp%2f>(U0}fW3SsA5ujm(CQa46zBhS#!B#?vX_L6=fJ?Uh4rNO=P7 kO@J##%A0S{`7X%+0Vz{p$_>zojsO4v07*qoM6N<$f?h(a(f|Me literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/1-vokal_interactive.png b/docs/img/sponsors/1-vokal_interactive.png new file mode 100644 index 0000000000000000000000000000000000000000..f7811ecde69cd7f5227dc9c5638292bd57e50254 GIT binary patch literal 12532 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 AI{*Lx literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/1-wiredrive.png b/docs/img/sponsors/1-wiredrive.png new file mode 100644 index 0000000000000000000000000000000000000000..c9befefe41da41783f994f772050faa394fa36d6 GIT binary patch literal 8082 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

    9}0&eyQ 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/2-crate.png b/docs/img/sponsors/2-crate.png new file mode 100644 index 0000000000000000000000000000000000000000..6ef6b5da50e83c7686a28b701764a8475b17eaec GIT binary patch literal 8257 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/2-heroku.png b/docs/img/sponsors/2-heroku.png new file mode 100644 index 0000000000000000000000000000000000000000..22447659685c2e1cbcf7ee6e716d1cb3c3b83719 GIT binary patch literal 7337 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/2-hipflask.png b/docs/img/sponsors/2-hipflask.png new file mode 100644 index 0000000000000000000000000000000000000000..c74735c34d59e987c71d2c54d5870e2dfab3d6b6 GIT binary patch literal 6016 zcmcIoc|276`~M6=i>PEv49apXV~nwjvK3iwl%?!u>@yRS!B|F#Hq^aBmT+ApTM`no zWh+rBvJNAJ>}6km2T}LF?)|;KukY{mJO7+>KIeJf&-+=<`8?0LZe*atvWsUI0D$Gx zNzF3=&_S1UfO-8y=F!N8PTWK-GompbOC;HPV8C$)yd4I9%H7rxa|UDU;B&D7qXGcE zkh6&y(M`)nArQe+ABH;svU!?cq>5w?iivi+}qs^=c(kaD!75G1YNH$BLv|a zB1BhJ!A(+T`bKaKyaxu3mO@L~OCx3B@`_SOG*VUpDFK&}MxqeXC2R#ZZw z;olEI$eM=(R_TnU_IF#*ovNS{kw{QNAV?&V6bU7T_i#iY6%`c`(lQ7c8A(V&($fb= zwDp$6c?xYIXkt9=J)8+dXFLwRj%aI#zeH3OgqUu+;7-_v#d&@Y6BICnw=DsIl#*U| zX-iOF|9=;Cci)!wB%ZGa;PGx- zQ8aSG6Y-u-cmiBQ11_p>YwwI(zZct3(AQTwh4UoZ;_NY}G*ty52q|Z02PGsDjn+aT zQQ8VxnnKqVdjcgViM3;pz>M2 z+^$>bX1nq+IH=q`pt?3$=`#i3z|1Ml<0jr6U%JUzpG=QeOnC;(JZl~=Bk`BHmFQ7- zxDAV6zN-ThyDO*bbCO+aXAm>PE#zA#!6Qe6NBFg+wZdZAOr$dq>dC2BIr5%Zh_VOM zWp_t;kxDxKyGkOKVwGo2sN5K%!xY{%32HCpLgeRUDEpKV+;6ql{dq z18@MC*I#!E_^&Si&+7kg46-h3bhBx!<)uXmunXL3y~WB5L^{U@z78o@53#0=>1B`} zgt=ycZuSvmyc%zA;thOUcE;wdmdeGubE~p}@HYD~LW63; zJ%bqBY+HN2QSc-GW0v`OPZtEqbdO@Gn$xq6%FoCg?}*}YDS--)1xha}1YoTfzucN( zg|zkLuQnfM{)TPD3AU*`=K6Ylg$~eFTr?N0?tkSh8uBGPWkuvdL`f$d$V?lTthbnu zefCy4CXLNQ<#tr%UJwH#oM4&!1cL!}0N4Py-zZF2eRm+@bfTgSc z69fBR0_nF@Da#82Tt7=u9St4O?rS#kFWZpCb zJ+5?sgaeovw-Lqjm~N|)0fl?L)GCl>bUZ+8$Z?#Vfq`;B@dox;j1`ZHnSN6_gbiT8 zrnjBGK9LFo*zd(zq5B2)shJOK6UutQr@s2-Zvu!>RZy{K9bl7)fmB?@!io;kW7ux%7a0&Ayx8Blf3ax+KmMavofK&LmvvY5BqJNg!aVOd8#dBRs`w?-sqV;Q{ z-9+z~NV&l1Dk^wfU+8eWW0@nZxjVKi4+Q$<1$t54YWplE%=iywuU=oJSRQ?!X*%(C zesvA+Hvuod5K@DiPoy6nSH(iWeE5IG&}*3a@jmkAJlc@cTxT6A8q!P8~b zSi){wOQGug2(G=Cs#X_Fazm%I-99X?rPBE8tk`^KuB74u9ilrjdTtGcU3E=!ZA;jf zd*7>pk*ocuG-hO@ z{KC{L40{kII7_-UC-Jqs7?CIyu;f!%sL)z!siq=b0>LO*|cE7?GyZRj8!3G|L<94MN(T44(gs9`s zC(~xzuo5$mWyrv1bOPr0k`IXc7m*{l_*CPyd8Y6=S!?giy+V-m6ql1a=Yk8vYh+U8 z{hzP&kd=E5Fo0kFjH{|mR#3dKTxqI=FY|0aRK+?y2N-Fm5m#-FM(`)gvvOsIwt6p5 z`geEl9{DAIEZjKX!4#E%fCHn{>vj{J0fU;QWYdM0=cCX7 zMsYzW&6T4JZ(8@9wd!_NKTrR#$)Z=?FMA9&8L1|bC(mleS^m{*;o_Az>X@(iyr77?m-8oeM6yL|xQs3pp2kG%09aE;@?h-H{mE(@q^8y}=ro zLY_7An>f062NtN`<#{7f_1<~PpG3m>)$8{&fCs;&lHxkeRtthW+Rfg| zdewlthj5zFCfGY*~e#hS8Gglle`L>;|$gjF}n_h&>H=G>k={(&K{$Xu0bu@r77YGD@436oQy*>q4K3LpkT)l-Fsy9?Jr(EVYP2`j^%%3VB8 zNgwICC@m;5i1!HNdO1KCot^%0J^>vI0|%8~JF$eWbzxXe0?G(?X5G?kdhR&7otHO? z!bt0C+|QP7mC3v`*m5}HZnXR49PKrrJUlGn(kD*NS~psjF-So9U9vWh#X(s_R~O9J zS59Vg@8Dvah2DY}Zg1uQ?GCvGk-l19rlXr2_YSpuV-(qXlG>=F}zZ z%fy(+EO0<@AsRc8G)hkEbwBDFqlPBQo4cbmGC4wBldXc^`tE7|66B3jlxz-V)hiCf+<7 zudOolWd09F|6JuVuCC>iJ}pAsokh{g*p_;uwWxb%>(%o)_*9%2rQvqEwKDdeullAB zagv%+donJ189u+ZGDKaPd&g4K>?S5<&Yv4EC2Vo8?DjYsI<=mfuPrS#)}>iKeVVgN50^Mwn96&K`$iL1JZ%@B zd=#xp!rDAs5@lAdAdf&@!g$9mPm_ZX_J*eBkS?Fa?i3Bw z!DrqZ-fwi&@JeENo&rhmm_nSzy&l0POcKO#%9*|I9ZP`^=M_O zQ1u71L zWF%e1!^cnqSkBNim72a)`Bnzw0pNayHkKHArlY>x% z?OuE>5n^qwE4PKQ(afuf?__LRX9U#<9Dw!ydhp$WwPzU);Rx!F^>|&VqaE__pm*%u z?0OX#*LRNJZhwO;Hf~HA43tVNw}4MamM|I6Uu4URbldhxE&OQGa&g9=KeJh%49eM0 z47*ghSwCNQAZWs_et}7A7aI$2X3c&!h>y`xsLyc}Yp>Wc5nj39Xf^8ymjZq0#y|nd zJlanN3!bL7+r*%GXQLgL5^45*|8oTGp$JNe$p6LrazE4kxCJF{#Q7ygqfo(L*xXgH670Sx=o~B-5((*>{_4FRKd^C z>meskUDtt15Xu$Ybk>OIOihceuSG@1YwV>x-hd zR)75b&~SaK8W+QEm(j-0ib;RDpqZgL&8=+QZpd(WIKI1iF_ompZw0eq;nnHrX0yNF zd@RJ^YJ^a?8M)xb zI{?m^mvLBm1o+#gNzho`ei@wnOk8SSi@hoC%UxwA{`~XuVsiWYHAXC&p!Z5ZmWJjm z4$uT<^gZ8R4t{=SM@=^QFpa&K2jE-u73#{09MQNhO=H$MFgHe6Qpfwd~@>DI=c5Yp;DTTuJf$YNXa?a5LX|G>Voo z!F!RTeMV>P`B$>MREU>?yP%wlE3wztaAeZXh5vN3a`}8HDNH5sX3zo98bdDcTzTWz zN$R5ByQlZf!pwE36*HnHXUMKb@>x+eEozO>((rnJ7@FZ0XJ-@EIu)Nh;$X^&TQqcj zwMZ*4l?|AQ#b(;}LdEHV6B~;eKI~j=ao&79D*0?gvz0Y_>tRz5n`2{2Mtqje=Eh3h z(j7O>w;ht(n%OtzfA!4|$&X3?-&X%)!Oejkdj0-_qz+1V|M$PXC>vkO|Lo5h!DS^8 Z(1WGe0}qsdUJZbbQ(6X^IVWr`{|^o^aD)H= literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/2-hipo.png b/docs/img/sponsors/2-hipo.png new file mode 100644 index 0000000000000000000000000000000000000000..2b854c6d4b70b3516b3b93a8153e1c72a875e8dc GIT binary patch literal 8111 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/2-koordinates.png b/docs/img/sponsors/2-koordinates.png new file mode 100644 index 0000000000000000000000000000000000000000..f38601b34a0c3a446d37a1e730cf07bf44d58d54 GIT binary patch literal 1934 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 h@o(7mT7-XUgL5%l}`x_x$hc^HK literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/2-lightning_kite.png b/docs/img/sponsors/2-lightning_kite.png new file mode 100644 index 0000000000000000000000000000000000000000..ffdced04ef906b7546ce8440ecc1e281f3047951 GIT binary patch literal 6715 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/2-mirus_research.png b/docs/img/sponsors/2-mirus_research.png new file mode 100644 index 0000000000000000000000000000000000000000..b154407085ed5c406daf57852f60f54cb9eeaa97 GIT binary patch literal 12414 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 RI-URk002ovPDHLkV1o5*A%Xw^ literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/2-schuberg_philis.png b/docs/img/sponsors/2-schuberg_philis.png new file mode 100644 index 0000000000000000000000000000000000000000..fd9282eeb65f8c529d842b7b17dd183830e66ffd GIT binary patch literal 21870 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/2-vinta.png b/docs/img/sponsors/2-vinta.png new file mode 100644 index 0000000000000000000000000000000000000000..7c67f6ca8f9b5fbfd91d03dc17a6815e36ace556 GIT binary patch literal 9918 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 LYBH5lX2Jghb=w#j literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-gizmag.png b/docs/img/sponsors/3-gizmag.png new file mode 100644 index 0000000000000000000000000000000000000000..a8d41bd0257fcecff56b54f17787d6d37705f429 GIT binary patch literal 5370 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-imt_computer_services.png b/docs/img/sponsors/3-imt_computer_services.png new file mode 100644 index 0000000000000000000000000000000000000000..00643c97818e01ede8b609678c6b7a75c13806ef GIT binary patch literal 70397 zcmeHw2|QHa`~PUMD@59qy~s9pVr1XK8AJY4x|(} z&{WbgCTL5VNQX0Ksjg>vv9so8^~=|yTO;WCun=b2Vi#_oFi>eZTSWb^NoDH%yC|30>WUD4scuJCh86<59#HkM9 z+yOBbfjDj4(bWVAPZ>EDOa-w!%FRxdb{N970=ZWm;=Tov+hDm*17a->VRO~HDGzxf z0uk4-a@B^MzXYkQXJ#mZ&@w{AP4?~zFM=Q6DtbNe*lLLgspT&Q?m{4GhuMLv9T*F! zUzT5AFB4m`%u(cB1@#1vvvbFIXG0ElD+KZqAJ#Z7ELOZGd<%8B_xLIPz9sGs3~9c@ zF|I|+w_b##y?tgifF)p~k$$N3>C?{Fub=4NRJDWGT7{0gU3aXs8uR)Rruc1m=*6{H zd=V1%5n4+|UR=LBsGqT};RxL>=jUAqwZ2_qnE1xo#B)=}xzt*St;2+Md#w8L{R0vi zJkhF01aI*_m~eU3E?*XFUX%Rx_Rn`txtrD)JURSmBc@@Jq zCz)NUd^fMFGON2ho>Rz4?BP~UsY(;~s5tC0(d!nMmnmVFe}&VY ztlCr-LAONZHth;H_p`lJ&ikx+V`({#?H=K-(q6(E!^d@JFPjOE-eGBNsOdg)E(>kW zPfEB&@_V*vUOBwwHbf%wTU41A^k_tpiPA&Hi)y9kWY_G{JjHO|1rI%baAjKZecXjL zO8aHfyYJ_?FMoCrrdA_f`DPW3u4lafrG?o?OP9>NV#ra~EHY9zrw} z`Y&OP(5R%7S>mtC1z)eCZLM!@_(c00*E&8amS=Rbbc=VHT;IqOcTVquz!jD+H&;1J zK=)W{3-dO!9$X>D9K2WUrYs+8w3@`m(nE`M3eT87V11zZ0By?qRyZqJN_!1&+?{7G zo<%hBM}@gI-`@Q?<@Jr%lCRgk=Kf?ypW?iA`O{K7 z&r0c+LXq1%vl9{%taG$;Omeh(%4PCY58cPW`AR=S_V8Gp7?Ut;nGIM(~mR{XfEH6s@~SY*Wq35 zU42@BT7X+X&)}Uw-*Hoe(zAkRx9D%tueo>k-l==d$E1^$ppPZGle?22C%0d6vJJ2m zE;(0n8L3nB+V-qXUr8Byx2?QQhP8#QeX(X(CmoogbhvEGku+K1DY6q-CQMOH@&BpPrY~`ifm+Fje zU`{&jZdcpEJK-`BG|>pj4CkkETO1vs5@EM!bR;N*yCPOup#}N5=xMLY{;^TGFA&j{7J_AqdMG}Vy&`o*xmwiK=I$fw6l)zXy>;@ULisXi zuWRk!dI$ErN_oZ55zLV!cH15sm|lN&qi08BuyxBAnJ; z<+&d6MT*-4Tj)F3mu`--q|Q~myst#a`;k#juT^4xSh+xufoFt6Rc&O|MJmr81`GPm z-Jw0tANSfDS&P}UUxa^+N?8kIkKUKh*20m6V@M!oIx486Da)+~Ooc#MX3S;^zlhmSZPD4ClcY~I|XwX9~tweggGe-S1bCW`|n zs$cP9)dOF@9j0p^UeBcP%x`~i?n|1c4St+%oMIlT$3Gp{Og1|{YUgZs#po`qvH4wF z&^N7?4a-kRDcS_;4w&CTJ$oD36zBG-VSh?|N@$qqm*-9`r%Hmm5%ujJ%Q5{|nDH+k zXiUU(Zzy9=Ti-fZ9{6$PMDwGQ@=C4c13{SKTf;-`D_g^G8^`MFu0HkO*q2vkROUFiwO8e- zTI-%i%68+=pY%W9;ZfBLkE|uEj@VbU|kebRL@B{n7sTeW!%`? zZ<%ThE)4=FImS<@l#@4g?C8C*t^P})delp*Q?iGyAvP@ zbr`f9?XB{E?$6nOx_!CP>kzITnVU zhIB`12jP%rL5AjtAWww63y(64UNJxcaNv!^!?^;yz0f`i0ZKeXzY4(LgkmusE@BhB zrxK3}p+hcf17j{V3=YX9Eh;U75SM^*$;yjDrKRMdP+=|!aj2viR9s9#UIYqNkdjq^ z%5Y76@xbVTzZ7vUt_r5=np52YM@l^Icsy1?Ow8ZkU({bx6oYdUgUZXxi-}8!Nl1tQ zEkt|*(Rg@(2-=5t(#fD# z5|6@rA!k?vK8TZ1#S1^Xbh^R|MVO=tUT`!pjsYSrNLRS87oG>EV_*&h0B@`>9*GlG z!+2qEieiM>=Y^W>XR>s*d*bPV&qNUzCj~Ve5{}2<%rO`**kqvjv4jRz(3=e42o##I zq!v^Z1Gk@Xq2H^J>To;~CLt~^2Sg$faanVyl!Calg0zH)xQv3h_zWkLEkQmEFfJ(9 zz$qV4DZqhKKGCAx`P2 z!+;AEbwRl($U-IIGSaR{5qSw2aS4$ViL8;j(ffQj!vI5jhuUq=YwK zbtDeuhjh`zVZ4bc01Lmjkdpe%J>KP9I#S8yqivH8^ zLzot%(Dawt>;FvCUuR|tUuW`lhANu`=D)*Cm&qJCJw2vEDtK!A2%j^~^80M&`e|y; zF#!p4^nc`<|9Ce3GZ8d7td!YEAP;d7FgyKEYl)xbf+DbZR4~S2TznBo+_c~awgtRU z&Nw(OkV{rnR#s7LR-+$;C5RAUb*W$g4=@JSJ!YsNEhw zfVCFP9OpZirymV|c4O=Xthx+=bnY|b+NO2IY;FLh(-pwV(Ezw?V3X%}!USf6^zy>^ z|F)T`7qAyLcdK9Wlz`Wq=RZ9*X8v$I!d(l8@x_vOdH;}|Q;+}ytfk;a=!SqKOnq|U z%{AFQ7qNdLn`SopF%*+d2+;oULs$^OCfB@EM#Pd|8WC6ZaA03-_9*^vO;GZ44d?6@ zBuf4t#>6xCA;L|BKu>A<1hkAzjnp*E%%&`f zMZdRn!@&b*;0zr6(q?XQe!l?V59033e-J|G)Q*Lg0vUiPAuau#v zz|WtsQq90d@84o2A`p&-iGR;y#K}c0`z1LE>?1ZKY%2m`8EidW{Cfw1#CQ$1pDO*m zxicY-gRLhEe`);h&n$^)u9CsTl9BwMoL72E(qt6kp%Q>Nol39&OVLSBNtTSL2$i3d zxdch_zl94ktmM^zI8UCW zkftc8@5A~ZUYn=^X^T8bAxnuA|NlqkrkLaf*{O@4^83HcjGy{nVALS$ z3#cR@JjrZ33!LCd6Hzv$^Kur48)6=1!)z_zmpk%Fi<0dD+7v;gNHl4$|v)dHjiQEvju^Dm;sAL~u9 z-d%vSnADp<%=n9V@#lII9B>vQF$nj{{;o6v76+6xGV=p`C*MLu6rF!pxB!a-l4xX5_1S>R7qw9Seh&_S^#Vy&!@k^!{r=z{Zm8X>ovW&hT3&JiIc`inNO7H(NF~H$!fe~Yx7JtW7GVyFNsMv^zQzz!A|F^G~AUD-uWMY6FEHGkB%1+{aRN#fIZlnIG?gWP%lBof9u)wH6 zR2jr%@|UR$a4sO37=MGxAchQp9b^?Uz%!Oal>s&{V=ETC@-~t5a)aPB0oAd-rKN1xN zSRjzh3vmAnELITLBxET)!6Qo|F~B*1WMY8(Utq)l1n1OeUXYvg1WP{>i9wN`KRyd( zu76hr1PcS=hk=uy@&wO$5}82}o->#+7d3vU407AY0B3m;nE}oPlw>mdjfg)~2D$Bt zgGZJ`Vt^wK$;1FVSg<#-GcKpFAuu za!@w7C>vZ_vlRwWdIB%x_n^)nN>8vk$qECka28msAWBc*h5S~l;DI4AjeyNbCI&c- zEihsbH@JY8zwTX$VC70OG5!YWN!$|u+mt7G#*@s9g)Tga$%HIb1~?m#m`o@tW5(9! z9RFy7xLhSml>wgfBoYJ6E0RM7xc>#FGJZTKPHr1q;4DufF(@iy{)j=)7E_=7OLj8j zr!-F@H^6hA4>2`s3nVXR_kZe*yz1(lQLQnOSPBmN(_8J{Np`+o(855yv2X2wS5 zQ^v&Oe_)0}<6%Exizk%+-q;)Cf`q9V*yv65Nj(0&8Id}mhp$MalRQ*ZocJ}}Gu#b{ z#>4-?IZx3>R%W(?|0bIh#c(dJ3TDXdbDjRw1*&1ZFgQ3G@AFfeNoz0E?25U35?z@a zyD6CY!|@1rEgZ%dJJX*c)*XY#__$-R3dHUV2*WF>C^lE|k5QgsrK6^1jKjF1ypS*- zQ!Q049St=ZsJx7fh=eF~rkfe}3)YOtxz{H8Oj!KObfEzl@DOk%1xQ1J{aWB|%_5>zuR9DyWU8n_7D z+87mp^s-h*c_Yz2L^LFUiy|6)-pJofu^IXA&;I;zNq^>NUTaDx^TGqJq`(D6WZq*6T=T*MuB5;PMr7V&3S9HT z1FodN1x94vV+vgJ!UL|Pzy(HR-eU?}^TGqJq`(D6WZq*6T=T*MuB5;PMr7V&3S9HT z1FodN1x94vV+vgJ!UL|Pzy(HR-eU?}^TGqJq`(D6WZq*6T=T*MuB5;PMr7V&3S9HT z1FodN1x94vV+vgJ!UL|Pzy(HR-eU?}^TGqJq`(D6WZq*6T=T*MuB5;PMr7V&3S9HT z1FodN1x94vV+vgJ!UL|Pzy(HR-eU?}^TGqJq`(D6WZq*6T=T*MuB5;PMr7V&3S9HT z1FodN1x94vV+vgJ!UL|H7cP25HEhFR^-ehh&?xDeW=1`tRP zKLirK69V}%0sMXof%u9;An%+Y5QQWNgadQL@s=h8!WN~Yu3{eW?0rh$QS(PBHOl=K z7XDWKQmfdpOE`>8I@WJHwO@7J%{QBF%B{GSq>eD(+UT@j)5Iit?RG0Zp?zzX>|Uj> zFLit`{l0xthKo==Au^(EQ<>Yw&)$jKYKFEA-Ex6k9O$NILtFlL7(ams%F{%E5sLRR8I|NU)K5yf$ zL)*Q4_RMQD7ed*4UPX9!& zpXxJJA9aasyYh{%A*-?C*nn8Sx)Yvn)(QAYNwe)kY6Nxaa#Gbxz{X<{yQw|-Gm9QP zhBuFzIt8qJEI_49-Td~+HYZ`@p1t?CKX*RXQvbsE#c(D1=?1AqI}g;12Y9<)UObXM zG^}mIh_H#{h`1U)l(3~Vb!#0?{S8F)XkfokEX392>RG9XHNHnby1%myo`A?eHd3X# zj@zfLKUe>9*$w8Kf=^Spu18m@wFdNJAj4hsYZ|21@8;CwS6=FHWF2%H`c+S5GQBb9 zm1x@0+Djb114Cj4)Su@un;Sy*1J!r1$Y#9!Y)P0u6 zAG1oWgzz3{XNh5^=~}!avGa;$&BWdnLRYy%2Nm5tt~}s7(*BKgl(}m7^uQZtkD=v>i0XWUY;xd5VFB&bMva5 zGyx|c7kTtf$lM<&OAv2ewSrmb;HE=R>f%5>iyIwHZifw1wfTymdEY*ncMP0Qyd$7` zSo%w0E}~d{Nq8cid5`gtO}860ADTG1EpB{*-|J`sEgx*8`Syl;=e5Du0HvH|y3et7 zO%671R|h!w#6?zTmT!gZy%-d$QQ2K7urxX*+~{V`a0b&L)k)K)4Vx|O4O?Vngj%p% zj=N12TI1QtQ+H)3G8&mA)R39T|YHqGAyarJ_ zHr$wLv|+p?M1Dp7wKWX`C=ajK?@dmRyciqK%FE+tMfK#bUAy)&{FVAO89C-nJ=G2x z9dVL<146YmyWchWI7F=-V>H=j-&)bkvfur8KdU`qnapqz+s;RJ0>kIBIn6h!|jEoFER=Q=%pS0Todqg}uJT`3> z+qmX{^L}m|xC28->ee@mjcb$6_JrwkStzG_-CDsSyI2e&89`T>RcUu2`#cBa^rHsn zQn4*ikAymfX8Ls1Z3bz#Po~DBCJe^%do6jAyjwO1eH~xQ8?yuFTZ1!h?@sl6yvW$z z-ati#IT0zbT31(hl_c7WYbZteIJ>m8xuqp;ATRD*Ztgc{yWsbRcaEkGw}th+c`7BF zdMH*?IeNu=eRMT5lg!>ONTy55m5Q5Zio=^C8l=*j-lv|UlZ$kc9kg{Sa&C&t>o-_D zepKTuw*8c9Ro91gZSRC5@8wZ>Gzc_pYiH<)4l_7MM`khPgQa$B-;66%*X zsTY@{Ro}mk6BWKT))m{m<1lT{;{vzNjhnyZmL#}Kd8}TI*uDNOSCe>@I?v%t z-5+co4>P|pw%ll7v(UA%_{#CV9E)Wi%p#diGsi@<*IGP`;bWI*n1CCoGktI?kL5e( zcA2;5Q~S}tg2mKSjx8!yjwW^nti_)b&c`nod?C%RYDVj)({8+F)Yj!#iqhS$!;&v# zzm8hJsCfPP$~ob)O-Tjs0Kwdf@3B^l|c~o6i`UW(Np;C}G!gNjzmC z7!{soxjlzpO)RdV@ARFmU0((i1;TP~<4!%$axXWk5q)={Tbp6vrCFr?(~p($SNv85 zJhB}NjaY}=ky1=s{UScm!0YQ+PJ;mYa>fm&GfgOJ#&z1lD|zGYFdwXW>R&OYoq5%- z!Sr44?uU*(W8DrWFE1Sr!3^LSq1H@_xdqR8Dnc&5il90AsXQ(GlVRRbUbYh{f)xs; zh3f8teI)|*alQdfm4tIH z(q67#vy-Y-ZP$6L&Q1kOJJrP#Exln;|DyCopGC3qRH*z1g@_9@9}7=?EO<8} zT`MFc+*!D4B!&HhNZf|}^OmKVPRl(ro&{(ooHbw6lp1d!WoucD=Jl{SV_X@ef9jE# zM%R9gBQ(g1MHtEV)GBud-jxS@J8|>CrMp8pLF*jEi$b$d3h}4>``ZTJwj^5QSTF8*ewM*!xb)t&I*`pDJu1S z$8dI@!g><2dfNwSWoi;OZQ6FKgCpx;MkU=o?<+dav6{_?Jk}SEX(8H$HjA`ozId3v z#j*StV`nEj<0|p+#}1+KJE=L<%PwDTf~t;Pls)uyz|Ja&y|W2gmD1P~Zd5MAE^x21 zVQ8QGi%&}t6(y(M(U$fX+h@Odu31tfV^+YtCFhj`8nwREc%#Xk>M~A!_lvhVUiB|I z$dzViujX01(!M9KO@0VbjWH6lYZFk?7l`AF)7cL_V=8#$4qvnjYy0rq#}yl*&uUUL zuG%UPhyH^0@yRj4n671FVv>)3@ZmN>>KpEBk3-;71U%pbb@dmNhqyOAJ=@)(`&JSw z3lTO2do3-rlW4tj5}XgU4sxv2i*SH`vQq5Lf^~jXJmvR({3*iG(0IH3{fNLGwbATq zq0R)M77UB6e-i9=$hD87z8sF@Az$>0KWyIo*VlVzd_JpO zrunMc&MJWaR(sXJgO{6H-@94AIJVMF=be1bu5EiU#R^)93TU4Q3-C<|gg zN6Z1fmB~BlwpC_U${%(uxg>43QD_k+Awbgf?NeKCb&-b$E6n0JxSwPX`8u2yC{f87 zG5+#w@0qTgyaU;b48Bs)>r1i*v^BNeIGCrjuf6ul!z@P*yEsV$mt2~#Q~e{ATj*VB z>K5r+loVbS5Zn?La47BFmb&mFEb`BkpE z@R$6ne3Iy>MKY^BHr{Gm2CEBxJ`hHKmoEPHst*aN@G*|~v_(0`Dm?CIzBzid(?8V` zvda0~#g_w@@4jY@He@ngD%Kd@cKS0-%o4%UJq)=r*;41+S$orZ4I;OcE_)Q-?~2^A z+=U7L=3|KtzoPAUNO1Eowe{=cs-E@&SDO3PUyJBwZKg$!taha{WYBo><$+dyq5ZaC zsgHao{WC;IkZi#)mGki}s*(~lLN(BcQ$sDVLs_xz#WitJ2Nywub0sV< zTsxW4ZrE=(x+-x~{bkjnL30y`Q8MzO+ZX@?4*}9frzn0B@Ref{k`nuut@J|{`Q5){tUhAv7 z`#CN+AiOzP{s!F!Zr1l1FMJ$gm7*H*lwt}lAoQq*dXYn(`9%q?OS5;LNkUt+3%Fe@ ztkON^KWKd9UP6+Lc!NK+ZN5ja*0!Aoa$F*{{0=Yad2RLB)h;r=k~8eMdEc3@Y+o)t zI%3ID_ic#l$q^>s-a7Z-fgZu;Yn2KaX{mw%IhTrJT8^?j&q6eOy!9@FXX*ZA^<6!_ zN_J+-wyv^>(xcusEIXDTPSar-=PS)>d3|zUuEwV*?Ix|lI|ep4*X2qdH8)nY^=_!g zEf2O!2#p9+%iMdYc}ab3`pt_R$@~?f0yVS^Ly}&%dBR$2OpAz_QHT8s1XjD7rJ0*QW+&YO)s-eTU|%EaI{{J^C>XDsow#*HL*C zHJ3Jr-rb2?YLb%WDlci}(0gamwzQ1}U4=WwI_rw{vicr+hQ|6hScVV9W?Xpstc-K_ z@}paK@_cNR?xmr*ClHGq(tY(p3}YL1cbl2RqpUpcj4j>Pg2ut(yn7}*xm6jh`>IZT`&hLSDHdES zJfXHi-h{f-Nr!oJ-s4m@y>*);<(H3Wm+7H0uH~{{HWAputyGn-^gd_lhx7;M@4Hmw zblmdjQ1V1Zz3phT3AN3N4!cbcQ@SOYXxHnu;)&Pyu{GcNMTrLL8?a{VTEta$v(Hr=>&ExV5yDr?<&IkA`a_J(Umx{K>x z*BE;g>qHvbU#n)V++Ll@(Z}67A%stK@nQ?yVARqZv4vJQPqpmq){HQ>1MGJUE^B@2 zvt3E0@!HQrQY+Fx=9%w7hw8&li5F&1Iz|3JNB2;5 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-infinite_code.png b/docs/img/sponsors/3-infinite_code.png new file mode 100644 index 0000000000000000000000000000000000000000..7a8fdcf16ab81ff2b539bb396851e2b7189b0c53 GIT binary patch literal 21786 zcmZU(WmFtdvo1U{*x-XZ4DRmk?(P=c-6c3A!6CT2I|O%kw-8(sB)A31&3n%I?mcUL zz1HsDT~$w2SN+(lx~e{@smP)t5h4Kq091K7DUE;6u>UMD{J;BW7|0s{fYfUzDXAte zDG60`cd@o}v;qL+P;1kCw6Het#&(qai>ajq3Vlj+e{p;MGNYslotFS3QWO<6wTCf& zjseplcY=#{4FLMc6rXVhqJqS6fnLT;>uMW_uQ36^C*S_QZRb25b3Xp)@YuaCs1n)G&9fl#ki9@e+PP~UMB3Tw zcm4cvSJvTj7=Tdb2qq1T*s1t=EU`BSX7nAxAa0-v5*dI4)m)b+VMLq{(|jp5RtXUg z>X`AAz&G*N43|-zFl9s`?Pkj$kv9oFpxhF9QNP-n>^)ynrW3z%&2dRg#@*XM3hG`? z$vtJ&97u7TIcBBszENWgy+O%8!N$;Rn$;InJtyOy+i#&u@Kc#WN&Rz+B`I-oaqh@k z^5AiKd`)3-oH=y=kZEIX-M%a;NGoAKci(Nsxnn4HwBlL+k*vJK zxVfLA19EpLI#_}QJ_exEPq+?1K17UZ@1U3fb#uT~N6=3QVGP0W!x?G9r_Ay2TIuQQ z8Nf_rCO-kL9=6Z*Lw`P9@(ct4a!!-p{Lj0bA~^uE4uHh!C+8*EhuT3uz-(;8$Y4%~ zuk8dXOpF-rdu+Nlzb@hVcxDtIWTww+VkkYL!!;Vv0->-nB53V5ZFB^l2l|Wxw0HOM`#TcVI1cs zG_1gt4ox8>nOuY_>ZUMG4N(hTTiiyFVAQvks@3clcFo{bCG6Dl%9mw~cm=A}nw z0v7Bib%hcmvWBPZG4Y|ZM0^|k>M9%vb`GB#(A^Wfg1mzAB!Jk6(z14i(`smRtXgyo zq+cU2i&l$1tK(GoYav%*t$gW>B$Vip3X-r`(Bgt|it?6uDK4IO^u+aN_DAkS`Vr$S z`B>mm7`>44(*++%2Gt_WZIF$OiSsG@iT#yJJib8Il_CO@FG4#)V=!2Pb`Rb>l}$O7mJnhblQgWg@Be|i zG152mDds6nio!01SpJJVeF;c|`WN^Yrd`I}ry4OHnt-JARtxM*Xmwyqf59_YI*9qz#KEUQ5$S zjKjO5?n6Cyhoh<^l}Xe|CPk;J1 zx;K`$ThKimDRMh9538-dtH%HWwKla6b)80|M)e{|>D(lW71J?d#87oOb&z!pYui*WvNlsYqdN0D#e%;5@w-na^hEWE z(~5JC<0Qn&(o)u)!JcuSY0jD>_>6)sBT$^YFqE;Zv2?SB>9AsVZ>Q`)WGiduGVi<+ zTqC*cHZQ+OzxHc2b9rSMb#-RVX*H-ocHU$D;Mc|C*~&|SO95GNpM0P3$1Smm5W7U1 zdB>~q#1B8MGpzJne7}Po4$V@nTgEDO@Oj#}j|p^%cGyCd&y_Tk9x_leJ}8kWKbPH? zgLPpLjk0d_Hu&uEA{&13{PKp2&WytZ!^ErSZP44u z(e}Ym+wezQncj;*ee>^zU?+5-M2=k(ZQmcNb9-{<>qeXhP1ZyPdPjrj zLysZH4I_KWl%oqvZn+2aW2!3NDir0#Dj)f?`OEpqJV{M08)CX_{}y53IpL)#=1wIt z4sj3M+pLlEGV`Vx_I%g$A@PY12!F|lPLiz}BTvxH!_AYJ!^$J;&gowD_3|t3e(V0y z^Ad<1SQJIK>xUKmlzY0dNnMjr(DN^xCPqERcxp4u5142)f##7-r}~a#DLq8SBv85iRwsJaX;L|ydM>VBe`NY`9_;%3n zVE=`Z&brN1&OWzu_#ZVIJsSo8Zq**G=BX!~x}ABk;`Zkb@bc-^dH?ge$4%;Rx+>v- zwdP&Fq_5lV+|plP;3n)AwE5?Zh>0iF$@d$(wS`Tgzq$C{IC`)s7v2Om;um;cewV5@ zj5mwPgrMwN-plRR+6>LW(&3_#qQfcNhcre}{dcYBrRmj@+MrVJ(tvN~->$#?`0=}~ zr%|bwsov32_A0*3UB=Jtxht2^+oZ0cvf_vI7WFo_7J=4AqoL1Z+Yi$!Uu~Z%;c7#N zWqY2r!1bXG50Cnn?KFe?&E(BF(el^8`q}!rT9dxs4`kk^v%NMi#gC8g`&WvZ;R=%S zLu)XvFp_b5al*T2cb(&uu; zImjG#UM7FGk9U>g{QZs5O-IsioFC|OV|jf)n%m1SC3Yhf@q$?MF7JN|2TU9-R!*s; ztD3mAF9xstw*Suk-GR35yNT$6&Dxsx(7TLmpT)T{SQPE@%L;PcR{y=m2$VmD^c=F%JO}^G_z}p7JlO zc}pYBoZf(CA0R{<0Kou?F}ttHc6JK_%;ge15gJ-sTf#fiaxI;>6(N-*Ni;Z|oGuu- zF`S^?v)$=w&zI@xHF&8rAwa>|Th3~l?-U8BKy;OYkMG7`X7`{&ZfC3yn1=p1z9JSO z@Gqf7bd}Th005A&{p*0GMm1rRS-qtR!IJ;>2QZ>GHvf#n;L8pELj<ty9=4)t|%boLPN6{h$v34wq6|BzWJp#LS}=^#v@r>q8*baA(Wa}l@H z?Ce4L-%9>JJyKR47VdVgo^~$I(EsQ)|KQ@~DNI4}A4C85`tR?w^0oVamYhBQ$E|-3 zvi^s|%Fe>Z`hRu*g9`nJDxhZPYvrgXW#?q&?D5Zr2nPp;(0|GQ|2Y58;{QVG|1TsP z*Z)TTFV6oUg;@XN!2fdSzoYeE)PLa;K@wv9zo8dFvSBeH2LQwX@>1ejzQBthMBiLp zzhl}T)zv{eR*Gppv?@7#-oz=1kqr^Bw)7+dZ2hUYWi6T+e!dFjOIB$jPGl0_{HzN! z3v$b?w6XSjB?`U4EpxfvJ=o5POM#%sfcN+TZ z%m|P~3G996zEub?w_ltmrv)b%(!bC9lLm zzKqgJIZlflHZ>JP!KA2EI2|&G@?~1ed?HgmGf_S>%sV3#US4@lZs#zjD<0`9w| z%{9-4L9cY%sxkfjNHs8ncqSyIJ;Q8VEHqxZ4$cKQMHpfF8l5>|BFe+h@^%ou2p3BB z%O!aYA?xtawwhSpbG$m`f}+xyiXmww36tL3Wj0oU3&x%#xyS6p4M8}i!@AJAuT zy!?s^&PY)rhv?EIOW70v%gTdju#V8C?*O zC2PWzADKO>Kg*wxcKGZjWVGu1y{<>zR`knDc?9zmvV>_Zwhbp^RS_3kn z$wR}AqNEPmEhXZQt-WJRiE9K_le?lp(#Wp&MwBL-K%{ z4OhW;c%Flx42{SLC2>mn)`)U)tzT_I>ZIUT_r*!EZhP2fWf%w^kCrX5Sbqo1BbR%l z8i`WuWN4+y^MKPw^2?oITAH>b9N-XjPXZerAZH!}FxPVa6~QnsnGD73CR%>=xQ1b3 za6La_7ojW#91ehDusqQb+Zz)WkeYk4EyMnlU^0UHLMAgvhf2a=WRSH98!86}aJsY( zQIt}^F1I4$n-&e%4#!yF@S%eZwQkpnD`uu{l(5UdPwNZb`>3ESAGg)uT#mV3l186C zX4E96rmZ5gm*4F~bdi>y>#zU*{FHDQ@Wx8X#z54>01RPrTvM8YZGqiz9-K6@3BUfZ z>d2*{U+%U{J}a*Al_B5*^taf&NqY$mL8R2T-^dkYd*J!*OuPe({!$7U`SPp*yz)=n z5|zxQ37+Cviczlj%{&2Lg}85$U*B&3LRX}<7xfkj{L1KAx4b4$Q9WAw)!da#oJFY~ zd)_Modv0wKm$?9HVajzFG~Y@5XRu%+RcXiDX9F+3Z| z*e@`ll|dyQcS^;0Z{qj|#=>UmZ$_ok1gt@$>(uD4g$G7gY zs_*LX_Mh&ifKiGcYzx%1)OHDfls8}po)3H6e6>KIa2Qff4wv7JIu}3za&p9EV%v?w z-ekmri7bRXJfa5G5*7>z->g26l;x_HS~F~AD3aUpQ<>R~M+-uj!u|eu3V?Q_?aC!W zc4aWCWyRIoRt3^$%cEW~&7GQ#X7nePBz&D9J?yUqr@A5|%ysNd^qLh;-U5e~__X4=>ctfJbu* zPXLv!!nsjB79bYwWn`#~qjqE;_O`i9{(kE!!A%((%sJB6=&h8L)T?ccf0!I{5u*I) zeIC8Y5vZUeJJo=mNR8ErQUWE`G$$&~*S>70Yo8jl%le`adek7VtmAw9Mc`2@NXC* zXA>#c{bJF_m2{-uukrerT)sCb9@NZ=(=*x0A1g0_4`GPL^3n%OeztC7xnJ(d9cfHQPdouFZKTZ~)*E9rngW8H(5HQ<)q*(kUn^oR#zY zpfVxqNqw!-a{XQ+^QMG_+3nqQ{&I|mCpJ@#V<)azWkxc06W{m_pr9~I>#Rs?R~e)@ zd85}M#QWMVC8oD%~|n zi*==iZ~-lh4^lt5Is^n2n+KGigg&&Z@f~~}jr8OF)mk2XAjzd53$)9Sw!K^hpIw+J zK6Pse>J0+Skc1+vFu~n13Uv@qD>L)E>L@BqUw(tjviz(MgjrO`j5K-0zD}LSq(M77 z6J2$^BnF6I%*)KJ=7x;4;>;>!rLT8HR$0+M&^=v9{-vTn$EX>~>t`E0;Dfh*eB)svfnsh%@ykmQmcOrmRF8fiB|6VjZ3Xz&n0#+ z-wx&y{iX^vQ~X76@r#Q@Xxp04E{s$KJ+}0==SL*@{NOK}`rBZz_K$+{i$VF38HRhx z*iWs*EeW=>u8PvLcay=aM}!)K-L6upM8*Jl{##YH`%MD56R4WA#nw+3%(y zrWjGOT32uJOPG@4y47enS3e22K+_9dP z=0KtVz=M8T`6EXY0TssB@P41b;G&grkQvovC)|Fj*yAF0L$M$wg$SgGnaA4X)Y3uF zzqJ9|tSeBhRc8AO?>$X^{E`Aye^OVcR4zO?lxk{B%}BaD?=o%UHK`Vi7s)Q&G?*); zF+l1;7{G5+AQNnq75Tf!dpJ@z)I|D-kj#h5RUsLAB$r3`(RN5&`;_%hcspCJ4`&|A z5@T*&z>6gX>v4VhVi-IrGcBYQ^sI-|U-tGe$KSM?i^QOU zdhGvN2Ye4qVpq!iQIeFX>*VJ^gs}obFYAkpf@wEr9%7bU*I8zPF))*p}O<`dG6WhT|Y>u+_MF0@R zFj89HX4cD-edWb&s7oO?(TuZ^VKn90D|G!QWs!5MdLw6U?(-bRA6KIL0gJf_Jd?Nh zyZ!#3RW%g9q75u8v}&6!LfudbMQg}TZW)o83w7_0n@b9FxESMZLmObqJ*r~@p)vZr zReqR@Hx?o}0xeIZ!%bgNM1?kpLs_%zzLh?0g?quz|I7do*)|2ic!R)cx3CdzA_Z+< zXpua(#fY9Wh<-KNo8_zp^XV|UISzBrc#cz&y_RkD-s>!9*NO|&5;z1pH%oZHD)35n z(nKBh4vD7y^}PyaLKrPUOBodTK|uqq@rgL(?`mrXgjICBt5GhgA4}M16ocI5S1!OS z8iX9n>(Vo&x3Faq;XP?h*P#5H>ljLs+kZbQpZ%p8J(tQc0j?5(Lfn<#hCP~_P?Vs@ z>|8iBG*K8^^a(VHk5o^IMfnmoekJAu-pa+UCYlRMneiYkF|;;JCQ{FuG3>NWKcR4T ze}mvCG@rLW7aBQ+j$HBRa&9Wk9|%LCPqT%zhqo`6Sn^b*G>oe7{d7DWMxqoxf4-tJE%6dQXkl*^ z9?uQdeRzmfI#!l10_dW}ZvpXPL1R@+#5%u}P~`kfDz_dN>ZiU|f2$ZZ3&L|q)2382 zZoEoMB7J^vNSGoc{Zn|TQdRgZ*_#xhgEPi4-QG-p}2X(wnTgZ;2@=Am#DzhCPv?G|pNzCcH$cv{NQ zr={TWrG%?o&b_h5;+p#)a`AnOXwkn@+FkLpCC8u}0E13iaWrA=4rNbB+)9;q!pxJ! z3~lQp;CD!0PEyR_y_LL*la&7%4Hbc+yoib|`%-yU-q**=c1;H4DekuqAJgE#HB7X0 z2fnyh*9QTIs_`OB)CAA?YwoU|jv8a?JoAJd>s8FTvz%*Qmt6Q-50)(NsbD|OX<$r< z8Qb?H)RlyHWTGm3hQFTffEG=S9AgR}9H+0jUv~|j zTo?$`RN|{)>a!|K(w4UNDTnS3a8=tQ`?BDJP2Q*$`o|LbsPco3Ofr>du24*SJuMPK zQs_A$5*3BW%QpJqFuO@-Oj|^JUTJ8ett{lpaZjMwR{eRCpp@c8ZefZr+RU9VCzw83 z#`ImMZ3>iaZVuP6z<}URW09qnMEt=MVh?xs!#}xeQ`Pn7DaEpAit6g@){q(m>NDx8 zVHCFkSNeQ>34!~FL1JO*=%2~Ig;4M`7Y&KhWl+#%X|qZ#3N+j5H_Du>>CXWt9C)y> z=pa%YzvDaLbFpqOBTNR_vK5X+z8O-pU3Ja95RcN?sA(GzwUJFgcdMYn)y@b`O%YEF z=5QC&w(enqJc6pE*R?gU>Vc%G`9Yzh%09P-r7G2ZdbooNg{W;OEOzCVy}P$i-1|p^ zVmvR2rmIvjhAmvb>e+#PNg7NPdOnV|r$Ju*zr$^ULz6&bF`w0?DEyz)pXNkW&RPTiJHhxB&qDd9%Xsr`mlhYZ8*=W7U4z8{Z8(M_%R z*`UEF7XSA#>0PMY|0Rwilx! zadpBsW>tU3oHD509yJc#pkthdDSrASD`7hyAtQd51RRtSp5ga(d~9`#<{Ot0t>9^! zoP}Ln>Ru3>0jnhmu2kpP%X)MA1-G@AS6t0bgSdgAsv-?f_Bg7sV57zWBsAHGx>bqy z5R&&Ho16B0k7oiwqMg9lLxAy|y-2@7aYdhI%)h5iq3))02(U6~nI%Izfuhn^8VlVqNfd#DW8evBk+#7 zg4KWP^%>UH=+kOHQzs|zW@gAEk361J;|hKeLN_qG==8doUQ(^Z{p21VqlksiYU?f) z=@dE%J%~+)^UT}cltw~9U-z2KosFWA+bboiE``#fdZh$%*vzQb_-8D&0s;bJK zikBB1bwq_K3b~guwD!iz{(Ea?9hzMF1BX%&?x{J1YZ3I(9Y4Var!dq-nH$^o@ z3O*@rg}H#Qyx9DtfOy}!x77Po=%pl_nFjdy!6ULi>ZK`2I!HgSnaXvhP%#`8?yk|; z(OT`JX0#Ma<$ZK~Goh+>yfz>|V7D&Y?AOC3Pq%Kc`OVZdw3@UsPpg)L@D>@|XD zma7CI2}wlA5~{Goky|(z-7m6a=Eoel49Y-gDQ$R3CQRzG_nGYb;UB%V?2DTAkq`SU zqu3tz`6gpcfgs`I@8;pA=Em=N%jxz6K0+SYXq)o@eA`x=By`HqRem`caTu_z1H4Z* zhXD?3DIDJo!jR__8mvx8PgJxb=~&{`#i~syjSh{)Pi3tE|MK{-Wrm1gIx&2tOqv+l zXi$O&RBnPmnhl!49^4>*AXXW+^^+1{Th%~_Kc>;Zyg;9ge0IdC8?5zvGqAXZxq66! z{BDpTwU79q?`!83Yve>(Q;iY|#vgC)zxzyc0H<;3f#o!gEkO-IOX`g?r^`rp=tZB&=d zb`)#ipv@^^CXM$qbh{sbKq&krXPAYAEN}vWQ(?Z@@H!dM8Rlc)Mc3h4eTSm=swsKo zJDj$GO@U*(cB~c+{=pU*HMvA)y-!k> zLdl7XdXbJpiX9(}xaXM)Grxes(xS3FzPkQh$e(O9M?u`+ZjbJN1Fb@+H~Wf0Zx#fg zlPjiYrJ{tPzIcUcQG2pPc{KNTX%SegQTR~&QFq_IdK6yYrpC|}fo*a_{74ul#b<0d0MroM*TBAz{E8rClq+j0d( zsg?fDohtQ8tNIlFnpLJUaar?{sPz^9DSdO<12kacOU%pB`}XHR%$=Cm0O?+2FjJFh z4T(!HAb)j@eCe>8hlE!wxP!+Dr&b0Gk?Qm#qo7~|V zA0IVRY?QTB*LD!+NuFwdacQY`N{)qu-f32K=s;wnfC-G4={bt~dJ}=9zkh%+)JIqL zIXnZBp1w9Q>-GI!IBxZ!l>4wo%uL*p>JcG$>)&~>{`Pr!RKYSM2*Y?I!bv;VxCYrH zM2(C{mA+|&3!L+nV>d#YG4o|1iaf)_zR)Q4llHvy*a696qA8{6<~3r+?dR+gd;|$w zxsUV!F~;!xq$CVLA;K+83@Vn)cIaU)+9cO*n1><06oB|tT9V;V=BGnWmL#o;~mAfK%=;QtF&;ch5L|XGl9ys~Q+{}Bz ziz2ga@$-@_@f?4azQ8$Wi(fq&zhJ8|Sii4a!W`>7?wf zm4ksnesSutAV&<|1JZ%MovYyIX|F!I?^SE`xuW<7?(LQ}R1c{C5&&w_{6wRzG#x(% zbbwwHKy(!S4C?>8pG4`7x6~61k3nyYxh^yiTgbjmlg;CjMsij+a*9f%hz3=(>JJmU zBFH5?Po-7q1vmk`aKm{3Y$$V^m9-yUS5_O$Q$IkR^5dH0o`A2}fa% zsOyD!3btW9-E?7R5u$y+b&n`@`-rN;6lT^Q(R9bDt-O4EV8FGVwbMbc!mZP*O}Umh z_~pvpqQtH%{c}#2_e2T+YPtA-J}nTz&`rW+Y!t;k=J6{7iQrf+0f<{elUH1#S3lbsxVs zdN(1t`NJ3xB=zDrRhWAyPcIV&`e=>eqZK%QXW@0+{6zjTxZTFX3K7B+lzBPI)amYE z;$~3K8Tlf~sHU8WFYI0Gt~7MjKQ=ml5LpsOslBeJq5$6XGzbBZ<`GhWvuFgYbguEf>!@2exSuq~3<`cPidC&M}mlSH6MOvr-*dd6$kSwjE!NEr;r|CBw zBpi@1M@cmpu$04LAjb*U3dM1{obWkzJh;d;yql zgM_2&E-lSK3>C66B*4I-O~_z5$3=)FufJ;x(h-+(%f!Dyi6g}>(SO9A+LW%Xi9jc_p|BIu%6YZd7p_fz3&K>fm@bs?bX8*qOsEw$I@%BKA!c{4 zp|1wS;+9*Qm&qSz0U*W0X`ayBPAolUVp5kBZG7BO?0B7=Du&u}9Fi&yhFyEqZ21^x zPMITvB2*92ybyZNp}09Tt$rV&ZR*RK4RW8ICkH;BHtApO+Fi-KpTZ`MLL{G~jfb&l zRmRtUR#<8h?fPalQOqP3z%B;-wl#}!Gk>6e6wg-JD@^Ma+UO20RuSv*M&S@bAIlUo zi&Oif7N~{x$X)!h=S(n4xow0bnp<)q9vQb)XVK?fpp@=gA=|X(VWzEnN9X|JOBneQ~iUDB3~D-@JfB6hz8MCivlc%<&GL+RO(#bml}J^boSeS)_KGNB z+I;M?bACg_{K!80EzwDPwViG8XVuX2+TO6H8#}3!A&p0wj4J^;Elr6>PW`tzcyP`< z#Ag3~h5G}mDI82u`(Gz|rqPOvHLi$K&JGToL^M@6nkZUKVRwgn@8>sTvahu`aSA^P z94lGgcEq=I`Mge3^K{yBr9NI!8~fBFE|TwNvr_@WA5)_4)NRArZNn!g6)IgSbpIUf z&oDtV^=8xghQV9l2t+~0bQU%+4V0l z*0vxWJa`k(NqBA)eEmmt6194%I{X>`__j0Eafg%E6+3RzEHIF`{NTxQ>74xD(TgTw z!{lS6T?c|V#I?fC(Q(=0+Q2&FCuplVv_E!UT3-w^TKIWh7Q3@QUf*CY*KpNQtR$eT z6H*y6Z>l)qsygBaL{A@OL&w-scyF%l3rjF`TJtp|@ty1_HR*icGY@1(+YC9D5I+Zs ze}^e7Y62mcH@fVi?xzjqAi^r370puLr8)X7B=|4gqlk98F%^PL7GL+Ry_J<|(b-YgnJvST6XOu1ap)2`96MK%r_H8cjL+2!1p0 zAb$!tY4WuF-`g^QBypGE-L!gibd9_b3|#-L=`(Qb-wfaw-CZw>pY8>kzG065v}>zH zT<N!3B915bGPha}!ir6X^ml(-O z&S;KXb1ty~269CUeM^x@FD=2sv^nBk`)})eN5!g7q z=zhmH~^qx}qv$35sCKJDQm4jl$WNCd{Gq3?RYC5?n4jXJ=>NN-+p z3S}#^RhSTrDqolcW|d>A{yskLq9XKYJ52Z7GJ-}#$0FDA-BJMDJv>+`AUXcWU79BY0?r6?;NvDVY(c=Of7&WWVd^YtvrOw<5<`t6gVL53Zk@ zkS85roptfBcR#7{o->uNk{6#WfQ^o?6-zKG$0pMMY;g#!@_f>ravQYWS3%w}dbL8C z(FcluWg|`}Frte6;2l)CqrNPYdo+d|5dhBF2>7d?oa2*WLCBXup3i|}Z1MC00qR|177N7AJL8^V-te}JDMD^?dqlR=TC zT+4G9P1ju9Gwx`Dy`(mP0fnZY+P>bd0lx&cJn+Zd(NEy4XRwaL`!~3yM)`VI1mNQe zPMLK#y9_ODiHBfOzQWZ{tx&mAd|Gq2hh=HcB!inP+%vKvVu|p%g&8=~i|vW1)jo3z zX)qeSs>nt~kC8fxL6jJH3<{V2lfD<4aL4D!&xt?AXmh=ylhec4k7xK!K=vTCuu!z) z)kMC36CQS%n?reJpaM+{pU#&A-5ys-@v9uOHfQ32Nh4#si|lIyD|~xA52!ryoSDS+ z80xw3YYalyaX^j{ZvJQ?nhYz z7+KL{p&tl{QxT#O?1^2{x^nzNcae){cw>#!8-Wso2Do4Mob0~hCsu`VKF<}COJLB? zyNAU*a-<{%z#j-D5gD>RlDt+DJNijpDlq_9Ik|jM`S|;ksEv%|L z?xr>UGKCgR%dmvf(83D_fh(bCG;y6}~mz@OUk- zp4c8KK1)y83qWtb^?9fHVS*#%E16w*2-gallen?GQ^;rrg?`SZK|jIusqa8@njiA2yYmu!t#aG-#{{%m0qVpXiTU%Wi0DJRKSrju(*ul%Ea%2en<*>5ib21d+- zKQuIKUy`yZDthH0d z@RiyEl+38vaE|?^l0+nY_p}siQx6od{sSN zmoXv7S0?x*ly%A?I&Vytr4a{}jmgGB{Syu+G2!RWpRvLIoeoLVjX}L}+twOK<)eGH zkbEiG^1W>B=R54^m{QP%(F#8;q+4hAyPUI2w@Mv-#R!qWD0Mz-{KKLyte(1vTr?04 z)B+UI<3>gpjtC2}p(Jb zTTRPq7Mc$`5T$2nT_bNmCixlv6eXifj2!Cf79*xviq{thcfYv(ohd)ywfbi0P4(aO z+SYtRShx4hhLJsw3)U?x#-&u=lrJRWS8AjqEvySPgr|TEW1170b)RsMKfbJtH_UkY z`3CW%L+`ODWVg!$E-m@e;G3e3c-*&xCqE2W+x^B#`$9)lRR0kr5OlXeY#>0A_FD!H zitEs^+#qePuWczzU-ivoX~$z~;L~+ql1tB1L(85K<*z3n`Z*$BpG4W2xzmmHL-??WYOnXne4*r29S$xW>|{KsyaPVJ^6yjDw>^H?J1to0uu`gl zT|#mHuq-TTVu?`f%;i`jOs<&0k)vWtSn%W?Z-M>w52{T3v^n8t?dM6(ofJx8W&;>e z7cl3N&71XiBktwp<)7iqHTi8b+6r)_;7t&Btjf*I2z#uAt}+0%a!S^dVOG$W7%C_z zyLoMNv^NwT)|IQ|QE06$)*zvjf#YjF`og;%b4jowZ{sfYxdCS{M3I0HpvO9xDfp?T zWGoaZH;i|$Ir{9(+{}YRN{RjRw=Tq}Nqm3&S%ICmo1U8&(LbH9_@-<`nZs{LkZTTW zV>)COA>lI#bJq>UI5h(U0Y$l6#7Qj?f>GqZyLdiE7ex}%V?{b%<|Wyy} zr0rm!X;V_~g3}*3!UNUtB>8=a4%$VpJWm*tqirakA2e6+Qz>E>KgeZ}4k{=6+EU85 z0?V#iVK~msVZD3D0HwKzJNy!wC+j7_cKpt z1^}6_m1J%K9V3-zWtZlt%B-0_r7S3dKtxipMRN-B&V^EqhEHbSMc%m3A}1c9Rph$u zAMk;YtvDK1qa|3Cd9y7erw~AnZiBBf3}LUT9Y`0kw`dIVs} z-`k5B-bn>cT_~_3VXbnrYDU}A+Q&@j3JKH{=#bF9jKkxgzWi}m{Z2Uzd)Y6>3<1Cx zq+QP=!Q#6p`gnUyW{t|A$Uflbv=MsgRq1m;#?5%>;Ro=JaOHN)>d$`(GL$ZWy7My= z#xpYqI$#L)NMvgWK?52@P=p76BiPUfd@ZB!rvp2FEhuNMWdFoA?%25}rczxjjv_n$ zP4T}P$VNOb710ZhG{72>Gq{-U9%(BmQmVGVv`9PyPk{001UP>Wsex~2 zV-4JlrKDq;w%wy)gN))iSzC>jbTuQB!4Rako~?ezS{(l!xY_-$zd&w_l#v^n@ZtFE z;kL&qx&YX4AB%3j|B7LW*x)LMA1Nb;0mg1HKK#@P1aC(!lY=DTj^@aOd!!7G_Xa(u z)prYQLLKq3CJ4F%n`sQ_#;#kIhb{t2k$qUp)pg9#R&VApE#d$63(S1cuWd2UJl})m zt>@W4y7=v0Q+K2R%@|oWJ*jlE_^i#LfTchJ| z_ZfOkLyEcufe5At?-ux!wr;L4uE@N)nW%j^K4S@4iEs~8EP(|us31AJ15_&jdrB0$ z3K9?`q6H{k(4%mmoO6C3_+^T&8|Ue_JN@?Q&EULSN1sAwX}P=S#AZUw zhq*w>^mKHAwy^DXSlGC#4Z9Ke2dHbQ_cKzHygc_H$U3m zI97M!m}3cYryvjmGzS#G(tICg)U!S907OQ)--lkE@d_uvjyr!9#G}+s-jAG}B?jlb zCm>rumac;Tbv6`ekQkVgnUSU&f0^=Gdu36tOGfaQM!)N}7?fy2W3UjrhgFo*@V%1c z(o$)}D<(s%lv5bekG%vhC(R7_A{l1l$r%Ee|Kf}qF$0>eABv#i>cgXIh1r=N7*`kP|vVb5xA_&+l%^pT)9R_HQ z-C0XKjYea1$hPlC0NE`AiLud zY#3SsOVF(ErtzMQ8qA6KJd0plzh|C++dt2D3>uk-$G21gTgD{&6%HIj*6RtuG;nZ$ z!`3frxb(x<7njAgk~hr@x|UAH;asVmR@A=kb!o;B$T%CW7~9oB=2yNQJ|Ql;IdebV z@I3xlO?d)m$xj{tg&z^BP9dqk1vIfTLJ$EkK;#epQIsT3gs7Z0>r?3;F(A|cDY#?i zp{LJpNrl$W;%8r)>Mt^s_L~I)TZ$klKOFCX2SxX10S!i1yP*AH zH~IZ5`Y8ou46DkkAA$YAP>*l|m6%`U^FZt+kViBE0xm)<_N@1z&GD3(Zo>JDPz~9i zD&`t~TJU-UGJMzx8gPI-jNvKiVQ4Y(;)FqIUuNuIr9UHkTg+Wi1`cTTs}&fD!pl^T z+P%ACEsg{#2dU*%0ZoCMD&8%=Zg4{|yCeAjD%Y8xPWTeyye5X6<|cw_qpA3Z%_*rn zGVE+%#`R4U`P875*28VN3D@?S-^yjLO1K8h7{T4_@I9o=sCJ--z&?-kL#5896Op`@VpU05cbMRtSMF;a3RhJe>On&91UqLbjg5~%OhjC%`gk(pf zgQ4nuId+V`JsuTTYWKrDoZ;BD08oHRI&?wY@N0C0?bPowikH|%IIaZ+otyF8djRFb z-?FIP$E6M;-6-SvFtCUoQo(zTL;luMlWM#Vk0QtH+7GEa9oD|&QJf@uRwP-E>r56> zTqV`st9gZBaS?Bj>OSBUDsmNh+4g~~-@p6bAFB*8doSnbroy0@S=PXa=G8K8gLXhc zu#(HOIo;^T38g%twA;_;HCqfsjH?cYV!*VSAv!K6{g48tqFuF2p}RVTzz}zUE%z|L-9v@Dy z)pqOv(}BnXio5!=EB|ah<#Ds3!!*$FN<1RV!@<@&#^yL&v&N4EEKbPy}RhNK>z~I4G=cJ zHeic1^K?T9qOh6c4M)xACb>_-VS`$NBX$vngfe{HHWzor4vx0SPjGkjTc=)>L!HND zaIi=E$A!a;RZ{-+*N#q z>?+?N4V7zUb6EqvHeV{mxW`eBug=?3U}x5e%;z8f`Fgzf-gzfp!`CIP*mW!@D#m;O zr9-HGDac;!cN&2Zb(;tez&s8eWwBBm!pWAG$6DmczP<85=RSF=Wv?9U zJS@liTBUQaOL`{;WfU7~gspDoXrAezgqned2Up5U%4Gk6{W3T_pby4yzF=F**4xvd zaJ`*^R-qdR=q9otdR{!#H!A zKjgjuX9m7q$ZR!hltG(-m_{A>&_1-S;xP+IS)E&r2YhPuZF2)S&Hepe?56hY1r@f_ zbI{uh*UGlsIw>!xz&of)WOZS+)aO^}K^ne}h$|AlE@<1e*T`>t>N9fRJ@-ma7d{S( zl|%}fK{sVrc#Q$* zC8I(HCx>Kwk~1QhF#jw>e^&$uS{@jxDiMuoz}MJ*WA`sf-`J3RWAFX4Cci=Ypbe6M z3p+eP!wSyQ3=Hs<_BQF3$!L}=!sQ)+lKDgO4Ccl9`Fe2p=*LjOG<=72KAs2V7Zae< zf??#*fur(h@AF6lp+@nX1HOLqo=CA&;S6SOUYV5R4K&qouPdsPtQ_1f+EyeT2Tn)< zj^yF}0Fud`O~He28A*ULNjGjgX9E}!Omhj4CPZgBDDDocA+ueZEx>+P3<%7g%w>aEv(2TeM()Ep+H?+kUeP-NI&B+J%r6AD437k zKLUjxAC00B5iHr($t zB@ukqHw)|MD3t>1f!`?P4i>=&0YhuemEd_#@<0cnNI(O8_fnk zfTop$EN5u}gtIKw02=4YWF4HJX-Pvr!K;iX8GwNu0e!rax5p>&&MD^2H_YT>ez6tz zJ=nesH}RX|_A%N#j;(LLcOo|%Vt^`-BM&Wu`LmvCOlvwkYP@P7#4(K6&^R^=7X_~s zq#u8s(_>PRT?*~x-d@Mb&tp~`2rScsfsDHWUvmG`yFM#*Pgls_9)1Ad^sA87xDnJ2 z<|uc#-q0}o^eWNZsRb8>f`1C zfu~m**K5H|cwK%LM!~ZKaLLU4IC!T3js&zua)z@cPMQ&{aaJdviH5tnsgU7Tg9rmS z(=p+EhUKF|)16Y0Q-&LC_UhgMCv%cqG$62C0|P({Fw_;5%WvKMD{{}fzkv5S*2yV+ z(5ni!vx~4?n{;i{#wvqCwRV0V9N5m0$rS+H0TaP8maF`N;3@nC0l@C!(&8kY_QJHP zHee_?NTcmRUPwbG0>fqp;j9A%Mq1LPZE6h#ND!})L3;Si?aQ%fS9e!0#xYaYc^<=( zIRpvrz82zUV8rbqXp(v8FUrsmE?2<-AP@}2IEwz^Yu+jKc=6`nJpWy}@6=E5;*8b! z&SbWjG4KcNzB{ycZn^EL|H9n_%EJ?ahtX;9yIctb3tazIgOLM3l=qyb$~} zz`!M&K7y~Du(~S~$T8+&EOH#8WwHEK2Et(>&JOvFp%y?IyU1;yi#km(h&M0_ zu1o5XFJ>7mm&lsOXxruqU-2Mq94f`i@!C{gFQ32dlk&GWeE}0leD-gmRaW5{hGNVe zCS5mT8>}Jfpojl@vBIkM!5ZzcNW-2L^jM$7wan*5n@r#p2$Sq}X*CbVYkC_7=;qLY zf4y`TukoA@kj9q8jAzCH9ceT#4h?jh09h7As;dyK7?T+o)$7uc%(R0C0n^Q6G(VA( zm?&<-8}TZ!xkbaY3z;_!2);rdp#(!EcH(~Z`VYwC@A?DzS9LeY$#}EG@ZpeJ+%hIZ z*$ic#b~6f#!VX=C_ktrY98Z<`${YOXSWe-<&Bm(`k+aPmT)2;eO@Y}^0Gd$#^k^O^ z--Kl#VEq&{u6$*%E05`|PH2fw&$2Z22$fkp`|4=+$lLl%3Wz#Zi0K%!;Z z4?lSj%z5A@EsLPVAIx=wFjraM0~BCd9X519gAauQzWq}}vLkmrUWB$umyYDE?{xDe zRlv>{ak0b52n7Tx3`F2mO}%{f#*fMWz4<@MO{I;{4kz(OH#{MY*{|`zF`2!Mt-!^5 zSjt#t=E7Bt)9W<{QfrT$L!+96g76pURJic#3W8<$INHpIK`4-Pbo+b}9i$E1j8}T2 zv!Ow=banRivT}W1NP>8Vk(R$_H@aj5gAS+K<&N50WK;FVvnOz|Sk{yCmzH6b_@}3M zpxzF39hYw$cu@Ywi3ibRvZWq(0w(|pZci(N9DmYm@{IGXG={UG+FRl9fYJ0moHoj( z@%aWe9CQ-^XT+1<4-udW)H)M@UbPWi+S?twH7~l=Z^CC-P=*U<4lL5gskmGoJ~?^C zc-pwXD&&@Mt=OX^&SL3K|yzyp}OH=LlrAX$Phw-%Vq{O)vdOXP`lS6+BjNN3P{4cu31M zE*;Yw1nD}{FplMOf8s#uw0yYk4%xo?8YLun&ISS!U6L9>Ywv!%!XiVtU3(bX;hsZ} z$X}j(NY9+rW|d-3KT}2!#~wkW40q{OAPjRCgVSMTkQoAk#@lq0jsVah!UbqP5TI!W z0+tazf8<9&TabZ_ZtcNHqXMDd<*ByCTNzd#P0uu*-e&<@^V0m%PHQ*Lf8dn%0B&AQ z;$Y3+-})uFY29um;;!VmfN)7n7TLaq3;{qx?ZA(TTwAjV9~7yPTUK2wUpxAcJk-4h z50Vs010J>G_B4kK9@kYs5MmL?)C1su?++j_h9V#Mg5PY20r9^MZ- z3?SD8q(M04t8})?Q0y|ofr3t7t|owAX*4`=GIW;V1`_dcFm*z{a>aj>9SzrNJc6{Y zvxw*MT=E(rT<1W=fm>|g$-xeJ_}DY@568YQFOQ$bx~W_?;Q0l;1Bev{ZrwY9k{L8i zWp3@s2()ms>DG&bx^q*)6igZzAuSLFoXg0AifQO3AXu#C;d(d)Msn3E^pF6R4TFba zK1#E2X~J^^mrT>=;L(An5^u<-YwnOgy5$S9rnCX+SqE1!W^xvDSpmVfBiz6Q&f>k+ z^@e=^^`FQ$+n$uuiC#RFje{WkW+gZkr-pz{L8?%YNticelU`1!EJIlbA{Cy}Q&HB!*HzP75ecO<+DfGg3Exv`2@v zgyxA#;-5x=KmPJkivH=K+gl4erVF@%*^nzi`V= zCIHjOd;@_K3i#(IgADg^O3oyfOvBZ!eoGr@9SQ;-^*f1bIVxf1iFoHGQdIjmfzz*1 z@T+m*EQ{^s(KXhBAm~W;N=xRr+_URX<^5OQsSo$^;44>MOEH%{5PV}55cHVTqz8c5 z-}SmYeflN2zvEdsJb^hxMy^~LDFL&xa9$DzMj#CT3?kVUK2$O?!&mgNeccNA`s-@EQ}7!KZ!WA}KTl7M0*&cnRrfZ%yRrci30Eu%QjeQK~x4)-?6 z^LT>k>pd^ZSiBQ7IkE=tiY&+SlaJ%^^iT@g!o)*qIN>8B6CiC9b^rhrYMVnwLqbmz z7Ph8$5@tNptFT!Ng{U@L5E>e^3ZRAXvmxv;9HHNnJS-opyi0z2`=@31n(I~g=QA9z zByZlbKwtyGL&s?fieljXxEbjl>%~JUE%I958}ee$0eN!hsGLZ&A#xh8;Hi*GOfY!| zPtzcoJze0&X80*U%?*-MFu+51g%w2@9hz*<1)yqw?-3~9eoAdumFKN4>u zO`&cFlC2WOZTa6_`w98r*7wOKerpTnLP5Yh-qJu|1HDlV0gnd+!^iMq@xHMEX&LU2 zL%m1kSpRXnZKO$FjJJThIOc`PSPjHbik?`2gQncUq=mKAGtL%#vf`cc(M=yxEl`BF-)a^} zoYs39bY4F{H$d>*At1&J_$68r=dH>dZc5dTaFDk%l^S*^2XR{EJcT&ZUrA)C9r5X7oZkfE-j#$x}SdmLYNzH8y@hh|4{AoFrGB} z*gA;#HnTXEqj5Y1O|bCsb6zU6klJP%&JAcxNic6bE{7+*c;#RZpa7pG;6t+93<_@u ztt0@fMyk0&y32?Di#>}2vowaPdpC?YYZmcxcnCsWQdnl;xy*88KVKgIAN=ny{^~g_ Qvj6}907*qoM6N<$g7qYafB*mh literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-life_the_game.png b/docs/img/sponsors/3-life_the_game.png new file mode 100644 index 0000000000000000000000000000000000000000..9292685e7c7e8daf823beaf409f9246f688b79c6 GIT binary patch literal 5485 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-pkgfarm.png b/docs/img/sponsors/3-pkgfarm.png new file mode 100644 index 0000000000000000000000000000000000000000..9224cc2eef3150ecc7f3fe3dcb2c1678cd817bb0 GIT binary patch literal 2275 zcmb7F`#+Nn8^6uUjTZN4Bx;+LoGW6X*=BZ#w2UWI&Xzp@vCr)blgxK~D68eUS`|`OYOHg zzOxf&jsBkj@NPC-0RV+U7LDQ(HTX1l%;Ph0gD@|ndb`--tU?aP?YVYD&FkaNb1SB& z)vOh^5f~ABjIoHNK@Ap_a}J_Ju7y&RH<-ua13Oo0{amG#w2lXYEWvx0No9 ze8t8D7c=p@FvcV~pXk-CC@^KX{GwUQ-zyeL=Ey&U2jI)eO=K{W2DSuku~(%Q5Zr1( zK!9achC*>*S1!slhlC8%r$^J4FCwTKia`P*X>xK!K#;=`CQj6+qLax2 z92yi5-%m_F=B%xifgSpabP>{+isDsmze>jMg52UA;KO)I8atmp$7SNt(_Iq-oD23gVKK z?4CP)ogK5X{U}^-7x-!;t;y3^6K5S#USVlDW@A0+`?M>x!^ysTD}{^&1V*p&gMfH! zF_NHoWk6p8&OF5v7{z)hBShFDq<0{Zdpj3omsvsr@bqaHfysds>;{zq5qq_@LAh{K zyojAI9O3Qb-ljv2tP;?@K3WTKL6Xth4>(BV{%L*iA?WFfKs8H{&&Ji)QPOE?1xWx+ zECE!@EK}hE?c>Sp$Q?3!CMCC36pw1BCwtL(I9P|!2hqVdm zfc691zMaf$P3+Zz-G zWt9XWmdPT6OL6Us{Kun-d11JwYqOG}sY z$!fQCLgcvWFacUosO{r`cn;-e>N{KvLxQ>yI2SXA<8o+``W+UzneD{Qh`j4I;Nt!;I2p6qmALcUhq)Ywa=D(sr80oPQ++Q-R+p-_Z-aR`2z%rPtC)zKXZE4 zJM0*Ko9Wt>H_J=#%w2l#uJ^w6g5Q<5H^vX~)w_2$0ydXj(R&)jtv6~0Ml~`Q0`C+z zs-AiE0sb#BBi@XzS=p+BkA1W}i^@OW1bLFgqOJZNniSQbn0wF^eKTC3vTaX$u5rJY z_{S|;QwMb0ab!<>_3kb&8J?2>B&a)OALM3apE6`@nEs0a$Qwo`HSPX(g4IH1bN&MZ zOqes;yE$OSIRgL|NGrVoz@-?JcDAJWpi2}UHmm8ZykN0RkOZ$$8KzWay`Ix3$g~xBzoakz^9ij zzL$6WGuy3JSWrIOZ7ICD&6EVr9JI)9xMK@|$dA(d^&>U=xef@L)YCBdrZvCQYbygv znlK$IRFl*m8v+~8rFTgLqN&No-p$pnive+6P~wtr`1o7W{391gGI8eJy-ku@Pe(!4 zhetj(%pyjVis#(T$`BJmHy$bSwt~ep4o8^ey-`3%$F8aw!S1SBi4%IOtIFXqzf7Be X%a+zbBOb~_Yrh^~**nq7sowCv^^FAq literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-providenz.png b/docs/img/sponsors/3-providenz.png new file mode 100644 index 0000000000000000000000000000000000000000..55d9c992aa6d951bb9ee4669e0f9e33ce1249f15 GIT binary patch literal 12580 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̓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

    ``=*;AGKa;;DY`R|-cX6!6?Q}7 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 UQjSd0c?>YzZKjv6>wN4#0KqDhfdBvi literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-tivix.png b/docs/img/sponsors/3-tivix.png new file mode 100644 index 0000000000000000000000000000000000000000..90d20753942f54e3ba34a163f510ce485396ae99 GIT binary patch literal 4091 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-triggered_messaging.png b/docs/img/sponsors/3-triggered_messaging.png new file mode 100644 index 0000000000000000000000000000000000000000..4f8e5063591102057aae0a3fcee34413c304bb8b GIT binary patch literal 10509 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 E0D{2m+5i9m literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-wildfish.png b/docs/img/sponsors/3-wildfish.png new file mode 100644 index 0000000000000000000000000000000000000000..fa13ea70374d1a68e14eb531a585ff45bba564c6 GIT binary patch literal 4137 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 literal 0 HcmV?d00001 diff --git a/docs/topics/kickstarter-announcement.md b/docs/topics/kickstarter-announcement.md index 98cf12e3b..5f5b848e9 100644 --- a/docs/topics/kickstarter-announcement.md +++ b/docs/topics/kickstarter-announcement.md @@ -29,3 +29,32 @@ I can't wait to see where this takes us! Many thanks to everyone for your support so far, Tom Christie :) + +--- + +## Sponsors + +We've now blazed way past all our goals, with a staggering £30,000 (~$50,000), meaning I'll be in a position to work on the project significantly beyond what we'd originally planned for. I owe a huge debt of gratitude to all the wonderful companies and individuals who have been backing the project so generously, and making this possible. + +--- + +### Platinum sponsors + +Our platinum sponsors have each made a hugely substantial contribution to the future development of Django REST framework, and I simply can't thank them enough. + +

    + + From c0f002b09d07275d8dbba7266d1180623b78f6a5 Mon Sep 17 00:00:00 2001 From: Kevin London Date: Thu, 31 Jul 2014 12:41:15 -0700 Subject: [PATCH 162/225] Updated Permissions doc link to Django docs The previous link went to version 1 docs so it was a dead link. --- docs/api-guide/permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index c44b22de8..38ae3d0a9 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -244,7 +244,7 @@ The [REST Condition][rest-condition] package is another extension for building c [authentication]: authentication.md [throttling]: throttling.md [filtering]: filtering.md -[contribauth]: https://docs.djangoproject.com/en/1.0/topics/auth/#permissions +[contribauth]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#custom-permissions [objectpermissions]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#handling-object-permissions [guardian]: https://github.com/lukaszb/django-guardian [get_objects_for_user]: http://pythonhosted.org/django-guardian/api/guardian.shortcuts.html#get-objects-for-user From 6677374e9b7ec0d4379042cc3e2ee601bf95a9ab Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 31 Jul 2014 22:33:45 +0100 Subject: [PATCH 163/225] Kickstarter sponsors --- docs/css/default.css | 46 +++++++++++++- docs/img/sponsors/2-cryptico.png | Bin 0 -> 9970 bytes docs/img/sponsors/2-django.png | Bin 0 -> 5055 bytes docs/img/sponsors/2-galileo_press.png | Bin 0 -> 11451 bytes docs/img/sponsors/2-security_compass.png | Bin 0 -> 4107 bytes docs/img/sponsors/2-sirono.png | Bin 0 -> 4941 bytes docs/img/sponsors/3-brightloop.png | Bin 0 -> 6864 bytes docs/topics/kickstarter-announcement.md | 75 +++++++++++++++++++++++ 8 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 docs/img/sponsors/2-cryptico.png create mode 100644 docs/img/sponsors/2-django.png create mode 100644 docs/img/sponsors/2-galileo_press.png create mode 100644 docs/img/sponsors/2-security_compass.png create mode 100644 docs/img/sponsors/2-sirono.png create mode 100644 docs/img/sponsors/3-brightloop.png diff --git a/docs/css/default.css b/docs/css/default.css index 090d42a62..7f3acfed2 100644 --- a/docs/css/default.css +++ b/docs/css/default.css @@ -314,12 +314,27 @@ ul.sponsor.diamond li a { width: 600px; height: 20px; text-align: center; - margin: 10px 40px; + margin: 10px 70px; padding: 300px 0 0 0; background-position: 0 50%; background-size: 600px auto; background-repeat: no-repeat; - font-size: 150%; + font-size: 200%; +} + +@media (max-width: 1000px) { + ul.sponsor.diamond li a { + float: left; + width: 300px; + height: 20px; + text-align: center; + margin: 10px 40px; + padding: 300px 0 0 0; + background-position: 0 50%; + background-size: 280px auto; + background-repeat: no-repeat; + font-size: 150%; + } } ul.sponsor.platinum li a { @@ -335,6 +350,33 @@ ul.sponsor.platinum li a { font-size: 150%; } +ul.sponsor.gold li a { + float: left; + width: 130px; + height: 20px; + text-align: center; + margin: 10px 30px; + padding: 150px 0 0 0; + background-position: 0 50%; + background-size: 130px auto; + background-repeat: no-repeat; + font-size: 120%; +} + +ul.sponsor.silver li a { + float: left; + width: 130px; + height: 20px; + text-align: center; + margin: 10px 30px; + padding: 150px 0 0 0; + background-position: 0 50%; + background-size: 130px auto; + background-repeat: no-repeat; + font-size: 120%; +} + ul.sponsor { list-style: none; + display: block; } diff --git a/docs/img/sponsors/2-cryptico.png b/docs/img/sponsors/2-cryptico.png new file mode 100644 index 0000000000000000000000000000000000000000..2d86afe817a1422b183217842d6682ef8a59c757 GIT binary patch literal 9970 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 X>kQ0f{KUydC2|0;6hyL2T;KOU`3<;p literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/2-galileo_press.png b/docs/img/sponsors/2-galileo_press.png new file mode 100644 index 0000000000000000000000000000000000000000..f77e6c0a86cc502d25718545d7a19f4db8427045 GIT binary patch literal 11451 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-brightloop.png b/docs/img/sponsors/3-brightloop.png new file mode 100644 index 0000000000000000000000000000000000000000..8d5e85a66a05d1a7040c01306a50e795e4820dcf GIT binary patch literal 6864 zcmcIpWmHscxJ9HJBn3f2V(4y^?r!Nux;v!15rmQc00HS15G4g>VCWczAta=c24U{; z&;5UY+*#+G_pGztcxvzctTjnGTFQiY&+*XE&xjLz0PKOYNtlEDNQOu;+MC`N8%{0ubsBmQH{phfzbrdE}W~!w)l7`4J@Z_X8 zG-5Y2Vyac$hB`iK=yeFY9wu2f&j0&WqRB)ZLdHxx5vrYG&KP>D?);$^W*z>jEyV9S_do9=HQrYpkzTt;TdMV-t3xdF~k)I2rfOnt* zM!~gFR?5p4_tuTE1|&y!p5;@Y`l_*o$zrMu)za4TlMVz=N)veP_6qK+AR0znBVLKe zSu}bDhfXm#NEVp2RTgsf@Vg}IJ({*- zPgxfX-AmWc+>xp5E+*%i!@WHuwb<>y!R(K_Po9SF@$&729M7;&-$tHb&oPl4bje63 ztzExp+d%aV^g`j{?)zUo?vq|cuicMk7GPad=B`9WHGNoxzl@^NK}XSDkiSI+o_Ii? z_eZ&(OzHf!x~HBO)*iUu(~@caQBMCYmVXg*Ab#a0%|GWmj4ZR4P-%hSdC`+t9a7rj z_|sP(59Db7;?IpBO<8h;lB&f>(?O{ag8xP|hv919Y4}c@?B6VrCWqg4{pLs=dP)6G zM;gb8a6mUL3pXkcErGa9%xD!~$@Y6M(UVD+*ghuIV*!a$h`=>Ev$T7zapQ_c9k0^bF}2!sZvpGY{xvE1l?0@D6Ujij`>uN3h=v@F2Z6Tl-3ZRJ4Y8Wk_;Q?Ni-1B`wOglqur5XA zdyT|d(dY?#5qI|Of`oGiw1~S2%c=@hdgm{;pu*CFD*_r#?C%c7%XI8pbs25zZE&Mz zoVNsaGNt+lkK1=#KMpZd$F_DFuG|LsW=MLXNQ;#Y?q0aTQDl{W(e9kNS+Mx4;H1J< z1&cmpEu^VG9a0NsOUW5~;gIs&Cg{lxt_qR7qob*;`Uz@k2R{(M{DRSURI5ae;jeEK zMGWOrX^ZdJj*$0av{=T>UC%Yhs+C~a5${ir--wBRbhT)~CZ`>l16y+{)!d=SmvNKq z6YyrRy7Ia z<++T?DW7`Mx=aKvn8(iCqaX&KPB`Y`BU}0Mzc*G`ptw~qjVTw(OG9+wkJ#m(-$k&w z>`6zyCp}UieiYuXLJGE)j*s&9!eLcw7@^{7>v6($j-c%IH1!7@YbQGjUOMw84e5&3 z-nodF$0VHoL;7vw6-da0-(2pKrDdHld~CD&m%V|Oj$Q)e-QWtk70XUH$d{_K%`JDg zdhX6ONimA;{*t|4mF2q)mVU%Spvi$gOsuHKUD7>M3lK zcPq;X)eG_aQK;yv$-ymL8GJHtOwrod{rtV4Xf&r2p^`0*4oQ0LW$Sl%yq5uwXi6n< zw5=#K(gkDVV#+L2_2=Za)nP4XPzDA6q9B7#-jDL(w)Iu(hz=(Ff-dQX@5V2b=X4gyS1ihNJ9u(bQyKQm*Pg>(z4nv zYn_{QR%Gw6YD2cnKCb=~+Xqe64HHI}4&#r5dmO13UG3}6-h!p(U-=I7&;N4K-SRkY z#P67J%3P?O_eL6`A^23Bpca<;MoTl?md=AFO!}54Bkd+t-?w-Pv8YA_xUbgj{V161 zD*_dx{}vB|#Jl+Wy_BvV+2J68-<;20(cWS>9BeXYezY=az7r5X+6O6k-Dte-Dz`O0 zQ72arapXzdR1ga@x{KTFvTF-h7OT@VeCO*3(fzEH+52$D)AwCqnz_uxhBzPci@THr z9SqvhM48JL1c6$=WcLI`{#u>Zi`}u1+s>9ypeK!O9pPGWZoZrfYN)e*I+|$C&*`0v z|EZmN`LuW9b9AViHvWlLjz+=n+f=6{Y3h40=C!vKeyXb{drh)!cWl&{^N;VxEALe| z_p_M{6iY$Tg#uH(8nN~v-H_080mE|vb$$UsWv=cp>B%)AZ~&ar;n_seI>G_ub%p3T zPv}EVN@m)!#KYJ!3OL@KJ;e#-P})1X{aC&NIXj8NnIudcXSTMVR*p=S37_G5IeZTr zeI&BGKp}eO$7o?PREm{>bGJNh*FMJ zfeu}ln|x$da}W8cyf)dtG|zT*WjiTIPJjKDbn1aAokkiybunFwUHKc#YFd>JE>Jzw zifuxiudrYCCp8T7J8D%B|1}oe6!nAICeBQmzALqHzNY`M#7VODf_nNI7SeB**Ut|b z-%LlfmVa!VgXUj}oUPoNnRg4TB7<7W*GJ^OqLhVz9|L=oe}8>3@XlD($zP6tmRssuYq~&& z?}zd7dUD-fTILYK@O>(FL+yrc&sS;;azStyHfr^Y7M_hvb`Pfi`8A20Vn%?R%RnlX z+VxI;$dj@LJ>RJ;$1x(R{lo0bF)~-3nMIh!(rVk!5^H%&{R`t5D(wTKlmjUV zMloz3TPrEvQH_rv1$#bcxjM>@3wav7vF@x{avvAye>kOlO7TZXeh4qNLhoe|#*j~`ntRH=Ln9+z7!b##48+HPy0 zC3ifOA5!Vc)3SIYsS<@Kp_fRLIkiH=^dGfCG*?;gaR|b7KH-@0D+Nq`f!n>@Q6KJ> ziH|sVZ5_+Y1j;BCc{r~1zuks@=_>H4U7N?vSxhWc2t|pa0S!*GgC;1}B|B2J%6^Z= zH;d5j$e(cVjp)lf4d&SIM{QIWh_4>7*QBzu{se;;XCH?^zZ`e@AtaJlu8KFUDlXO@g}}y)J-3arueY!wVLVGU5mA+#RIVd@TMjF41mz z+Cj6qMsYE#r6{L=2U%`B(z2b$>NI@5&(^C(SyDGSklU0BDujIhS z7fERyjNGoGI=HqXtoOgoz+D5Krg!qcErJCNNpsgW3hr!k1=P8o*#PalI zC}eTIaaWkn;mnOb;jdrZ+*wxR7Ps;e))3wNBFo+oX&@QOBBZP|%W*AsyjQiL5o@kf zc^N@V7IUsO8uR%MU2V9`0oyoruB%`qjX}5~kf7-U;Qr(tQMXFi1qH1Ak!=GoxjaGQ zB@LwU%knO#K?-py4#lC2!t&rwf$}hi@PjF%+$08VGwlv;#n5|GzHtpR@ox5DnA@w+ z4yIM`!V7(sFtx;&XUlPaMcQInqW||AQdW=klh70>$X&5u^Rr+p(D>uc;xiFKn4}P` zp3g%{P`JA`F+RT3>O@Q{;$||dAR!?^O(#^B$@}ewY1)~*ak0nN_-t$F;zG!{(bB=k zhs@9r7X$rpt&g^G@xEC~n~QR5so75I;bOK6;ZZ)#UR%2vfGn~4lr##RwTnBh=0c3U zM%_-SR_f771!cxy6YCinjr{ucs}QTOpuoo7KDIIP;h^_sGlhVF0JgR^$<}PZ&&T(D zxp=Cx|+k1IsdWoe@Y?pugR$N}rR8?L5`{-!cqf<~yO2g2Q!p_d_ zzu9+p_xH{4qul)bXLxa+8yl5%$(gcnT3cIh9wH6{B%4iARal~tCu})md;$WtN6CeS z&p{xFAbMdHzl=;e{K(PX9wT8;*4J0?w_o9pAF|nFEU#X@5)u)iY*Q%Ei1*!{^#1je zbpPO>{`z2l|6@^6yJfqpFI_%*^ausAOUB!ok5IuVG|l z}LL1qS+)mSrx$THq!3mbY<7q+n{d^_V;MC0SDDn0=xG6A-<1FEJbpbcG6C zV4(^NS(iD_b6Q$&2`E_vJ2?{=YTLSugB2rIq*Yj0=p{x6%(HE#76GS4MLj_SKH-^R zDC(+I>Rnu4^Xgk_l^q=&_1+)%?0wa;vSJubf5BX!L53Iiv9OTogBm|zE(@8Bk59G% zX!r=;Nkc=!Sfu5S&5 zK99u!9|4dMr^!!@V2i!^Uex2gUqArQ?B(GhSB*ik3D*b=mMMG<7WD+Y<0FVEE-seB zUznYh4-J*1;Zj_i9TeHaV_HB9YD@6%{s!~zfin379YYF6jH03< z02MH%?9r{V{{9l6vVgLHLZKKjvOvB3J~*(u_BeO5F*5@@@K7lJ@aUYKowXYVeQh{j zvdb`)tf{DoNlwN~rxQp2p8i5I=yp4whL&~$KGv-m(1TcV<1Fcw2)`4$yu757@cY@4 zTCG)NJ6ok0B(dpH%FW)0PcC(b}O%cL4i~vnw195f?3;T3-<~>;?ISQ16w$vHMvuDq8KYZB6 z)UU6vU;P$NJIDv~M}+)bMXvVKk-mNVwk!CY)vVKF*s--a@Wc`bH-Mp6St=$~5@boR z`FTYaGFcp1F3Q1!%kp=HiYw-@ig(We_=?dHO%_YLMueK`ay{$G#Yryz^1Ks(I{Gl@e~ymhqk<9NdS}qSwer~;P6VOggF0TZA$T%xmIAH zXn_Wnp&>wJ5C}vE@Vnt4n5U|%i&}H`r@cV%wTb=$+Pa^_J7?$8va%>3o?_Fwf7t*xDzpO2f^ZqYag;80UjV{?6sz+v1wIdO7ybS$VF z*VJ0{udS;)yMLg(1rYk>o_i#okaq9*n6*f2%S#Lo)C;3l$8FPNotv4F3l0{qsHj-& z@{aKmgHZL~496k!zD^K;C{w1Vr+=*Vg>rCm4sLjn5MV7{XawGwo7TP`J~ZWGk1cI! z$<|@d4TJ*O0C)!z+e9OK6`7q)Mn{Cb*l1Nv5aTA|O1OC$k~F&YkKV{BD1g<;0Q>-fI6n3Ofj~OsaR8<~)_PTa zeOo{58W|d%O-i1?78XWsZo~izHZV4B+;e?>-96>KIfScKETw8^#|$w4^0H}%J6tI^ z_*NhefLo%V(K$*HK! zj-8tYd>aV|*Y7S5fFcA8NgTEXUH~B@N{IRTRRTC^>=@BgP*~U!ji}@irkc0v2_RI| zW#{3k>h3PkU>$6CU1aCvWTqy_)?lq4p7%$5HD)x{F*BREJ>L}%Jf zSOjRo&e1WxyIUN{vP?&C@>8XF;DHA0sx;#DIN($N_8%fCf0?#fwTHYrN|z!?pmQ z0h?)6KP#D1_V>R`mbKxb02dW08X5wA0_17LCnheg%16xthU)3-=j2}z5)$g_>&Fak zeCH!zWn=U1K?L<#))fngiYCnd9y+-;W6`SA12oU%e5$)@f8T}Ep`pGWAPCcgEOiQ3 zS6AFPIU^B6D=Tf#hw`qjk25oRa%CjH{UmrZv z3ajhG--r#55z|JEvXAQ#5P#wB&}n2*@wLZGZFbe4#}z|JF!HuSQv}}bN>ZYJFUqWs$G#&s2 zIiB~`p()V702q)u{r*2SkxDohJ}q?9~Odod@xGe zTJ82VFff225Sk)j2jG(%ydzDAeF!*Lg*AsxI)uD>c5GuXVOl8de0R#s0>1iitQ;vz zX7fVaAJD+Gv^1&5o2}SEn-w^mgM*_CU^WAgqLUL_0x^RlplASs&KAr(v$U#%ua=#F zU0R){d@lD5|EZ&prk%5MB0z-2!8a)TwFdD7w=3fGbk?lyNS>O zQp-Y?bb2wX{nQJvpU3JOq#{EG)Eb|G-u*3Hpy0pydcurR`IjWtfPRJDY$Q`^-X{f3HsVjgZeW1Gp$gVs5zQeHgGNjLKxdpQmt_k#1K=s$kP*PCXbi(T! zYTV>HH_D*B9m4v{|&E4HCF8WYP8fAiSgXMWDA z9BaOsMJtcf?BdJzL2X;O!kj06zW#&4P2#GYs%nxy8GC{WS6=5$N=kLNAV!Mh``>=g z6?OC4mlpA=XH}V+PoL(G*|%7SnvrcVWa3yhQe}+Vy7q7M47348*&$Oew{(;z7gO-+7bVMQ^w$DqB1db94ZTLFM`0MKtwU5Cd5jOtZ2irQ9 zWWfeDEGe@K>hMHP4h>;=_MTz!Purple Bit
  • KuwaitNET
  • + +
    + +--- + +### Gold sponsors + +Our gold sponsors include companies large and small. Many thanks for their significant funding of the project and their commitment to sustainable open-source development. + + + + + +
    + +--- + +### Silver sponsors + +The serious financial contribution that our silver sponsors have made is very much appreciated. I'd like to say a particular thank you to individuals who have choosen to privately support the project at this level. + + + +
    + +**Individual contributions**: Paul Hallet, Paul Whipp Consulting, Jannis Leidel. + From ebcc78d96cf6f4cd6e464cd6b8eccd83305900c2 Mon Sep 17 00:00:00 2001 From: Anler Hp Date: Fri, 1 Aug 2014 10:20:10 +0200 Subject: [PATCH 164/225] Leave status responsibility to parent class Django's `HttpResponse` class checks for the `status` param when it's initialized, if it's `None` it uses the class attribute `status_code` and thanks to that we can do things like: ``` class BadRequest(HttpResponse): status_code = 400 ``` Now, that doesn't work when inheriting from rest-framework's `Response`: ``` class BadRequest(rest_framework.response.Response): status_code = 400 # Bad, it's always ignored ``` Because a default status of `200` is being specified in `rest_framework.response.Response`. I think is more Django-friendly to just leave that status default value to `None` and leave the responsibility of choosing its value to the parent class: `HttpResponse`. --- rest_framework/response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/response.py b/rest_framework/response.py index 5c02ea508..25b785245 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -20,7 +20,7 @@ class Response(SimpleTemplateResponse): if django.VERSION >= (1, 4): rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_closable_objects'] - def __init__(self, data=None, status=200, + def __init__(self, data=None, status=None, template_name=None, headers=None, exception=False, content_type=None): """ From f87aadb3ced996f54d26d3505ec3df76fcba23df Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 1 Aug 2014 11:35:48 +0100 Subject: [PATCH 165/225] Latest sponsor updates --- docs/img/sponsors/2-vinta.png | Bin 9918 -> 6844 bytes docs/img/sponsors/3-beefarm.png | Bin 0 -> 13066 bytes docs/img/sponsors/3-fluxility.png | Bin 0 -> 10064 bytes docs/img/sponsors/3-nephila.png | Bin 0 -> 5842 bytes docs/img/sponsors/3-tivix.png | Bin 4091 -> 3552 bytes docs/img/sponsors/3-trackmaven.png | Bin 0 -> 28609 bytes docs/topics/kickstarter-announcement.md | 12 +++++++++--- 7 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 docs/img/sponsors/3-beefarm.png create mode 100644 docs/img/sponsors/3-fluxility.png create mode 100644 docs/img/sponsors/3-nephila.png create mode 100644 docs/img/sponsors/3-trackmaven.png diff --git a/docs/img/sponsors/2-vinta.png b/docs/img/sponsors/2-vinta.png index 7c67f6ca8f9b5fbfd91d03dc17a6815e36ace556..4f4d75bc1256fbabd6850b4bdbaeff524d499114 100644 GIT binary patch delta 4133 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 delta 7231 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 B=DYv^ literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-nephila.png b/docs/img/sponsors/3-nephila.png new file mode 100644 index 0000000000000000000000000000000000000000..12fa46d0001b3c4137b0f814d9a169fc73ad17ed GIT binary patch literal 5842 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-tivix.png b/docs/img/sponsors/3-tivix.png index 90d20753942f54e3ba34a163f510ce485396ae99..bc2616a624fbad664d96e774e676c63061e4886e 100644 GIT binary patch literal 3552 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 literal 4091 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 diff --git a/docs/img/sponsors/3-trackmaven.png b/docs/img/sponsors/3-trackmaven.png new file mode 100644 index 0000000000000000000000000000000000000000..8d613696929eeee2610f233a96ea5c05a72e3a06 GIT binary patch literal 28609 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 X0PLwfsb;Wu{*9LslNYV}VHog#R^h`Z literal 0 HcmV?d00001 diff --git a/docs/topics/kickstarter-announcement.md b/docs/topics/kickstarter-announcement.md index 24c609faa..00d4778ff 100644 --- a/docs/topics/kickstarter-announcement.md +++ b/docs/topics/kickstarter-announcement.md @@ -119,17 +119,23 @@ The serious financial contribution that our silver sponsors have made is very mu
  • Bright Loop
  • - +
  • beefarm.ru
  • -
  • Infinate Code
  • +
  • Infinite Code
  • PkgFarm
  • Life. The Game.
  • Blimp
  • + +
  • Fluxility
  • +
  • TrackMaven
  • +
  • Nephila
  • + +
    -**Individual contributions**: Paul Hallet, Paul Whipp Consulting, Jannis Leidel. +**Individual contributions**: Paul Hallet, Paul Whipp, Jannis Leidel, Johannes Spielmann, Chris Heisel. From 27af7a3f4f9870c85d44fde37b041d51d3611473 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 1 Aug 2014 11:36:33 +0100 Subject: [PATCH 166/225] Latest sponsor updates --- docs/img/sponsors/3-trackmaven.png | Bin 28609 -> 5331 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/img/sponsors/3-trackmaven.png b/docs/img/sponsors/3-trackmaven.png index 8d613696929eeee2610f233a96ea5c05a72e3a06..3880e3707cec8e71b527efd346daf182453807f8 100644 GIT binary patch literal 5331 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 T>a%}eN`MwvSG`pAb;$n#s%N1M literal 28609 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 X0PLwfsb;Wu{*9LslNYV}VHog#R^h`Z From 5377bda9420b5806cfcfceddcfdfbedf5629f6ce Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 1 Aug 2014 13:42:51 +0100 Subject: [PATCH 167/225] Fix LaterPay link --- docs/topics/kickstarter-announcement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/kickstarter-announcement.md b/docs/topics/kickstarter-announcement.md index 00d4778ff..93f670aaa 100644 --- a/docs/topics/kickstarter-announcement.md +++ b/docs/topics/kickstarter-announcement.md @@ -68,7 +68,7 @@ Our platinum sponsors have each made a hugely substantial contribution to the fu Our gold sponsors include companies large and small. Many thanks for their significant funding of the project and their commitment to sustainable open-source development. From d0fdcdfdadc51a16074c5ed11643ae187837a9d1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 2 Aug 2014 15:18:03 +0100 Subject: [PATCH 171/225] Added pathwright --- docs/img/sponsors/3-pathwright.png | Bin 0 -> 13036 bytes docs/topics/kickstarter-announcement.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 docs/img/sponsors/3-pathwright.png diff --git a/docs/img/sponsors/3-pathwright.png b/docs/img/sponsors/3-pathwright.png new file mode 100644 index 0000000000000000000000000000000000000000..71be3b28b8ef246442764b5cb261e3434dfe87f6 GIT binary patch literal 13036 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
  • Life. The Game.
  • Blimp
  • - +
  • Pathwright
  • Fluxility
  • TrackMaven
  • Nephila
  • From 7bc9954fdb195d202aa70165a60af350ffde46b2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 2 Aug 2014 15:28:34 +0100 Subject: [PATCH 172/225] Update sponsorships --- docs/img/sponsors/2-sga.png | Bin 0 -> 11112 bytes docs/img/sponsors/3-garfo.png | Bin 0 -> 4621 bytes docs/img/sponsors/3-safari.png | Bin 0 -> 4419 bytes docs/topics/kickstarter-announcement.md | 6 +++--- 4 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 docs/img/sponsors/2-sga.png create mode 100644 docs/img/sponsors/3-garfo.png create mode 100644 docs/img/sponsors/3-safari.png diff --git a/docs/img/sponsors/2-sga.png b/docs/img/sponsors/2-sga.png new file mode 100644 index 0000000000000000000000000000000000000000..2b2a3b3bbf3536a40a5159d8030a9e690fd040ca GIT binary patch literal 11112 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-safari.png b/docs/img/sponsors/3-safari.png new file mode 100644 index 0000000000000000000000000000000000000000..c03e40e841ac33d8a437bd17c1343808be98f5d8 GIT binary patch literal 4419 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
  • Schuberg Philis
  • ProReNata AB
  • - +
  • SGA Websites
  • Sirono
  • Vinta Software Studio
  • @@ -111,12 +111,12 @@ The serious financial contribution that our silver sponsors have made is very mu
  • Triggered Messaging
  • - +
  • Garfo
  • Shippo
  • Gizmag
  • Tivix
  • - +
  • Safari
  • Bright Loop
  • ABA Systems
  • beefarm.ru
  • From 322476c3863090dc2ddbedfcb21ddab7bb64ab35 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 4 Aug 2014 09:20:27 +0100 Subject: [PATCH 173/225] Latest sponsor update --- docs/img/sponsors/3-garfo.png | Bin 4621 -> 3322 bytes docs/topics/kickstarter-announcement.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/img/sponsors/3-garfo.png b/docs/img/sponsors/3-garfo.png index 7c3f8e05958b7daff4201e807ec723edb2e21c96..a9bdea0a042cdd08df7a8babfbf264c83c5f2a83 100644 GIT binary patch literal 3322 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 diff --git a/docs/topics/kickstarter-announcement.md b/docs/topics/kickstarter-announcement.md index f1a1b6dd7..49757c22b 100644 --- a/docs/topics/kickstarter-announcement.md +++ b/docs/topics/kickstarter-announcement.md @@ -137,5 +137,5 @@ The serious financial contribution that our silver sponsors have made is very mu
    -**Individual contributions**: Paul Hallet, Paul Whipp, Jannis Leidel, Johannes Spielmann, Chris Heisel. +**Individual contributions**: Paul Hallet, Paul Whipp, Jannis Leidel, Johannes Spielmann, Chris Heisel, Marwan Alsabbagh. From 2d6469348de71f04507f00a5e0e608ae49829dd1 Mon Sep 17 00:00:00 2001 From: Jason Alan Palmer Date: Tue, 5 Aug 2014 10:25:48 -0400 Subject: [PATCH 174/225] Remove duplicate class attributes These duplicate attributes are ignored by at least Firefox and Chrome, so this change has no effect on the style --- rest_framework/templates/rest_framework/base.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index 7067ee2f0..e96fa8ec5 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -93,7 +93,7 @@ {% endif %} {% if options_form %} -
    + {% csrf_token %} @@ -101,7 +101,7 @@ {% endif %} {% if delete_form %} - + {% csrf_token %} From 83597a4b0f80f6766d49c95d0102896af487f78c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 5 Aug 2014 15:31:39 +0100 Subject: [PATCH 175/225] Latest sponsor update --- docs/img/sponsors/2-rapasso.png | Bin 0 -> 13667 bytes docs/img/sponsors/3-aditium.png | Bin 0 -> 3028 bytes docs/img/sponsors/3-alwaysdata.png | Bin 0 -> 9349 bytes docs/img/sponsors/3-vzzual.png | Bin 0 -> 12008 bytes docs/topics/kickstarter-announcement.md | 10 ++++++---- 5 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 docs/img/sponsors/2-rapasso.png create mode 100644 docs/img/sponsors/3-aditium.png create mode 100644 docs/img/sponsors/3-alwaysdata.png create mode 100644 docs/img/sponsors/3-vzzual.png diff --git a/docs/img/sponsors/2-rapasso.png b/docs/img/sponsors/2-rapasso.png new file mode 100644 index 0000000000000000000000000000000000000000..618e294be25ad5061e11de924548e2da80d541bb GIT binary patch literal 13667 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 B8BYKJ literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-aditium.png b/docs/img/sponsors/3-aditium.png new file mode 100644 index 0000000000000000000000000000000000000000..0952b08c810dbcedc410bb63440836801714ea5f GIT binary patch literal 3028 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-vzzual.png b/docs/img/sponsors/3-vzzual.png new file mode 100644 index 0000000000000000000000000000000000000000..98edce028dfe8792911e42bb00ccdb604fc033d8 GIT binary patch literal 12008 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 X^_+;Y%S`Lf>qGL=%2JgQ#zFrJVp;ic literal 0 HcmV?d00001 diff --git a/docs/topics/kickstarter-announcement.md b/docs/topics/kickstarter-announcement.md index 49757c22b..1ffa0ed35 100644 --- a/docs/topics/kickstarter-announcement.md +++ b/docs/topics/kickstarter-announcement.md @@ -74,7 +74,7 @@ Our gold sponsors include companies large and small. Many thanks for their signi
  • SGA Websites
  • Sirono
  • Vinta Software Studio
  • - +
  • Rapasso
  • Mirus Research
  • Hipo
  • Byte
  • @@ -108,7 +108,7 @@ The serious financial contribution that our silver sponsors have made is very mu
  • Wildfish
  • Thermondo GmbH
  • Providenz
  • - +
  • alwaysdata.com
  • Triggered Messaging
  • Garfo
  • @@ -120,6 +120,7 @@ The serious financial contribution that our silver sponsors have made is very mu
  • Bright Loop
  • ABA Systems
  • beefarm.ru
  • +
  • Vzzual.com
  • Infinite Code
  • @@ -131,8 +132,9 @@ The serious financial contribution that our silver sponsors have made is very mu
  • Fluxility
  • TrackMaven
  • Nephila
  • - - + +
  • Aditium
  • +
    From 62d0d4e5d2f203d2c1694af363bce88cc1a5d483 Mon Sep 17 00:00:00 2001 From: nimiq Date: Wed, 6 Aug 2014 12:45:58 +0200 Subject: [PATCH 176/225] Fix style for some text --- docs/tutorial/5-relationships-and-hyperlinked-apis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md index 2cf44bf99..aef92d08a 100644 --- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md @@ -29,7 +29,7 @@ Unlike all our other API endpoints, we don't want to use JSON, but instead just The other thing we need to consider when creating the code highlight view is that there's no existing concrete generic view that we can use. We're not returning an object instance, but instead a property of an object instance. -Instead of using a concrete generic view, we'll use the base class for representing instances, and create our own `.get()` method. In your snippets.views add: +Instead of using a concrete generic view, we'll use the base class for representing instances, and create our own `.get()` method. In your `snippets.views` add: from rest_framework import renderers from rest_framework.response import Response From 3217842346cebda578e9398b89fe60fed7d1b2d8 Mon Sep 17 00:00:00 2001 From: Rob Terhaar Date: Wed, 6 Aug 2014 18:55:08 -0400 Subject: [PATCH 177/225] minor doc fix, @api_view() needs an iterable --- docs/api-guide/throttling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index b7c320f01..92f4c22bb 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -58,7 +58,7 @@ using the `APIView` class based views. Or, if you're using the `@api_view` decorator with function based views. - @api_view('GET') + @api_view(['GET']) @throttle_classes([UserRateThrottle]) def example_view(request, format=None): content = { From 617745eca027dc17c37718a67f82700caef5be3a Mon Sep 17 00:00:00 2001 From: Kevin London Date: Wed, 6 Aug 2014 16:26:56 -0700 Subject: [PATCH 178/225] Update description of OrderingFilter I added a brief description of how you could specify a different query parameter for the OrderingFilter. --- rest_framework/filters.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 96d15eb9d..c3b846aed 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -116,6 +116,10 @@ class OrderingFilter(BaseFilterBackend): def get_ordering(self, request): """ Ordering is set by a comma delimited ?ordering=... query parameter. + + The `ordering` query parameter can be overridden by setting + the `ordering_param` value on the OrderingFilter or by + specifying an `ORDERING_PARAM` value in the API settings. """ params = request.QUERY_PARAMS.get(self.ordering_param) if params: From aac864a55f8aec80f35d43052b67c2558814df16 Mon Sep 17 00:00:00 2001 From: Kevin London Date: Thu, 7 Aug 2014 11:02:48 -0700 Subject: [PATCH 179/225] Updated documentation for urls.py I made a small change in the order of the documentation for urls.py. I feel it helps make it clear which lines you should add to the root settings. --- rest_framework/urls.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rest_framework/urls.py b/rest_framework/urls.py index 9c4719f1d..5d70f899a 100644 --- a/rest_framework/urls.py +++ b/rest_framework/urls.py @@ -2,15 +2,15 @@ Login and logout views for the browsable API. Add these to your root URLconf if you're using the browsable API and -your API requires authentication. - -The urls must be namespaced as 'rest_framework', and you should make sure -your authentication settings include `SessionAuthentication`. +your API requires authentication: urlpatterns = patterns('', ... url(r'^auth', include('rest_framework.urls', namespace='rest_framework')) ) + +The urls must be namespaced as 'rest_framework', and you should make sure +your authentication settings include `SessionAuthentication`. """ from __future__ import unicode_literals from rest_framework.compat import patterns, url From bc03d2b553bc2c5bc341bb6fd43f8fe382e3cd41 Mon Sep 17 00:00:00 2001 From: Kevin London Date: Thu, 7 Aug 2014 16:32:40 -0700 Subject: [PATCH 180/225] Updated links to Bootstrap components The previous links landed on the the main page and no longer directly linked to specific areas. --- docs/topics/browsable-api.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/topics/browsable-api.md b/docs/topics/browsable-api.md index e32db6958..96cdabe6b 100644 --- a/docs/topics/browsable-api.md +++ b/docs/topics/browsable-api.md @@ -167,10 +167,10 @@ You can now add the `autocomplete_light.ChoiceWidget` widget to the serializer f [bootstrap]: http://getbootstrap.com [cerulean]: ../img/cerulean.png [slate]: ../img/slate.png -[bcustomize]: http://twitter.github.com/bootstrap/customize.html#variables +[bcustomize]: http://getbootstrap.com/2.3.2/customize.html [bswatch]: http://bootswatch.com/ -[bcomponents]: http://twitter.github.com/bootstrap/components.html -[bcomponentsnav]: http://twitter.github.com/bootstrap/components.html#navbar +[bcomponents]: http://getbootstrap.com/2.3.2/components.html +[bcomponentsnav]: http://getbootstrap.com/2.3.2/components.html#navbar [autocomplete-packages]: https://www.djangopackages.com/grids/g/auto-complete/ [django-autocomplete-light]: https://github.com/yourlabs/django-autocomplete-light [django-autocomplete-light-install]: http://django-autocomplete-light.readthedocs.org/en/latest/#install From 5eb901cd2a7dc2e33034f49e076bf7af7656655a Mon Sep 17 00:00:00 2001 From: Federico Capoano Date: Fri, 8 Aug 2014 14:25:02 +0200 Subject: [PATCH 181/225] docs: added reference to DRF-gis in fields added django-rest-framework-gis to third party packages section in /docs/api-guide/fields.md --- docs/api-guide/fields.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 813fc381d..dd2795415 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -361,6 +361,9 @@ The [drf-compound-fields][drf-compound-fields] package provides "compound" seria The [drf-extra-fields][drf-extra-fields] package provides extra serializer fields for REST framework, including `Base64ImageField` and `PointField` classes. +## django-rest-framework-gis + +The [django-rest-framework-gis][django-rest-framework-gis] package provides geographic addons for django rest framework like a `GeometryField` field and a GeoJSON serializer. [cite]: https://docs.djangoproject.com/en/dev/ref/forms/api/#django.forms.Form.cleaned_data @@ -370,3 +373,4 @@ The [drf-extra-fields][drf-extra-fields] package provides extra serializer field [iso8601]: http://www.w3.org/TR/NOTE-datetime [drf-compound-fields]: http://drf-compound-fields.readthedocs.org [drf-extra-fields]: https://github.com/Hipo/drf-extra-fields +[django-rest-framework-gis]: https://github.com/djangonauts/django-rest-framework-gis From c462a43a872b45d27def08a3d17799b930950860 Mon Sep 17 00:00:00 2001 From: Federico Capoano Date: Fri, 8 Aug 2014 14:39:56 +0200 Subject: [PATCH 182/225] docs: added reference to gis serializer added reference to GeoFeatureModelSerializer of django-rest-framework-gis --- docs/api-guide/serializers.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 72568e53d..29b7851b6 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -590,6 +590,11 @@ The following third party packages are also available. The [django-rest-framework-mongoengine][mongoengine] package provides a `MongoEngineModelSerializer` serializer class that supports using MongoDB as the storage layer for Django REST framework. +## GeoFeatureModelSerializer + +The [django-rest-framework-gis][django-rest-framework-gis] package provides a `GeoFeatureModelSerializer` serializer class that supports GeoJSON both for read and write operations. + [cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion [relations]: relations.md [mongoengine]: https://github.com/umutbozkurt/django-rest-framework-mongoengine +[django-rest-framework-gis]: https://github.com/djangonauts/django-rest-framework-gis From 09c53bbac9205b46bafd77e456b495c63c9d46ef Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 11 Aug 2014 16:20:27 +0100 Subject: [PATCH 183/225] Refactor JSONRenderer slightly for easier overriding --- rest_framework/renderers.py | 41 +++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 484961add..7048d87de 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -54,32 +54,37 @@ class JSONRenderer(BaseRenderer): format = 'json' encoder_class = encoders.JSONEncoder ensure_ascii = True - charset = None - # JSON is a binary encoding, that can be encoded as utf-8, utf-16 or utf-32. + + # We don't set a charset because JSON is a binary encoding, + # that can be encoded as utf-8, utf-16 or utf-32. # See: http://www.ietf.org/rfc/rfc4627.txt # Also: http://lucumr.pocoo.org/2013/7/19/application-mimetypes-and-encodings/ + charset = None - def render(self, data, accepted_media_type=None, renderer_context=None): - """ - Render `data` into JSON. - """ - if data is None: - return bytes() - - # If 'indent' is provided in the context, then pretty print the result. - # E.g. If we're being called by the BrowsableAPIRenderer. - renderer_context = renderer_context or {} - indent = renderer_context.get('indent', None) - + def get_indent(self, accepted_media_type, renderer_context): if accepted_media_type: # If the media type looks like 'application/json; indent=4', # then pretty print the result. base_media_type, params = parse_header(accepted_media_type.encode('ascii')) - indent = params.get('indent', indent) try: - indent = max(min(int(indent), 8), 0) - except (ValueError, TypeError): - indent = None + return max(min(int(params['indent']), 8), 0) + except (KeyError, ValueError, TypeError): + pass + + # If 'indent' is provided in the context, then pretty print the result. + # E.g. If we're being called by the BrowsableAPIRenderer. + return renderer_context.get('indent', None) + + + def render(self, data, accepted_media_type=None, renderer_context=None): + """ + Render `data` into JSON, returning a bytestring. + """ + if data is None: + return bytes() + + renderer_context = renderer_context or {} + indent = self.get_indent(accepted_media_type, renderer_context) ret = json.dumps(data, cls=self.encoder_class, indent=indent, ensure_ascii=self.ensure_ascii) From f46b55b75f284a2fa3b1c79300c282f6cbdfc1ee Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 12 Aug 2014 13:44:00 +0100 Subject: [PATCH 184/225] Added OpenEye as a silver sponsor --- docs/img/sponsors/3-openeye.png | Bin 0 -> 14155 bytes docs/topics/kickstarter-announcement.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 docs/img/sponsors/3-openeye.png diff --git a/docs/img/sponsors/3-openeye.png b/docs/img/sponsors/3-openeye.png new file mode 100644 index 0000000000000000000000000000000000000000..573140ed6b6bf34c78718b8d07e4c359c902a54c GIT binary patch literal 14155 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

    h*$d3((gICI>AV!|tP8~1&?mtj6 zeDCL1Q<70J2P;weK=&8JZ%Ml!0Q2Za_;;92FgmFdRhh!@cDEipm?RN1soYb4g2rW5PqP|BMxf$0%nJON!&S3b(ZLeHK zVD`rmd~hmltM-&Nn<_zFSC>sP&eR7QBOw`DDY-p&EhI1*7vb1#efz_aBioxmouKXN zc*l7EoDGew2`N!)M@&y0-^649n@c0~Mwz!=^Jo3WwiNAF!1q`8U)%`+gM(dfzJQr? z?V!Htjr*ds$+WERVQqFlO+Kod9?qI)F2^Y2i(-AJpv8%NE=$tzXeNi^<+v;@fCRTU zSk<+$kl`!1kFM!WhPoB?ciO?`OqTCs6)aJpppg>70RF+62=rMAWD$gk} z+{}`|v9a=X{WyTfXEv9pB?8RaY|wsIv^PMgiFad|^zLS9N?DlVPDNGcgtjbR;5s57 z511c%aw#>wfMo)Gcb;&-_dR5JO9C!i?P3U{?I67uhbK1Q zt9{u6ymaEZqTa%AGk1R>jbx9-()IuvHl?^KClVl4utZH*l>Q z6c;s{Go47+*d0jm#*;!ELoSLBFTS zC1Z&rgM)o*^fIdy(=4jg4Jnd8ak7DW6rW5YA;@tEIzRG)zTcKT+P#IcW;UQ&9yl$; z&3So!xyZJinsWtUhg4AoHv8Z6H6~;`^)2ZtwWxPshNQ1A}Z}b-K20H(l-p~ z$?3|Z)bDFNWJNNhAl(HSUDM|8j1W28rq{k|apPd1kUPFe9fnIiQy4J%J+^Q7HbX%R z8c<5cDBB-nkpz6-C4TvREm~1=S+J^Krrp6ZW;fIMoLNEm8))LG1=*J%eQR@}d>3z! zuk*+Ckl{KeA_bRewn&4agu{WETw1jgS(kgYIJ}y+=d}&#{?y9CVK#Bqs4D&{7>!mz z_0K^(yREF(q*!EelL$@i<(_cn3nG{8YQ;bsnS4%!O?~%yC^H|+c4^$rm&`Vjn}D^L zmRfifU6s!;lj;u5wZ%WLg^m*?7ZI5SCzVqhhYau$6U|-i>_O$&7aZ!T*0+txFE`;g@a4@Vvf*BJ>vLira3rjO_ph|Ryt;G zQr10~d(7N9>fFKr3QJ=(B#M0YNl?;Kf6Fs;ht-_1vFYsn^^e1Ywt6sXaQMNm(ywXO zv_w1><*5^9(;y}bp+y*I>gm`=@&@&LVr$y(Sut67jfZfz<%MYHJIe5Wv-ah^eb&6LnBVyB zRfMb8$YKuo2vNS%FDU0CSBAHIKB>IPtPqDt!2kItZXXI_^iUz&u{mRVBE8xhyFGA+ zr35jyaElNSViGqj&M)t{nQpHFI2VS8tc*iC_MeLbfLDQ`xDq5d52L`tk>%=IL&)REO0f1 z<+&3CAIE@^w#5^^Rzq9mtudk!f1Y?e-xzk1B6hye>yua|Jw3_PA9RLk_A4E!^Cp5G zSfCGJC|dn9N!uF|9>kY2be(@Y5T@ZSsK8ge+kmyfsbp5VhfPnZTQ?X0QQkb;u6A6XlsoK6qFApc9^CaCVvua}<{6D5M zRnIT14=>!bnFF$ScIJHZkm8p`r>#|}PbWoCkyqkiQ6->GmYKe!aFn<%ingk9e+ocN zLEM=)KXf2^A{ZA1GU?BeWlwI466>E>Xw)I?3{{b1b4Wv?3T%r++Mt2w|BZMZu} z zic9wyU7Fkxi1kFWIOkEweBQTsomyF`-Cz{t?MN)>bK7V5{-mYH6c`~0>B7Ehx-y;n zuq{QlJBUVWe6d9j(SY*DE7ua<6^0ODc50m4HGi=2H{)7r%MY)YPy1MM=}gs75;rN> zdOm+zI}|%W7eanw-d9$`j52+4rG*Ko+Zl)Z zjVSJ3-wY0E`FIH(<-2Q|R5N{i19SP^RFgGF4yUp;;L}N<;)bKsD}E&AY7QjQBBC0=&+x6%s#@)r#cHQ^Ayp25k4b9X1>%sn)x_?O=O}yi*W2om z%e`t#N=gdYhES0mENAv2QT`sAPQCGD-tW@|RiDuPl?$RvJ3=D}7KMff(9%lX;cvCs z5xM_}Tkj3%%@Ur>sg%KnRK|3H$l?@7W|d3E#7ebDrX6ygL0zX)uN}Tcc0BBJcIrwhJz3# zCpuyCAfJx;9>2VI<%#|}y6Y&KNyPh65WNJ!&%i+RjCQ7VNPw5f)y?NCaTt}%*hisl z*$Olm7^e05#UPA@lgiTVA2qt~r3TSm*_F(YB30qqUT_mSefKVkY&6&I&wL^iUX6x* z3;L+%F9uKG68=n)>y-H8$Y+0jeY51F=sz9OHomk=N?IFM*Iz-OSBqP{M~y<>Si+)e z?kXRHtAUNU=V%EZbyDf|1+191E%mwKS)JW(io#tm1)cr&N0as60|KVziK`PQ#a_Cy zRn5$SCdWz4DgxwL`n=S=O6Jo$d6Pj5RJ!*IyuERbfUi5{$MyC(MT5ixD`{mj7H(o6 zAl&z9>bTRcY8E4C6fU4)S$2T@-e$aTA21(kMcTYWJD*0Muc4M`;l$_ku%Z~_T?j8L zTzwW=I++Sbw||DrnvQ&yix!JJ%-;|?DbVf7SF6__Gf0t7)iq8 zCR)inPjI(uoZ;h&mClPxm720^(y@124A^G^Oi$+<> zddI?-a19k2yt7&gGM$C|H3!+3`gz%qh-q)sLJsipNephila

  • Aditium
  • - +
  • OpenEye Scientific Software
  • From 7551db23caddbe7a0e01d01fb314daeaf2a61257 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 12 Aug 2014 13:47:34 +0100 Subject: [PATCH 185/225] Added Rob Spectre --- docs/topics/kickstarter-announcement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/kickstarter-announcement.md b/docs/topics/kickstarter-announcement.md index 132153375..ab12c4cfa 100644 --- a/docs/topics/kickstarter-announcement.md +++ b/docs/topics/kickstarter-announcement.md @@ -139,5 +139,5 @@ The serious financial contribution that our silver sponsors have made is very mu
    -**Individual contributions**: Paul Hallet, Paul Whipp, Jannis Leidel, Johannes Spielmann, Chris Heisel, Marwan Alsabbagh. +**Individual contributions**: Paul Hallet, Paul Whipp, Jannis Leidel, Johannes Spielmann, Rob Spectre, Chris Heisel, Marwan Alsabbagh. From c716f35825b708a01c4c1fab07860634cc0e4b9a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 12 Aug 2014 14:57:27 +0100 Subject: [PATCH 186/225] Added Cantemo --- docs/img/sponsors/3-cantemo.gif | Bin 0 -> 4526 bytes docs/img/sponsors/3-teonite.png | Bin 0 -> 7882 bytes docs/topics/kickstarter-announcement.md | 3 ++- 3 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 docs/img/sponsors/3-cantemo.gif create mode 100644 docs/img/sponsors/3-teonite.png diff --git a/docs/img/sponsors/3-cantemo.gif b/docs/img/sponsors/3-cantemo.gif new file mode 100644 index 0000000000000000000000000000000000000000..17b1e8d05f7f7526e4043c181801f0f226fd7dec GIT binary patch literal 4526 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 M$5zCOk^r#y7k4IG=Kufz literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-teonite.png b/docs/img/sponsors/3-teonite.png new file mode 100644 index 0000000000000000000000000000000000000000..0c09847837b9dfcf8e79909f3a8b4b668c3491da GIT binary patch literal 7882 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 Systems
  • beefarm.ru
  • Vzzual.com
  • -
  • Infinite Code
  • @@ -130,11 +129,13 @@ The serious financial contribution that our silver sponsors have made is very mu
  • Blimp
  • Pathwright
  • Fluxility
  • +
  • Teonite
  • TrackMaven
  • Nephila
  • Aditium
  • OpenEye Scientific Software
  • +
  • Cantemo
  • From 5d674927b8d98d6c76a97e06496f2033108689eb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 12 Aug 2014 14:59:17 +0100 Subject: [PATCH 187/225] Minor docs style change --- docs/topics/kickstarter-announcement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/kickstarter-announcement.md b/docs/topics/kickstarter-announcement.md index 370d950db..7726cb83b 100644 --- a/docs/topics/kickstarter-announcement.md +++ b/docs/topics/kickstarter-announcement.md @@ -138,7 +138,7 @@ The serious financial contribution that our silver sponsors have made is very mu
  • Cantemo
  • -
    +
    **Individual contributions**: Paul Hallet, Paul Whipp, Jannis Leidel, Johannes Spielmann, Rob Spectre, Chris Heisel, Marwan Alsabbagh. From c52075f392420356c471860dc07f9371002efe39 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 13 Aug 2014 14:02:09 +0100 Subject: [PATCH 188/225] Latest sponsor update --- docs/img/sponsors/2-nexthub.png | Bin 0 -> 2562 bytes docs/img/sponsors/3-crosswordtracker.png | Bin 0 -> 6715 bytes docs/img/sponsors/3-phurba.png | Bin 0 -> 3064 bytes docs/img/sponsors/3-transcode.png | Bin 0 -> 8615 bytes docs/topics/kickstarter-announcement.md | 6 ++++-- 5 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 docs/img/sponsors/2-nexthub.png create mode 100644 docs/img/sponsors/3-crosswordtracker.png create mode 100644 docs/img/sponsors/3-phurba.png create mode 100644 docs/img/sponsors/3-transcode.png diff --git a/docs/img/sponsors/2-nexthub.png b/docs/img/sponsors/2-nexthub.png new file mode 100644 index 0000000000000000000000000000000000000000..9bf76e0bf26a5cd16a916cb38d87dfa7801ceaa9 GIT binary patch literal 2562 zcmV+d3jOtoP)_I%Ai~h3 zM2$a;6&Z&?5fl^^f)Yfk1LKT^83Z8^ngOLs3HC;Gupyv`0>b((Y&3?Dd}Yho`<)-p z48xD)oZTmT_wJtEd!7*DJci*$U|0l(MPOJ2hDBgl1cpUmSOg~UB6N87rZV6fFdC$R zR4@t*0S&lw11bVKw=_rw)4^tN42UAg0Z)S3+|dCL0UenDCW8INbo{wE2>Nkndny7t zuMT(%oQhI_g3JM>xYIomK|KQGGyjMt0rE42JKX~j&{0*uCIbTGXApNhVG+NBQdA)CUog97&Kz z7mp;U3O@^_J4unM5 zLO_I>nse&miwKnoi6A$jDmZM5v6}N)>DBHd0{)7S44oXc!640f{0*AOBHTIR{BbpsI*at&7(tv3{R-=I`6 z4<$4zG&dx{Iz#cM2dSLBqJ}M3T|BBew+eE(*HEl-UQjk@2V6!I5n->ap3*468bfi) zc|k%DPddrcwQM3XBx^dkWojglJBzml`Rrp811oLOuqnnoZj9j=zqZO`-%mq7MPyV+ ziz4>(!Lu5V@@t99R}spA&x!0vc8u^sk1d8{8W&d=Wgy{1BBO#Vw;x?htku9H7E1G< zkJlShW)oUl$O89)8$eCaMx8*O3;qnkIZetUlr=c(_K8I{4&hKg>U9y=6UP!vvvp9{ zmwNzm<4gd#PH;cps#bUr$DufpKu$V)^7VS>mZ4J^mu&jZcc3L-uUEVb{R}VGxMaJ_ zOTK)(HfDgj8BJR!bYGq*Zd-Z1=%0C=7_{nYF+8iK7`>+HY2?4<|MkmkBl@gpFS;zd z(S!s`BDwE~MfgvK*3nKNyP_leX0{b~XSEP%>+6Z}*|o&e8$%H>X2TUC1(NiK97)T& z##(GTO4n{@yxz`UJ=}AGva{r0^@HmZ#DtACBJTL&kU#g%%xgvBvTl(`unJtx_blqW zs()5t4c!@?G7z2r@cO#iJN{fenO#$)u5BceS9I`V6bRghJLK_iwMlgPc|S6%c~nEb zPSV!Z6TQwBg48Gw$S=4&kliBX;rHvEn&7bW3*!L@(eu5I`zekBff8Zi-q0mr7C5;e zCv+-(dq_+qBEiH>Au(-RNX*X(9YuVzK@}E3zW}8`DmVy4e&+58iK$y03IJY$0Q2_z zKQ8bC=*J@Hlb{pWRZxI}yt*qSp5JUwfJtfun7gOwxWH0Sn?;CTfZAYjaRL-%&hC(S zajUsUhLkO1XGkoFEUsTb5{nR-0A;}#a3oR*@-r_dBwpTe=CL84-?9AV%bzi0N1?-i zB%B0e0WT<*ud|9=jD@h@yWBhhQS^~RC! zQBj3OIJX4%g5w4nzDO|37q)*V_zdu-Qvm_Wg9Q#c!!@6~l7XTGb-~U!lpz1eP&2MY zKm+tFY+Zn5h!YWzbV)ECoMH+7|sLd8v4Y?i61CN6N;1TdT*zJi3 zC&0B{i+}`LI+Eab@QNIXiixiq*yqU_Lq#t|P!b%l++cD|A{|tYAkGWHA`fK9@J<8- zeU$?VazMK%<2@f_dmuwEZ$wZNL)#Ke17%{2yB7G7xClRi>f}X0kku?N7Rl}E5gOya z-GfnK8EFwzVd-kie;w^L=TJf|@nxHnF%OS_vHUKm@shTFJEtFIkkJse_HO)t-zBL!F7Bj-S~`@>f~I;Aq?Y zK~F?j=R^ecNXW5};9sDF!y=j{o`@j#SFUs+LYk%I`V*+F+~CublII-yvj5 z1XBeksh-{JHr^8vE;1#;N@{!Pjzs9_fdqdUnIp(!*=h*a8SJYb-}p+s)cspiB6Qar z=uCM_ipSUg&Ll3vW>X@hxLoxjC_)l(5vH3G;b~2;G->$RG=luT8EYB*Fez&OkHn+q#YU?Sapq5g2DW`KN>R3 zcDUU^F#;WCSxQ7fZ8(ZnLyk)d6b>n-6TZ>$}}-+lB$ zhKA~fSKXIv%7eI7`MZz)tvXJL@k1oXRQY^o8=nK@jx#Gneef9A5=O_%N6H59oUGvl z7xU--f+fngCC7J*?A7#4wH5txYh Y2SX%gy8p5YasU7T07*qoM6N<$f{a#oIRF3v literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-crosswordtracker.png b/docs/img/sponsors/3-crosswordtracker.png new file mode 100644 index 0000000000000000000000000000000000000000..f72362ea97e1f4a627e897920a56ca4155a0a733 GIT binary patch literal 6715 zcmdT}^;ZlGu|I&?zv}X&dho4oqOj#6RWGOMn=Lwf`fxYrlGE)kCoy7YeWRt9`=|5jTOZI zshfD=;5>Q$zlMtg&!NM?p+C@2Q8e_=Imq?%27|i>74Fiqv$HK(*&1*rm0}~Lz148l zfcO{0FT<%6b*WH>Px=Y=NNs89ReR`@3KQ^a<)|ssl1uT@GRBpNiIbMn7PSLHmr^x{ z$?U(EjwxI^^3#~C-j6;U{C&tn-yL+0nGh{DC99`l+zC0lpMIm@_(&Drr%0msNS#fc z@&AR|M;1Z2=;p`QR>c+>4xwWmbu$K=L>2P1EFYdZj}y^f-^)LH_H1-Nw z4%B^!LIz|t_ADne7h-yimKR;DGr4Z=*4w(!H!;zuF#0ZORw@RvMGvy1W@V|5S#}DN z7KQOv5b^~n6qHdh%ML}8y`U5F>@z{_-5uXnR0}!(qFWLYnsYoq@otBg)CSHtOMCCU zDILuf`W?Mc2`xo1$TYb-Zja_JiEfmLT$^5zKMG~-$=1yHZ?KQp5mMpHA_}s-soOk1 zSj=?bH?=m~q7SZwH196Dp9FTA2h4!=g1+DUn-dG+J-$I}(MgblrbKPznB{NmT8FLUqO@XnaY00(5i9{f2j4k`TG6dk@0 zTyY7UGQ)v+P=^{C8+~fHbx`6t%gl2c0xfyKmv{0Xv0IRn6Z1 zC8Z!QZ~4&Om*LzG|E^3R-;UymQiJW zg-&EOOs~{KIt9!czm;$=*oe^4P2Xv?m=MFl#^ydJ8FWK9#OTY81qT&!toQ4bBy#c% zh!@Y8CI<6Bvl#uk;CLqVD<@~F*ey-U-KF*4)X-kO3)Mzi_(1H1Rg#1&1Z3R^&jdgq2?5Z~nFB%QD~ z5!TW67&yBZ)E|m;Qsvg=C#z9}k_Iq)<%18p2eAyan(O<7YYN8d*=j70zOHRs&~fTe zPI0h18_^VXvAeCb=f%TjK7B|Tt`llWe>GM>6|@n(jv8V!sgUJ<<0`uh%TKv z*k#~Uz`%obWmkdfSZ4zs9>=H;9jF}lajQIpBnVi9Wn{g{4iGtR z74NrvIpfT(flAnVp?Ay<(qic3gQzYa6i}UQcpj8PWhXZ zjl4{X)ET_Rx;gYe+i&D8pFh8-Dn=C64OmASsQ2dJn+v)FVwc-gZ_9QVV1x0yXNqa* zNxEPJ3Az6LN{lFjY*Ma)<8w*V$c&Ued>uI$1B+FKd^s%(@|)A9jA*tb!3)8UkxQd# zA!inn&u5%Ogs;HcL2Rs{mm+rL-P|w#7aq;M8QL9kbVcJ+^Mj+HbUWqThD?%u_+BMN zj-sj)hLuTr?$Y|A>8Vz39R8JA4t}41R34^L7_&vd^8F@vZo{T$64wQlW+7Mivapqq z7+c;y6w;RAjzpIBRC#GTGv z@S$pcvcAEcz!#cbnXD|;hJxA%Gk7^o2n&KtSNu4QawWMDKWaBCjvHK*O}o2FqpA?s zPnmipP2ReWZ0QAqsT5aTG}YUkaX7o@-b{J0|I8D|mwP2;el%^&6gha_Fa|5pzdfL- z)cv#}FIPkQ9B4qyD3busq6i^y`J*ZJf(HI}KI40&cHT>uU;k+#NqKMmHNLCvua;FG zH-;vO`Qkyh6|{H!iNlJ3l$0uBkKfAQr%mIADxLMIk^sO4QV$P%%LD7ll0jYLl{{yX! z`)%48A#DsR@mx#A4A7839zR58q^VyvaHGCRXCv;>T!H7mDPIsV^ZuBs0?oZ<)6Ffc zI8J}n5cKb`#y~8B7tfcg@1c~o?yQQCI{;(*`IO|m!O&2L?+`%W0=HAi6-M@&AQL81 zCEL95du<0N3=H(v*JCv_`#+6(kC?3Ebvj&-G zlo_6!KEDy<QB`PjsaxL$K$O1`1|Ly!xyIyASc%+YT=lb-~+CtMnmB`=|)-Fx* z;=itd^#K>8NMET{9cG>u5`eMj^E=e728j*y$0l~O8heIU@&0||)n1DmswV`jSShkw z-kwwaB<|%nJshS4E?@Bbm~xSydUk8|pQGpx0wsIPw-Rn8*h$xyOspSn(N5*uJ}jvJ zu&L7HU@>L7^M%n6tM@6DX^<=^8bj+~K`B{O)F1E(vXX|4g2M-JnS2VL*r`WGX^TeZ;uSV@=gX^8HmKY-sVbUU8uzzzfKaIT* z%8cfmAL&P)-u(&)S@+|}H)TA~ z{2bsKkXDsC4{F+yE%EBVYc83iFVWC>I(ti3WY2P)6urdXH_qK%M4%^x+;tsw6WEtC zd^B&@ivk|K%Rg9#cv<3MtAA_tHhbP+;@R^b2deXH@z8nMAa^j(SUgXB-j2Kz_Z6U| zH>OW@Jn+wBRIX6sL<^P5jUos~ulqxwW$p3P{N@vIy*PugvaPd(spksRdZ59XC8 z*4v`&7Ua=fLLlM2`Am0Alr8V<+OU(#dFt-Skv-{O!rFl%=aDT#qb8l4{dMD0 z`c4v|;?w~$HQ|cp@smhj2zT;>#9Y<;6glYImLxGPitvPL*_i)YN3WCSN@7bvF4AZ; z5OGQMwG^=KHcz@(vf6Zy3;!$s(!6K3quHL#F#U4u_j-@lYmcDccMVz3RtBm}_}YX; zcJ8-EuxY|cBi2cSSR2Jq#VbnW4){`1U*rV;WGs>Qr2V5k3j`nCv)K-Y9ijr7$J~tzxoX@GS#Q#zf z`yZsw_kbt^#6|HbFsF#?(IR<%Vi`gyP3~nWr4xF5c{N1l2_sNXFBcpmBbPpRZuoL* zR4z@rK8Iq}i-Zl&re_n4a;fU1$sF++_#L}I&Wl=UjLXWRGj(vBT}XDNpp@U>;^i&= zYR$6T<|8A~&Jffb@SN7dlei6$duH+#!LtD!Dbg!a6&)Rt+?FR{mWeHDh8E*^QWi^)MtSpry`A*{8k?7~Eoku?+2(R}ls#~Db zWnN7c04frpz+ZD^G1_DW`wSIEU7(l*rro9D5vlx_&hX*Ojsw*FCT;YR?0HTwwbH+< zyITJ2#lb`c&TM#>kY~V)UykK7XB?xCj zHp4|6N*ORX*8o(Ums{nMyua6ktterID%#tKU^1QJ#x-M2^BIpax9Y8KBe|)7 ziTZasdxw1R5)DN~dHfrE*RheJ>uV3!ub7Xxq%Af*^n{%PN<^ec>j{@?p`F|PpTB;| zR!sU>uu~A79t_0aci0k2tav|%Kp z^Sgpar4H^*=bGZfKNc3ggo{IAW83S~y?>OaUCUvq#hJ4sDMw zBG^NsORsNV>QnU&Q)3Djfwc8O^TL0EuPK|Cq;s-kmctQRhfJsNz$lV1J%RCetOLz7&3M4(^3qkuxZ)_Ydh2w7={VuJmOKCsMQyI)*Bt^c_ zE8?}PuEn>|)$Iv)%zJ7wIefGKJE&F0vg5C(yZ4{In{OlBL z?wzK{O2k%(VwW7HrDocW!Q96H z<7o566kOoL3t}$nAWhq&mCl{E(TJxCTJ>AMJzCER2-S$fD23;mpf5HzEgyv_oaKi^ zUtjx+yD#*;mI>MVd(=5Hw>SuFY=p+O);o&W?TmjSq5?`4bt4cpSl1_KlI|Oxso0&b z;LNbk2fwMJqfMtyC+*l`aAT5s1k{`G$=%{N%@G=`K>X-l35;txSg7?_SFO4}%l{(e z0x+rk-A2Kb_$-AL~ow`iF;kGG1E@qXN8QV`Ja{Y1r-K z?911~qhQv`|2v4F=H=q!vqR2WiQNA&gDjVjmDs1GtsV($t?<+HCxC@pq+5nu2taM@O_F{CmKjCbpwe z=#p^IwTV0j7eNmj!xLxtz@bGu zWTs6l6!oB{HDz;}#vO#-%nWKjSuC?5J93Ql$1Z8w-mDGU)gMS;_}wC8+Y)^4q#94B z7!ndPZK5+J)`i}BaeHy3gXLpi7S=+ut~OFRH~_+KC}VOK`5 zaB}xrCq@5xfS0UZ-FdW{U!y~`rbxR-te*D%Mn-%5Zk)xE)lr39hF~xy5ndSr@k3Bc z)M9X--~H?N1*;3gMUxWx6EOh+fzwE}_QVnh-Xq)~c@{8Oo^OPMf=J~sbfTYD4>vZDo3g_I1%M;gjKOd=lcNU~(=YNBw$ewqb>})4jX9q}LWUFPg6}YaLu8DX3^^4ZJINBs6K8ps8?p^!4{N&YNI%g}6){eWGWC+<3&SQLG+G gN=g53)EEqYcj`pBZ`rLc_BRMe1E{T1rDPrcf9#jqfdBvi literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-phurba.png b/docs/img/sponsors/3-phurba.png new file mode 100644 index 0000000000000000000000000000000000000000..657d872c99df0ac4eea35032c996ac830353ff1e GIT binary patch literal 3064 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-transcode.png b/docs/img/sponsors/3-transcode.png new file mode 100644 index 0000000000000000000000000000000000000000..1faad69d946facc0a733a863204d7a0aac511d42 GIT binary patch literal 8615 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
  • Crate
  • Cryptico Corp
  • +
  • NextHub
  • Envision Linux
  • @@ -110,7 +111,7 @@ The serious financial contribution that our silver sponsors have made is very mu
  • Providenz
  • alwaysdata.com
  • Triggered Messaging
  • - +
  • Transcode
  • Garfo
  • Shippo
  • @@ -123,7 +124,7 @@ The serious financial contribution that our silver sponsors have made is very mu
  • Vzzual.com
  • Infinite Code
  • - +
  • Crossword Tracker
  • PkgFarm
  • Life. The Game.
  • Blimp
  • @@ -131,6 +132,7 @@ The serious financial contribution that our silver sponsors have made is very mu
  • Fluxility
  • Teonite
  • TrackMaven
  • +
  • Phurba
  • Nephila
  • Aditium
  • From 34c1da3515dbf4e9a0d645ebf81cde6f61254e31 Mon Sep 17 00:00:00 2001 From: John Whitlock Date: Wed, 13 Aug 2014 15:31:25 -0500 Subject: [PATCH 189/225] ModelSerializer.restore_object - errors as list When a ValueError is raised in ModelSerializer.restore_object, the error is set to a one-element list, rather than a bare string. --- rest_framework/serializers.py | 2 +- rest_framework/tests/test_serializer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index c2b414d7a..43d339da0 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -977,7 +977,7 @@ class ModelSerializer(Serializer): try: setattr(instance, key, val) except ValueError: - self._errors[key] = self.error_messages['required'] + self._errors[key] = [self.error_messages['required']] # Any relations that cannot be set until we've # saved the model get hidden away on these diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index 91248ce7c..fb2eac0ba 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -685,7 +685,7 @@ class ModelValidationTests(TestCase): photo_serializer = PhotoSerializer(instance=photo, data={'album': ''}, partial=True) self.assertFalse(photo_serializer.is_valid()) self.assertTrue('album' in photo_serializer.errors) - self.assertEqual(photo_serializer.errors['album'], photo_serializer.error_messages['required']) + self.assertEqual(photo_serializer.errors['album'], [photo_serializer.error_messages['required']]) def test_foreign_key_with_partial(self): """ From bdb884aceb57d8d4e17a9176d557b6ff879f5a02 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 14 Aug 2014 16:57:21 +0100 Subject: [PATCH 190/225] Latest sponsor update --- docs/img/sponsors/3-makespace.png | Bin 0 -> 8420 bytes docs/topics/kickstarter-announcement.md | 1 + 2 files changed, 1 insertion(+) create mode 100644 docs/img/sponsors/3-makespace.png diff --git a/docs/img/sponsors/3-makespace.png b/docs/img/sponsors/3-makespace.png new file mode 100644 index 0000000000000000000000000000000000000000..80b793619c304b4b6a09b4bac0001e0a35b54d68 GIT binary patch literal 8420 zcmbVycQjmI_qQ?_UDPnjAQ2@P6LpL-jNWV1Fc`g#QKR=rL~jwjCwhbsU6kmecY@#> zL6GSDsc-G?WG!LCAtMcj`@(JkT+mn)*w@9`)f46`#ql>T?6&@=S&#$#Hwo5B zisN5O8EI&P<=rr7u!z7Tert%35Li@HKuB0bR8;687zz=B3PNsAQGOv|n20D0DhB@d z!Eq}MV`B@`MJoPV*6ouNhdmbS4igmg@$nJxc_iS5u@e-MkdXL;0fqA4Qt*5Fxnfbi z{H~sy{~#dIp4J!#cdUb(EBFs0%F4|PE5&iE>A$+*;;y0bA7EF{e;ex7WP-jZcR?Wm zh@gwhpML#K?TOVz{~tB}E48P-pF3Jm7wzfhg|WW%hb`wn%(u4t-v#|4ywwJ#g>krb z3d$MjX6@yIcEze7r8sWi2-rB-z!c@g6orJugdt*3h^V-bkT?{IKuCxyB1PpyAwr6f ze=zQtVVJ{z(Lho0|Lr+a42lp|d?cnQtf0v8Z(f`KAFB!8DkJzOJpQk6`KReN z2LDw56a2Ri{~11L*V{3Jxg8vaUu)ZNa9F5SkO+O>`CW6O0R5h68B;w`Vt#g7TzSDz z06+(*k?^F5;m+BCJfH|@<)@W$!efPt(5Cvi^PB@-#GS7!cqdVl8&?i^e=&eLL%(KzVM|LAa{pwsazWwqTEjr&AWn&Y@>?R)qD7i!o zJTqlrg{ld5lN}m>D`Cppm00DfQ!H@G+qy^uoS+CG=+hGjYm_se{-uWZLNSO=f^5$8 zco%wTvQeGjmckD_kYR2(p)>+$@XdIzfy?N4aRF$u2$H-_OEbAei`5>#PEiH_Y#Fk$tHkB&!!v2><>S_I-uDhlsO&ar3Aawx%p1 z;L8WW8z&$7TDvCXdGsVVvYlH*f8%rt2{R8-~lQFkO8pFR+mcz7AW?_u*gBVMEd@_ku!vR`Zr8gFg{j?3C#9&fTb-@6UqOdQ;yuK6HrT4}(qfzh=@U{?}0lUg~ z4Mpv>2pjdKkppbpx30;J?QEmdUw>I7l#JrW4J=GYVxf+l8e9_}9A6E1?tLrTAAHAR z=1LB*Xi}FJy8j@M{&$9DX53V^^&~sB`dH(%9)vGIJc3Zf_wIu9C4?}W`o{k~%4c?Zi^jLI2Yi6|m?vJadY51hSfWHzw4zEaF8?nK4QRqc z?~IUo&BI5azR_k8s*P{TAi6^v!)Fs~M>==!!RZFYn5hcVY@nEV^cwYJ2ZXkK^vc!V#??kq5Ia(UbMQID7D*1j=3bHc? zRls%7N!Shf527H)@75YtN$Zv)i8mdcA3av}lx-l4FMfnEb-pg`lk=TYG~7yj20q=} z_}7`^-uJ@su_Ug9Zts~XT%3$#%5Q`C+Sirup;RMdm2EL_B=521>OC*}I%J<381DctWbdHyPoOFW4~_Wsb1LPw;*s1vt{W!t2|+AHsyL@iO|t ze%DKprM^m+(l(NuZ>&$d!nXTK>+i3wr-!?4?gCd_r*B$h9f~JX7QaXknve88VQX-H zTxtAT4QFkJj|djGaCFv&rdXT*DxV^>jUbP-MNCH{inib(Zew?^d=|Zx$I&EA1Fy0z7@L{znpLkC z&}G}TtZj>W#y30%lNo4eh zc23^LUH6F?Mt3bvex<89!XEWu+w9|egB_{^&N1LMV<`OQ*#AT6AosL)Lgx4BwTso) zo7%k72mK=3dFH=t!$e&9fz-M*sKezm2xb(1`uz#~x2{E29O7xrVNr_;VN#?G$6N&- zorYd5MVUaECbLds(l~HhJhtMH$YwcmQyfsyT)&@RTb$M*`qUya-Ez084TIyo{32B6 zmt;498T#geK%!E>Oo3B8G6||@c{8k+5aH=4xT~TIa|*R#&r}vgD5gxM_7`_mb-xt} zWx=QK7-eYarWT$Su;Vh_yoaGTey!zo^`e}f?}3P21i`NKE1xEkN6I`K-A!qHEA!DLeM* zaviOV#Q^Xmu~A5o{ilgo%mgRbwpd%N~i|$ zxM8psd1Lby^QnLplQ2aYdqe`GDwUk=YJ&KAR9msIGcN9D^*TMqvWGeOorY^RBtUJa z)y42Eh=4ykw!NNVnG8h8|JHbh8=naqRpm9GcU41>_WF4kSvY;m#z0@dd*J~4@w>;5 z{W~Rxqw`qC`brW$B>Rkz)>28j_5feS4CJNoz-4RFTO#jUhPW8j5+#px_BUsv98amr zY$Hk2nXsyZsIfMG?ib5PJVhW3s}r*c@NLmyWa?-udF319_N7D3Z{|kK?=(UgKOfw(y-&Rpe`mbPtm%6hyNAot>wtHvgp5 zdo-U7rv>&gn@ZZB>GHZVk?JNKFXqJ9{4E^G-;LF|F&R~#%B?rjB2t3|p;v}qWEVVn zhz}%2lpB`CxqOAM)OhH-@FV2#zqYfl84RL|PH+iZ7B{@P`;n21z*$C3F8pQJ zdoBi{TgwM0)YLxW^0z#J{PRCjS;&s970RJ=MxSX(F=E%!4S{yu%WXPm%=NVBWpj{y^y?ty& zuRb#&I{5T$K&QD1v8y`$1^bj`j+K9^`vRi^(;en)G7hrVh~KD*(Eoa8 zZ0%91+3h;~W3oGji79CXn}Ihfb9TZb(#_-@8T9xjZ06cXclDXmp62G>vJwnvEtk9; zAJQRr43gs8{v<+4wHRYhn14IV?8746TuSYja$I~vv}5T56GvH`KO}}KmlgwPYgN*2 zS1~V_{q)U@2mJj_N@ThBfF_kXTf)PZgO8q;eW*+M<ZZmKJRNJ^ronXYO=I@DhfQ71sR(0(zBgyW=n6w-dO1m%Aksup$R-h3fFKKEy zxB!;doCo7cpHP~Qf!+)een|DAR9ro-Z04d$s@v0IBjPO-<5i4!0a@Pa&u zW)M~%!~mSuk6JM1IaqoA5jC}WC1;*uDued}Y>0AuranvRRK-jcrpQwxET%QuH{KMV z9uH6B-Tk@JvrqfmRJpQZpl+WZ%HE&+qMi&^qbfiYw;1S6)ud{is{7`i!X3SbG$DgK z(OotIRTY~|z{oo+Ee^6yS%!Gz;yt?kbj1zpbA|=kUwYx|@|mgL%=J6Ccp33aP6iTy zsOj&3_u~mI+1Vr@fg5(_oZ?ThzmjGrlSKQpl}(XTToA}Tyf+tyVMt-%4A*Wn%}I3# ze;-x)Q!o{9dpER)r8KmfTi6-9zGv^SAJ$_R6#ksi=bo39s{D;3(}(4V-sq%g=YAQbLj5 zO5_y{zxHn7WjKH9Ru>o$ zIg%%4v1<_PjkBrZmrQm~EtNwPRa~c?S?0_dXBqkahn^hWe3>e+V*8w8x7qtH{ilk^ z?%D6E>z`5@Ldh*{PqnuVOI@C|7eiING$xE?Yt)n`bn5LUszS%@JM!~vICR6Y6^s-z z=Cc%P)`hF8)8@?aEKjXSma?b{h{IHd&%4!bljD5s&zu1{wC-Fx-#pKHu3i*jMZM|o z0KwC5dNgZ&N2Zk*6rgXZW7jp6`=yX34>oq@I9Csmx$E7xylq;ETmgfo+HdyWUuI_SM+LB#) zU_wk3ykI3WCU(qUjJW+ILMS*F^TjB8^Wy(O)bg1wFm%u_|DCVus8OPZlkb*o*Fbm< zFcX;#>ZE&%WgL=xPw(#%L^N%&c;)lSgScG5oik=v8pI(v8uE?KDfRS=<{-M@m^@I; zp&r<981x_|)rMIz(fJqVs3b(LqtLLD`Po5{nu+?^G1(7YuQ~o>8FKJUY}%QWl*XOVIXI zP?9dFuVuNTeDyL$MZqc~%e1Csb3-%K5r18By4K@9Va$i{ZqRB7I5=>Di{)Fwki$FJA=AFEEE z7G2j=h?`G&zq!+ZRf(QidHa_h&w?@9{k%0E`Ey4sTygKx{=Bjxg$GC&tW>13?puW* z<>!m;7^-0(`{;_y46*#aiC?oi2^06bA@N;T@6yS#R;ICJETnJ9J+8j<>*H&8ZwXZb zATqGw6945=oYP;G+QA_=l0!N>UyNf)THxEy(xl9ZXl=WTvm3_BH^kR&XI3%I5Ovop z#+N-~Du6C#=d7}AhPDVQ5XEUqtbDm_G}#x4jkSImFbz1TR?K|ulgMs_X$|69meWQ5 zN&q6_3;{UdW$V5ys-QIraFn`TjWV!~Ddh7;v%c~oI@8;51FqBOr4y#6KF@SjSRwc( znXsZzg|2=(ObJ59TblX@@i}Y2@v6nXQx<|wa0FAWIYR2s&5Zm|k^yv}Uk6Yfnbhur z=VkA2;x2{eUZcms=%#IC?b2Yv0$7kEYjCd#V$tlSbb({ba>YvM8mRRFIQgVNn44cU z`AZ3$`NOeBb^+&MQ5_z_7=JClL7{|{e*rbRTvobDA=#3ItY1ZzW-+U^;tBq^BJ16M zMdWc(Wt2@3D8Hh6yVcT;FL0;OIDR^yVZv|YPJe3xWx5xqvcoT-UQ1}3cDvj#?oM+> z@O|5Rk>li!zty`RJ}7ZkhLb&Y%?+(3|47I5C}E`>Sxa%#q@4Z?CNLeUi8<2oU7{qd z_y`vG(Mv5%_^3$tEGbqg4;;VjHSfAOZ@2s|S&nE(c!Mzs>haQbR8)BInG@Fff}s{f zkS=J>tob%Hp0WNEBvt6MZGPT+=Rs*Aq4I?7E7%n9BFH?R(qz<0@_OAYTtHscOR(G* zKqKa-pfd_9v_mfsXgqK73Y*&F=$6r*)r4*t6&4ajr;% z_!kG7=_wYaD0!RJClN$;<2k*hVN83$?;Ua}2_x!PwAy=?+e!Qqh*-h2a`m8X@sZK? zXG`~v5p2aXaiWMguD;TxQy$S@^X-#Rw9m(*4xyY=%K-)P=BKP=#6pq<)8-Q3A=o9e zFNP@$y!?3c%46n%iDH56qe}5cg$zZDQFX#pbrT^(@i*-P{I^;qpBQ$0+3D0jM)j!m zc9l>u7nUn!4fRerZ~B@S3sbT6JKZgG~5Cp>uatXOZ&;V%bXO-ZYT z-R%UfP84(@@fAaRx_Rc6>g⁣^Z8P_t( zRTS`p(q5J%_v0rJapZcJ#(K`I{}KSl7a8*%vJ(~gfc#+4r5n)ZOS*4+TMu1{js}*I z8%R)F=~R9(d?SFEnRE4avy)onn;3k_40zvS@0z6v|ImiJdvonefvzX__*b1~0a9!% zbOE5MxJ0&*JNt6`vGSR9f(+t`Uc+?t+&lY+#YYTku?YHLi?Z*dD`h~2rQ&PbZ$9q@U7AYH>2L1%Oug}Q`zei5}J#bUx8 zg`rLL`8TAHK7i3STYn{qUv3!})O=92cA@?YHR-pIv9AHEfQp98eH~Zr0(#4rY+xK+ z$j)8>4%6Vxm$pGSWIN5>bBBV_;Mdy}J zHjD6)NfPmrXUoJ2FIVf#R_nxtg+~~lYwGV{#c_WiP6^H)vxYmEl}2WZ`(By>V$Y4c z3Fsy$HpcsXj;>tCxhC&|KA^ZQ@4XsC)<#Sm1|AV*aK3OmI7z#E6oR+Gpv0c6QI{;z zck|PoW0~MxA|kc-X5~40fUUbN`bdlVN)fg1_U)+{!C4@r4>8~u^D0xYGW_+l2+t@} zS$a!P@Pzk?|D_-~<9=;iLasolpFsjsLRzfMW^XBB&f^fm@w(l_ON!7qO_a(OyhLa< z%A?!)>ys1Cy0D45e#B0-d=O)k2Rr)9v)xy5i$K8c1=&}0fzM~}XQ(*w@QG4ZP27gu z=gUuX(IU`}{wkA!>}{T)Ef>Ph`+G)GA1aFS4KdlOhMZ$}uh}4izrMV>c>-aJ@JJwq zwS!G*hC1`C=7*$O_})196Af zokLuSuiql;HfrgOJ9bRBA*E7bouF=HZljmIu9C4?lbs8NW6H83lA#skY{Yl z&)LMT*iccu-^h7NGo*BEbO?>(5T)Xrowgq}V;H(KPM@hl8knu+ig$MB zy5YVUm>fhSF$}BtOR#qF8cvq5i=|!6<)Dh`BzSF$^EZVO=Zy+2N3`Lq zcqebn67Tq!3YR>0AkAoOb+MY#Z2d~K=SLs@JS3%O1;i*$l9)gDBJ`egW$OS( zv)2#a-lV}@XSR$(lLCR|XCbE|@ki3|2!1<$OdKv9ILQJ|lhHeRe0kx~!1$)Nx1#S+ ziLEo4rghEkSD$6OOz2!Mn6d9!6jH?jkP5s~NNnuyL}FhoH4o3(zgYX()Lb_68QBnV zw?hWg-7=)e`9mgv?s{2BBS#qqFJ~`(m&_*}U7B^gCzJV3q0a7z3Pt;?MLp;Ihi%BI z=N0;$hAfWc@VneJGkPXWj*XaTOq&p9IcwH#JUS5AS>SyZ`{PYzMZRKPntg|fw@mu| z$BwcwTea7|Li;S6c&5FZ*s2(xIc3r{xvJG226vPOeE97>ru5Jior8TXPkApZFm`=q z6sm3LUVSfTNXq}-g-F;3o%o;H^vPPDOnzz5j2i=wZb9l)iDWy~{kq!6>F-CNdz+5q z?jC7_lBu1{9VYf7PhoLzx*tRP@KLlxpTL1zXXwR4X8M+9o6nEy4*+d#-r3?D_3|24lBPy>8PH8H2Xb;)wx$u#BQI-SM_>1G zMQu#)@rc+Zf|%bsIeatVlwwL`s%gVH;^4qtn>Urh9GD0!ewRoL&d(kNgpG}9M;C9+ o&8+6@W=i}2lWfupZx}3bKzjwvx#a1ifBrjAQP4zI$ytW|54kT#Z2$lO literal 0 HcmV?d00001 diff --git a/docs/topics/kickstarter-announcement.md b/docs/topics/kickstarter-announcement.md index aca6b327c..74b19deb4 100644 --- a/docs/topics/kickstarter-announcement.md +++ b/docs/topics/kickstarter-announcement.md @@ -138,6 +138,7 @@ The serious financial contribution that our silver sponsors have made is very mu
  • Aditium
  • OpenEye Scientific Software
  • Cantemo
  • +
  • MakeSpace
  • From 63d5634c660d21638114ccb35b0b756aae47b713 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 14 Aug 2014 16:59:40 +0100 Subject: [PATCH 191/225] Minor sponsor correction --- docs/topics/kickstarter-announcement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/kickstarter-announcement.md b/docs/topics/kickstarter-announcement.md index 74b19deb4..cb8a13070 100644 --- a/docs/topics/kickstarter-announcement.md +++ b/docs/topics/kickstarter-announcement.md @@ -138,7 +138,7 @@ The serious financial contribution that our silver sponsors have made is very mu
  • Aditium
  • OpenEye Scientific Software
  • Cantemo
  • -
  • MakeSpace
  • +
  • MakeSpace
  • From bfd0b261dbcdab5993f83baefcd732d3d2d194e8 Mon Sep 17 00:00:00 2001 From: catherinedodge Date: Thu, 14 Aug 2014 13:39:21 -0400 Subject: [PATCH 192/225] Fixed small typo --- docs/topics/documenting-your-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/documenting-your-api.md b/docs/topics/documenting-your-api.md index 6291c924a..e20f97122 100644 --- a/docs/topics/documenting-your-api.md +++ b/docs/topics/documenting-your-api.md @@ -95,7 +95,7 @@ You can modify the response behavior to `OPTIONS` requests by overriding the `me To be fully RESTful an API should present its available actions as hypermedia controls in the responses that it sends. -In this approach, rather than documenting the available API endpoints up front, the description instead concentrates on the *media types* that are used. The available actions take may be taken on any given URL are not strictly fixed, but are instead made available by the presence of link and form controls in the returned document. +In this approach, rather than documenting the available API endpoints up front, the description instead concentrates on the *media types* that are used. The available actions that may be taken on any given URL are not strictly fixed, but are instead made available by the presence of link and form controls in the returned document. To implement a hypermedia API you'll need to decide on an appropriate media type for the API, and implement a custom renderer and parser for that media type. The [REST, Hypermedia & HATEOAS][hypermedia-docs] section of the documentation includes pointers to background reading, as well as links to various hypermedia formats. From 4dd7b5f376be7d5f28ba8865cf4ede7ad553b8de Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 15 Aug 2014 12:05:19 +0100 Subject: [PATCH 193/225] Replace docs title. Easy is a sucky word choice. --- mkdocs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.py b/mkdocs.py index 529d2314c..adeb60538 100755 --- a/mkdocs.py +++ b/mkdocs.py @@ -142,7 +142,7 @@ for (dirpath, dirnames, filenames) in os.walk(docs_dir): toc += template + '\n' if filename == 'index.md': - main_title = 'Django REST framework - APIs made easy' + main_title = 'Django REST framework - Web APIs for Django' else: main_title = main_title + ' - Django REST framework' From a5561954d79659ef987847fc361f83e6226fc885 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 15 Aug 2014 15:36:49 +0100 Subject: [PATCH 194/225] Latest sponsor updates --- docs/img/sponsors/3-ax_semantics.png | Bin 0 -> 11509 bytes docs/img/sponsors/3-ipushpull.png | Bin 0 -> 10089 bytes docs/img/sponsors/3-isl.png | Bin 0 -> 19203 bytes docs/topics/kickstarter-announcement.md | 24 +++++++++++++++++++++--- 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 docs/img/sponsors/3-ax_semantics.png create mode 100644 docs/img/sponsors/3-ipushpull.png create mode 100644 docs/img/sponsors/3-isl.png diff --git a/docs/img/sponsors/3-ax_semantics.png b/docs/img/sponsors/3-ax_semantics.png new file mode 100644 index 0000000000000000000000000000000000000000..c072e028a5501bf27cecc2c9c3160c5477bd27a6 GIT binary patch literal 11509 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-ipushpull.png b/docs/img/sponsors/3-ipushpull.png new file mode 100644 index 0000000000000000000000000000000000000000..e70b8bad20c07101a2623bfdb1d6f0fff48fa113 GIT binary patch literal 10089 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 LNkvXXu0mjfSrR97 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/3-isl.png b/docs/img/sponsors/3-isl.png new file mode 100644 index 0000000000000000000000000000000000000000..0bf0cf7c91fe86be1393a066796ed19f8948ac45 GIT binary patch literal 19203 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 Linux - +
    -
    +**Individual backers**: Xitij Ritesh Patel, Howard Sandford. --- @@ -111,6 +111,7 @@ The serious financial contribution that our silver sponsors have made is very mu
  • Providenz
  • alwaysdata.com
  • Triggered Messaging
  • +
  • PushPull Technology Ltd
  • Transcode
  • Garfo
  • Shippo
  • @@ -139,9 +140,26 @@ The serious financial contribution that our silver sponsors have made is very mu
  • OpenEye Scientific Software
  • Cantemo
  • MakeSpace
  • +
  • AX Semantics
  • +
  • ISL
  • -**Individual contributions**: Paul Hallet, Paul Whipp, Jannis Leidel, Johannes Spielmann, Rob Spectre, Chris Heisel, Marwan Alsabbagh. +**Individual backers**: Paul Hallet, Paul Whipp, Dylan Roy, Jannis Leidel, Xavier Ordoquy, Johannes Spielmann, Rob Spectre, Chris Heisel, Marwan Alsabbagh, Haris Ali, Tuomas Toivonen, Simon Haugk. +--- + +### Advocates + +The following individuals made a significant financial contribution to the development of Django REST framework 3, for which I can only offer a huge, warm and sincere thank you! + +**Individual backers**: Jure Cuhalev, Kevin Brolly, Ferenc Szalai, Dougal Matthews, Stefan Foulis, Carlos Hernando, Alen Mujezinovic, Ross Crawford-d'Heureuse, George Kappel, Alasdair Nicol, John Carr, Steve Winton, Trey, Manuel Miranda, David Horn, Vince Mi, Daniel Sears, Jamie Matthews, Ryan Currah, Marty Kemka, Scott Nixon, Moshin Elahi, Kevin Campbell, Jose Antonio Leiva Izquierdo, Kevin Stone, Andrew Godwin, Tijs Teulings, Roger Boardman, Xavier Antoviaque, Darian Moody, Lujeni, Jon Dugan, Wiley Kestner, Daniel C. Silverstein, Daniel Hahler, Subodh Nijsure, Philipp Weidenhiller, Yusuke Muraoka, Danny Roa, Reto Aebersold, Kyle Getrost, Décébal Hormuz, James Dacosta, Matt Long, Mauro Rocco, Tyrel Souza, Ryan Campbell, Ville Jyrkkä, Charalampos Papaloizou, Nikolai Røed Kristiansen, Antoni Aloy López, Celia Oakley, MichaÅ‚ Krawczak, Ivan VenOsdel, Tim Watts, Martin Warne. + +**Corporate backers**: Savannah Informatics, Prism Skylabs. + +--- + +### Supporters + +There were also almost 300 further individuals choosing to help fund the project at other levels or choosing to give anonymously. Again, thank you, thank you, thank you! \ No newline at end of file From e624871a687b9d723f0aa6c7d26cd25ed032a772 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 15 Aug 2014 15:46:26 +0100 Subject: [PATCH 195/225] Latest backer update --- docs/topics/kickstarter-announcement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/kickstarter-announcement.md b/docs/topics/kickstarter-announcement.md index 654750738..48c4673b5 100644 --- a/docs/topics/kickstarter-announcement.md +++ b/docs/topics/kickstarter-announcement.md @@ -154,7 +154,7 @@ The serious financial contribution that our silver sponsors have made is very mu The following individuals made a significant financial contribution to the development of Django REST framework 3, for which I can only offer a huge, warm and sincere thank you! -**Individual backers**: Jure Cuhalev, Kevin Brolly, Ferenc Szalai, Dougal Matthews, Stefan Foulis, Carlos Hernando, Alen Mujezinovic, Ross Crawford-d'Heureuse, George Kappel, Alasdair Nicol, John Carr, Steve Winton, Trey, Manuel Miranda, David Horn, Vince Mi, Daniel Sears, Jamie Matthews, Ryan Currah, Marty Kemka, Scott Nixon, Moshin Elahi, Kevin Campbell, Jose Antonio Leiva Izquierdo, Kevin Stone, Andrew Godwin, Tijs Teulings, Roger Boardman, Xavier Antoviaque, Darian Moody, Lujeni, Jon Dugan, Wiley Kestner, Daniel C. Silverstein, Daniel Hahler, Subodh Nijsure, Philipp Weidenhiller, Yusuke Muraoka, Danny Roa, Reto Aebersold, Kyle Getrost, Décébal Hormuz, James Dacosta, Matt Long, Mauro Rocco, Tyrel Souza, Ryan Campbell, Ville Jyrkkä, Charalampos Papaloizou, Nikolai Røed Kristiansen, Antoni Aloy López, Celia Oakley, MichaÅ‚ Krawczak, Ivan VenOsdel, Tim Watts, Martin Warne. +**Individual backers**: Jure Cuhalev, Kevin Brolly, Ferenc Szalai, Dougal Matthews, Stefan Foulis, Carlos Hernando, Alen Mujezinovic, Ross Crawford-d'Heureuse, George Kappel, Alasdair Nicol, John Carr, Steve Winton, Trey, Manuel Miranda, David Horn, Vince Mi, Daniel Sears, Jamie Matthews, Ryan Currah, Marty Kemka, Scott Nixon, Moshin Elahi, Kevin Campbell, Jose Antonio Leiva Izquierdo, Kevin Stone, Andrew Godwin, Tijs Teulings, Roger Boardman, Xavier Antoviaque, Darian Moody, Lujeni, Jon Dugan, Wiley Kestner, Daniel C. Silverstein, Daniel Hahler, Subodh Nijsure, Philipp Weidenhiller, Yusuke Muraoka, Danny Roa, Reto Aebersold, Kyle Getrost, Décébal Hormuz, James Dacosta, Matt Long, Mauro Rocco, Tyrel Souza, Ryan Campbell, Ville Jyrkkä, Charalampos Papaloizou, Nikolai Røed Kristiansen, Antoni Aloy López, Celia Oakley, MichaÅ‚ Krawczak, Ivan VenOsdel, Tim Watts, Martin Warne, Nicola Jordan. **Corporate backers**: Savannah Informatics, Prism Skylabs. From 4c60639595db16e5b102f3dc20992ccb233b9a80 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 15 Aug 2014 16:19:13 +0100 Subject: [PATCH 196/225] Added Holvi --- docs/img/sponsors/3-holvi.png | Bin 0 -> 7533 bytes docs/topics/kickstarter-announcement.md | 1 + 2 files changed, 1 insertion(+) create mode 100644 docs/img/sponsors/3-holvi.png diff --git a/docs/img/sponsors/3-holvi.png b/docs/img/sponsors/3-holvi.png new file mode 100644 index 0000000000000000000000000000000000000000..255e391e09ef5574721d15dbf4559e8600149096 GIT binary patch literal 7533 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
  • Aditium
  • OpenEye Scientific Software
  • +
  • Holvi
  • Cantemo
  • MakeSpace
  • AX Semantics
  • From 967798e83296a9d80fdbd847548d090e633156de Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 15 Aug 2014 17:39:51 +0100 Subject: [PATCH 197/225] Added compile to the sponsors --- docs/img/sponsors/2-compile.png | Bin 0 -> 3108 bytes docs/topics/kickstarter-announcement.md | 1 + 2 files changed, 1 insertion(+) create mode 100644 docs/img/sponsors/2-compile.png diff --git a/docs/img/sponsors/2-compile.png b/docs/img/sponsors/2-compile.png new file mode 100644 index 0000000000000000000000000000000000000000..858aa09d46e2dcf750522cc182651b6843347a9a GIT binary patch literal 3108 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 GL;nYaw&I8Y literal 0 HcmV?d00001 diff --git a/docs/topics/kickstarter-announcement.md b/docs/topics/kickstarter-announcement.md index fde2a5bc0..d91d2e416 100644 --- a/docs/topics/kickstarter-announcement.md +++ b/docs/topics/kickstarter-announcement.md @@ -91,6 +91,7 @@ Our gold sponsors include companies large and small. Many thanks for their signi
  • Crate
  • Cryptico Corp
  • NextHub
  • +
  • Compile
  • Envision Linux
  • From 172b9c74f9151a826162d5de703dc3b42e8147e9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 15 Aug 2014 17:48:43 +0100 Subject: [PATCH 198/225] Latest sponsor update --- docs/topics/kickstarter-announcement.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/topics/kickstarter-announcement.md b/docs/topics/kickstarter-announcement.md index d91d2e416..71bb48a62 100644 --- a/docs/topics/kickstarter-announcement.md +++ b/docs/topics/kickstarter-announcement.md @@ -81,8 +81,6 @@ Our gold sponsors include companies large and small. Many thanks for their signi
  • Lightning Kite
  • Opbeat
  • Koordinates
  • - -
  • Heroku
  • Galileo Press
  • Security Compass
  • @@ -97,7 +95,7 @@ Our gold sponsors include companies large and small. Many thanks for their signi
    -**Individual backers**: Xitij Ritesh Patel, Howard Sandford. +**Individual backers**: Xitij Ritesh Patel, Howard Sandford, Simon Haugk. --- @@ -116,7 +114,6 @@ The serious financial contribution that our silver sponsors have made is very mu
  • Transcode
  • Garfo
  • Shippo
  • -
  • Gizmag
  • Tivix
  • Safari
  • @@ -124,7 +121,6 @@ The serious financial contribution that our silver sponsors have made is very mu
  • ABA Systems
  • beefarm.ru
  • Vzzual.com
  • -
  • Infinite Code
  • Crossword Tracker
  • PkgFarm
  • @@ -136,7 +132,6 @@ The serious financial contribution that our silver sponsors have made is very mu
  • TrackMaven
  • Phurba
  • Nephila
  • -
  • Aditium
  • OpenEye Scientific Software
  • Holvi
  • @@ -148,7 +143,7 @@ The serious financial contribution that our silver sponsors have made is very mu
    -**Individual backers**: Paul Hallet, Paul Whipp, Dylan Roy, Jannis Leidel, Xavier Ordoquy, Johannes Spielmann, Rob Spectre, Chris Heisel, Marwan Alsabbagh, Haris Ali, Tuomas Toivonen, Simon Haugk. +**Individual backers**: Paul Hallet, Paul Whipp, Dylan Roy, Jannis Leidel, Xavier Ordoquy, Johannes Spielmann, Rob Spectre, Chris Heisel, Marwan Alsabbagh, Haris Ali, Tuomas Toivonen. --- From 14867705e90b2b5f7e84dc7385d4ffba0c82b3e1 Mon Sep 17 00:00:00 2001 From: sshquack Date: Fri, 15 Aug 2014 20:41:21 -0600 Subject: [PATCH 199/225] Specify file names using standard format + Explicitly specify module names in the standard format similar to all the other tutorials + Remove the extra quote around module name --- docs/tutorial/4-authentication-and-permissions.md | 2 +- docs/tutorial/5-relationships-and-hyperlinked-apis.md | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index 491df1608..bdbe00ab4 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -129,7 +129,7 @@ Then, add the following property to **both** the `SnippetList` and `SnippetDetai If you open a browser and navigate to the browsable API at the moment, you'll find that you're no longer able to create new code snippets. In order to do so we'd need to be able to login as a user. -We can add a login view for use with the browsable API, by editing the URLconf in our project-level urls.py file. +We can add a login view for use with the browsable API, by editing the URLconf in our project-level `urls.py` file. Add the following import at the top of the file: diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md index aef92d08a..deddb13f1 100644 --- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md @@ -4,7 +4,7 @@ At the moment relationships within our API are represented by using primary keys ## Creating an endpoint for the root of our API -Right now we have endpoints for 'snippets' and 'users', but we don't have a single entry point to our API. To create one, we'll use a regular function-based view and the `@api_view` decorator we introduced earlier. +Right now we have endpoints for 'snippets' and 'users', but we don't have a single entry point to our API. To create one, we'll use a regular function-based view and the `@api_view` decorator we introduced earlier. In your `snippets/views.py` add: from rest_framework import renderers from rest_framework.decorators import api_view @@ -29,7 +29,7 @@ Unlike all our other API endpoints, we don't want to use JSON, but instead just The other thing we need to consider when creating the code highlight view is that there's no existing concrete generic view that we can use. We're not returning an object instance, but instead a property of an object instance. -Instead of using a concrete generic view, we'll use the base class for representing instances, and create our own `.get()` method. In your `snippets.views` add: +Instead of using a concrete generic view, we'll use the base class for representing instances, and create our own `.get()` method. In your `snippets/views.py` add: from rest_framework import renderers from rest_framework.response import Response @@ -43,7 +43,7 @@ Instead of using a concrete generic view, we'll use the base class for represent return Response(snippet.highlighted) As usual we need to add the new views that we've created in to our URLconf. -We'll add a url pattern for our new API root: +We'll add a url pattern for our new API root in `snippets/urls.py`: url(r'^$', 'api_root'), @@ -73,7 +73,7 @@ The `HyperlinkedModelSerializer` has the following differences from `ModelSerial * Relationships use `HyperlinkedRelatedField`, instead of `PrimaryKeyRelatedField`. -We can easily re-write our existing serializers to use hyperlinking. +We can easily re-write our existing serializers to use hyperlinking. In your `snippets/serializers.py` add: class SnippetSerializer(serializers.HyperlinkedModelSerializer): owner = serializers.Field(source='owner.username') @@ -105,7 +105,7 @@ If we're going to have a hyperlinked API, we need to make sure we name our URL p * Our user serializer includes a field that refers to `'snippet-detail'`. * Our snippet and user serializers include `'url'` fields that by default will refer to `'{model_name}-detail'`, which in this case will be `'snippet-detail'` and `'user-detail'`. -After adding all those names into our URLconf, our final `'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: # API endpoints urlpatterns = format_suffix_patterns(patterns('snippets.views', From 867e441ec07fc182569c3dbe6f86fe42aa6b0cbf Mon Sep 17 00:00:00 2001 From: sshquack Date: Fri, 15 Aug 2014 20:45:28 -0600 Subject: [PATCH 200/225] Strip trailing spaces in tutorial --- docs/tutorial/1-serialization.md | 20 +++++----- docs/tutorial/2-requests-and-responses.md | 8 ++-- docs/tutorial/3-class-based-views.md | 4 +- .../4-authentication-and-permissions.md | 12 +++--- .../5-relationships-and-hyperlinked-apis.md | 16 ++++---- docs/tutorial/quickstart.md | 38 +++++++++---------- 6 files changed, 49 insertions(+), 49 deletions(-) diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index 55b194576..96214f5b4 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -81,8 +81,8 @@ For the purposes of this tutorial we're going to start by creating a simple `Sni LEXERS = [item for item in get_all_lexers() if item[1]] LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS]) STYLE_CHOICES = sorted((item, item) for item in get_all_styles()) - - + + class Snippet(models.Model): created = models.DateTimeField(auto_now_add=True) title = models.CharField(max_length=100, blank=True, default='') @@ -94,7 +94,7 @@ For the purposes of this tutorial we're going to start by creating a simple `Sni style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100) - + class Meta: ordering = ('created',) @@ -122,12 +122,12 @@ The first thing we need to get started on our Web API is to provide a way of ser default='python') style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly') - + def restore_object(self, attrs, instance=None): """ Create or update a new snippet instance, given a dictionary of deserialized field values. - + Note that if we don't define this method, then deserializing data will simply return a dictionary of items. """ @@ -180,7 +180,7 @@ At this point we've translated the model instance into Python native datatypes. content # '{"pk": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}' -Deserialization is similar. First we parse a stream into Python native datatypes... +Deserialization is similar. First we parse a stream into Python native datatypes... # This import will use either `StringIO.StringIO` or `io.BytesIO` # as appropriate, depending on if we're running Python 2 or Python 3. @@ -196,7 +196,7 @@ Deserialization is similar. First we parse a stream into Python native datatype # True serializer.object # - + Notice how similar the API is to working with forms. The similarity should become even more apparent when we start writing views that use our serializer. We can also serialize querysets instead of model instances. To do so we simply add a `many=True` flag to the serializer arguments. @@ -264,7 +264,7 @@ The root of our API is going to be a view that supports listing all the existing return JSONResponse(serializer.data, status=201) return JSONResponse(serializer.errors, status=400) -Note that because we want to be able to POST to this view from clients that won't have a CSRF token we need to mark the view as `csrf_exempt`. This isn't something that you'd normally want to do, and REST framework views actually use more sensible behavior than this, but it'll do for our purposes right now. +Note that because we want to be able to POST to this view from clients that won't have a CSRF token we need to mark the view as `csrf_exempt`. This isn't something that you'd normally want to do, and REST framework views actually use more sensible behavior than this, but it'll do for our purposes right now. We'll also need a view which corresponds to an individual snippet, and can be used to retrieve, update or delete the snippet. @@ -277,11 +277,11 @@ We'll also need a view which corresponds to an individual snippet, and can be us snippet = Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: return HttpResponse(status=404) - + if request.method == 'GET': serializer = SnippetSerializer(snippet) return JSONResponse(serializer.data) - + elif request.method == 'PUT': data = JSONParser().parse(request) serializer = SnippetSerializer(snippet, data=data) diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md index 603edd081..e70bbbfc4 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -33,7 +33,7 @@ The wrappers also provide behaviour such as returning `405 Method Not Allowed` r ## Pulling it all together -Okay, let's go ahead and start using these new components to write a few views. +Okay, let's go ahead and start using these new components to write a few views. We don't need our `JSONResponse` class in `views.py` anymore, so go ahead and delete that. Once that's done we can start refactoring our views slightly. @@ -69,7 +69,7 @@ Here is the view for an individual snippet, in the `views.py` module. def snippet_detail(request, pk): """ Retrieve, update or delete a snippet instance. - """ + """ try: snippet = Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: @@ -115,7 +115,7 @@ Now update the `urls.py` file slightly, to append a set of `format_suffix_patter url(r'^snippets/$', 'snippet_list'), url(r'^snippets/(?P[0-9]+)$', 'snippet_detail'), ) - + urlpatterns = format_suffix_patterns(urlpatterns) We don't necessarily need to add these extra url patterns in, but it gives us a simple, clean way of referring to a specific format. @@ -146,7 +146,7 @@ Similarly, we can control the format of the request that we send, using the `Con curl -X POST http://127.0.0.1:8000/snippets/ -d "code=print 123" {"id": 3, "title": "", "code": "print 123", "linenos": false, "language": "python", "style": "friendly"} - + # POST using JSON curl -X POST http://127.0.0.1:8000/snippets/ -d '{"code": "print 456"}' -H "Content-Type: application/json" diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md index b37bc31bd..e04072ca5 100644 --- a/docs/tutorial/3-class-based-views.md +++ b/docs/tutorial/3-class-based-views.md @@ -30,7 +30,7 @@ We'll start by rewriting the root view as a class based view. All this involves return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) -So far, so good. It looks pretty similar to the previous case, but we've got better separation between the different HTTP methods. We'll also need to update the instance view in `views.py`. +So far, so good. It looks pretty similar to the previous case, but we've got better separation between the different HTTP methods. We'll also need to update the instance view in `views.py`. class SnippetDetail(APIView): """ @@ -72,7 +72,7 @@ We'll also need to refactor our `urls.py` slightly now we're using class based v url(r'^snippets/$', views.SnippetList.as_view()), url(r'^snippets/(?P[0-9]+)/$', views.SnippetDetail.as_view()), ) - + urlpatterns = format_suffix_patterns(urlpatterns) Okay, we're done. If you run the development server everything should be working just as before. diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index bdbe00ab4..74ad9a551 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -73,12 +73,12 @@ We'll also add a couple of views to `views.py`. We'd like to just use read-only class UserList(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer - - + + class UserDetail(generics.RetrieveAPIView): queryset = User.objects.all() serializer_class = UserSerializer - + Make sure to also import the `UserSerializer` class from snippets.serializers import UserSerializer @@ -157,8 +157,8 @@ To do that we're going to need to create a custom permission. In the snippets app, create a new file, `permissions.py` from rest_framework import permissions - - + + class IsOwnerOrReadOnly(permissions.BasePermission): """ Custom permission to only allow owners of an object to edit it. @@ -201,7 +201,7 @@ If we try to create a snippet without authenticating, we'll get an error: We can make a successful request by including the username and password of one of the users we created earlier. curl -X POST http://127.0.0.1:8000/snippets/ -d "code=print 789" -u tom:password - + {"id": 5, "owner": "tom", "title": "foo", "code": "print 789", "linenos": false, "language": "python", "style": "friendly"} ## Summary diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md index deddb13f1..9c61fe3d3 100644 --- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md @@ -1,6 +1,6 @@ # Tutorial 5: Relationships & Hyperlinked APIs -At the moment relationships within our API are represented by using primary keys. In this part of the tutorial we'll improve the cohesion and discoverability of our API, by instead using hyperlinking for relationships. +At the moment relationships within our API are represented by using primary keys. In this part of the tutorial we'll improve the cohesion and discoverability of our API, by instead using hyperlinking for relationships. ## Creating an endpoint for the root of our API @@ -37,7 +37,7 @@ Instead of using a concrete generic view, we'll use the base class for represent class SnippetHighlight(generics.GenericAPIView): queryset = Snippet.objects.all() renderer_classes = (renderers.StaticHTMLRenderer,) - + def get(self, request, *args, **kwargs): snippet = self.get_object() return Response(snippet.highlighted) @@ -78,16 +78,16 @@ We can easily re-write our existing serializers to use hyperlinking. In your `sn class SnippetSerializer(serializers.HyperlinkedModelSerializer): owner = serializers.Field(source='owner.username') highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html') - + class Meta: model = Snippet fields = ('url', 'highlight', 'owner', 'title', 'code', 'linenos', 'language', 'style') - - + + class UserSerializer(serializers.HyperlinkedModelSerializer): snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail') - + class Meta: model = User fields = ('url', 'username', 'snippets') @@ -126,9 +126,9 @@ After adding all those names into our URLconf, our final `snippets/urls.py` file views.UserDetail.as_view(), name='user-detail') )) - + # Login and logout views for the browsable API - urlpatterns += patterns('', + urlpatterns += patterns('', url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), ) diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 8bf8c7f5c..029b56a2b 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -46,14 +46,14 @@ First up we're going to define some serializers in `quickstart/serializers.py` t from django.contrib.auth.models import User, Group from rest_framework import serializers - - + + class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = User fields = ('url', 'username', 'email', 'groups') - - + + class GroupSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Group @@ -68,16 +68,16 @@ Right, we'd better write some views then. Open `quickstart/views.py` and get ty from django.contrib.auth.models import User, Group from rest_framework import viewsets from quickstart.serializers import UserSerializer, GroupSerializer - - + + class UserViewSet(viewsets.ModelViewSet): """ API endpoint that allows users to be viewed or edited. """ queryset = User.objects.all() serializer_class = UserSerializer - - + + class GroupViewSet(viewsets.ModelViewSet): """ API endpoint that allows groups to be viewed or edited. @@ -144,22 +144,22 @@ We're now ready to test the API we've built. Let's fire up the server from the We can now access our API, both from the command-line, using tools like `curl`... - bash: curl -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/ + bash: curl -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/ { - "count": 2, - "next": null, - "previous": null, + "count": 2, + "next": null, + "previous": null, "results": [ { - "email": "admin@example.com", - "groups": [], - "url": "http://127.0.0.1:8000/users/1/", + "email": "admin@example.com", + "groups": [], + "url": "http://127.0.0.1:8000/users/1/", "username": "admin" - }, + }, { - "email": "tom@example.com", - "groups": [ ], - "url": "http://127.0.0.1:8000/users/2/", + "email": "tom@example.com", + "groups": [ ], + "url": "http://127.0.0.1:8000/users/2/", "username": "tom" } ] From 38a0e3e6278db96660c89bfcb3e660704c068ff5 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 16 Aug 2014 14:02:44 +0100 Subject: [PATCH 201/225] Sponsor update --- docs/topics/kickstarter-announcement.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/kickstarter-announcement.md b/docs/topics/kickstarter-announcement.md index 71bb48a62..84dc8511f 100644 --- a/docs/topics/kickstarter-announcement.md +++ b/docs/topics/kickstarter-announcement.md @@ -151,9 +151,9 @@ The serious financial contribution that our silver sponsors have made is very mu The following individuals made a significant financial contribution to the development of Django REST framework 3, for which I can only offer a huge, warm and sincere thank you! -**Individual backers**: Jure Cuhalev, Kevin Brolly, Ferenc Szalai, Dougal Matthews, Stefan Foulis, Carlos Hernando, Alen Mujezinovic, Ross Crawford-d'Heureuse, George Kappel, Alasdair Nicol, John Carr, Steve Winton, Trey, Manuel Miranda, David Horn, Vince Mi, Daniel Sears, Jamie Matthews, Ryan Currah, Marty Kemka, Scott Nixon, Moshin Elahi, Kevin Campbell, Jose Antonio Leiva Izquierdo, Kevin Stone, Andrew Godwin, Tijs Teulings, Roger Boardman, Xavier Antoviaque, Darian Moody, Lujeni, Jon Dugan, Wiley Kestner, Daniel C. Silverstein, Daniel Hahler, Subodh Nijsure, Philipp Weidenhiller, Yusuke Muraoka, Danny Roa, Reto Aebersold, Kyle Getrost, Décébal Hormuz, James Dacosta, Matt Long, Mauro Rocco, Tyrel Souza, Ryan Campbell, Ville Jyrkkä, Charalampos Papaloizou, Nikolai Røed Kristiansen, Antoni Aloy López, Celia Oakley, Michał Krawczak, Ivan VenOsdel, Tim Watts, Martin Warne, Nicola Jordan. +**Individual backers**: Jure Cuhalev, Kevin Brolly, Ferenc Szalai, Dougal Matthews, Stefan Foulis, Carlos Hernando, Alen Mujezinovic, Ross Crawford-d'Heureuse, George Kappel, Alasdair Nicol, John Carr, Steve Winton, Trey, Manuel Miranda, David Horn, Vince Mi, Daniel Sears, Jamie Matthews, Ryan Currah, Marty Kemka, Scott Nixon, Moshin Elahi, Kevin Campbell, Jose Antonio Leiva Izquierdo, Kevin Stone, Andrew Godwin, Tijs Teulings, Roger Boardman, Xavier Antoviaque, Darian Moody, Lujeni, Jon Dugan, Wiley Kestner, Daniel C. Silverstein, Daniel Hahler, Subodh Nijsure, Philipp Weidenhiller, Yusuke Muraoka, Danny Roa, Reto Aebersold, Kyle Getrost, Décébal Hormuz, James Dacosta, Matt Long, Mauro Rocco, Tyrel Souza, Ryan Campbell, Ville Jyrkkä, Charalampos Papaloizou, Nikolai Røed Kristiansen, Antoni Aloy López, Celia Oakley, Michał Krawczak, Ivan VenOsdel, Tim Watts, Martin Warne, Nicola Jordan, Ryan Kaskel. -**Corporate backers**: Savannah Informatics, Prism Skylabs. +**Corporate backers**: Savannah Informatics, Prism Skylabs, Musical Operating Devices. --- From a6901ea36de19a35fa82783c984841b4c3ca0dad Mon Sep 17 00:00:00 2001 From: Aymeric Derbois Date: Sat, 16 Aug 2014 15:49:31 +0200 Subject: [PATCH 202/225] Add test for SerializerMethodField --- rest_framework/tests/test_fields.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/rest_framework/tests/test_fields.py b/rest_framework/tests/test_fields.py index b04b947f2..17d12f231 100644 --- a/rest_framework/tests/test_fields.py +++ b/rest_framework/tests/test_fields.py @@ -1002,3 +1002,21 @@ class BooleanField(TestCase): bool_field = serializers.BooleanField(required=True) self.assertFalse(BooleanRequiredSerializer(data={}).is_valid()) + + +class SerializerMethodFieldTest(TestCase): + """ + Tests for the SerializerMethodField field_to_native() behavior + """ + class SerializerTest(serializers.Serializer): + def get_my_test(self, obj): + return obj.my_test[0:5] + + class Example(): + my_test = 'Hey, this is a test !' + + def test_field_to_native(self): + s = serializers.SerializerMethodField('get_my_test') + s.initialize(self.SerializerTest(), 'name') + result = s.field_to_native(self.Example(), None) + self.assertEqual(result, 'Hey, ') From 5f63d31b002020148c526f2d5ec27f723a06f2e9 Mon Sep 17 00:00:00 2001 From: Andrew Fong Date: Sat, 16 Aug 2014 15:05:46 -0700 Subject: [PATCH 203/225] override_method should substitute action A view's action is dependent on the request method. When overriding the method (e.g. to generate a form for a POST request on a GET call to the browseable API), the action should be updated as well. Otherwise, viewset functions may be in a weird limbo state where a 'list' action has a POST method. --- rest_framework/request.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rest_framework/request.py b/rest_framework/request.py index 40467c03d..1ea61586d 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -42,12 +42,16 @@ class override_method(object): self.view = view self.request = request self.method = method + self.action = getattr(view, 'action', None) def __enter__(self): self.view.request = clone_request(self.request, self.method) + action_map = getattr(self, 'action_map', {}) + self.view.action = action_map.get(self.method.lower()) return self.view.request def __exit__(self, *args, **kwarg): + self.view.action = self.action self.view.request = self.request From 21cbf3484e04bb015c1921307cdf0306a81e571d Mon Sep 17 00:00:00 2001 From: Andrew Fong Date: Sat, 16 Aug 2014 23:22:18 +0000 Subject: [PATCH 204/225] Fixed action_map being pulled from wrong object --- rest_framework/request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/request.py b/rest_framework/request.py index 1ea61586d..735a92885 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -46,7 +46,7 @@ class override_method(object): def __enter__(self): self.view.request = clone_request(self.request, self.method) - action_map = getattr(self, 'action_map', {}) + action_map = getattr(self.view, 'action_map', {}) self.view.action = action_map.get(self.method.lower()) return self.view.request From 6edbabe0e1ffa8111284c0af94a8f878f7056413 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 18 Aug 2014 10:58:00 +0100 Subject: [PATCH 205/225] Link to Django docs on widgets. Closes #1760. --- docs/api-guide/fields.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index dd2795415..b41e0ebca 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -62,7 +62,7 @@ A dictionary of error codes to error messages. ### `widget` Used only if rendering the field to HTML. -This argument sets the widget that should be used to render the field. +This argument sets the widget that should be used to render the field. For more details, and a list of available widgets, see [the Django documentation on form widgets][django-widgets]. ### `label` @@ -370,6 +370,7 @@ The [django-rest-framework-gis][django-rest-framework-gis] package provides geog [FILE_UPLOAD_HANDLERS]: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FILE_UPLOAD_HANDLERS [ecma262]: http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 [strftime]: http://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior +[django-widgets]: https://docs.djangoproject.com/en/dev/ref/forms/widgets/ [iso8601]: http://www.w3.org/TR/NOTE-datetime [drf-compound-fields]: http://drf-compound-fields.readthedocs.org [drf-extra-fields]: https://github.com/Hipo/drf-extra-fields From dce30207dae1725a2b08232fc4dee34e0949b14b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 18 Aug 2014 11:09:53 +0100 Subject: [PATCH 206/225] Remove kickstarter links from homepage and README --- README.md | 10 ---------- docs/index.md | 8 -------- 2 files changed, 18 deletions(-) diff --git a/README.md b/README.md index eea002b4a..da5f27aef 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,3 @@ ---- - -#### Django REST framework 3 - Kickstarter announcement! - -We are currently running a Kickstarter campaign to help fund the development of Django REST framework 3. - -If you want to help drive sustainable open-source development forward, then **please check out [the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3) and consider funding us.** - ---- - # Django REST framework [![build-status-image]][travis] diff --git a/docs/index.md b/docs/index.md index d9c686c4a..dd407497b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,14 +9,6 @@ --- -#### Django REST framework 3 - Kickstarter announcement! - -We are currently running a Kickstarter campaign to help fund the development of Django REST framework 3. - -If you want to help drive sustainable open-source development **please [check out the Kickstarter project](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3) and consider funding us.** - ---- -

    Date: Mon, 18 Aug 2014 15:34:23 +0100 Subject: [PATCH 212/225] Copy filter_backends class attribute before returning it. --- rest_framework/generics.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 42204841c..aea636f15 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -189,7 +189,13 @@ class GenericAPIView(views.APIView): """ Returns the list of filter backends that this view requires. """ - filter_backends = self.filter_backends or [] + if self.filter_backends is None: + filter_backends = [] + else: + # Note that we are returning a *copy* of the class attribute, + # so that it is safe for the view to mutate it if needed. + filter_backends = list(self.filter_backends) + if not filter_backends and self.filter_backend: warnings.warn( 'The `filter_backend` attribute and `FILTER_BACKEND` setting ' @@ -199,6 +205,7 @@ class GenericAPIView(views.APIView): PendingDeprecationWarning, stacklevel=2 ) filter_backends = [self.filter_backend] + return filter_backends From 1effc983b07ed7aece609f0f4b24a33164196546 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 18 Aug 2014 20:51:08 +0100 Subject: [PATCH 213/225] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index da5f27aef..0eaf5c83c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ # Overview -Django REST framework is a powerful and flexible toolkit that makes it easy to build Web APIs. +Django REST framework is a powerful and flexible toolkit for building Web APIs. Some reasons you might want to use REST framework: From 97d8f037cc1292407eae965ceb4df34a52bd6161 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 18 Aug 2014 20:56:17 +0100 Subject: [PATCH 214/225] Only set .action attribute in override_method if it already existed on the view --- rest_framework/request.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rest_framework/request.py b/rest_framework/request.py index 4f9345f3e..d508f9b43 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -46,13 +46,16 @@ class override_method(object): def __enter__(self): self.view.request = clone_request(self.request, self.method) - action_map = getattr(self.view, 'action_map', {}) - self.view.action = action_map.get(self.method.lower()) + if self.action is not None: + # For viewsets we also set the `.action` attribute. + action_map = getattr(self.view, 'action_map', {}) + self.view.action = action_map.get(self.method.lower()) return self.view.request def __exit__(self, *args, **kwarg): - self.view.action = self.action self.view.request = self.request + if self.action is not None: + self.view.action = self.action class Empty(object): From bf09c32de8f9d528f83e9cb7a2773d1f4c9ab563 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 19 Aug 2014 13:28:07 +0100 Subject: [PATCH 215/225] Code linting and added runtests.py --- .travis.yml | 2 +- pytest.ini | 2 - optionals.txt => requirements-test.txt | 6 ++ requirements.txt | 2 - rest_framework/__init__.py | 8 +- rest_framework/authentication.py | 2 +- rest_framework/authtoken/models.py | 1 - .../south_migrations/0001_initial.py | 9 +- rest_framework/decorators.py | 7 +- rest_framework/exceptions.py | 1 + rest_framework/filters.py | 2 +- rest_framework/generics.py | 15 ++-- rest_framework/negotiation.py | 6 +- rest_framework/permissions.py | 22 +++-- rest_framework/renderers.py | 17 ++-- rest_framework/request.py | 25 ++++-- rest_framework/response.py | 6 +- rest_framework/serializers.py | 40 +++++---- rest_framework/settings.py | 12 +-- rest_framework/status.py | 4 + rest_framework/templatetags/rest_framework.py | 6 +- rest_framework/test.py | 7 +- rest_framework/urls.py | 10 ++- rest_framework/utils/encoders.py | 31 ++++--- rest_framework/utils/formatting.py | 4 +- rest_framework/utils/mediatypes.py | 2 +- rest_framework/views.py | 2 +- rest_framework/viewsets.py | 10 +-- runtests.py | 86 +++++++++++++++++++ conftest.py => tests/conftest.py | 0 tests/serializers.py | 1 - tests/settings.py | 11 ++- tests/test_authentication.py | 73 ++++++++++------ tests/test_breadcrumbs.py | 61 +++++++++---- tests/test_fields.py | 14 +-- tests/test_files.py | 11 +-- tests/test_filters.py | 9 +- tests/test_genericrelations.py | 24 +++--- tests/test_htmlrenderer.py | 3 +- tests/test_hyperlinkedserializers.py | 3 +- tests/test_pagination.py | 12 ++- tests/test_permissions.py | 58 +++++++++---- tests/test_relations.py | 11 ++- tests/test_relations_hyperlink.py | 15 ++-- tests/test_relations_pk.py | 12 +-- tests/test_renderers.py | 59 ++++++++----- tests/test_request.py | 3 +- tests/test_response.py | 3 +- tests/test_reverse.py | 3 +- tests/test_routers.py | 9 +- tests/test_serializer.py | 30 ++++--- tests/test_serializer_bulk_update.py | 6 +- tests/test_serializer_nested.py | 2 + tests/test_serializers.py | 4 +- tests/test_status.py | 2 +- tests/test_templatetags.py | 2 +- tests/test_testing.py | 6 +- tests/test_throttling.py | 48 ++++++----- tests/test_urlizer.py | 1 - tox.ini | 10 ++- 60 files changed, 548 insertions(+), 305 deletions(-) delete mode 100644 pytest.ini rename optionals.txt => requirements-test.txt (62%) create mode 100755 runtests.py rename conftest.py => tests/conftest.py (100%) diff --git a/.travis.yml b/.travis.yml index 5a6900a5e..9894ee4ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ install: - export PYTHONPATH=. script: - - py.test + - ./runtests.py matrix: exclude: diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index bbd083ac1..000000000 --- a/pytest.ini +++ /dev/null @@ -1,2 +0,0 @@ -[pytest] -addopts = --tb=short diff --git a/optionals.txt b/requirements-test.txt similarity index 62% rename from optionals.txt rename to requirements-test.txt index 262e76443..a91dd0d4b 100644 --- a/optionals.txt +++ b/requirements-test.txt @@ -1,3 +1,9 @@ +# Test requirements +pytest-django==2.6 +pytest==2.5.2 +pytest-cov==1.6 + +# Optional packages markdown>=2.1.0 PyYAML>=3.10 defusedxml>=0.3 diff --git a/requirements.txt b/requirements.txt index 360acb14d..730c1d07a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1 @@ --e . Django>=1.3 -pytest-django==2.6 diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 01036cefa..f30012b9b 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -1,9 +1,9 @@ """ -______ _____ _____ _____ __ _ -| ___ \ ___/ ___|_ _| / _| | | -| |_/ / |__ \ `--. | | | |_ _ __ __ _ _ __ ___ _____ _____ _ __| | __ +______ _____ _____ _____ __ +| ___ \ ___/ ___|_ _| / _| | | +| |_/ / |__ \ `--. | | | |_ _ __ __ _ _ __ ___ _____ _____ _ __| |__ | /| __| `--. \ | | | _| '__/ _` | '_ ` _ \ / _ \ \ /\ / / _ \| '__| |/ / -| |\ \| |___/\__/ / | | | | | | | (_| | | | | | | __/\ V V / (_) | | | < +| |\ \| |___/\__/ / | | | | | | | (_| | | | | | | __/\ V V / (_) | | | < \_| \_\____/\____/ \_/ |_| |_| \__,_|_| |_| |_|\___| \_/\_/ \___/|_| |_|\_| """ diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 82cea70fc..5721a869e 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -21,7 +21,7 @@ def get_authorization_header(request): Hide some test client ickyness where the header can be unicode. """ auth = request.META.get('HTTP_AUTHORIZATION', b'') - if type(auth) == type(''): + if isinstance(auth, type('')): # Work around django test client oddness auth = auth.encode(HTTP_HEADER_ENCODING) return auth diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index 167fa5314..db21d44c3 100644 --- a/rest_framework/authtoken/models.py +++ b/rest_framework/authtoken/models.py @@ -1,6 +1,5 @@ import binascii import os -from hashlib import sha1 from django.conf import settings from django.db import models diff --git a/rest_framework/authtoken/south_migrations/0001_initial.py b/rest_framework/authtoken/south_migrations/0001_initial.py index d5965e404..926de02b1 100644 --- a/rest_framework/authtoken/south_migrations/0001_initial.py +++ b/rest_framework/authtoken/south_migrations/0001_initial.py @@ -1,15 +1,10 @@ # -*- coding: utf-8 -*- -import datetime from south.db import db from south.v2 import SchemaMigration -from django.db import models - -from rest_framework.settings import api_settings - try: from django.contrib.auth import get_user_model -except ImportError: # django < 1.5 +except ImportError: # django < 1.5 from django.contrib.auth.models import User else: User = get_user_model() @@ -26,12 +21,10 @@ class Migration(SchemaMigration): )) db.send_create_signal('authtoken', ['Token']) - def backwards(self, orm): # Deleting model 'Token' db.delete_table('authtoken_token') - models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 18e41a18d..e06d6ff56 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -131,6 +131,7 @@ def list_route(methods=['get'], **kwargs): return func return decorator + # These are now pending deprecation, in favor of `detail_route` and `list_route`. def link(**kwargs): @@ -139,11 +140,13 @@ def link(**kwargs): """ msg = 'link is pending deprecation. Use detail_route instead.' warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) + def decorator(func): func.bind_to_methods = ['get'] func.detail = True func.kwargs = kwargs return func + return decorator @@ -153,9 +156,11 @@ def action(methods=['post'], **kwargs): """ msg = 'action is pending deprecation. Use detail_route instead.' warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) + def decorator(func): func.bind_to_methods = methods func.detail = True func.kwargs = kwargs return func - return decorator \ No newline at end of file + + return decorator diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py index 5f774a9f3..97dab77ea 100644 --- a/rest_framework/exceptions.py +++ b/rest_framework/exceptions.py @@ -23,6 +23,7 @@ class APIException(Exception): def __str__(self): return self.detail + class ParseError(APIException): status_code = status.HTTP_400_BAD_REQUEST default_detail = 'Malformed request.' diff --git a/rest_framework/filters.py b/rest_framework/filters.py index c3b846aed..538386cee 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -116,7 +116,7 @@ class OrderingFilter(BaseFilterBackend): def get_ordering(self, request): """ Ordering is set by a comma delimited ?ordering=... query parameter. - + The `ordering` query parameter can be overridden by setting the `ordering_param` value on the OrderingFilter or by specifying an `ORDERING_PARAM` value in the API settings. diff --git a/rest_framework/generics.py b/rest_framework/generics.py index cecb548fb..02d243654 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -25,6 +25,7 @@ def strict_positive_int(integer_string, cutoff=None): ret = min(ret, cutoff) return ret + def get_object_or_404(queryset, *filter_args, **filter_kwargs): """ Same as Django's standard shortcut, but make sure to raise 404 @@ -162,10 +163,11 @@ class GenericAPIView(views.APIView): raise Http404(_("Page is not 'last', nor can it be converted to an int.")) try: page = paginator.page(page_number) - except InvalidPage as e: - raise Http404(_('Invalid page (%(page_number)s): %(message)s') % { - 'page_number': page_number, - 'message': str(e) + except InvalidPage as exc: + error_format = _('Invalid page (%(page_number)s): %(message)s') + raise Http404(error_format % { + 'page_number': page_number, + 'message': str(exc) }) if deprecated_style: @@ -208,7 +210,6 @@ class GenericAPIView(views.APIView): return filter_backends - ######################## ### The following methods provide default implementations ### that you may want to override for more complex cases. @@ -284,8 +285,8 @@ class GenericAPIView(views.APIView): if self.model is not None: return self.model._default_manager.all() - raise ImproperlyConfigured("'%s' must define 'queryset' or 'model'" - % self.__class__.__name__) + error_format = "'%s' must define 'queryset' or 'model'" + raise ImproperlyConfigured(error_format % self.__class__.__name__) def get_object(self, queryset=None): """ diff --git a/rest_framework/negotiation.py b/rest_framework/negotiation.py index 4d205c0e8..ca7b53978 100644 --- a/rest_framework/negotiation.py +++ b/rest_framework/negotiation.py @@ -54,8 +54,10 @@ class DefaultContentNegotiation(BaseContentNegotiation): for media_type in media_type_set: if media_type_matches(renderer.media_type, media_type): # Return the most specific media type as accepted. - if (_MediaType(renderer.media_type).precedence > - _MediaType(media_type).precedence): + if ( + _MediaType(renderer.media_type).precedence > + _MediaType(media_type).precedence + ): # Eg client requests '*/*' # Accepted media type is 'application/json' return renderer, renderer.media_type diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index c95171389..6a1a00770 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -62,9 +62,11 @@ class IsAuthenticatedOrReadOnly(BasePermission): """ def has_permission(self, request, view): - return (request.method in SAFE_METHODS or - request.user and - request.user.is_authenticated()) + return ( + request.method in SAFE_METHODS or + request.user and + request.user.is_authenticated() + ) class DjangoModelPermissions(BasePermission): @@ -122,9 +124,11 @@ class DjangoModelPermissions(BasePermission): perms = self.get_required_permissions(request.method, model_cls) - return (request.user and + return ( + request.user and (request.user.is_authenticated() or not self.authenticated_users_only) and - request.user.has_perms(perms)) + request.user.has_perms(perms) + ) class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions): @@ -212,6 +216,8 @@ class TokenHasReadWriteScope(BasePermission): required = oauth2_constants.READ if read_only else oauth2_constants.WRITE return oauth2_provider_scope.check(required, request.auth.scope) - assert False, ('TokenHasReadWriteScope requires either the' - '`OAuthAuthentication` or `OAuth2Authentication` authentication ' - 'class to be used.') + assert False, ( + 'TokenHasReadWriteScope requires either the' + '`OAuthAuthentication` or `OAuth2Authentication` authentication ' + 'class to be used.' + ) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 7048d87de..3dabd277e 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -8,7 +8,6 @@ REST framework also provides an HTML renderer the renders the browsable API. """ from __future__ import unicode_literals -import copy import json import django from django import forms @@ -75,7 +74,6 @@ class JSONRenderer(BaseRenderer): # E.g. If we're being called by the BrowsableAPIRenderer. return renderer_context.get('indent', None) - def render(self, data, accepted_media_type=None, renderer_context=None): """ Render `data` into JSON, returning a bytestring. @@ -86,8 +84,10 @@ class JSONRenderer(BaseRenderer): renderer_context = renderer_context or {} indent = self.get_indent(accepted_media_type, renderer_context) - ret = json.dumps(data, cls=self.encoder_class, - indent=indent, ensure_ascii=self.ensure_ascii) + ret = json.dumps( + data, cls=self.encoder_class, + indent=indent, ensure_ascii=self.ensure_ascii + ) # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True, # but if ensure_ascii=False, the return type is underspecified, @@ -454,8 +454,10 @@ class BrowsableAPIRenderer(BaseRenderer): if method in ('DELETE', 'OPTIONS'): return True # Don't actually need to return a form - if (not getattr(view, 'get_serializer', None) - or not any(is_form_media_type(parser.media_type) for parser in view.parser_classes)): + if ( + not getattr(view, 'get_serializer', None) + or not any(is_form_media_type(parser.media_type) for parser in view.parser_classes) + ): return serializer = view.get_serializer(instance=obj, data=data, files=files) @@ -576,7 +578,7 @@ class BrowsableAPIRenderer(BaseRenderer): 'version': VERSION, 'breadcrumblist': self.get_breadcrumbs(request), 'allowed_methods': view.allowed_methods, - 'available_formats': [renderer.format for renderer in view.renderer_classes], + 'available_formats': [renderer_cls.format for renderer_cls in view.renderer_classes], 'response_headers': response_headers, 'put_form': self.get_rendered_html_form(view, 'PUT', request), @@ -625,4 +627,3 @@ class MultiPartRenderer(BaseRenderer): def render(self, data, accepted_media_type=None, renderer_context=None): return encode_multipart(self.BOUNDARY, data) - diff --git a/rest_framework/request.py b/rest_framework/request.py index d508f9b43..620b00ada 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -295,8 +295,11 @@ class Request(object): Return the content body of the request, as a stream. """ try: - content_length = int(self.META.get('CONTENT_LENGTH', - self.META.get('HTTP_CONTENT_LENGTH'))) + content_length = int( + self.META.get( + 'CONTENT_LENGTH', self.META.get('HTTP_CONTENT_LENGTH') + ) + ) except (ValueError, TypeError): content_length = 0 @@ -320,9 +323,11 @@ class Request(object): ) # We only need to use form overloading on form POST requests. - if (not USE_FORM_OVERLOADING + if ( + not USE_FORM_OVERLOADING or self._request.method != 'POST' - or not is_form_media_type(self._content_type)): + or not is_form_media_type(self._content_type) + ): return # At this point we're committed to parsing the request as form data. @@ -330,15 +335,19 @@ class Request(object): self._files = self._request.FILES # Method overloading - change the method and remove the param from the content. - if (self._METHOD_PARAM and - self._METHOD_PARAM in self._data): + if ( + self._METHOD_PARAM and + self._METHOD_PARAM in self._data + ): self._method = self._data[self._METHOD_PARAM].upper() # Content overloading - modify the content type, and force re-parse. - if (self._CONTENT_PARAM and + if ( + self._CONTENT_PARAM and self._CONTENTTYPE_PARAM and self._CONTENT_PARAM in self._data and - self._CONTENTTYPE_PARAM in self._data): + self._CONTENTTYPE_PARAM in self._data + ): self._content_type = self._data[self._CONTENTTYPE_PARAM] self._stream = BytesIO(self._data[self._CONTENT_PARAM].encode(self.parser_context['encoding'])) self._data, self._files = (Empty, Empty) diff --git a/rest_framework/response.py b/rest_framework/response.py index 25b785245..80225cac3 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -62,8 +62,10 @@ class Response(SimpleTemplateResponse): ret = renderer.render(self.data, media_type, context) if isinstance(ret, six.text_type): - assert charset, 'renderer returned unicode, and did not specify ' \ - 'a charset value.' + assert charset, ( + 'renderer returned unicode, and did not specify ' + 'a charset value.' + ) return bytes(ret.encode(charset)) if not ret: diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 2fdc9b9da..95288671c 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -449,9 +449,11 @@ class BaseSerializer(WritableField): # If we have a model manager or similar object then we need # to iterate through each instance. - if (self.many and + if ( + self.many and not hasattr(obj, '__iter__') and - is_simple_callable(getattr(obj, 'all', None))): + is_simple_callable(getattr(obj, 'all', None)) + ): obj = obj.all() kwargs = { @@ -601,8 +603,10 @@ class BaseSerializer(WritableField): API schemas for auto-documentation. """ return SortedDict( - [(field_name, field.metadata()) - for field_name, field in six.iteritems(self.fields)] + [ + (field_name, field.metadata()) + for field_name, field in six.iteritems(self.fields) + ] ) @@ -656,8 +660,10 @@ class ModelSerializer(Serializer): """ cls = self.opts.model - assert cls is not None, \ - "Serializer class '%s' is missing 'model' Meta option" % self.__class__.__name__ + assert cls is not None, ( + "Serializer class '%s' is missing 'model' Meta option" % + self.__class__.__name__ + ) opts = cls._meta.concrete_model._meta ret = SortedDict() nested = bool(self.opts.depth) @@ -668,9 +674,9 @@ class ModelSerializer(Serializer): # If model is a child via multitable inheritance, use parent's pk pk_field = pk_field.rel.to._meta.pk - field = self.get_pk_field(pk_field) - if field: - ret[pk_field.name] = field + serializer_pk_field = self.get_pk_field(pk_field) + if serializer_pk_field: + ret[pk_field.name] = serializer_pk_field # Deal with forward relationships forward_rels = [field for field in opts.fields if field.serialize] @@ -739,9 +745,11 @@ class ModelSerializer(Serializer): is_m2m = isinstance(relation.field, models.fields.related.ManyToManyField) - if (is_m2m and + if ( + is_m2m and hasattr(relation.field.rel, 'through') and - not relation.field.rel.through._meta.auto_created): + not relation.field.rel.through._meta.auto_created + ): has_through_model = True if nested: @@ -911,10 +919,12 @@ class ModelSerializer(Serializer): for field_name, field in self.fields.items(): field_name = field.source or field_name - if field_name in exclusions \ - and not field.read_only \ - and (field.required or hasattr(instance, field_name)) \ - and not isinstance(field, Serializer): + if ( + field_name in exclusions + and not field.read_only + and (field.required or hasattr(instance, field_name)) + and not isinstance(field, Serializer) + ): exclusions.remove(field_name) return exclusions diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 2727f5960..6806a4689 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -46,16 +46,12 @@ DEFAULTS = { 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.AllowAny', ), - 'DEFAULT_THROTTLE_CLASSES': ( - ), - 'DEFAULT_CONTENT_NEGOTIATION_CLASS': - 'rest_framework.negotiation.DefaultContentNegotiation', + 'DEFAULT_THROTTLE_CLASSES': (), + 'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation', # Genric view behavior - 'DEFAULT_MODEL_SERIALIZER_CLASS': - 'rest_framework.serializers.ModelSerializer', - 'DEFAULT_PAGINATION_SERIALIZER_CLASS': - 'rest_framework.pagination.PaginationSerializer', + 'DEFAULT_MODEL_SERIALIZER_CLASS': 'rest_framework.serializers.ModelSerializer', + 'DEFAULT_PAGINATION_SERIALIZER_CLASS': 'rest_framework.pagination.PaginationSerializer', 'DEFAULT_FILTER_BACKENDS': (), # Throttling diff --git a/rest_framework/status.py b/rest_framework/status.py index 764353711..90a755089 100644 --- a/rest_framework/status.py +++ b/rest_framework/status.py @@ -10,15 +10,19 @@ from __future__ import unicode_literals def is_informational(code): return code >= 100 and code <= 199 + def is_success(code): return code >= 200 and code <= 299 + def is_redirect(code): return code >= 300 and code <= 399 + def is_client_error(code): return code >= 400 and code <= 499 + def is_server_error(code): return code >= 500 and code <= 599 diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index 911b1b622..5b8fa3853 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -152,8 +152,10 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru middle = middle[len(opening):] lead = lead + opening # Keep parentheses at the end only if they're balanced. - if (middle.endswith(closing) - and middle.count(closing) == middle.count(opening) + 1): + if ( + middle.endswith(closing) + and middle.count(closing) == middle.count(opening) + 1 + ): middle = middle[:-len(closing)] trail = closing + trail diff --git a/rest_framework/test.py b/rest_framework/test.py index d4ec50a06..9242cf7c6 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -49,9 +49,10 @@ class APIRequestFactory(DjangoRequestFactory): else: format = format or self.default_format - assert format in self.renderer_classes, ("Invalid format '{0}'. " - "Available formats are {1}. Set TEST_REQUEST_RENDERER_CLASSES " - "to enable extra request formats.".format( + assert format in self.renderer_classes, ( + "Invalid format '{0}'. Available formats are {1}. " + "Set TEST_REQUEST_RENDERER_CLASSES to enable " + "extra request formats.".format( format, ', '.join(["'" + fmt + "'" for fmt in self.renderer_classes.keys()]) ) diff --git a/rest_framework/urls.py b/rest_framework/urls.py index eed4bd140..8fa3073e8 100644 --- a/rest_framework/urls.py +++ b/rest_framework/urls.py @@ -8,17 +8,19 @@ your API requires authentication: ... url(r'^auth', include('rest_framework.urls', namespace='rest_framework')) ) - + The urls must be namespaced as 'rest_framework', and you should make sure your authentication settings include `SessionAuthentication`. """ from __future__ import unicode_literals from django.conf.urls import patterns, url +from django.contrib.auth import views template_name = {'template_name': 'rest_framework/login.html'} -urlpatterns = patterns('django.contrib.auth.views', - url(r'^login/$', 'login', template_name, name='login'), - url(r'^logout/$', 'logout', template_name, name='logout'), +urlpatterns = patterns( + '', + url(r'^login/$', views.login, template_name, name='login'), + url(r'^logout/$', views.logout, template_name, name='logout') ) diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index c125ac8a8..00ffdfbae 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -98,14 +98,23 @@ else: node.flow_style = best_style return node - SafeDumper.add_representer(decimal.Decimal, - SafeDumper.represent_decimal) - - SafeDumper.add_representer(SortedDict, - yaml.representer.SafeRepresenter.represent_dict) - SafeDumper.add_representer(DictWithMetadata, - yaml.representer.SafeRepresenter.represent_dict) - SafeDumper.add_representer(SortedDictWithMetadata, - yaml.representer.SafeRepresenter.represent_dict) - SafeDumper.add_representer(types.GeneratorType, - yaml.representer.SafeRepresenter.represent_list) + SafeDumper.add_representer( + decimal.Decimal, + SafeDumper.represent_decimal + ) + SafeDumper.add_representer( + SortedDict, + yaml.representer.SafeRepresenter.represent_dict + ) + SafeDumper.add_representer( + DictWithMetadata, + yaml.representer.SafeRepresenter.represent_dict + ) + SafeDumper.add_representer( + SortedDictWithMetadata, + yaml.representer.SafeRepresenter.represent_dict + ) + SafeDumper.add_representer( + types.GeneratorType, + yaml.representer.SafeRepresenter.represent_list + ) diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py index 4b59ba840..6d53aed11 100644 --- a/rest_framework/utils/formatting.py +++ b/rest_framework/utils/formatting.py @@ -6,8 +6,6 @@ from __future__ import unicode_literals from django.utils.html import escape from django.utils.safestring import mark_safe from rest_framework.compat import apply_markdown -from rest_framework.settings import api_settings -from textwrap import dedent import re @@ -40,6 +38,7 @@ def dedent(content): return content.strip() + def camelcase_to_spaces(content): """ Translate 'CamelCaseNames' to 'Camel Case Names'. @@ -49,6 +48,7 @@ def camelcase_to_spaces(content): content = re.sub(camelcase_boundry, ' \\1', content).strip() return ' '.join(content.split('_')).title() + def markup_description(description): """ Apply HTML markup to the given description. diff --git a/rest_framework/utils/mediatypes.py b/rest_framework/utils/mediatypes.py index 92f99efd2..727f9c19e 100644 --- a/rest_framework/utils/mediatypes.py +++ b/rest_framework/utils/mediatypes.py @@ -57,7 +57,7 @@ class _MediaType(object): if key != 'q' and other.params.get(key, None) != self.params.get(key, None): return False - if self.sub_type != '*' and other.sub_type != '*' and other.sub_type != self.sub_type: + if self.sub_type != '*' and other.sub_type != '*' and other.sub_type != self.sub_type: return False if self.main_type != '*' and other.main_type != '*' and other.main_type != self.main_type: diff --git a/rest_framework/views.py b/rest_framework/views.py index a2668f2c0..bca0aaef1 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -31,6 +31,7 @@ def get_view_name(view_cls, suffix=None): return name + def get_view_description(view_cls, html=False): """ Given a view class, return a textual description to represent the view. @@ -119,7 +120,6 @@ class APIView(View): headers['Vary'] = 'Accept' return headers - def http_method_not_allowed(self, request, *args, **kwargs): """ If `request.method` does not correspond to a handler method, diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py index 7eb29f99b..bb5b304ee 100644 --- a/rest_framework/viewsets.py +++ b/rest_framework/viewsets.py @@ -127,11 +127,11 @@ class ReadOnlyModelViewSet(mixins.RetrieveModelMixin, class ModelViewSet(mixins.CreateModelMixin, - mixins.RetrieveModelMixin, - mixins.UpdateModelMixin, - mixins.DestroyModelMixin, - mixins.ListModelMixin, - GenericViewSet): + mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + mixins.ListModelMixin, + GenericViewSet): """ A viewset that provides default `create()`, `retrieve()`, `update()`, `partial_update()`, `destroy()` and `list()` actions. diff --git a/runtests.py b/runtests.py new file mode 100755 index 000000000..4da05ac37 --- /dev/null +++ b/runtests.py @@ -0,0 +1,86 @@ +#! /usr/bin/env python +from __future__ import print_function + +import pytest +import sys +import os +import subprocess + + +PYTEST_ARGS = { + 'default': ['tests'], + 'fast': ['tests', '-q'], +} + +FLAKE8_ARGS = ['rest_framework', 'tests', '--ignore=E501'] + + +sys.path.append(os.path.dirname(__file__)) + +def exit_on_failure(ret, message=None): + if ret: + sys.exit(ret) + +def flake8_main(args): + print('Running flake8 code linting') + ret = subprocess.call(['flake8'] + args) + print('flake8 failed' if ret else 'flake8 passed') + return ret + +def split_class_and_function(string): + class_string, function_string = string.split('.', 1) + return "%s and %s" % (class_string, function_string) + +def is_function(string): + # `True` if it looks like a test function is included in the string. + return string.startswith('test_') or '.test_' in string + +def is_class(string): + # `True` if first character is uppercase - assume it's a class name. + return string[0] == string[0].upper() + + +if __name__ == "__main__": + try: + sys.argv.remove('--nolint') + except ValueError: + run_flake8 = True + else: + run_flake8 = False + + try: + sys.argv.remove('--lintonly') + except ValueError: + run_tests = True + else: + run_tests = False + + try: + sys.argv.remove('--fast') + except ValueError: + style = 'default' + else: + style = 'fast' + run_flake8 = False + + if len(sys.argv) > 1: + pytest_args = sys.argv[1:] + first_arg = pytest_args[0] + if first_arg.startswith('-'): + # `runtests.py [flags]` + pytest_args = ['tests'] + pytest_args + elif is_class(first_arg) and is_function(first_arg): + # `runtests.py TestCase.test_function [flags]` + expression = split_class_and_function(first_arg) + pytest_args = ['tests', '-k', expression] + pytest_args[1:] + elif is_class(first_arg) or is_function(first_arg): + # `runtests.py TestCase [flags]` + # `runtests.py test_function [flags]` + pytest_args = ['tests', '-k', pytest_args[0]] + pytest_args[1:] + else: + pytest_args = PYTEST_ARGS[style] + + if run_tests: + exit_on_failure(pytest.main(pytest_args)) + if run_flake8: + exit_on_failure(flake8_main(FLAKE8_ARGS)) diff --git a/conftest.py b/tests/conftest.py similarity index 100% rename from conftest.py rename to tests/conftest.py diff --git a/tests/serializers.py b/tests/serializers.py index f2f85b6ea..be7b37722 100644 --- a/tests/serializers.py +++ b/tests/serializers.py @@ -1,5 +1,4 @@ from rest_framework import serializers - from tests.models import NullableForeignKeySource diff --git a/tests/settings.py b/tests/settings.py index de41dc66a..91c9ed09e 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -68,7 +68,6 @@ SECRET_KEY = 'u@x-aj9(hoh#rb-^ymf#g2jx_hp0vj7u5#b@ag1n^seu9e!%cy' TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', -# 'django.template.loaders.eggs.Loader', ) MIDDLEWARE_CLASSES = ( @@ -104,8 +103,8 @@ INSTALLED_APPS = ( # OAuth is optional and won't work if there is no oauth_provider & oauth2 try: - import oauth_provider - import oauth2 + import oauth_provider # NOQA + import oauth2 # NOQA except ImportError: pass else: @@ -114,7 +113,7 @@ else: ) try: - import provider + import provider # NOQA except ImportError: pass else: @@ -125,13 +124,13 @@ else: # guardian is optional try: - import guardian + import guardian # NOQA except ImportError: pass else: ANONYMOUS_USER_ID = -1 AUTHENTICATION_BACKENDS = ( - 'django.contrib.auth.backends.ModelBackend', # default + 'django.contrib.auth.backends.ModelBackend', # default 'guardian.backends.ObjectPermissionBackend', ) INSTALLED_APPS += ( diff --git a/tests/test_authentication.py b/tests/test_authentication.py index f5bfc5e61..9db4f62df 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -45,26 +45,39 @@ class MockView(APIView): return HttpResponse({'a': 1, 'b': 2, 'c': 3}) -urlpatterns = patterns('', +urlpatterns = patterns( + '', (r'^session/$', MockView.as_view(authentication_classes=[SessionAuthentication])), (r'^basic/$', MockView.as_view(authentication_classes=[BasicAuthentication])), (r'^token/$', MockView.as_view(authentication_classes=[TokenAuthentication])), (r'^auth-token/$', 'rest_framework.authtoken.views.obtain_auth_token'), (r'^oauth/$', MockView.as_view(authentication_classes=[OAuthAuthentication])), - (r'^oauth-with-scope/$', MockView.as_view(authentication_classes=[OAuthAuthentication], - permission_classes=[permissions.TokenHasReadWriteScope])) + ( + r'^oauth-with-scope/$', + MockView.as_view( + authentication_classes=[OAuthAuthentication], + permission_classes=[permissions.TokenHasReadWriteScope] + ) + ) ) + class OAuth2AuthenticationDebug(OAuth2Authentication): allow_query_params_token = True if oauth2_provider is not None: - urlpatterns += patterns('', + urlpatterns += patterns( + '', url(r'^oauth2/', include('provider.oauth2.urls', namespace='oauth2')), url(r'^oauth2-test/$', MockView.as_view(authentication_classes=[OAuth2Authentication])), url(r'^oauth2-test-debug/$', MockView.as_view(authentication_classes=[OAuth2AuthenticationDebug])), - url(r'^oauth2-with-scope-test/$', MockView.as_view(authentication_classes=[OAuth2Authentication], - permission_classes=[permissions.TokenHasReadWriteScope])), + url( + r'^oauth2-with-scope-test/$', + MockView.as_view( + authentication_classes=[OAuth2Authentication], + permission_classes=[permissions.TokenHasReadWriteScope] + ) + ) ) @@ -278,12 +291,16 @@ class OAuthTests(TestCase): self.TOKEN_KEY = "token_key" self.TOKEN_SECRET = "token_secret" - self.consumer = Consumer.objects.create(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, - name='example', user=self.user, status=self.consts.ACCEPTED) + self.consumer = Consumer.objects.create( + key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, + name='example', user=self.user, status=self.consts.ACCEPTED + ) self.scope = Scope.objects.create(name="resource name", url="api/") - self.token = OAuthToken.objects.create(user=self.user, consumer=self.consumer, scope=self.scope, - token_type=OAuthToken.ACCESS, key=self.TOKEN_KEY, secret=self.TOKEN_SECRET, is_approved=True + self.token = OAuthToken.objects.create( + user=self.user, consumer=self.consumer, scope=self.scope, + token_type=OAuthToken.ACCESS, key=self.TOKEN_KEY, secret=self.TOKEN_SECRET, + is_approved=True ) def _create_authorization_header(self): @@ -501,24 +518,24 @@ class OAuth2Tests(TestCase): self.REFRESH_TOKEN = "refresh_token" self.oauth2_client = oauth2_provider.oauth2.models.Client.objects.create( - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - redirect_uri='', - client_type=0, - name='example', - user=None, - ) + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + redirect_uri='', + client_type=0, + name='example', + user=None, + ) self.access_token = oauth2_provider.oauth2.models.AccessToken.objects.create( - token=self.ACCESS_TOKEN, - client=self.oauth2_client, - user=self.user, - ) + token=self.ACCESS_TOKEN, + client=self.oauth2_client, + user=self.user, + ) self.refresh_token = oauth2_provider.oauth2.models.RefreshToken.objects.create( - user=self.user, - access_token=self.access_token, - client=self.oauth2_client - ) + user=self.user, + access_token=self.access_token, + client=self.oauth2_client + ) def _create_authorization_header(self, token=None): return "Bearer {0}".format(token or self.access_token.token) @@ -569,8 +586,10 @@ class OAuth2Tests(TestCase): @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') def test_post_form_passing_auth_url_transport(self): """Ensure GETing form over OAuth with correct client credentials in form data succeed""" - response = self.csrf_client.post('/oauth2-test/', - data={'access_token': self.access_token.token}) + response = self.csrf_client.post( + '/oauth2-test/', + data={'access_token': self.access_token.token} + ) self.assertEqual(response.status_code, 200) @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') diff --git a/tests/test_breadcrumbs.py b/tests/test_breadcrumbs.py index f26c3eafe..780fd5c4d 100644 --- a/tests/test_breadcrumbs.py +++ b/tests/test_breadcrumbs.py @@ -24,7 +24,8 @@ class NestedResourceRoot(APIView): class NestedResourceInstance(APIView): pass -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^$', Root.as_view()), url(r'^resource/$', ResourceRoot.as_view()), url(r'^resource/(?P[0-9]+)$', ResourceInstance.as_view()), @@ -40,34 +41,60 @@ class BreadcrumbTests(TestCase): def test_root_breadcrumbs(self): url = '/' - self.assertEqual(get_breadcrumbs(url), [('Root', '/')]) + self.assertEqual( + get_breadcrumbs(url), + [('Root', '/')] + ) def test_resource_root_breadcrumbs(self): url = '/resource/' - self.assertEqual(get_breadcrumbs(url), [('Root', '/'), - ('Resource Root', '/resource/')]) + self.assertEqual( + get_breadcrumbs(url), + [ + ('Root', '/'), + ('Resource Root', '/resource/') + ] + ) def test_resource_instance_breadcrumbs(self): url = '/resource/123' - self.assertEqual(get_breadcrumbs(url), [('Root', '/'), - ('Resource Root', '/resource/'), - ('Resource Instance', '/resource/123')]) + self.assertEqual( + get_breadcrumbs(url), + [ + ('Root', '/'), + ('Resource Root', '/resource/'), + ('Resource Instance', '/resource/123') + ] + ) def test_nested_resource_breadcrumbs(self): url = '/resource/123/' - self.assertEqual(get_breadcrumbs(url), [('Root', '/'), - ('Resource Root', '/resource/'), - ('Resource Instance', '/resource/123'), - ('Nested Resource Root', '/resource/123/')]) + self.assertEqual( + get_breadcrumbs(url), + [ + ('Root', '/'), + ('Resource Root', '/resource/'), + ('Resource Instance', '/resource/123'), + ('Nested Resource Root', '/resource/123/') + ] + ) def test_nested_resource_instance_breadcrumbs(self): url = '/resource/123/abc' - self.assertEqual(get_breadcrumbs(url), [('Root', '/'), - ('Resource Root', '/resource/'), - ('Resource Instance', '/resource/123'), - ('Nested Resource Root', '/resource/123/'), - ('Nested Resource Instance', '/resource/123/abc')]) + self.assertEqual( + get_breadcrumbs(url), + [ + ('Root', '/'), + ('Resource Root', '/resource/'), + ('Resource Instance', '/resource/123'), + ('Nested Resource Root', '/resource/123/'), + ('Nested Resource Instance', '/resource/123/abc') + ] + ) def test_broken_url_breadcrumbs_handled_gracefully(self): url = '/foobar' - self.assertEqual(get_breadcrumbs(url), [('Root', '/')]) + self.assertEqual( + get_breadcrumbs(url), + [('Root', '/')] + ) diff --git a/tests/test_fields.py b/tests/test_fields.py index 97ef016fa..094ac1eb0 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -648,7 +648,7 @@ class DecimalFieldTest(TestCase): s = DecimalSerializer(data={'decimal_field': '123'}) self.assertFalse(s.is_valid()) - self.assertEqual(s.errors, {'decimal_field': ['Ensure this value is less than or equal to 100.']}) + self.assertEqual(s.errors, {'decimal_field': ['Ensure this value is less than or equal to 100.']}) def test_raise_min_value(self): """ @@ -660,7 +660,7 @@ class DecimalFieldTest(TestCase): s = DecimalSerializer(data={'decimal_field': '99'}) self.assertFalse(s.is_valid()) - self.assertEqual(s.errors, {'decimal_field': ['Ensure this value is greater than or equal to 100.']}) + self.assertEqual(s.errors, {'decimal_field': ['Ensure this value is greater than or equal to 100.']}) def test_raise_max_digits(self): """ @@ -672,7 +672,7 @@ class DecimalFieldTest(TestCase): s = DecimalSerializer(data={'decimal_field': '123.456'}) self.assertFalse(s.is_valid()) - self.assertEqual(s.errors, {'decimal_field': ['Ensure that there are no more than 5 digits in total.']}) + self.assertEqual(s.errors, {'decimal_field': ['Ensure that there are no more than 5 digits in total.']}) def test_raise_max_decimal_places(self): """ @@ -684,7 +684,7 @@ class DecimalFieldTest(TestCase): s = DecimalSerializer(data={'decimal_field': '123.4567'}) self.assertFalse(s.is_valid()) - self.assertEqual(s.errors, {'decimal_field': ['Ensure that there are no more than 3 decimal places.']}) + self.assertEqual(s.errors, {'decimal_field': ['Ensure that there are no more than 3 decimal places.']}) def test_raise_max_whole_digits(self): """ @@ -696,7 +696,7 @@ class DecimalFieldTest(TestCase): s = DecimalSerializer(data={'decimal_field': '12345.6'}) self.assertFalse(s.is_valid()) - self.assertEqual(s.errors, {'decimal_field': ['Ensure that there are no more than 4 digits in total.']}) + self.assertEqual(s.errors, {'decimal_field': ['Ensure that there are no more than 4 digits in total.']}) class ChoiceFieldTests(TestCase): @@ -729,7 +729,7 @@ class ChoiceFieldTests(TestCase): def test_invalid_choice_model(self): s = ChoiceFieldModelSerializer(data={'choice': 'wrong_value'}) self.assertFalse(s.is_valid()) - self.assertEqual(s.errors, {'choice': ['Select a valid choice. wrong_value is not one of the available choices.']}) + self.assertEqual(s.errors, {'choice': ['Select a valid choice. wrong_value is not one of the available choices.']}) self.assertEqual(s.data['choice'], '') def test_empty_choice_model(self): @@ -875,7 +875,7 @@ class SlugFieldTests(TestCase): s = SlugFieldSerializer(data={'slug_field': 'a b'}) self.assertEqual(s.is_valid(), False) - self.assertEqual(s.errors, {'slug_field': ["Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]}) + self.assertEqual(s.errors, {'slug_field': ["Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]}) class URLFieldTests(TestCase): diff --git a/tests/test_files.py b/tests/test_files.py index 78f4cf425..af110df91 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -85,11 +85,8 @@ class FileSerializerTests(TestCase): """ Validation should still function when no data dictionary is provided. """ - now = datetime.datetime.now() - file = BytesIO(six.b('stuff')) - file.name = 'stuff.txt' - file.size = len(file.getvalue()) - uploaded_file = UploadedFile(file=file, created=now) - - serializer = UploadedFileSerializer(files={'file': file}) + uploaded_file = BytesIO(six.b('stuff')) + uploaded_file.name = 'stuff.txt' + uploaded_file.size = len(uploaded_file.getvalue()) + serializer = UploadedFileSerializer(files={'file': uploaded_file}) self.assertFalse(serializer.is_valid()) diff --git a/tests/test_filters.py b/tests/test_filters.py index 85840e018..b29760fa4 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -74,7 +74,8 @@ if django_filters: def get_queryset(self): return FilterableItem.objects.all() - urlpatterns = patterns('', + urlpatterns = patterns( + '', url(r'^(?P\d+)/$', FilterClassDetailView.as_view(), name='detail-view'), url(r'^$', FilterClassRootView.as_view(), name='root-view'), url(r'^get-queryset/$', GetQuerysetView.as_view(), @@ -653,8 +654,8 @@ class SensitiveOrderingFilterTests(TestCase): self.assertEqual( response.data, [ - {'id': 1, username_field: 'userA'}, # PassB - {'id': 2, username_field: 'userB'}, # PassC - {'id': 3, username_field: 'userC'}, # PassA + {'id': 1, username_field: 'userA'}, # PassB + {'id': 2, username_field: 'userB'}, # PassC + {'id': 3, username_field: 'userC'}, # PassA ] ) diff --git a/tests/test_genericrelations.py b/tests/test_genericrelations.py index 3a8f3c7f1..95295eaa8 100644 --- a/tests/test_genericrelations.py +++ b/tests/test_genericrelations.py @@ -117,18 +117,18 @@ class TestGenericRelations(TestCase): serializer = TagSerializer(Tag.objects.all(), many=True) expected = [ - { - 'tag': 'django', - 'tagged_item': 'Bookmark: https://www.djangoproject.com/' - }, - { - 'tag': 'python', - 'tagged_item': 'Bookmark: https://www.djangoproject.com/' - }, - { - 'tag': 'reminder', - 'tagged_item': 'Note: Remember the milk' - } + { + 'tag': 'django', + 'tagged_item': 'Bookmark: https://www.djangoproject.com/' + }, + { + 'tag': 'python', + 'tagged_item': 'Bookmark: https://www.djangoproject.com/' + }, + { + 'tag': 'reminder', + 'tagged_item': 'Note: Remember the milk' + } ] self.assertEqual(serializer.data, expected) diff --git a/tests/test_htmlrenderer.py b/tests/test_htmlrenderer.py index 88d11c46b..5a680f99b 100644 --- a/tests/test_htmlrenderer.py +++ b/tests/test_htmlrenderer.py @@ -34,7 +34,8 @@ def not_found(request): raise Http404() -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^$', example), url(r'^permission_denied$', permission_denied), url(r'^not_found$', not_found), diff --git a/tests/test_hyperlinkedserializers.py b/tests/test_hyperlinkedserializers.py index d478ea730..d45485391 100644 --- a/tests/test_hyperlinkedserializers.py +++ b/tests/test_hyperlinkedserializers.py @@ -94,7 +94,8 @@ class OptionalRelationDetail(generics.RetrieveUpdateDestroyAPIView): model_serializer_class = serializers.HyperlinkedModelSerializer -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^basic/$', BasicList.as_view(), name='basicmodel-list'), url(r'^basic/(?P\d+)/$', BasicDetail.as_view(), name='basicmodel-detail'), url(r'^anchor/(?P\d+)/$', AnchorDetail.as_view(), name='anchor-detail'), diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 293146c08..d5b9244d9 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals import datetime from decimal import Decimal -from django.db import models from django.core.paginator import Paginator from django.test import TestCase from django.utils import unittest @@ -12,6 +11,7 @@ from .models import BasicModel, FilterableItem factory = APIRequestFactory() + # Helper function to split arguments out of an url def split_arguments_from_url(url): if '?' not in url: @@ -274,8 +274,8 @@ class TestUnpaginated(TestCase): BasicModel(text=i).save() self.objects = BasicModel.objects self.data = [ - {'id': obj.id, 'text': obj.text} - for obj in self.objects.all() + {'id': obj.id, 'text': obj.text} + for obj in self.objects.all() ] self.view = DefaultPageSizeKwargView.as_view() @@ -302,8 +302,8 @@ class TestCustomPaginateByParam(TestCase): BasicModel(text=i).save() self.objects = BasicModel.objects self.data = [ - {'id': obj.id, 'text': obj.text} - for obj in self.objects.all() + {'id': obj.id, 'text': obj.text} + for obj in self.objects.all() ] self.view = PaginateByParamView.as_view() @@ -483,8 +483,6 @@ class NonIntegerPaginator(object): class TestNonIntegerPagination(TestCase): - - def test_custom_pagination_serializer(self): objects = ['john', 'paul', 'george', 'ringo'] paginator = NonIntegerPaginator(objects, 2) diff --git a/tests/test_permissions.py b/tests/test_permissions.py index a2cb0c362..93f8020f3 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -12,6 +12,7 @@ import base64 factory = APIRequestFactory() + class RootView(generics.ListCreateAPIView): model = BasicModel authentication_classes = [authentication.BasicAuthentication] @@ -101,42 +102,54 @@ class ModelPermissionsIntegrationTests(TestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_options_permitted(self): - request = factory.options('/', - HTTP_AUTHORIZATION=self.permitted_credentials) + request = factory.options( + '/', + HTTP_AUTHORIZATION=self.permitted_credentials + ) response = root_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn('actions', response.data) self.assertEqual(list(response.data['actions'].keys()), ['POST']) - request = factory.options('/1', - HTTP_AUTHORIZATION=self.permitted_credentials) + request = factory.options( + '/1', + HTTP_AUTHORIZATION=self.permitted_credentials + ) response = instance_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn('actions', response.data) self.assertEqual(list(response.data['actions'].keys()), ['PUT']) def test_options_disallowed(self): - request = factory.options('/', - HTTP_AUTHORIZATION=self.disallowed_credentials) + request = factory.options( + '/', + HTTP_AUTHORIZATION=self.disallowed_credentials + ) response = root_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertNotIn('actions', response.data) - request = factory.options('/1', - HTTP_AUTHORIZATION=self.disallowed_credentials) + request = factory.options( + '/1', + HTTP_AUTHORIZATION=self.disallowed_credentials + ) response = instance_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertNotIn('actions', response.data) def test_options_updateonly(self): - request = factory.options('/', - HTTP_AUTHORIZATION=self.updateonly_credentials) + request = factory.options( + '/', + HTTP_AUTHORIZATION=self.updateonly_credentials + ) response = root_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertNotIn('actions', response.data) - request = factory.options('/1', - HTTP_AUTHORIZATION=self.updateonly_credentials) + request = factory.options( + '/1', + HTTP_AUTHORIZATION=self.updateonly_credentials + ) response = instance_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn('actions', response.data) @@ -153,6 +166,7 @@ class BasicPermModel(models.Model): # add, change, delete built in to django ) + # Custom object-level permission, that includes 'view' permissions class ViewObjectPermissions(permissions.DjangoObjectPermissions): perms_map = { @@ -205,7 +219,7 @@ class ObjectPermissionsIntegrationTests(TestCase): app_label = BasicPermModel._meta.app_label f = '{0}_{1}'.format perms = { - 'view': f('view', model_name), + 'view': f('view', model_name), 'change': f('change', model_name), 'delete': f('delete', model_name) } @@ -246,21 +260,27 @@ class ObjectPermissionsIntegrationTests(TestCase): # Update def test_can_update_permissions(self): - request = factory.patch('/1', {'text': 'foobar'}, format='json', - HTTP_AUTHORIZATION=self.credentials['writeonly']) + request = factory.patch( + '/1', {'text': 'foobar'}, format='json', + HTTP_AUTHORIZATION=self.credentials['writeonly'] + ) response = object_permissions_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data.get('text'), 'foobar') def test_cannot_update_permissions(self): - request = factory.patch('/1', {'text': 'foobar'}, format='json', - HTTP_AUTHORIZATION=self.credentials['deleteonly']) + request = factory.patch( + '/1', {'text': 'foobar'}, format='json', + HTTP_AUTHORIZATION=self.credentials['deleteonly'] + ) response = object_permissions_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_cannot_update_permissions_non_existing(self): - request = factory.patch('/999', {'text': 'foobar'}, format='json', - HTTP_AUTHORIZATION=self.credentials['deleteonly']) + request = factory.patch( + '/999', {'text': 'foobar'}, format='json', + HTTP_AUTHORIZATION=self.credentials['deleteonly'] + ) response = object_permissions_view(request, pk='999') self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) diff --git a/tests/test_relations.py b/tests/test_relations.py index cd276d30c..bc1db69fc 100644 --- a/tests/test_relations.py +++ b/tests/test_relations.py @@ -108,19 +108,25 @@ class RelatedFieldSourceTests(TestCase): doesn't exist. """ from tests.models import ManyToManySource + class Meta: model = ManyToManySource + attrs = { 'name': serializers.SlugRelatedField( slug_field='name', source='banzai'), 'Meta': Meta, } - TestSerializer = type(str('TestSerializer'), - (serializers.ModelSerializer,), attrs) + TestSerializer = type( + str('TestSerializer'), + (serializers.ModelSerializer,), + attrs + ) with self.assertRaises(AttributeError): TestSerializer(data={'name': 'foo'}) + @unittest.skipIf(get_version() < '1.6.0', 'Upstream behaviour changed in v1.6') class RelatedFieldChoicesTests(TestCase): """ @@ -141,4 +147,3 @@ class RelatedFieldChoicesTests(TestCase): widget_count = len(field.widget.choices) self.assertEqual(widget_count, choice_count + 1, 'BLANK_CHOICE_DASH option should have been added') - diff --git a/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py index ab1c66646..0c8eb2544 100644 --- a/tests/test_relations_hyperlink.py +++ b/tests/test_relations_hyperlink.py @@ -16,7 +16,8 @@ request = factory.get('/') # Just to ensure we have a request in the serializer def dummy_view(request, pk): pass -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^dummyurl/(?P[0-9]+)/$', dummy_view, name='dummy-url'), url(r'^manytomanysource/(?P[0-9]+)/$', dummy_view, name='manytomanysource-detail'), url(r'^manytomanytarget/(?P[0-9]+)/$', dummy_view, name='manytomanytarget-detail'), @@ -86,9 +87,9 @@ class HyperlinkedManyToManyTests(TestCase): queryset = ManyToManySource.objects.all() serializer = ManyToManySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/manytomanysource/1/', 'name': 'source-1', 'targets': ['http://testserver/manytomanytarget/1/']}, - {'url': 'http://testserver/manytomanysource/2/', 'name': 'source-2', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/']}, - {'url': 'http://testserver/manytomanysource/3/', 'name': 'source-3', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']} + {'url': 'http://testserver/manytomanysource/1/', 'name': 'source-1', 'targets': ['http://testserver/manytomanytarget/1/']}, + {'url': 'http://testserver/manytomanysource/2/', 'name': 'source-2', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/']}, + {'url': 'http://testserver/manytomanysource/3/', 'name': 'source-3', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']} ] self.assertEqual(serializer.data, expected) @@ -114,9 +115,9 @@ class HyperlinkedManyToManyTests(TestCase): queryset = ManyToManySource.objects.all() serializer = ManyToManySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/manytomanysource/1/', 'name': 'source-1', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']}, - {'url': 'http://testserver/manytomanysource/2/', 'name': 'source-2', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/']}, - {'url': 'http://testserver/manytomanysource/3/', 'name': 'source-3', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']} + {'url': 'http://testserver/manytomanysource/1/', 'name': 'source-1', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']}, + {'url': 'http://testserver/manytomanysource/2/', 'name': 'source-2', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/']}, + {'url': 'http://testserver/manytomanysource/3/', 'name': 'source-3', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']} ] self.assertEqual(serializer.data, expected) diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index ff59b250a..c051b0769 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -65,9 +65,9 @@ class PKManyToManyTests(TestCase): queryset = ManyToManySource.objects.all() serializer = ManyToManySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'targets': [1]}, - {'id': 2, 'name': 'source-2', 'targets': [1, 2]}, - {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]} + {'id': 1, 'name': 'source-1', 'targets': [1]}, + {'id': 2, 'name': 'source-2', 'targets': [1, 2]}, + {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]} ] self.assertEqual(serializer.data, expected) @@ -93,9 +93,9 @@ class PKManyToManyTests(TestCase): queryset = ManyToManySource.objects.all() serializer = ManyToManySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'targets': [1, 2, 3]}, - {'id': 2, 'name': 'source-2', 'targets': [1, 2]}, - {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]} + {'id': 1, 'name': 'source-1', 'targets': [1, 2, 3]}, + {'id': 2, 'name': 'source-2', 'targets': [1, 2]}, + {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]} ] self.assertEqual(serializer.data, expected) diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 1d8adfa74..0403cde28 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -76,7 +76,6 @@ class MockGETView(APIView): return Response({'foo': ['bar', 'baz']}) - class MockPOSTView(APIView): def post(self, request, **kwargs): return Response({'foo': request.DATA}) @@ -102,7 +101,8 @@ class HTMLView1(APIView): def get(self, request, **kwargs): return Response('text') -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^.*\.(?P.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])), url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])), url(r'^cache$', MockGETView.as_view()), @@ -312,16 +312,22 @@ class JSONRendererTests(TestCase): class Dict(MutableMapping): def __init__(self): self._dict = dict() + def __getitem__(self, key): return self._dict.__getitem__(key) + def __setitem__(self, key, value): return self._dict.__setitem__(key, value) + def __delitem__(self, key): return self._dict.__delitem__(key) + def __iter__(self): return self._dict.__iter__() + def __len__(self): return self._dict.__len__() + def keys(self): return self._dict.keys() @@ -330,22 +336,24 @@ class JSONRendererTests(TestCase): x[2] = 3 ret = JSONRenderer().render(x) data = json.loads(ret.decode('utf-8')) - self.assertEquals(data, {'key': 'string value', '2': 3}) + self.assertEquals(data, {'key': 'string value', '2': 3}) def test_render_obj_with_getitem(self): class DictLike(object): def __init__(self): self._dict = {} + def set(self, value): self._dict = dict(value) + def __getitem__(self, key): return self._dict[key] - + x = DictLike() x.set({'a': 1, 'b': 'string'}) with self.assertRaises(TypeError): JSONRenderer().render(x) - + def test_without_content_type_args(self): """ Test basic JSON rendering. @@ -394,35 +402,47 @@ class JSONPRendererTests(TestCase): """ Test JSONP rendering with View JSON Renderer. """ - resp = self.client.get('/jsonp/jsonrenderer', - HTTP_ACCEPT='application/javascript') + resp = self.client.get( + '/jsonp/jsonrenderer', + HTTP_ACCEPT='application/javascript' + ) self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp['Content-Type'], 'application/javascript; charset=utf-8') - self.assertEqual(resp.content, - ('callback(%s);' % _flat_repr).encode('ascii')) + self.assertEqual( + resp.content, + ('callback(%s);' % _flat_repr).encode('ascii') + ) def test_without_callback_without_json_renderer(self): """ Test JSONP rendering without View JSON Renderer. """ - resp = self.client.get('/jsonp/nojsonrenderer', - HTTP_ACCEPT='application/javascript') + resp = self.client.get( + '/jsonp/nojsonrenderer', + HTTP_ACCEPT='application/javascript' + ) self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp['Content-Type'], 'application/javascript; charset=utf-8') - self.assertEqual(resp.content, - ('callback(%s);' % _flat_repr).encode('ascii')) + self.assertEqual( + resp.content, + ('callback(%s);' % _flat_repr).encode('ascii') + ) def test_with_callback(self): """ Test JSONP rendering with callback function name. """ callback_func = 'myjsonpcallback' - resp = self.client.get('/jsonp/nojsonrenderer?callback=' + callback_func, - HTTP_ACCEPT='application/javascript') + resp = self.client.get( + '/jsonp/nojsonrenderer?callback=' + callback_func, + HTTP_ACCEPT='application/javascript' + ) self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp['Content-Type'], 'application/javascript; charset=utf-8') - self.assertEqual(resp.content, - ('%s(%s);' % (callback_func, _flat_repr)).encode('ascii')) + self.assertEqual( + resp.content, + ('%s(%s);' % (callback_func, _flat_repr)).encode('ascii') + ) if yaml: @@ -467,7 +487,6 @@ if yaml: def assertYAMLContains(self, content, string): self.assertTrue(string in content, '%r not in %r' % (string, content)) - class UnicodeYAMLRendererTests(TestCase): """ Tests specific for the Unicode YAML Renderer @@ -592,13 +611,13 @@ class CacheRenderTest(TestCase): """ Return any errors that would be raised if `obj' is pickled Courtesy of koffie @ http://stackoverflow.com/a/7218986/109897 """ - if seen == None: + if seen is None: seen = [] try: state = obj.__getstate__() except AttributeError: return - if state == None: + if state is None: return if isinstance(state, tuple): if not isinstance(state[0], dict): diff --git a/tests/test_request.py b/tests/test_request.py index 0cde0fb40..8b048b5c2 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -272,7 +272,8 @@ class MockView(APIView): return Response(status=status.INTERNAL_SERVER_ERROR) -urlpatterns = patterns('', +urlpatterns = patterns( + '', (r'^$', MockView.as_view()), ) diff --git a/tests/test_response.py b/tests/test_response.py index 0551f4a80..c28f186e0 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -100,7 +100,8 @@ new_model_viewset_router = routers.DefaultRouter() new_model_viewset_router.register(r'', HTMLNewModelViewSet) -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^setbyview$', MockViewSettingContentType.as_view(renderer_classes=[RendererA, RendererB, RendererC])), url(r'^.*\.(?P.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB, RendererC])), url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB, RendererC])), diff --git a/tests/test_reverse.py b/tests/test_reverse.py index 0d3fddf07..675a9d5a0 100644 --- a/tests/test_reverse.py +++ b/tests/test_reverse.py @@ -10,7 +10,8 @@ factory = APIRequestFactory() def null_view(request): pass -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^view$', null_view, name='view'), ) diff --git a/tests/test_routers.py b/tests/test_routers.py index 381569bde..b076f134e 100644 --- a/tests/test_routers.py +++ b/tests/test_routers.py @@ -93,7 +93,8 @@ class TestCustomLookupFields(TestCase): from tests import test_routers urls = getattr(test_routers, 'urlpatterns') - urls += patterns('', + urls += patterns( + '', url(r'^', include(self.router.urls)), ) @@ -104,7 +105,8 @@ class TestCustomLookupFields(TestCase): def test_retrieve_lookup_field_list_view(self): response = self.client.get('/notes/') - self.assertEqual(response.data, + self.assertEqual( + response.data, [{ "url": "http://testserver/notes/123/", "uuid": "123", "text": "foo bar" @@ -113,7 +115,8 @@ class TestCustomLookupFields(TestCase): def test_retrieve_lookup_field_detail_view(self): response = self.client.get('/notes/123/') - self.assertEqual(response.data, + self.assertEqual( + response.data, { "url": "http://testserver/notes/123/", "uuid": "123", "text": "foo bar" diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 7d57fcf01..d27bdcf16 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -7,10 +7,12 @@ from django.utils import unittest from django.utils.datastructures import MultiValueDict from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers, fields, relations -from tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel, - BlankFieldModel, BlogPost, BlogPostComment, Book, CallableDefaultValueModel, DefaultValueModel, - ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, RESTFrameworkModel, - ForeignKeySource, ManyToManySource) +from tests.models import ( + HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel, + BlankFieldModel, BlogPost, BlogPostComment, Book, CallableDefaultValueModel, + DefaultValueModel, ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, + RESTFrameworkModel, ForeignKeySource +) from tests.models import BasicModelSerializer import datetime import pickle @@ -99,6 +101,7 @@ class ActionItemSerializer(serializers.ModelSerializer): class Meta: model = ActionItem + class ActionItemSerializerOptionalFields(serializers.ModelSerializer): """ Intended to test that fields with `required=False` are excluded from validation. @@ -109,6 +112,7 @@ class ActionItemSerializerOptionalFields(serializers.ModelSerializer): model = ActionItem fields = ('title',) + class ActionItemSerializerCustomRestore(serializers.ModelSerializer): class Meta: @@ -295,8 +299,10 @@ class BasicTests(TestCase): in the Meta data """ serializer = PersonSerializer(self.person) - self.assertEqual(set(serializer.data.keys()), - set(['name', 'age', 'info'])) + self.assertEqual( + set(serializer.data.keys()), + set(['name', 'age', 'info']) + ) def test_field_with_dictionary(self): """ @@ -331,9 +337,9 @@ class BasicTests(TestCase): — id field is not populated if `data` is accessed prior to `save()` """ serializer = ActionItemSerializer(self.actionitem) - self.assertIsNone(serializer.data.get('id',None), 'New instance. `id` should not be set.') + self.assertIsNone(serializer.data.get('id', None), 'New instance. `id` should not be set.') serializer.save() - self.assertIsNotNone(serializer.data.get('id',None), 'Model is saved. `id` should be set.') + self.assertIsNotNone(serializer.data.get('id', None), 'Model is saved. `id` should be set.') def test_fields_marked_as_not_required_are_excluded_from_validation(self): """ @@ -660,10 +666,10 @@ class ModelValidationTests(TestCase): serializer.save() second_serializer = AlbumsSerializer(data={'title': 'a'}) self.assertFalse(second_serializer.is_valid()) - self.assertEqual(second_serializer.errors, {'title': ['Album with this Title already exists.'],}) + self.assertEqual(second_serializer.errors, {'title': ['Album with this Title already exists.']}) third_serializer = AlbumsSerializer(data=[{'title': 'b', 'ref': '1'}, {'title': 'c'}], many=True) self.assertFalse(third_serializer.is_valid()) - self.assertEqual(third_serializer.errors, [{'ref': ['Album with this Ref already exists.']}, {}]) + self.assertEqual(third_serializer.errors, [{'ref': ['Album with this Ref already exists.']}, {}]) def test_foreign_key_is_null_with_partial(self): """ @@ -959,7 +965,7 @@ class WritableFieldDefaultValueTests(TestCase): self.assertEqual(got, self.expected) def test_get_default_value_with_callable(self): - field = self.create_field(default=lambda : self.expected) + field = self.create_field(default=lambda: self.expected) got = field.get_default_value() self.assertEqual(got, self.expected) @@ -974,7 +980,7 @@ class WritableFieldDefaultValueTests(TestCase): self.assertIsNone(got) def test_get_default_value_returns_non_True_values(self): - values = [None, '', False, 0, [], (), {}] # values that assumed as 'False' in the 'if' clause + values = [None, '', False, 0, [], (), {}] # values that assumed as 'False' in the 'if' clause for expected in values: field = self.create_field(default=expected) got = field.get_default_value() diff --git a/tests/test_serializer_bulk_update.py b/tests/test_serializer_bulk_update.py index 8b0ded1a8..67a8ed0dc 100644 --- a/tests/test_serializer_bulk_update.py +++ b/tests/test_serializer_bulk_update.py @@ -83,9 +83,9 @@ class BulkCreateSerializerTests(TestCase): self.assertEqual(serializer.is_valid(), False) expected_errors = [ - {'non_field_errors': ['Invalid data']}, - {'non_field_errors': ['Invalid data']}, - {'non_field_errors': ['Invalid data']} + {'non_field_errors': ['Invalid data']}, + {'non_field_errors': ['Invalid data']}, + {'non_field_errors': ['Invalid data']} ] self.assertEqual(serializer.errors, expected_errors) diff --git a/tests/test_serializer_nested.py b/tests/test_serializer_nested.py index 6d69ffbd0..c09c24db2 100644 --- a/tests/test_serializer_nested.py +++ b/tests/test_serializer_nested.py @@ -328,12 +328,14 @@ class NestedModelSerializerUpdateTests(TestCase): class BlogPostSerializer(serializers.ModelSerializer): comments = BlogPostCommentSerializer(many=True, source='blogpostcomment_set') + class Meta: model = models.BlogPost fields = ('id', 'title', 'comments') class PersonSerializer(serializers.ModelSerializer): posts = BlogPostSerializer(many=True, source='blogpost_set') + class Meta: model = models.Person fields = ('id', 'name', 'age', 'posts') diff --git a/tests/test_serializers.py b/tests/test_serializers.py index 2e276f15c..09de9f4c1 100644 --- a/tests/test_serializers.py +++ b/tests/test_serializers.py @@ -1,9 +1,7 @@ -from django.db import models from django.test import TestCase - +from rest_framework.compat import six from rest_framework.serializers import _resolve_model from tests.models import BasicModel -from rest_framework.compat import six class ResolveModelTests(TestCase): diff --git a/tests/test_status.py b/tests/test_status.py index 7b1bdae31..721a6e30b 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -30,4 +30,4 @@ class TestStatus(TestCase): self.assertFalse(is_server_error(499)) self.assertTrue(is_server_error(500)) self.assertTrue(is_server_error(599)) - self.assertFalse(is_server_error(600)) \ No newline at end of file + self.assertFalse(is_server_error(600)) diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py index d4da0c23b..b96bc0caf 100644 --- a/tests/test_templatetags.py +++ b/tests/test_templatetags.py @@ -48,4 +48,4 @@ class Issue1386Tests(TestCase): self.assertEqual(i, res) # example from issue #1386, this shouldn't raise an exception - _ = urlize_quoted_links("asdf:[/p]zxcv.com") + urlize_quoted_links("asdf:[/p]zxcv.com") diff --git a/tests/test_testing.py b/tests/test_testing.py index 1b126e002..9c472026a 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -28,7 +28,8 @@ def session_view(request): }) -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^view/$', view), url(r'^session-view/$', session_view), ) @@ -142,7 +143,8 @@ class TestAPIRequestFactory(TestCase): assertion error. """ factory = APIRequestFactory() - self.assertRaises(AssertionError, factory.post, + self.assertRaises( + AssertionError, factory.post, path='/view/', data={'example': 1}, format='xml' ) diff --git a/tests/test_throttling.py b/tests/test_throttling.py index 8c5eefe9c..b0cb2fe73 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -27,7 +27,7 @@ class NonTimeThrottle(BaseThrottle): if not hasattr(self.__class__, 'called'): self.__class__.called = True return True - return False + return False class MockView(APIView): @@ -125,36 +125,42 @@ class ThrottlingTests(TestCase): """ Ensure for second based throttles. """ - self.ensure_response_header_contains_proper_throttle_field(MockView, - ((0, None), - (0, None), - (0, None), - (0, '1') - )) + self.ensure_response_header_contains_proper_throttle_field( + MockView, ( + (0, None), + (0, None), + (0, None), + (0, '1') + ) + ) def test_minutes_fields(self): """ Ensure for minute based throttles. """ - self.ensure_response_header_contains_proper_throttle_field(MockView_MinuteThrottling, - ((0, None), - (0, None), - (0, None), - (0, '60') - )) + self.ensure_response_header_contains_proper_throttle_field( + MockView_MinuteThrottling, ( + (0, None), + (0, None), + (0, None), + (0, '60') + ) + ) def test_next_rate_remains_constant_if_followed(self): """ If a client follows the recommended next request rate, the throttling rate should stay constant. """ - self.ensure_response_header_contains_proper_throttle_field(MockView_MinuteThrottling, - ((0, None), - (20, None), - (40, None), - (60, None), - (80, None) - )) + self.ensure_response_header_contains_proper_throttle_field( + MockView_MinuteThrottling, ( + (0, None), + (20, None), + (40, None), + (60, None), + (80, None) + ) + ) def test_non_time_throttle(self): """ @@ -170,7 +176,7 @@ class ThrottlingTests(TestCase): self.assertTrue(MockView_NonTimeThrottling.throttle_classes[0].called) response = MockView_NonTimeThrottling.as_view()(request) - self.assertFalse('X-Throttle-Wait-Seconds' in response) + self.assertFalse('X-Throttle-Wait-Seconds' in response) class ScopedRateThrottleTests(TestCase): diff --git a/tests/test_urlizer.py b/tests/test_urlizer.py index 3dc8e8fe5..a77aa22ab 100644 --- a/tests/test_urlizer.py +++ b/tests/test_urlizer.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals from django.test import TestCase from rest_framework.templatetags.rest_framework import urlize_quoted_links -import sys class URLizerTests(TestCase): diff --git a/tox.ini b/tox.ini index 484053a69..200dad38d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,19 @@ [tox] downloadcache = {toxworkdir}/cache/ envlist = + flake8, py3.4-django1.7,py3.3-django1.7,py3.2-django1.7,py2.7-django1.7, py3.4-django1.6,py3.3-django1.6,py3.2-django1.6,py2.7-django1.6,py2.6-django1.6, py3.4-django1.5,py3.3-django1.5,py3.2-django1.5,py2.7-django1.5,py2.6-django1.5, - py2.7-django1.4,py2.6-django1.4, + py2.7-django1.4,py2.6-django1.4 [testenv] -commands = py.test -q +commands = ./runtests.py --fast + +[testenv:flake8] +basepython = python2.7 +deps = pytest==2.5.2 +commands = ./runtests.py --lintonly [testenv:py3.4-django1.7] basepython = python3.4 From 19f31340627c949ca07a9e7b59299734fd991f75 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 19 Aug 2014 13:42:01 +0100 Subject: [PATCH 216/225] Add flake8 to requirements --- .travis.yml | 1 + requirements-test.txt | 1 + tox.ini | 1 + 3 files changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9894ee4ec..ececf3e9d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ install: - pip install Pillow==2.3.0 - pip install django-guardian==1.2.3 - pip install pytest-django==2.6.1 + - pip install flake8==2.2.2 - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install oauth2==1.5.211; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth-plus==2.2.4; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth2-provider==0.2.4; fi" diff --git a/requirements-test.txt b/requirements-test.txt index a91dd0d4b..411daeba2 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -2,6 +2,7 @@ pytest-django==2.6 pytest==2.5.2 pytest-cov==1.6 +flake8==2.2.2 # Optional packages markdown>=2.1.0 diff --git a/tox.ini b/tox.ini index 200dad38d..6f588de12 100644 --- a/tox.ini +++ b/tox.ini @@ -13,6 +13,7 @@ commands = ./runtests.py --fast [testenv:flake8] basepython = python2.7 deps = pytest==2.5.2 + flake8==2.2.2 commands = ./runtests.py --lintonly [testenv:py3.4-django1.7] From d2795dd26d7483ea0de119ae135eab0a94cf23d8 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 19 Aug 2014 13:54:52 +0100 Subject: [PATCH 217/225] Resolve linting issues --- rest_framework/fields.py | 10 ++++--- rest_framework/generics.py | 26 +++++++------------ rest_framework/relations.py | 16 +++++------- rest_framework/renderers.py | 2 +- rest_framework/request.py | 2 +- rest_framework/templatetags/rest_framework.py | 2 +- tests/conftest.py | 10 +++---- tests/test_pagination.py | 8 +++--- tests/test_serializer.py | 12 ++++----- tests/test_templatetags.py | 2 +- 10 files changed, 42 insertions(+), 48 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 43a74ae6e..85fcbd965 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -63,8 +63,10 @@ def get_component(obj, attr_name): def readable_datetime_formats(formats): - format = ', '.join(formats).replace(ISO_8601, - 'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]') + format = ', '.join(formats).replace( + ISO_8601, + 'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]' + ) return humanize_strptime(format) @@ -425,7 +427,7 @@ class ModelField(WritableField): } -##### Typed Fields ##### +# Typed Fields class BooleanField(WritableField): type_name = 'BooleanField' @@ -484,7 +486,7 @@ class URLField(CharField): type_label = 'url' def __init__(self, **kwargs): - if not 'validators' in kwargs: + if 'validators' not in kwargs: kwargs['validators'] = [validators.URLValidator()] super(URLField, self).__init__(**kwargs) diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 02d243654..77deb8e4f 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -210,9 +210,8 @@ class GenericAPIView(views.APIView): return filter_backends - ######################## - ### The following methods provide default implementations - ### that you may want to override for more complex cases. + # The following methods provide default implementations + # that you may want to override for more complex cases. def get_paginate_by(self, queryset=None): """ @@ -340,12 +339,11 @@ class GenericAPIView(views.APIView): return obj - ######################## - ### The following are placeholder methods, - ### and are intended to be overridden. - ### - ### The are not called by GenericAPIView directly, - ### but are used by the mixin methods. + # The following are placeholder methods, + # and are intended to be overridden. + # + # The are not called by GenericAPIView directly, + # but are used by the mixin methods. def pre_save(self, obj): """ @@ -417,10 +415,8 @@ class GenericAPIView(views.APIView): return ret -########################################################## -### Concrete view classes that provide method handlers ### -### by composing the mixin classes with the base view. ### -########################################################## +# Concrete view classes that provide method handlers +# by composing the mixin classes with the base view. class CreateAPIView(mixins.CreateModelMixin, GenericAPIView): @@ -535,9 +531,7 @@ class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, return self.destroy(request, *args, **kwargs) -########################## -### Deprecated classes ### -########################## +# Deprecated classes class MultipleObjectAPIView(GenericAPIView): def __init__(self, *args, **kwargs): diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 3b234dd58..1acbdce26 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -19,8 +19,7 @@ from rest_framework.compat import smart_text import warnings -##### Relational fields ##### - +# Relational fields # Not actually Writable, but subclasses may need to be. class RelatedField(WritableField): @@ -66,7 +65,7 @@ class RelatedField(WritableField): else: # Reverse self.queryset = manager.field.rel.to._default_manager.all() - ### We need this stuff to make form choices work... + # We need this stuff to make form choices work... def prepare_value(self, obj): return self.to_native(obj) @@ -113,7 +112,7 @@ class RelatedField(WritableField): choices = property(_get_choices, _set_choices) - ### Default value handling + # Default value handling def get_default_value(self): default = super(RelatedField, self).get_default_value() @@ -121,7 +120,7 @@ class RelatedField(WritableField): return [] return default - ### Regular serializer stuff... + # Regular serializer stuff... def field_to_native(self, obj, field_name): try: @@ -181,7 +180,7 @@ class RelatedField(WritableField): into[(self.source or field_name)] = self.from_native(value) -### PrimaryKey relationships +# PrimaryKey relationships class PrimaryKeyRelatedField(RelatedField): """ @@ -269,8 +268,7 @@ class PrimaryKeyRelatedField(RelatedField): return self.to_native(pk) -### Slug relationships - +# Slug relationships class SlugRelatedField(RelatedField): """ @@ -305,7 +303,7 @@ class SlugRelatedField(RelatedField): raise ValidationError(msg) -### Hyperlinked relationships +# Hyperlinked relationships class HyperlinkedRelatedField(RelatedField): """ diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 3dabd277e..ac7175a7b 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -414,7 +414,7 @@ class BrowsableAPIRenderer(BaseRenderer): """ Returns True if a form should be shown for this method. """ - if not method in view.allowed_methods: + if method not in view.allowed_methods: return # Not a valid method if not api_settings.FORM_METHOD_OVERRIDE: diff --git a/rest_framework/request.py b/rest_framework/request.py index 620b00ada..275326614 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -403,7 +403,7 @@ class Request(object): self._not_authenticated() raise - if not user_auth_tuple is None: + if user_auth_tuple is not None: self._authenticator = authenticator self._user, self._auth = user_auth_tuple return diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index 5b8fa3853..9110aedb3 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -166,7 +166,7 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru url = smart_urlquote_wrapper(middle) elif simple_url_2_re.match(middle): url = smart_urlquote_wrapper('http://%s' % middle) - elif not ':' in middle and simple_email_re.match(middle): + elif ':' not in middle and simple_email_re.match(middle): local, domain = middle.rsplit('@', 1) try: domain = domain.encode('idna').decode('ascii') diff --git a/tests/conftest.py b/tests/conftest.py index fa5184dd8..f3723aeae 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -47,8 +47,8 @@ def pytest_configure(): ) try: - import oauth_provider - import oauth2 + import oauth_provider # NOQA + import oauth2 # NOQA except ImportError: pass else: @@ -57,7 +57,7 @@ def pytest_configure(): ) try: - import provider + import provider # NOQA except ImportError: pass else: @@ -68,13 +68,13 @@ def pytest_configure(): # guardian is optional try: - import guardian + import guardian # NOQA except ImportError: pass else: settings.ANONYMOUS_USER_ID = -1 settings.AUTHENTICATION_BACKENDS = ( - 'django.contrib.auth.backends.ModelBackend', # default + 'django.contrib.auth.backends.ModelBackend', 'guardian.backends.ObjectPermissionBackend', ) settings.INSTALLED_APPS += ( diff --git a/tests/test_pagination.py b/tests/test_pagination.py index d5b9244d9..80c33e2eb 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -363,11 +363,11 @@ class TestMaxPaginateByParam(TestCase): self.assertEqual(response.data['results'], self.data[:3]) -### Tests for context in pagination serializers +# Tests for context in pagination serializers class CustomField(serializers.Field): def to_native(self, value): - if not 'view' in self.context: + if 'view' not in self.context: raise RuntimeError("context isn't getting passed into custom field") return "value" @@ -377,7 +377,7 @@ class BasicModelSerializer(serializers.Serializer): def __init__(self, *args, **kwargs): super(BasicModelSerializer, self).__init__(*args, **kwargs) - if not 'view' in self.context: + if 'view' not in self.context: raise RuntimeError("context isn't getting passed into serializer init") @@ -398,7 +398,7 @@ class TestContextPassedToCustomField(TestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) -### Tests for custom pagination serializers +# Tests for custom pagination serializers class LinksSerializer(serializers.Serializer): next = pagination.NextPageField(source='*') diff --git a/tests/test_serializer.py b/tests/test_serializer.py index d27bdcf16..90f37cf2e 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -415,7 +415,7 @@ class ValidationTests(TestCase): mistaken for not having a default.""" data = { 'title': 'Some action item', - #No 'done' value. + # No 'done' value. } serializer = ActionItemSerializer(self.actionitem, data=data) self.assertEqual(serializer.is_valid(), True) @@ -1282,7 +1282,7 @@ class BlankFieldTests(TestCase): self.fail('Exception raised on save() after validation passes') -#test for issue #460 +# Test for issue #460 class SerializerPickleTests(TestCase): """ Test pickleability of the output of Serializers @@ -1506,7 +1506,7 @@ class NestedSerializerContextTests(TestCase): callable = serializers.SerializerMethodField('_callable') def _callable(self, instance): - if not 'context_item' in self.context: + if 'context_item' not in self.context: raise RuntimeError("context isn't getting passed into 2nd level nested serializer") return "success" @@ -1519,7 +1519,7 @@ class NestedSerializerContextTests(TestCase): callable = serializers.SerializerMethodField("_callable") def _callable(self, instance): - if not 'context_item' in self.context: + if 'context_item' not in self.context: raise RuntimeError("context isn't getting passed into 1st level nested serializer") return "success" @@ -1822,7 +1822,7 @@ class MetadataSerializerTestCase(TestCase): self.assertEqual(expected, metadata) -### Regression test for #840 +# Regression test for #840 class SimpleModel(models.Model): text = models.CharField(max_length=100) @@ -1856,7 +1856,7 @@ class FieldValidationRemovingAttr(TestCase): self.assertEqual(serializer.object.text, 'foo') -### Regression test for #878 +# Regression test for #878 class SimpleTargetModel(models.Model): text = models.CharField(max_length=100) diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py index b96bc0caf..75ee0eaa8 100644 --- a/tests/test_templatetags.py +++ b/tests/test_templatetags.py @@ -11,7 +11,7 @@ class TemplateTagTests(TestCase): def test_add_query_param_with_non_latin_charactor(self): # Ensure we don't double-escape non-latin characters - # that are present in the querystring. + # that are present in the querystring. # See #1314. request = factory.get("/", {'q': '查询'}) json_url = add_query_param(request, "format", "json") From 2d2737f367c241c29c9c3913f2dba986c7c9a4a5 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 19 Aug 2014 14:11:26 +0100 Subject: [PATCH 218/225] Resolve python3 linting issue --- rest_framework/utils/mediatypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/utils/mediatypes.py b/rest_framework/utils/mediatypes.py index 727f9c19e..87b3cc6a3 100644 --- a/rest_framework/utils/mediatypes.py +++ b/rest_framework/utils/mediatypes.py @@ -79,7 +79,7 @@ class _MediaType(object): return 3 def __str__(self): - return unicode(self).encode('utf-8') + return self.__unicode__().encode('utf-8') def __unicode__(self): ret = "%s/%s" % (self.main_type, self.sub_type) From 06b6b96f933088ba36d4c98e13893274f29bed6a Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Tue, 19 Aug 2014 16:32:30 +0200 Subject: [PATCH 219/225] Remove duplicated model declaration. --- tests/models.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/models.py b/tests/models.py index e378c1cfe..fe064b46a 100644 --- a/tests/models.py +++ b/tests/models.py @@ -184,10 +184,3 @@ class NullableOneToOneSource(RESTFrameworkModel): class BasicModelSerializer(serializers.ModelSerializer): class Meta: model = BasicModel - - -# Models to test filters -class FilterableItem(models.Model): - text = models.CharField(max_length=100) - decimal = models.DecimalField(max_digits=4, decimal_places=2) - date = models.DateField() From 00c0dfc66fd2426a63e6eec498395740b2c3e63b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 19 Aug 2014 16:29:05 +0100 Subject: [PATCH 220/225] Documentation on runtests.py --- docs/topics/contributing.md | 38 +++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/docs/topics/contributing.md b/docs/topics/contributing.md index d33843e19..3400bc8f9 100644 --- a/docs/topics/contributing.md +++ b/docs/topics/contributing.md @@ -62,10 +62,44 @@ To run the tests, clone the repository, and then: virtualenv env source env/bin/activate pip install -r requirements.txt - pip install -r optionals.txt + pip install -r requirements-test.txt # Run the tests - py.test + ./runtests.py + +### Test options + +Run using a more concise output style. + + ./runtests -q + +Run the tests using a more concise output style, no coverage, no flake8. + + ./runtests --fast + +Don't run the flake8 code linting. + + ./runtests --nolint + +Only run the flake8 code linting, don't run the tests. + + ./runtests --lintonly + +Run the tests for a given test case. + + ./runtests MyTestCase + +Run the tests for a given test method. + + ./runtests MyTestCase.test_this_method + +Shorter form to run the tests for a given test method. + + ./runtests test_this_method + +Note: The test case and test method matching is fuzzy and will sometimes run other tests that contain a partial string match to the given command line input. + +### Running against multiple environments You can also use the excellent [tox][tox] testing tool to run the tests against all supported versions of Python and Django. Install `tox` globally, and then simply run: From 63d02dbea855a060ec4cdb194497188e2f40cb66 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 19 Aug 2014 17:06:55 +0100 Subject: [PATCH 221/225] Drop six from compat. 1.4.2 is now the lowest supported version. --- README.md | 2 +- docs/index.md | 2 +- docs/topics/2.4-accouncement.md | 1 - rest_framework/compat.py | 7 +------ rest_framework/decorators.py | 2 +- rest_framework/fields.py | 4 ++-- rest_framework/filters.py | 3 ++- rest_framework/parsers.py | 3 ++- rest_framework/renderers.py | 6 ++---- rest_framework/response.py | 2 +- rest_framework/serializers.py | 2 +- rest_framework/settings.py | 5 +---- rest_framework/templatetags/rest_framework.py | 3 ++- rest_framework/test.py | 3 ++- tests/test_authentication.py | 3 +-- tests/test_files.py | 2 +- tests/test_generics.py | 2 +- tests/test_htmlrenderer.py | 4 ++-- tests/test_relations_pk.py | 2 +- tests/test_renderers.py | 4 ++-- tests/test_request.py | 2 +- tests/test_response.py | 2 +- tests/test_serializers.py | 2 +- tests/utils.py | 2 +- 24 files changed, 31 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 0eaf5c83c..7052ab638 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ There is a live example API for testing purposes, [available here][sandbox]. # Requirements * Python (2.6.5+, 2.7, 3.2, 3.3) -* Django (1.3, 1.4, 1.5, 1.6) +* Django (1.4.2+, 1.5, 1.6, 1.7) # Installation diff --git a/docs/index.md b/docs/index.md index 6abc4f040..83e30a690 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) -* Django (1.3, 1.4, 1.5, 1.6) +* Django (1.4.2+, 1.5, 1.6, 1.7) The following packages are optional: diff --git a/docs/topics/2.4-accouncement.md b/docs/topics/2.4-accouncement.md index 91472b9c4..cdc99bd5e 100644 --- a/docs/topics/2.4-accouncement.md +++ b/docs/topics/2.4-accouncement.md @@ -1,4 +1,3 @@ -* Writable nested serializers. * List/detail routes. * 1.3 Support dropped, install six for <=1.4.?. * `allow_none` for char fields diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 4b16a8ca2..fa0f0bfb1 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -9,14 +9,9 @@ import django import inspect from django.core.exceptions import ImproperlyConfigured from django.conf import settings +from django.utils import six -# Try to import six from Django, fallback to external `six` package. -try: - from django.utils import six -except ImportError: - import six - # Handle django.utils.encoding rename in 1.5 onwards. # smart_unicode -> smart_text # force_unicode -> force_text diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index e06d6ff56..449ba0a29 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -7,7 +7,7 @@ based views, as well as the `@detail_route` and `@list_route` decorators, which used to annotate methods on viewsets that should be included by routers. """ from __future__ import unicode_literals -from rest_framework.compat import six +from django.utils import six from rest_framework.views import APIView import types import warnings diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 85fcbd965..9d707c9b5 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -18,14 +18,14 @@ from django.conf import settings from django.db.models.fields import BLANK_CHOICE_DASH from django.http import QueryDict from django.forms import widgets -from django.utils import timezone +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, six, smart_text, + BytesIO, smart_text, force_text, is_non_str_iterable ) from rest_framework.settings import api_settings diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 28927eec7..e20800130 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -5,7 +5,8 @@ returned by list views. from __future__ import unicode_literals from django.core.exceptions import ImproperlyConfigured from django.db import models -from rest_framework.compat import django_filters, six, guardian, get_model_name +from django.utils import six +from rest_framework.compat import django_filters, guardian, get_model_name from rest_framework.settings import api_settings from functools import reduce import operator diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 4990971b8..aa4fd3f11 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -10,7 +10,8 @@ from django.core.files.uploadhandler import StopFutureHandlers from django.http import QueryDict from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter -from rest_framework.compat import etree, six, yaml, force_text +from django.utils import six +from rest_framework.compat import etree, yaml, force_text from rest_framework.exceptions import ParseError from rest_framework import renderers import json diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index ac7175a7b..748ebac94 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -15,11 +15,9 @@ from django.core.exceptions import ImproperlyConfigured from django.http.multipartparser import parse_header from django.template import RequestContext, loader, Template from django.test.client import encode_multipart +from django.utils import six from django.utils.xmlutils import SimplerXMLGenerator -from rest_framework.compat import StringIO -from rest_framework.compat import six -from rest_framework.compat import smart_text -from rest_framework.compat import yaml +from rest_framework.compat import StringIO, smart_text, yaml from rest_framework.exceptions import ParseError from rest_framework.settings import api_settings from rest_framework.request import is_form_media_type, override_method diff --git a/rest_framework/response.py b/rest_framework/response.py index 80225cac3..0a7d313f4 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -8,7 +8,7 @@ from __future__ import unicode_literals import django from django.core.handlers.wsgi import STATUS_CODE_TEXT from django.template.response import SimpleTemplateResponse -from rest_framework.compat import six +from django.utils import six class Response(SimpleTemplateResponse): diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 95288671c..be8ad3f24 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -20,9 +20,9 @@ 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.core.exceptions import ObjectDoesNotExist -from rest_framework.compat import six from rest_framework.settings import api_settings diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 6806a4689..644751f87 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -18,12 +18,9 @@ REST framework settings, checking for user settings first, then falling back to the defaults. """ from __future__ import unicode_literals - from django.conf import settings -from django.utils import importlib - +from django.utils import importlib, six from rest_framework import ISO_8601 -from rest_framework.compat import six USER_SETTINGS = getattr(settings, 'REST_FRAMEWORK', None) diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index 9110aedb3..b80a7d771 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -2,11 +2,12 @@ from __future__ import unicode_literals, absolute_import from django import template from django.core.urlresolvers import reverse, NoReverseMatch from django.http import QueryDict +from django.utils import six from django.utils.encoding import iri_to_uri from django.utils.html import escape from django.utils.safestring import SafeData, mark_safe -from rest_framework.compat import urlparse, force_text, six from django.utils.html import smart_urlquote +from rest_framework.compat import urlparse, force_text import re register = template.Library() diff --git a/rest_framework/test.py b/rest_framework/test.py index 9242cf7c6..f89a6dcd0 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -8,10 +8,11 @@ from django.conf import settings from django.test.client import Client as DjangoClient from django.test.client import ClientHandler from django.test import testcases +from django.utils import six from django.utils.http import urlencode from rest_framework.settings import api_settings from rest_framework.compat import RequestFactory as DjangoRequestFactory -from rest_framework.compat import force_bytes_or_smart_bytes, six +from rest_framework.compat import force_bytes_or_smart_bytes def force_authenticate(request, user=None, token=None): diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 9db4f62df..2b9d73e4c 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -3,7 +3,7 @@ from django.conf.urls import patterns, url, include from django.contrib.auth.models import User from django.http import HttpResponse from django.test import TestCase -from django.utils import unittest +from django.utils import six, unittest from django.utils.http import urlencode from rest_framework import HTTP_HEADER_ENCODING from rest_framework import exceptions @@ -20,7 +20,6 @@ from rest_framework.authentication import ( OAuth2Authentication ) from rest_framework.authtoken.models import Token -from rest_framework.compat import six from rest_framework.compat import oauth2_provider, oauth2_provider_scope from rest_framework.compat import oauth, oauth_provider from rest_framework.test import APIRequestFactory, APIClient diff --git a/tests/test_files.py b/tests/test_files.py index af110df91..de4f71d1a 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -1,8 +1,8 @@ from __future__ import unicode_literals from django.test import TestCase +from django.utils import six from rest_framework import serializers from rest_framework.compat import BytesIO -from rest_framework.compat import six import datetime diff --git a/tests/test_generics.py b/tests/test_generics.py index 36832aff2..e9f5bebdd 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -2,11 +2,11 @@ from __future__ import unicode_literals from django.db import models from django.shortcuts import get_object_or_404 from django.test import TestCase +from django.utils import six from rest_framework import generics, renderers, serializers, status from rest_framework.test import APIRequestFactory from tests.models import BasicModel, Comment, SlugBasedModel from tests.models import ForeignKeySource, ForeignKeyTarget -from rest_framework.compat import six factory = APIRequestFactory() diff --git a/tests/test_htmlrenderer.py b/tests/test_htmlrenderer.py index 5a680f99b..2edc6b4bd 100644 --- a/tests/test_htmlrenderer.py +++ b/tests/test_htmlrenderer.py @@ -4,12 +4,12 @@ from django.conf.urls import patterns, url from django.http import Http404 from django.test import TestCase from django.template import TemplateDoesNotExist, Template -import django.template.loader +from django.utils import six from rest_framework import status from rest_framework.decorators import api_view, renderer_classes from rest_framework.renderers import TemplateHTMLRenderer from rest_framework.response import Response -from rest_framework.compat import six +import django.template.loader @api_view(('GET',)) diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index c051b0769..e3f836ed6 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -1,12 +1,12 @@ from __future__ import unicode_literals from django.db import models from django.test import TestCase +from django.utils import six from rest_framework import serializers from tests.models import ( BlogPost, ManyToManyTarget, ManyToManySource, ForeignKeyTarget, ForeignKeySource, NullableForeignKeySource, OneToOneTarget, NullableOneToOneSource, ) -from rest_framework.compat import six # ManyToMany diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 0403cde28..91244e261 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -6,10 +6,10 @@ from django.conf.urls import patterns, url, include from django.core.cache import cache from django.db import models from django.test import TestCase -from django.utils import unittest +from django.utils import six, unittest from django.utils.translation import ugettext_lazy as _ from rest_framework import status, permissions -from rest_framework.compat import yaml, etree, six, StringIO +from rest_framework.compat import yaml, etree, StringIO from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \ diff --git a/tests/test_request.py b/tests/test_request.py index 8b048b5c2..8ddaf0a70 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -8,6 +8,7 @@ from django.contrib.auth import authenticate, login, logout from django.contrib.sessions.middleware import SessionMiddleware from django.core.handlers.wsgi import WSGIRequest from django.test import TestCase +from django.utils import six from rest_framework import status from rest_framework.authentication import SessionAuthentication from rest_framework.parsers import ( @@ -21,7 +22,6 @@ from rest_framework.response import Response from rest_framework.settings import api_settings from rest_framework.test import APIRequestFactory, APIClient from rest_framework.views import APIView -from rest_framework.compat import six from io import BytesIO import json diff --git a/tests/test_response.py b/tests/test_response.py index c28f186e0..2eff83d3d 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals from django.conf.urls import patterns, url, include from django.test import TestCase +from django.utils import six from tests.models import BasicModel, BasicModelSerializer from rest_framework.response import Response from rest_framework.views import APIView @@ -14,7 +15,6 @@ from rest_framework.renderers import ( ) from rest_framework import viewsets from rest_framework.settings import api_settings -from rest_framework.compat import six class MockPickleRenderer(BaseRenderer): diff --git a/tests/test_serializers.py b/tests/test_serializers.py index 09de9f4c1..31c417306 100644 --- a/tests/test_serializers.py +++ b/tests/test_serializers.py @@ -1,5 +1,5 @@ from django.test import TestCase -from rest_framework.compat import six +from django.utils import six from rest_framework.serializers import _resolve_model from tests.models import BasicModel diff --git a/tests/utils.py b/tests/utils.py index a8f2eb0b0..28be81bd1 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,5 +1,5 @@ from contextlib import contextmanager -from rest_framework.compat import six +from django.utils import six from rest_framework.settings import api_settings From 0c65e028b604490d498e43083fc3b46da05144fe Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 19 Aug 2014 23:25:12 +0100 Subject: [PATCH 222/225] Release notes --- docs/api-guide/viewsets.md | 2 +- docs/topics/2.4-accouncement.md | 111 +++++++++++++++++++++++++++++++- docs/topics/release-notes.md | 21 ++++-- 3 files changed, 125 insertions(+), 9 deletions(-) diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index b32f5a805..9030e3ee0 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -146,7 +146,7 @@ The decorators can additionally take extra arguments that will be set for the ro def set_password(self, request, pk=None): ... -The `@action` decorator will route `POST` requests by default, but may also accept other HTTP methods, by using the `methods` argument. For example: +Theses decorators will route `GET` requests by default, but may also accept other HTTP methods, by using the `methods` argument. For example: @detail_route(methods=['post', 'delete']) def unset_password(self, request, pk=None): diff --git a/docs/topics/2.4-accouncement.md b/docs/topics/2.4-accouncement.md index cdc99bd5e..709a5c969 100644 --- a/docs/topics/2.4-accouncement.md +++ b/docs/topics/2.4-accouncement.md @@ -1,4 +1,111 @@ -* List/detail routes. -* 1.3 Support dropped, install six for <=1.4.?. +# REST framework 2.4 announcement + +The 2.4 release is largely an intermediate step, tying up some outstanding issues prior to the 3.x series. + +## Version requirements + +Support for Django 1.3 has been dropped. +The lowest supported version of Django is now 1.4.2. + +The current plan is for REST framework to remain in lockstep with [Django's long-term support policy][lts-releases]. + +## Django 1.7 support + +The optional authtoken application now includes support for *both* Django 1.7 schema migrations, *and* for old-style `south` migrations. + +**If you are using authtoken, and you want to continue using `south`, you must upgrade your `south` package to version 1.0.** + +## Updated test runner + +We now have a new test runner for developing against the project,, that uses the excellent [py.test](http://pytest.org) library. + +To use it make sure you have first installed the test requirements. + + pip install -r requirements-test.txt + +Then run the `runtests.py` script. + + ./runtests.py + +The new test runner also includes [flake8](https://flake8.readthedocs.org) code linting, which should help keep our coding style consistent. + +#### Test runner flags + +Run using a more concise output style. + + ./runtests -q + +Run the tests using a more concise output style, no coverage, no flake8. + + ./runtests --fast + +Don't run the flake8 code linting. + + ./runtests --nolint + +Only run the flake8 code linting, don't run the tests. + + ./runtests --lintonly + +Run the tests for a given test case. + + ./runtests MyTestCase + +Run the tests for a given test method. + + ./runtests MyTestCase.test_this_method + +Shorter form to run the tests for a given test method. + + ./runtests test_this_method + +Note: The test case and test method matching is fuzzy and will sometimes run other tests that contain a partial string match to the given command line input. + +## Improved viewset routing + +The `@action` and `@link` decorators were inflexible in that they only allowed additional routes to be added against instance style URLs, not against list style URLs. + +The `@action` and `@link` decorators have now been moved to pending deprecation, and the `@list_route` and `@detail_route` decroators have been introduced. + +Here's an example of using the new decorators. Firstly we have a detail-type route named "set_password" that acts on a single instance, and takes a `pk` argument in the URL. Secondly we have a list-type route named "recent_users" that acts on a queryset, and does not take any arguments in the URL. + + class UserViewSet(viewsets.ModelViewSet): + """ + A viewset that provides the standard actions + """ + queryset = User.objects.all() + serializer_class = UserSerializer + + @detail_route(methods=['post']) + def set_password(self, request, pk=None): + user = self.get_object() + serializer = PasswordSerializer(data=request.DATA) + if serializer.is_valid(): + user.set_password(serializer.data['password']) + user.save() + return Response({'status': 'password set'}) + else: + return Response(serializer.errors, + status=status.HTTP_400_BAD_REQUEST) + + @list_route() + def recent_users(self, request): + recent_users = User.objects.all().order('-last_login') + page = self.paginate_queryset(recent_users) + serializer = self.get_pagination_serializer(page) + return Response(serializer.data) + +For more details, see the [viewsets](../api-guide/viewsets.md) documentation. + +## Other features + +## Deprecations + +## Labels and milestones + +TODO + * `allow_none` for char fields * `trailing_slash = True` --> `[^/]`, `trailing_slash = False` --> `[^/.]`, becomes simply `[^/]` and `lookup_value_regex` is added. + +[lts-releases]: https://docs.djangoproject.com/en/dev/internals/release-process/#long-term-support-lts-releases \ No newline at end of file diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index b0e5b1982..a31be28f1 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -38,26 +38,35 @@ You can determine your currently installed version using `pip freeze`: --- +## 2.4.x series + ### 2.4.0 -* Added compatibility with Django 1.7's native migrations. +**Django version requirements**: The lowest supported version of Django is now 1.4.2. - **IMPORTANT**: In order to continue to use South with Django <1.7 you **must** upgrade to - South v1.0. +**South version requirements**: This note applies to any users using the optional `authtoken` application, which includes an associated database migration. You must now *either* upgrade your `south` package to version 1.0, *or* instead use the built-in migration support available with Django 1.7. -* Use py.test +* Added compatibility with Django 1.7's database migration support. +* New test runner, using `py.test`. * `@detail_route` and `@list_route` decorators replace `@action` and `@link`. -* `six` no longer bundled. For Django <= 1.4.1, install `six` package. * Support customizable view name and description functions, using the `VIEW_NAME_FUNCTION` and `VIEW_DESCRIPTION_FUNCTION` settings. * Added `NUM_PROXIES` setting for smarter client IP identification. * Added `MAX_PAGINATE_BY` setting and `max_paginate_by` generic view attribute. * Added `cache` attribute to throttles to allow overriding of default cache. +* Added `lookup_value_regex` attribute to routers, to allow the URL argument matching to be constrainted by the user. +* Added `allow_none` option to `CharField`. +* Support Django's standard `status_code` class attribute on responses. +* More intuitive behavior on the test client, as `client.logout()` now also removes any credentials that have been set. * Bugfix: `?page_size=0` query parameter now falls back to default page size for view, instead of always turning pagination off. +* Bugfix: Always uppercase `X-Http-Method-Override` methods. +* Bugfix: Copy `filter_backends` list before returning it, in order to prevent view code from mutating the class attribute itself. +* Bugfix: Set the `.action` attribute on viewsets when introspected by `OPTIONS` for testing permissions on the view. +* Bugfix: Ensure `ValueError` raised during deserialization results in a error list rather than a single error. This is now consistent with other validation errors. +--- ## 2.3.x series - ### 2.3.14 **Date**: 12th June 2014 From 874d2be83c612fb5e04aa6a28901c2afe4bf9d3b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 20 Aug 2014 00:19:03 +0100 Subject: [PATCH 223/225] Release notes --- docs/img/labels-and-milestones.png | Bin 0 -> 84026 bytes docs/topics/2.4-accouncement.md | 48 +++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 docs/img/labels-and-milestones.png diff --git a/docs/img/labels-and-milestones.png b/docs/img/labels-and-milestones.png new file mode 100644 index 0000000000000000000000000000000000000000..e7c829adcc8ca5b0f0b39c5830aad0fc81c4e0f2 GIT binary patch literal 84026 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⋙dIlJ^7*83kQ!uJ6J1jMQyjwvVISU~-m1SyC+dL+I8mJXph%=#>~qIXJwP<)owGcH4o7 zmGwMcf|%#*W%u)!v?=jX<;KpPIRN0G2x+hHoeN`z{30VAmRo>)D8?}{k zm@G+VDX9OcxKxDNgU)j1_>?Wx+6|PMb2JoKhiU3hmyr{?&9ri!`04k-!JPdL2A1>z^d|)j!_$A z_=>MCVQorCx_NsfDbzNx4yMvb3$c`DuJDv95Kys=ieW8$nzEo{JPSlyH}(BE1m72I zz>U35cUME;+th0b(RXa-qiL_7X1|5=k%zNh z2lmw$|H$+ui!ipjOlQLr{WgX@Z8(;|)n2?DR95}Al8q#4=1zJ-Y!2u9n7}`PJ51w; zdt7cCUBS9K(=fyL6@>-srAolVj~pw4pA|k+tbFw{F&Q5leVQIZwH_5a8~7u9Poy>@@g=w7aRs z>CrbBwdw_|-)qNP-`umZ-4maoojV56fzr%j0aaA1DUogo^1Jrpg7Pg_Fqs6SGZw_1 z+3i3{Kdo3dcMUA3#pve6|O1D>{3&2PM2RehW1YDsOv z2)Eq7swInN73F`RetDj5lDoajOk*?bU6kfFPxv?JF8u!ux+7WLX!#Sv_sv?d;H@Y@ zOy=xl@b=KKsHO)krnp((z(5CCv$w~sQqf?RdQqOWaE5|R**>)t;lbMOz54lLi|_YV z1kU>Zq21};{)cw=OZ@$pc4tC(uSm*x81)zJ?s!fOh*a?47x$=FyaFK)uT5b?JC4O!g#&(~f z25cP(_6Mv3KIV~>b^p3&;E){s6)An|i&L_%sjG0=wfKh@CH6g~pS=e&&?bxV=v*=T z$2wj?P99xj?NFSq6y6BiH}1Qk+WzCSyCGqmck6EUnsoY|qg3z$DJMZh14La1^ zJA>0Hk7gTy4p%=4b~5E>+y^-5zn_57iYD?d@VDV)2d!?oE~Uy#^@g=(9|rX$5) zuk6*gWqp12>R;qLPiVNsx%Pu-3pOxY4ZMDwDk%eT{f6I_jXWs*-|#!1z3?2Kp>m(X z$EUE7u7U43^OE-0*LKr`MHO~iD^n9;`jlz#7hWnw{I+21oz8f{*ZTy@iwA!9w5)?4 zvci&Wt)t<6!wE5(ZVZs{98uRppzC6?!hmW0u=|V|aK{PqmzL>{r)%vauOm99g)+$l z;KR9LL)qEzBhq5I9B8+7Y!4s2Wg6`sk;b{}ho5YD-jTHI{B8J|>OPRY83%r+tyhLh zZvHY*F!ur&JnHe5W%9b=)bx@QHE7B@xGgK34L+E(+MF|)sDMK8ePp?0$h+DoQ$0nm zEgy>wha;vFXo8VR^xV7)dzdqj{}=tPoCvXkF@MGjsmwB=9zIYMDwBsZ)|3v|r{QsP zxIh6|0Q;l%eF)%}O80#K9-VlAS){ac>8*;dXRzJ3MIesl=HoK=Hx7^B@)r(I@Qn|@ znwc59*7VYSy-c%>9%xiw@E7{-E4SyqIvK(lhutZWu z-~WK`BB^nYRM2)C`pPCPNNd4}Ms1pk9Pnl|5Eo=$!B2Jeo*$eDrUnaWB)+Ee zGqx=`l7@u&6FylM+U+KwLHAx7jd;Qz1913{aAwvP^>_rx-|h*lJPAyBF!SFx&gm9Q zoUW{74cO2`xiOPxo~}1ib@%ZibfePR%T#>8>u}*xDg`v9tqV zYzqWilMWoU5LjU@{yWQ%hw>`mY%SFFuX&-GpQ3B!LHvn?8VRny{%y0mTTafn1ZdIOmbBLzo_y4DI#%z1Q%cU1zdgB)^z9oD4eQ=Em?Dk9d|>s8;vJ z+mUads5~wT+x$(a`mHq9&NGM$OiUUnXB#7VlIfT3B6UFq4O6ASE9Cus8BJDKfBBO- z9z;ia{vg2EBpu;RT<=LyXW-OKKi@uOB-_8Xjx9sN5IkkYFdw6z%m?4#Rfr3HC=O<3 zqKAHYTyj?SUOX&12&jJr`>w{Q?Ed9w1>PNdW)<$#Z3-Hh5+(sdnHKUJ42C87SNgBw z*f3%YZ^T3BbKC*99y90_p6wfIVinj*mHr5{?Qiv({3kP9L?RFwlPD<{Y_56DKbpHG z9#SWM4o-5*rde26a30OP$e?=s35F1pbgp0Fw^kViKE)qVpFxl`!{Bg0F&W07P@i=X zRf8WyfxvkZFQDjNr2aO5UvtHDc9GqqaJJtY0M|%=&&EmxODhD4r05TYohuZAO?&=s z1Lwkgefy@!Na)hlZ|FXa>(J?jgxz(P>`HS^>N^JFY#UOO*yOE z>9D?W#gYcK(YM<#uZXq%@1s0i6%5oSi(ZGVkDPS&wP!CZRiZt-6^fUY{;MzuSX#vj z*vPgb11ngV+3)vM$fsNLb1XkE26r)eRTnsnCS3}E2BF0v2WO3>QRsygBH-OEeH4be z451lZwU?x6+!yA$@52}TtQ#eYfWSQaBI;{CSh*kq1j6@_&{)obW5X*hm|mUP(Uw2l z=y6((G~o5%qeX2h8!SR zg48{&4B7>Py4Lg=mDHwh<|pxQcNAUEEMy%E)^|8;<`qfNT@Io``x%xOSqO6!o?n@x zZirUvqD6E!X&8t%dL}2|Jc|EOTNcZ#pi5xuG%i#(<1el?&85I+ikAdg{SXK4gP|sl~H|CV;|ctTx4AR zBs+S^@HiK(L{kFG@u2E%s;0Yw67kl9Iy<8w4$S-Yfbm-kuTDnNBlW2hl^dw|aW^|O zBROL>wtxhu21q1@Hi;ewnqgvDUfWoiRoBnFvl$0}*9v@D;E3i91U+}e#i@Fpo2@b7 z9rf$u?^<_1XTdyoYVYT*H2J+XYrXRg^Rweea`StSpw1C#;CHNF2*~qk-)E*uQ@qNS zXs~_ZNh_uw83{U=;nQ+GOZ9sp)t0Omxrm=SRhDs;Z3hTN`Fx%zi0cpU@+Nx+gVvjv z_;aJ@PVlQ}EXz%U_d9iYz9$wqiIDEjg*T4{89Y$*++8yGDO;G+L@_$0oX^=d3vj5S zK}L)G28>df3~lUYe0jYbU);X;N+ut{RlcA1pzG^fZTbR*O=}H>U$QswtWjBB?|)VB ztMLL7aB0t3o)$HIxPv9(?qZ`dM3qJ8 zW4T2TzW!#s{1B5h*XQLbH1a({VP0@*RvGJDf#T!z8M~(?H<7*%#%eQi)k&^x26s6l zoT^mMhK_)7`4f(=?lt#EEkU`1bI3yZ-E?dBIL6w<FCXUSDv8sXQ%#;50iA){+7Vjb!V#avBT;6p>&pc_P zgLp>-1D_{eZGk%!?UF>zZG*`Lv$X1Wh=3e#Z8wr1)gfukC9EWbQ-sfVC&E%+c@=i# zGP%xatT-%TI-#C$IwE46e{P6H^3u6^BPpnc#vG&>KeTc=L zq&(PS9bR<6I?Bcm#T|Wq#H-Q$nEz``BY;7=1jVc(Swps&terGfG-kFzF^h)g41Jbrkq~u>S-vkf9H(d0t{J&-UGyRq#54BVO^K zwr=%Dal{L3i_k!QWSl!RM7V(tJ9wRJB{i%Gl#v1@MP9Xd(O}kjvb`O-FRgS<< z%QO{2tEu_ne4zAAM+un64qJF^+b;X168X9Bvz-uk$B(?m)~b*ilks-%3~{N8ekBMF zCi3W{w5-b@eIUaf*;%wQzep?Ep7cx`{qW8rX!B$Baga7~3myL%+zne39n|@!7&@|x zH7MB^W6XpEREzuLxm^H-TzPdN?FrXbEu?^c3-T0P1EhRqf4Q0QGg%nRd1J~|mx1|; zvOKHA^C_(U>(iQfP~&S}$Mkx;bJP6!;&p=)5~XLBg6zuCg~+~qQX!#pONJZ{H5)v= zug31m?Tf4~m{&U@0me9|LNPq3y?h$RETGUhgnLvnR19rN{=^X;4sT`*B`D;I^(f)(I$R-RLtK^m(BHtNV0t56{N^{xaiG;%D~ zSA?=Hp>0~FKxSi3yq>LP;j7F6#@BWf``L5xnPmeki?Ke_oey|LwqdS1V;Gay7+5jy z4q~N;t2z7Pm^B&QWQLr2@3XK=yG+DrZ(A7A0ke zfXptUKv22sxSL&aP1EAXi^dfBB|`r($IV8?*oybEj8{`DnFhW#tf3o}O&Z(?nuhXa z39&Q_tMFn>bRStL%se4Z_^302(H+<@V2!y`?C36_38yI3j)Qd?S^P8nYupman(M|F z&T%j{c`yWuiq25oYj&mlC3M;+%3YzFul<@lR*uqB*;e^P~22!VWDOs1tu$ zm!k!e?h^BApJM{uVJ*v>p<*?e%z^&O9#+iL*7t|uzQ$I|9~xl}b?Zr3!LMvQ$HI*= z99J%b&Evh@Z0S*HtDRK&oZ^Hgxly+9j25HFcm?GP@`U+f1Q`G72qEjJb7rE8%TSUU z@4eUZik;3QfoWsc!@lm=U67p5esejU(W;$zzlc80>rJ|N2yHV|emceZ&dIwa#+wY^ z&1i&MIRv|TBv%61lDqVJiK*zTcS}rRB?H%ln1h%wbZK z%4pBzP`=Q|d#mv1^{D+vO$IDC7Ben#hn`Md4W16ol#4G<>0 zH6UgW6EF_2MWxim0Mb)oYKW0WX`%eQSAu!0d3y466%xDmIMdS(;QI3NsGVwu*5{o3 zORY9zRY}J6P1j`x_^o^1wy)VtOZ#fL+mu>IJ2pFy`|{u!+JB<TbcM}Q< z-`Snhxx6EIx8~p-EA1tor(xsO^e`h?TbRY1D}x0C7y4#+>`mzD7xBudQwESAgBC0tW9j%v^mtlob^&r5feaIr{9xSq(i9WNJEaRex;QZN$Z9$-ax<6MHaX$zpuXB~uJdkMWa8u|w>M-A(3O|}8l1neU6IpsM4iK6< ziW7VVAEm>Oa)=*tdOs0M^Fcyazh7Mg*eO_oXk`x7TQ#`I=w%|*2tQuk(loTL z_+n+Z?;rw*-Shg?YN2rbsp!#h){=OJEYiRBBs{;~Ccnf9J9}?|AfEBTPz<;~#F7F$ zcnokJVj-qpG@viX_e8fW^B(&YUPgu6`5_fV2t03=2W#ovHbkyxKXn^hXQF)yH*&9s zdRxy=biiSB?RvtXOv=&nr|>lLi+J4}Q`q{xuDyqGrne-6_!~~0uhB3tGJRYsR@r+r zo+E*;D#`SM=lgRZ-j9z~1NSE?*MIDu8Mi3A@+jS98fsXYsV@|+)B6D(thqlJ5_Bz4ta9O6D9m%erIG-80wNd3uP!`ijEiO=~$GBBfdJ!Fs&$NH* zkP0KVW{W6}Wo`O}s{d{&$Z?{NK^|guG%X%rH)l7Pc(_b63Wu3gq^B8o$JU@Z)%rG| z1zy>aXB9%lJ&iFHH@XaQhKIdswp(-=IuRE;udrS}tICbhLM`5dy!RC2s(leQ{)tjV z#?o(uB5poTcltS)ZxL|W(%Mse)uD4?7U8$y(FnrYb0wU&OW}~X0;(u&^jeKPL*dUI zF5}mCf`SD(9w~jrLi#W=uhHLQ;<%M}+C+#F_qF`k)a*%TlHr4j~6j(-^dBvd)%3X)cLZuw9n?PV@(y(`bU+@;+ZMSHP@3-$=xq{+&+UT+2={1LqV9?HNY&g-Fj4EA*xmP)!+={q_hO0NT zayG(ZT%0qc{q5Z#nPyFps2<89juuNu4a3seZBJ%}E@bJ>Q3~)zfF7B#CI-X zmKN_IAf?fobwpEnEji2i{paGRM<)&(twsf-kwUC+tef{RxZ_;Db9Ci8ZIF0v)_E1~ z@={Heo-xp9S99ZaJUbpl_1-@d)=OgrW*2~UPW2i9@2htB@1ov)EUV$JMG1x-P7uhP zdap=5!i9CSi*3Y$3s%W zqcvNnBo7;qq}x)np3m&1R}DXBYCn!p*rfyBUOOw*T@57mh_`k+F512Cz4mX!3Tj&d zkuow313O$n>vhV2{ax%S3<;AgD11p53;3J@uj+m)6c)o`p92RL&$B?l^Fy9!AIYKX z@CBFW!jjKr$=AGCXXhDAM9EB{ordcJb_CnXm)xe2l=q(#`NJPFw#wyw(Jv*Z?@k;P z>kOVgxl`nM8BC{A{XA>O2PsllHS~EyJI=l9zGxuQ-`Ro~_@?`NK}`l3B+hU*ZWq!zi;Z|~OlhL=b_AAQx| z+qgBrxp7fLMHzb58v>5Z2sCH?nwqt64aW^q;`CW&vxNOvHfk)86xU*5Wr4UxqdV5j zFw^1`n>gEFknK=OzYn}~J#MnW{pCUB@*y$rGQ~;OBP{mjb|PSC!GN!oawrXjNy z$EK8Y*iLHV2{vh;SmEm9q!4iL!KTfJLb?C38$f?-#kR|2XOyDwkKrOMFEn?^zj57| zP=k{@mv*1Pj?<+ITzam$#JUvrCRz@5Xf#uv?Bgtgslk$JQrB!5sInN*z zRVvn5AucRu(8b-PW=rx&p@)+fw=OtbPlfE{Tm9eDu{>=THQONm*HMFjrOWoOSm(!y z3t=q$k5TfwbRjT9Y23g!mP$wDpLoHIUIea8R*eJ2pC=fKg9v&)cV<|4^&gY&KWE{> zF&sZh3L$*^w|DnzW#tR=`Z6`gWH9Bg8vZu+x=J8|e2W;CKm1Wpz&XWQd{UCCuXaz0 zVs0S9?Zbn{=aiZ+f7>3|ArPdaYS@tsLBBKg`#kfM?&7CQb(aloneH z?!Sjaz)DwxbD`|*p!xpKTwsUzhh>W};9{ZR7eT1xeaJ4PL7u{H^+#V*(|HubeaLQt z{_oENY8Z1*)^+zJ#k>3}Y#Qs@W>?vWPVIE{b%39K&0K%kS=a&o9qat~XW~`#xJnFA zg))8049LITlK+HGD+qNT=qvj1&eE~j8Zk^Kxm9!s?w{iW$Z^NCr8`Xzie{37cGIk{iD(q+$aBC)eB3g~}MHckb5U-6$WT7cc|MBhdzu5u@ zVWtJP$$aronRM!*>3!YtgzKTOGkOIpjklt5=ng_qAK6$l*$7eQ;;ZZlSAFaV?S`MA z6oSpU71Er`x`Soxz&CJjO`S&-?y~=^+O|aQK-kRy$@7AWLGL}_VZyCTC+(&O?w22( zQn?E0p{$tJx0JV&!2eKP$rr@=!lU_kDa_evs^d8JVj{?j0=M+=sP4_cqkwTvAD;7T z#Q3X$6~SE;+e4w##@f&Uby%kg(!QM)`^1+EPZZu&rD5dJ<&pU2meij6btV5Ykv{Gf z19(@A+}WUyyr0jiy3C0ZvgiKVH5( z`wt@S%XI>Poxx@@px!WOD>_T<%Iwe($L27Iy&5ZIahU~@t9A5V zmtgU#-Ngj@A>R5`qhoFs*~z)`5d8FyL%!$_`nLzTDokx8wlKX_If*ZNk{iP4$Ea$BzyMYf_zgQzr0=B)((tMt0SH-F zCgVB8DJOq4Boe*imT+fzIBpj&+;i+f2%vn}tC)!SdE5_&V>$_RL5=oB9vPqZx|iHt2|f7~xYtB7~LnLZ(aJ#1&R zON#;GR4QItY{V{k&2A+79d~V;y7kI>K;IPi90J5Wc88`&lg00sK8r>?-$dXd)O<6V z8}2jK`38{odlxQz_%nV85lCYV+?=8b4fkOZEjl}}v2>XkOG&5yXZQeLmQ8E{#>n?( z^>u~<=1hJ@(7u3@221X3ID3k^yKBEtPmwi{$!h{%c4FX4<>u!4=2>-$QrpnFaD4YZ zqDXqCEwE*G$XRjp73IexOwowE9IhyiBJ@a~dFbb8mt6HAO!5)j*UPbnB9$!R2#Jk(mKO!#)s9$@1Y!<$+xd-9f)?)nl)YAE#C zy~98IenEN->0iw#+hvF;(;-e-Il?LBi&&@SZQ2ruwl`RL*e0OR>WR;}rJ8PRA+N}2 zi=e2_?f-aQob%8n_^tE+-iHi2!q4gk)xB9Z1JrR|QF5hXqM$dJ7XHUDZj6+qInLx! z5AS5z<4Z^F!BUqFmYlgYAO4vQ!MaGAeV0klrl6b(JpC>}8oB<{|AqEm(O$DW6_hoX zqy8B)T{d|7jdUAcF#hK>LIFtB;E6Vn*nuPUkMcJVnRhGQfti2myJAD|JWoFlY3}@o zJ_9Skj$BY4lUCm*Ju2h%JJe)D;(y}!rEUB>ELO|p-`a`5R;HUDA)$5c{4rl`(X7QA zKy5ppz->@rF;hj(x#(%76*s|(p?9L(CYz`%KN#Qr(K*eugg`ybLr%zAZvU+SB$BEF zq)(3*9oMUj|LSD0#ai=Af-#Wt&;Anc|40)au-G^dGX9tDOfmz@3bVW2>f=9th&>J# zYX `[^/]`, `trailing_slash = False` --> `[^/.]`, becomes simply `[^/]` and `lookup_value_regex` is added. +The [labels that we use in GitHub][github-labels] have been cleaned up, and all existing tickets triaged. Any given ticket should have one and only one label, indicating its current state. -[lts-releases]: https://docs.djangoproject.com/en/dev/internals/release-process/#long-term-support-lts-releases \ No newline at end of file +We've also [started using milestones][github-milestones] in order to track tickets against particular releases. + +--- + +![Labels and milestones](../img/labels-and-milestones.png) + +**Above**: *Overview of our current use of labels and milestones in GitHub.* + +--- + +We hope both of these changes will help make the management process more clear and obvious and help keep tickets well-organised and relevant. + +## Next steps + +The next planned release will be 3.0, featuring an improved and simplified serializer implementation. + +Once again, many thanks to all the generous [backers and sponsors][kickstarter-sponsors] who've helped make this possible! + +[lts-releases]: https://docs.djangoproject.com/en/dev/internals/release-process/#long-term-support-lts-releases +[2-4-release-notes]: ./topics/release-notes/#240 +[view-name-and-description-settings]: ../api-guide/settings/#view-names-and-descriptions +[client-ip-identification]: ../api-guide/throttling/#how-clients-are-identified +[2-3-announcement]: ./topics/2.3-announcement +[github-labels]: https://github.com/tomchristie/django-rest-framework/issues +[github-milestones]: https://github.com/tomchristie/django-rest-framework/milestones +[kickstarter-sponsors]: ./topics/kickstarter-announcement/#sponsors From f7b3e1e62b8e2c8bd1d1eb79a1cb0b3f4a0559a9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 20 Aug 2014 11:09:04 +0100 Subject: [PATCH 224/225] Latest sponsor update --- docs/img/1-kuwaitnet.png | Bin 0 -> 12302 bytes docs/img/sponsors/1-kuwaitnet.png | Bin 15489 -> 3674 bytes docs/img/sponsors/2-pulsecode.png | Bin 0 -> 2368 bytes docs/img/sponsors/2-singing-horse.png | Bin 0 -> 20831 bytes docs/topics/kickstarter-announcement.md | 4 +++- 5 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 docs/img/1-kuwaitnet.png create mode 100644 docs/img/sponsors/2-pulsecode.png create mode 100644 docs/img/sponsors/2-singing-horse.png diff --git a/docs/img/1-kuwaitnet.png b/docs/img/1-kuwaitnet.png new file mode 100644 index 0000000000000000000000000000000000000000..c73b68154dd3768118d5f623f21a4231f78d0da3 GIT binary patch literal 12302 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
      Q9|>9VxyGV>=r#^(|L?p*IDxk9faW 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 JDrX$@e*g+EQ#b$s literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/1-kuwaitnet.png b/docs/img/sponsors/1-kuwaitnet.png index 8b2d0550a5cb17513986fbf424ad2542d02c04f7..bb779ea7707df062fa7a95bc023ebed5f6579578 100644 GIT binary patch literal 3674 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 diff --git a/docs/img/sponsors/2-pulsecode.png b/docs/img/sponsors/2-pulsecode.png new file mode 100644 index 0000000000000000000000000000000000000000..49f9532cff745e85d22809b976dd50eba8dc4b11 GIT binary patch literal 2368 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 literal 0 HcmV?d00001 diff --git a/docs/img/sponsors/2-singing-horse.png b/docs/img/sponsors/2-singing-horse.png new file mode 100644 index 0000000000000000000000000000000000000000..84142ae6cdcdee8899ee6fcce2d5afbfdb41bd49 GIT binary patch literal 20831 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 Kite
    1. Opbeat
    2. Koordinates
    3. +
    4. Pulsecode Inc.
    5. +
    6. Singing Horse Studio. Ltd.
    7. Heroku
    8. Galileo Press
    9. Security Compass
    10. @@ -95,7 +97,7 @@ Our gold sponsors include companies large and small. Many thanks for their signi
      -**Individual backers**: Xitij Ritesh Patel, Howard Sandford, Simon Haugk. +**Individual backers**: Simon Haugk. --- From 59b47eac14778767a17e56bd8adc0610417f2878 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 20 Aug 2014 12:32:24 +0100 Subject: [PATCH 225/225] Fix cache_throttle typo --- docs/topics/2.4-accouncement.md | 11 +++++++++++ docs/topics/release-notes.md | 1 + rest_framework/throttling.py | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/topics/2.4-accouncement.md b/docs/topics/2.4-accouncement.md index 504842872..e3f9d57a8 100644 --- a/docs/topics/2.4-accouncement.md +++ b/docs/topics/2.4-accouncement.md @@ -97,6 +97,17 @@ Here's an example of using the new decorators. Firstly we have a detail-type rou For more details, see the [viewsets documentation](../api-guide/viewsets.md). +## Throttle behavior + +There's one bugfix in 2.4 that's worth calling out, as it will *invalidate existing throttle caches* when you upgrade. + +We've now fixed a typo on the `cache_format` attribute. Previously this was named `"throtte_%(scope)s_%(ident)s"`, it is now `"throttle_%(scope)s_%(ident)s"`. + +If you're concerned about the invalidation you have two options. + +* Manually pre-populate your cache with the fixed version. +* Set the `cache_format` attribute on your throttle class in order to retain the previous incorrect spelling. + ## Other features There are also a number of other features and bugfixes as [listed in the release notes][2-4-release-notes]. In particular these include: diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index a31be28f1..0a74a27d3 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -62,6 +62,7 @@ You can determine your currently installed version using `pip freeze`: * Bugfix: Copy `filter_backends` list before returning it, in order to prevent view code from mutating the class attribute itself. * Bugfix: Set the `.action` attribute on viewsets when introspected by `OPTIONS` for testing permissions on the view. * Bugfix: Ensure `ValueError` raised during deserialization results in a error list rather than a single error. This is now consistent with other validation errors. +* Bugfix: Fix `cache_format` typo on throttle classes, was `"throtte_%(scope)s_%(ident)s"`. Note that this will invalidate existing throttle caches. --- diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 7e9f9d717..361dbddf0 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -60,7 +60,7 @@ class SimpleRateThrottle(BaseThrottle): cache = default_cache timer = time.time - cache_format = 'throtte_%(scope)s_%(ident)s' + cache_format = 'throttle_%(scope)s_%(ident)s' scope = None THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES