From ffdf633aa53526d2868e485ecffe0411a4ce0874 Mon Sep 17 00:00:00 2001 From: Areski Belaid Date: Wed, 5 Jun 2013 14:19:36 +0200 Subject: Fix on documentation - wrong reference at previous created view UserList / UserDetail --- docs/tutorial/6-viewsets-and-routers.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index 4ed10e82..d40ef781 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -10,7 +10,9 @@ A `ViewSet` class is only bound to a set of method handlers at the last moment, Let's take our current set of views, and refactor them into view sets. -First of all let's refactor our `UserListView` and `UserDetailView` views into a single `UserViewSet`. We can remove the two views, and replace then with a single class: +First of all let's refactor our `UserList` and `UserDetail` views into a single `UserViewSet`. We can remove the two views, and replace then with a single class: + + from rest_framework import viewsets class UserViewSet(viewsets.ReadOnlyModelViewSet): """ @@ -23,15 +25,14 @@ 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 import viewsets from rest_framework.decorators import link class SnippetViewSet(viewsets.ModelViewSet): """ This viewset automatically provides `list`, `create`, `retrieve`, `update` and `destroy` actions. - - Additionally we also provide an extra `highlight` action. + + Additionally we also provide an extra `highlight` action. """ queryset = Snippet.objects.all() serializer_class = SnippetSerializer @@ -107,7 +108,7 @@ Here's our re-wired `urls.py` file. router = DefaultRouter() router.register(r'snippets', views.SnippetViewSet) router.register(r'users', views.UserViewSet) - + # The API URLs are now determined automatically by the router. # Additionally, we include the login URLs for the browseable API. urlpatterns = patterns('', @@ -131,7 +132,7 @@ With an incredibly small amount of code, we've now got a complete pastebin Web A We've walked through each step of the design process, and seen how if we need to customize anything we can gradually work our way down to simply using regular Django views. -You can review the final [tutorial code][repo] on GitHub, or try out a live example in [the sandbox][sandbox]. +You can review the final [tutorial code][repo] on GitHub, or try out a live example in [the sandbox][sandbox]. ## Onwards and upwards -- cgit v1.2.3 From f317699d47e89882ee446ebec0388a1444223754 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 5 Jun 2013 14:36:40 +0200 Subject: Added @areski for docs fix #912. Thanks! --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index bbe209c7..db522922 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -139,6 +139,7 @@ The following people have helped make REST framework great. * Pascal Borreli - [pborreli] * Alex Burgel - [aburgel] * David Medina - [copitux] +* Areski Belaid - [areski] Many thanks to everyone who's contributed to the project. @@ -314,3 +315,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [pborreli]: https://github.com/pborreli [aburgel]: https://github.com/aburgel [copitux]: https://github.com/copitux +[areski]: https://github.com/areski -- cgit v1.2.3 From f8a0d31d71bf722741a472c27ae0a10ecbcbcec3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 5 Jun 2013 13:45:28 +0100 Subject: Remove ConfigurationError in favor of Django's ImproperlyConfigured --- rest_framework/exceptions.py | 7 ------- rest_framework/generics.py | 2 +- rest_framework/renderers.py | 4 ++-- rest_framework/throttling.py | 6 +++--- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py index 0c96ecdd..425a7214 100644 --- a/rest_framework/exceptions.py +++ b/rest_framework/exceptions.py @@ -86,10 +86,3 @@ class Throttled(APIException): self.detail = format % (self.wait, self.wait != 1 and 's' or '') else: self.detail = detail or self.default_detail - - -class ConfigurationError(Exception): - """ - Indicates an internal server error. - """ - pass diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 9ccc7898..80efad01 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -285,7 +285,7 @@ class GenericAPIView(views.APIView): ) filter_kwargs = {self.slug_field: slug} else: - raise exceptions.ConfigurationError( + raise ImproperlyConfigured( 'Expected view %s to be called with a URL keyword argument ' 'named "%s". Fix your URL conf, or set the `.lookup_field` ' 'attribute on the view correctly.' % diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index b2fe43ea..8b2428ad 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -11,6 +11,7 @@ from __future__ import unicode_literals import copy import json from django import forms +from django.core.exceptions import ImproperlyConfigured from django.http.multipartparser import parse_header from django.template import RequestContext, loader, Template from django.utils.xmlutils import SimplerXMLGenerator @@ -18,7 +19,6 @@ 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.exceptions import ConfigurationError from rest_framework.settings import api_settings from rest_framework.request import clone_request from rest_framework.utils import encoders @@ -270,7 +270,7 @@ class TemplateHTMLRenderer(BaseRenderer): return [self.template_name] elif hasattr(view, 'get_template_names'): return view.get_template_names() - raise ConfigurationError('Returned a template response with no template_name') + raise ImproperlyConfigured('Returned a template response with no template_name') def get_exception_template(self, response): template_names = [name % {'status_code': response.status_code} diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 93ea9816..9d89d1cb 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -3,7 +3,7 @@ Provides various throttling policies. """ from __future__ import unicode_literals from django.core.cache import cache -from rest_framework import exceptions +from django.core.exceptions import ImproperlyConfigured from rest_framework.settings import api_settings import time @@ -65,13 +65,13 @@ class SimpleRateThrottle(BaseThrottle): if not getattr(self, 'scope', None): msg = ("You must set either `.scope` or `.rate` for '%s' throttle" % self.__class__.__name__) - raise exceptions.ConfigurationError(msg) + raise ImproperlyConfigured(msg) try: return self.settings.DEFAULT_THROTTLE_RATES[self.scope] except KeyError: msg = "No default throttle rate set for '%s' scope" % self.scope - raise exceptions.ConfigurationError(msg) + raise ImproperlyConfigured(msg) def parse_rate(self, rate): """ -- cgit v1.2.3 From fdb689f9b552f219dd677b3b8421ceebcfd5b1e2 Mon Sep 17 00:00:00 2001 From: gnunamed Date: Wed, 5 Jun 2013 13:53:00 -0500 Subject: Update serializers.md --- docs/api-guide/serializers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 71f0abb7..9b6a547f 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -39,7 +39,7 @@ Declaring a serializer looks very similar to declaring a form: an existing model instance, or create a new model instance. """ if instance is not None: - instance.title = attrs.get('title', instance.title) + instance.email = attrs.get('email', instance.email) instance.content = attrs.get('content', instance.content) instance.created = attrs.get('created', instance.created) return instance -- cgit v1.2.3 From ecb8a460c99238a959d8e7600af5b692f13c40d9 Mon Sep 17 00:00:00 2001 From: Alex Burgel Date: Wed, 5 Jun 2013 16:59:19 -0400 Subject: Fix serialization exception when using non-existent consumer --- rest_framework/authentication.py | 5 ++-- rest_framework/tests/test_authentication.py | 41 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 9caca788..f659a172 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -230,8 +230,9 @@ class OAuthAuthentication(BaseAuthentication): try: consumer_key = oauth_request.get_parameter('oauth_consumer_key') consumer = oauth_provider_store.get_consumer(request, oauth_request, consumer_key) - except oauth_provider.store.InvalidConsumerError as err: - raise exceptions.AuthenticationFailed(err) + except oauth_provider.store.InvalidConsumerError: + msg = 'Invalid consumer token: %s' % oauth_request.get_parameter('oauth_consumer_key') + raise exceptions.AuthenticationFailed(msg) if consumer.status != oauth_provider.consts.ACCEPTED: msg = 'Invalid consumer key status: %s' % consumer.get_status_display() diff --git a/rest_framework/tests/test_authentication.py b/rest_framework/tests/test_authentication.py index d46ac079..6a50be06 100644 --- a/rest_framework/tests/test_authentication.py +++ b/rest_framework/tests/test_authentication.py @@ -428,6 +428,47 @@ class OAuthTests(TestCase): response = self.csrf_client.post('/oauth-with-scope/', params) self.assertEqual(response.status_code, 200) + @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') + @unittest.skipUnless(oauth, 'oauth2 not installed') + def test_bad_consumer_key(self): + """Ensure POSTing using HMAC_SHA1 signature method passes""" + params = { + 'oauth_version': "1.0", + 'oauth_nonce': oauth.generate_nonce(), + 'oauth_timestamp': int(time.time()), + 'oauth_token': self.token.key, + 'oauth_consumer_key': 'badconsumerkey' + } + + req = oauth.Request(method="POST", url="http://testserver/oauth/", parameters=params) + + signature_method = oauth.SignatureMethod_HMAC_SHA1() + req.sign_request(signature_method, self.consumer, self.token) + auth = req.to_header()["Authorization"] + + response = self.csrf_client.post('/oauth/', HTTP_AUTHORIZATION=auth) + self.assertEqual(response.status_code, 401) + + @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') + @unittest.skipUnless(oauth, 'oauth2 not installed') + def test_bad_token_key(self): + """Ensure POSTing using HMAC_SHA1 signature method passes""" + params = { + 'oauth_version': "1.0", + 'oauth_nonce': oauth.generate_nonce(), + 'oauth_timestamp': int(time.time()), + 'oauth_token': 'badtokenkey', + 'oauth_consumer_key': self.consumer.key + } + + req = oauth.Request(method="POST", url="http://testserver/oauth/", parameters=params) + + signature_method = oauth.SignatureMethod_HMAC_SHA1() + req.sign_request(signature_method, self.consumer, self.token) + auth = req.to_header()["Authorization"] + + response = self.csrf_client.post('/oauth/', HTTP_AUTHORIZATION=auth) + self.assertEqual(response.status_code, 401) class OAuth2Tests(TestCase): """OAuth 2.0 authentication""" -- cgit v1.2.3 From 40e09472d8b32988ef8284f66569cd26b3204ac6 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 6 Jun 2013 08:56:39 +0100 Subject: Never deepcopy validators. Closes #913 --- docs/api-guide/generic-views.md | 2 +- rest_framework/fields.py | 39 ++++++++++++--------------------------- rest_framework/views.py | 9 ++++++++- 3 files changed, 21 insertions(+), 29 deletions(-) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 20b9440b..c8216954 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -131,7 +131,7 @@ You may want to override this method to provide more complex behavior such as mo For example: def get_paginate_by(self): - self.request.accepted_renderer.format == 'html': + if self.request.accepted_renderer.format == 'html': return 20 return 100 diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 535aa2ac..32e4c4ae 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -7,25 +7,24 @@ from __future__ import unicode_literals import copy import datetime -from decimal import Decimal, DecimalException import inspect import re import warnings +from decimal import Decimal, DecimalException +from django import forms from django.core import validators from django.core.exceptions import ValidationError from django.conf import settings from django.db.models.fields import BLANK_CHOICE_DASH -from django import forms from django.forms import widgets from django.utils.encoding import is_protected_type from django.utils.translation import ugettext_lazy as _ from django.utils.datastructures import SortedDict from rest_framework import ISO_8601 -from rest_framework.compat import (timezone, parse_date, parse_datetime, - parse_time) -from rest_framework.compat import BytesIO -from rest_framework.compat import six -from rest_framework.compat import smart_text, force_text, is_non_str_iterable +from rest_framework.compat import ( + timezone, parse_date, parse_datetime, parse_time, BytesIO, six, smart_text, + force_text, is_non_str_iterable +) from rest_framework.settings import api_settings @@ -256,6 +255,12 @@ class WritableField(Field): widget = widget() self.widget = widget + def __deepcopy__(self, memo): + result = copy.copy(self) + memo[id(self)] = result + result.validators = self.validators[:] + return result + def validate(self, value): if value in validators.EMPTY_VALUES and self.required: raise ValidationError(self.error_messages['required']) @@ -428,13 +433,6 @@ class SlugField(CharField): def __init__(self, *args, **kwargs): super(SlugField, self).__init__(*args, **kwargs) - def __deepcopy__(self, memo): - result = copy.copy(self) - memo[id(self)] = result - #result.widget = copy.deepcopy(self.widget, memo) - result.validators = self.validators[:] - return result - class ChoiceField(WritableField): type_name = 'ChoiceField' @@ -503,13 +501,6 @@ class EmailField(CharField): return None return ret.strip() - def __deepcopy__(self, memo): - result = copy.copy(self) - memo[id(self)] = result - #result.widget = copy.deepcopy(self.widget, memo) - result.validators = self.validators[:] - return result - class RegexField(CharField): type_name = 'RegexField' @@ -534,12 +525,6 @@ class RegexField(CharField): regex = property(_get_regex, _set_regex) - def __deepcopy__(self, memo): - result = copy.copy(self) - memo[id(self)] = result - result.validators = self.validators[:] - return result - class DateField(WritableField): type_name = 'DateField' diff --git a/rest_framework/views.py b/rest_framework/views.py index e1b6705b..0c1ea7d7 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -341,8 +341,15 @@ class APIView(View): Return a dictionary of metadata about the view. Used to return responses for OPTIONS requests. """ + + # This is used by ViewSets to disambiguate instance vs list views + view_name_suffix = getattr(self, 'suffix', None) + + # By default we can't provide any form-like information, however the + # generic views override this implementation and add additional + # information for POST and PUT methods, based on the serializer. ret = SortedDict() - ret['name'] = get_view_name(self.__class__) + ret['name'] = get_view_name(self.__class__, view_name_suffix) ret['description'] = get_view_description(self.__class__) ret['renders'] = [renderer.media_type for renderer in self.renderer_classes] ret['parses'] = [parser.media_type for parser in self.parser_classes] -- cgit v1.2.3 From e483c4fed686d7e3c7787d2e3eaa5ec4665399ff Mon Sep 17 00:00:00 2001 From: Ryan Kaskel Date: Fri, 7 Jun 2013 10:07:42 +0100 Subject: Remove pass statement before docstring. --- docs/api-guide/viewsets.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 2783da98..79257e2a 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -209,8 +209,6 @@ To create a base viewset class that provides `create`, `list` and `retrieve` ope mixins.ListMixin, mixins.RetrieveMixin, viewsets.GenericViewSet): - pass - """ A viewset that provides `retrieve`, `update`, and `list` actions. -- cgit v1.2.3 From ae2887ffc41b1e05d6706f51b00266efccad7a58 Mon Sep 17 00:00:00 2001 From: Ethan Fremen Date: Fri, 7 Jun 2013 19:25:39 -0700 Subject: Set the args and kwargs before initializing the request. Allows get_parser_context to function correctly. Signed-off-by: Ethan Fremen --- rest_framework/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/views.py b/rest_framework/views.py index 0c1ea7d7..c28d2835 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -304,10 +304,10 @@ class APIView(View): `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling. """ - request = self.initialize_request(request, *args, **kwargs) - self.request = request self.args = args self.kwargs = kwargs + request = self.initialize_request(request, *args, **kwargs) + self.request = request self.headers = self.default_response_headers # deprecate? try: -- cgit v1.2.3 From d89aade343ba816644c393d3d073b7ddcb795947 Mon Sep 17 00:00:00 2001 From: Ethan Fremen Date: Fri, 7 Jun 2013 19:49:18 -0700 Subject: Allow the default router to have a custom name. Signed-off-by: Ethan Fremen --- rest_framework/routers.py | 3 ++- rest_framework/tests/test_routers.py | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 9764e569..f70c2cdb 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -215,6 +215,7 @@ class DefaultRouter(SimpleRouter): """ include_root_view = True include_format_suffixes = True + root_view_name = 'api-root' def get_api_root_view(self): """ @@ -244,7 +245,7 @@ class DefaultRouter(SimpleRouter): urls = [] if self.include_root_view: - root_url = url(r'^$', self.get_api_root_view(), name='api-root') + root_url = url(r'^$', self.get_api_root_view(), name=self.root_view_name) urls.append(root_url) default_urls = super(DefaultRouter, self).get_urls() diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py index a7534f70..291142cf 100644 --- a/rest_framework/tests/test_routers.py +++ b/rest_framework/tests/test_routers.py @@ -6,7 +6,7 @@ from rest_framework import serializers, viewsets from rest_framework.compat import include, patterns, url from rest_framework.decorators import link, action from rest_framework.response import Response -from rest_framework.routers import SimpleRouter +from rest_framework.routers import SimpleRouter, DefaultRouter factory = RequestFactory() @@ -148,3 +148,17 @@ class TestTrailingSlash(TestCase): expected = ['^notes$', '^notes/(?P[^/]+)$'] for idx in range(len(expected)): self.assertEqual(expected[idx], self.urls[idx].regex.pattern) + +class TestNameableRoot(TestCase): + def setUp(self): + class NoteViewSet(viewsets.ModelViewSet): + model = RouterTestModel + self.router = DefaultRouter() + self.router.root_view_name = 'nameable-root' + self.router.register(r'notes', NoteViewSet) + self.urls = self.router.urls + + def test_router_has_custom_name(self): + expected = 'nameable-root' + self.assertEqual(expected, self.urls[0].name) + -- cgit v1.2.3 From 5ce1d6c86bb0831916ba4137560d84295ac7ec95 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 8 Jun 2013 09:38:21 +0200 Subject: Added @mindlace for work on #922. Thx! --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index db522922..b4bd3561 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -140,6 +140,7 @@ The following people have helped make REST framework great. * Alex Burgel - [aburgel] * David Medina - [copitux] * Areski Belaid - [areski] +* Ethan Freman - [mindlace] Many thanks to everyone who's contributed to the project. @@ -316,3 +317,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [aburgel]: https://github.com/aburgel [copitux]: https://github.com/copitux [areski]: https://github.com/areski +[mindlace]: https://github.com/mindlace -- cgit v1.2.3 From 777ecb5141d5d4f1a6b4e10e376965148022822c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 10 Jun 2013 09:05:44 +0100 Subject: Add renderer_classes kwarg when binding snippet_highlight explicitly. Closes #923 --- 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 d40ef781..f16add39 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -74,7 +74,7 @@ In the `urls.py` file we bind our `ViewSet` classes into a set of concrete views }) snippet_highlight = SnippetViewSet.as_view({ 'get': 'highlight' - }) + }, renderer_classes=[renderers.StaticHTMLRenderer]) user_list = UserViewSet.as_view({ 'get': 'list' }) -- cgit v1.2.3 From 5d0aeef69ecec70242513195c19edcb622e14371 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 10 Jun 2013 17:46:55 +0100 Subject: Better docs related to lookup_field and hyperlinked serializers. Closes #920. --- docs/api-guide/generic-views.md | 2 +- docs/api-guide/serializers.md | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index c8216954..cd1bc7a1 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -60,7 +60,7 @@ 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. * `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 field that should be used to lookup individual model instances. Defaults to `'pk'`. The URL conf should include a keyword argument corresponding to this value. More complex lookup styles can be supported by overriding the `get_object()` method. +* `lookup_field` - The field that should be used to lookup individual model instances. Defaults to `'pk'`. The URL conf should include a keyword argument corresponding to this value. More complex lookup styles can be supported by overriding the `get_object()` method. Note that when using hyperlinked APIs you'll need to ensure that *both* the API views *and* the serializer classes use lookup fields that correctly correspond with the URL conf. **Shortcuts**: diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 9b6a547f..0885eb52 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -387,7 +387,7 @@ There needs to be a way of determining which views should be used for hyperlinki By default hyperlinks are expected to correspond to a view name that matches the style `'{model_name}-detail'`, and looks up the instance by a `pk` keyword argument. -You can change the field that is used for object lookups by setting the `lookup_field` option. The value of this option should correspond both with a kwarg in the URL conf, and with an field on the model. For example: +You can change the field that is used for object lookups by setting the `lookup_field` option. The value of this option should correspond both with a kwarg in the URL conf, and with a field on the model. For example: class AccountSerializer(serializers.HyperlinkedModelSerializer): class Meta: @@ -395,6 +395,8 @@ You can change the field that is used for object lookups by setting the `lookup_ fields = ('url', 'account_name', 'users', 'created') lookup_field = 'slug' +Not that the `lookup_field` will be used as the default on *all* hyperlinked fields, including both the URL identity, and any hyperlinked relationships. + For more specfic requirements such as specifying a different lookup for each field, you'll want to set the fields on the serializer explicitly. For example: class AccountSerializer(serializers.HyperlinkedModelSerializer): -- cgit v1.2.3 From 656897c2da5f050b6282cbdb1fca765cc69d11bd Mon Sep 17 00:00:00 2001 From: David Sanders Date: Tue, 11 Jun 2013 16:09:32 -0600 Subject: Update ModelField to work with a broader range of fields Add support for fields that use min and max value validators. --- rest_framework/fields.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 32e4c4ae..d9f61b28 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -339,6 +339,10 @@ class ModelField(WritableField): getattr(self.model_field, 'min_length', None)) self.max_length = kwargs.pop('max_length', getattr(self.model_field, 'max_length', None)) + self.min_value = kwargs.pop('min_value', + getattr(self.model_field, 'min_value', None)) + self.max_value = kwargs.pop('max_value', + getattr(self.model_field, 'max_value', None)) super(ModelField, self).__init__(*args, **kwargs) @@ -346,6 +350,10 @@ class ModelField(WritableField): self.validators.append(validators.MinLengthValidator(self.min_length)) if self.max_length is not None: self.validators.append(validators.MaxLengthValidator(self.max_length)) + if self.min_value is not None: + self.validators.append(validators.MinValueValidator(self.min_value)) + if self.max_value is not None: + self.validators.append(validators.MaxValueValidator(self.max_value)) def from_native(self, value): rel = getattr(self.model_field, "rel", None) -- cgit v1.2.3 From fcaca737097f42168980426a25ec9a9616268d08 Mon Sep 17 00:00:00 2001 From: David Sanders Date: Tue, 11 Jun 2013 16:10:25 -0600 Subject: Pep8 lint --- rest_framework/fields.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index d9f61b28..35848b4c 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -336,13 +336,13 @@ class ModelField(WritableField): raise ValueError("ModelField requires 'model_field' kwarg") self.min_length = kwargs.pop('min_length', - getattr(self.model_field, 'min_length', None)) + getattr(self.model_field, 'min_length', None)) self.max_length = kwargs.pop('max_length', - getattr(self.model_field, 'max_length', None)) + getattr(self.model_field, 'max_length', None)) self.min_value = kwargs.pop('min_value', - getattr(self.model_field, 'min_value', None)) + getattr(self.model_field, 'min_value', None)) self.max_value = kwargs.pop('max_value', - getattr(self.model_field, 'max_value', None)) + getattr(self.model_field, 'max_value', None)) super(ModelField, self).__init__(*args, **kwargs) -- cgit v1.2.3 From 82c515c19cb4050804d8255f4904e45b9b1b884b Mon Sep 17 00:00:00 2001 From: Mark Hughes Date: Wed, 12 Jun 2013 17:36:16 +0100 Subject: Added test for custom fields with min_value and max_value validators --- rest_framework/tests/test_fields.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/rest_framework/tests/test_fields.py b/rest_framework/tests/test_fields.py index 69a0468e..6836ec86 100644 --- a/rest_framework/tests/test_fields.py +++ b/rest_framework/tests/test_fields.py @@ -866,3 +866,33 @@ class FieldCallableDefault(TestCase): into = {} field.field_from_native({}, {}, 'field', into) self.assertEqual(into, {'field': 'foo bar'}) + + +class CustomIntegerField(TestCase): + """ + Test that custom fields apply min_value and max_value constraints + """ + def test_custom_fields_can_be_validated_for_value(self): + + class MoneyField(models.PositiveIntegerField): + pass + + class EntryModel(models.Model): + bank = MoneyField(validators=[validators.MaxValueValidator(100)]) + + class EntrySerializer(serializers.ModelSerializer): + class Meta: + model = EntryModel + + entry = EntryModel(bank=1) + + serializer = EntrySerializer(entry, data={"bank": 11}) + self.assertTrue(serializer.is_valid()) + + serializer = EntrySerializer(entry, data={"bank": -1}) + self.assertFalse(serializer.is_valid()) + + serializer = EntrySerializer(entry, data={"bank": 101}) + self.assertFalse(serializer.is_valid()) + + -- cgit v1.2.3 From b1847412b57b4bc7db39739e0c7c0e8469d3bb48 Mon Sep 17 00:00:00 2001 From: inglesp Date: Wed, 12 Jun 2013 20:00:33 +0200 Subject: Typo --- rest_framework/generics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 80efad01..99e9782e 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -212,7 +212,7 @@ class GenericAPIView(views.APIView): You may want to override this if you need to provide different serializations depending on the incoming request. - (Eg. admins get full serialization, others get basic serilization) + (Eg. admins get full serialization, others get basic serialization) """ serializer_class = self.serializer_class if serializer_class is not None: -- cgit v1.2.3 From 1cc2a7b25e78fcb41f44dc5b580f0f00a0a6318a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 12 Jun 2013 22:46:07 +0200 Subject: Added @davesque for work on #926. Thanks! :) --- docs/topics/credits.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index b4bd3561..3f0ee429 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -141,6 +141,7 @@ The following people have helped make REST framework great. * David Medina - [copitux] * Areski Belaid - [areski] * Ethan Freman - [mindlace] +* David Sanders - [davesque] Many thanks to everyone who's contributed to the project. @@ -318,3 +319,5 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [copitux]: https://github.com/copitux [areski]: https://github.com/areski [mindlace]: https://github.com/mindlace +[davesque]: https://github.com/davesque + -- cgit v1.2.3 From 250dfef158b107178ff9bea1743c767af9210d5e Mon Sep 17 00:00:00 2001 From: Toby Champion Date: Wed, 12 Jun 2013 13:53:39 -0700 Subject: Changes 'python' to 'Python' when used in prose. --- docs/api-guide/fields.md | 12 ++++++------ docs/api-guide/responses.md | 4 ++-- docs/api-guide/serializers.md | 6 +++--- docs/api-guide/settings.md | 18 +++++++++--------- docs/index.md | 2 +- docs/tutorial/1-serialization.md | 4 ++-- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 94fff7e2..d69730c9 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -214,10 +214,10 @@ In the case of JSON this means the default datetime representation uses the [ECM **Signature:** `DateTimeField(format=None, input_formats=None)` -* `format` - A string representing the output format. If not specified, this defaults to `None`, which indicates that python `datetime` objects should be returned by `to_native`. In this case the datetime encoding will be determined by the renderer. +* `format` - A string representing the output format. If not specified, this defaults to `None`, which indicates that Python `datetime` objects should be returned by `to_native`. In this case the datetime encoding will be determined by the renderer. * `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `DATETIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`. -DateTime format strings may either be [python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style datetimes should be used. (eg `'2013-01-29T12:34:56.000000Z'`) +DateTime format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style datetimes should be used. (eg `'2013-01-29T12:34:56.000000Z'`) ## DateField @@ -227,10 +227,10 @@ Corresponds to `django.db.models.fields.DateField` **Signature:** `DateField(format=None, input_formats=None)` -* `format` - A string representing the output format. If not specified, this defaults to `None`, which indicates that python `date` objects should be returned by `to_native`. In this case the date encoding will be determined by the renderer. +* `format` - A string representing the output format. If not specified, this defaults to `None`, which indicates that Python `date` objects should be returned by `to_native`. In this case the date encoding will be determined by the renderer. * `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `DATE_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`. -Date format strings may either be [python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style dates should be used. (eg `'2013-01-29'`) +Date format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style dates should be used. (eg `'2013-01-29'`) ## TimeField @@ -242,10 +242,10 @@ Corresponds to `django.db.models.fields.TimeField` **Signature:** `TimeField(format=None, input_formats=None)` -* `format` - A string representing the output format. If not specified, this defaults to `None`, which indicates that python `time` objects should be returned by `to_native`. In this case the time encoding will be determined by the renderer. +* `format` - A string representing the output format. If not specified, this defaults to `None`, which indicates that Python `time` objects should be returned by `to_native`. In this case the time encoding will be determined by the renderer. * `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `TIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`. -Time format strings may either be [python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style times should be used. (eg `'12:34:56.000000'`) +Time format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style times should be used. (eg `'12:34:56.000000'`) ## IntegerField diff --git a/docs/api-guide/responses.md b/docs/api-guide/responses.md index 374276dc..f83b8194 100644 --- a/docs/api-guide/responses.md +++ b/docs/api-guide/responses.md @@ -8,7 +8,7 @@ REST framework supports HTTP content negotiation by providing a `Response` class which allows you to return content that can be rendered into multiple content types, depending on the client request. -The `Response` class subclasses Django's `SimpleTemplateResponse`. `Response` objects are initialised with data, which should consist of native python primatives. REST framework then uses standard HTTP content negotiation to determine how it should render the final response content. +The `Response` class subclasses Django's `SimpleTemplateResponse`. `Response` objects are initialised with data, which should consist of native Python primitives. REST framework then uses standard HTTP content negotiation to determine how it should render the final response content. There's no requirement for you to use the `Response` class, you can also return regular `HttpResponse` objects from your views if you want, but it provides a nicer interface for returning Web API responses. @@ -22,7 +22,7 @@ Unless you want to heavily customize REST framework for some reason, you should **Signature:** `Response(data, status=None, template_name=None, headers=None, content_type=None)` -Unlike regular `HttpResponse` objects, you do not instantiate `Response` objects with rendered content. Instead you pass in unrendered data, which may consist of any python primatives. +Unlike regular `HttpResponse` objects, you do not instantiate `Response` objects with rendered content. Instead you pass in unrendered data, which may consist of any Python primitives. The renderers used by the `Response` class cannot natively handle complex datatypes such as Django model instances, so you need to serialize the data into primative datatypes before creating the `Response` object. diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 0885eb52..f8761cb2 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -8,7 +8,7 @@ will take some serious design work. > > — Russell Keith-Magee, [Django users group][cite] -Serializers allow complex data such as querysets and model instances to be converted to native python datatypes that can then be easily rendered into `JSON`, `XML` or other content types. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data. +Serializers allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into `JSON`, `XML` or other content types. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data. REST framework's serializers work very similarly to Django's `Form` and `ModelForm` classes. It provides a `Serializer` class which gives you a powerful, generic way to control the output of your responses, as well as a `ModelSerializer` class which provides a useful shortcut for creating serializers that deal with model instances and querysets. @@ -57,7 +57,7 @@ We can now use `CommentSerializer` to serialize a comment, or list of comments. serializer.data # {'email': u'leila@example.com', 'content': u'foo bar', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)} -At this point we've translated the model instance into python native datatypes. To finalise the serialization process we render the data into `json`. +At this point we've translated the model instance into Python native datatypes. To finalise the serialization process we render the data into `json`. json = JSONRenderer().render(serializer.data) json @@ -65,7 +65,7 @@ At this point we've translated the model instance into python native datatypes. ## Deserializing objects -Deserialization is similar. First we parse a stream into python native datatypes... +Deserialization is similar. First we parse a stream into Python native datatypes... stream = StringIO(json) data = JSONParser().parse(stream) diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 8d8c00cf..4a5164c9 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -199,9 +199,9 @@ Default: `'format'` #### DATETIME_FORMAT -A format string that should be used by default for rendering the output of `DateTimeField` serializer fields. If `None`, then `DateTimeField` serializer fields will return python `datetime` objects, and the datetime encoding will be determined by the renderer. +A format string that should be used by default for rendering the output of `DateTimeField` serializer fields. If `None`, then `DateTimeField` serializer fields will return Python `datetime` objects, and the datetime encoding will be determined by the renderer. -May be any of `None`, `'iso-8601'` or a python [strftime format][strftime] string. +May be any of `None`, `'iso-8601'` or a Python [strftime format][strftime] string. Default: `None` @@ -209,15 +209,15 @@ Default: `None` A list of format strings that should be used by default for parsing inputs to `DateTimeField` serializer fields. -May be a list including the string `'iso-8601'` or python [strftime format][strftime] strings. +May be a list including the string `'iso-8601'` or Python [strftime format][strftime] strings. Default: `['iso-8601']` #### DATE_FORMAT -A format string that should be used by default for rendering the output of `DateField` serializer fields. If `None`, then `DateField` serializer fields will return python `date` objects, and the date encoding will be determined by the renderer. +A format string that should be used by default for rendering the output of `DateField` serializer fields. If `None`, then `DateField` serializer fields will return Python `date` objects, and the date encoding will be determined by the renderer. -May be any of `None`, `'iso-8601'` or a python [strftime format][strftime] string. +May be any of `None`, `'iso-8601'` or a Python [strftime format][strftime] string. Default: `None` @@ -225,15 +225,15 @@ Default: `None` A list of format strings that should be used by default for parsing inputs to `DateField` serializer fields. -May be a list including the string `'iso-8601'` or python [strftime format][strftime] strings. +May be a list including the string `'iso-8601'` or Python [strftime format][strftime] strings. Default: `['iso-8601']` #### TIME_FORMAT -A format string that should be used by default for rendering the output of `TimeField` serializer fields. If `None`, then `TimeField` serializer fields will return python `time` objects, and the time encoding will be determined by the renderer. +A format string that should be used by default for rendering the output of `TimeField` serializer fields. If `None`, then `TimeField` serializer fields will return Python `time` objects, and the time encoding will be determined by the renderer. -May be any of `None`, `'iso-8601'` or a python [strftime format][strftime] string. +May be any of `None`, `'iso-8601'` or a Python [strftime format][strftime] string. Default: `None` @@ -241,7 +241,7 @@ Default: `None` A list of format strings that should be used by default for parsing inputs to `TimeField` serializer fields. -May be a list including the string `'iso-8601'` or python [strftime format][strftime] strings. +May be a list including the string `'iso-8601'` or Python [strftime format][strftime] strings. Default: `['iso-8601']` diff --git a/docs/index.md b/docs/index.md index 0fb5706e..b04e2346 100644 --- a/docs/index.md +++ b/docs/index.md @@ -43,7 +43,7 @@ The following packages are optional: * [django-oauth-plus][django-oauth-plus] (2.0+) and [oauth2][oauth2] (1.5.211+) - OAuth 1.0a support. * [django-oauth2-provider][django-oauth2-provider] (0.2.3+) - OAuth 2.0 support. -**Note**: The `oauth2` python package is badly misnamed, and actually provides OAuth 1.0a support. Also note that packages required for both OAuth 1.0a, and OAuth 2.0 are not yet Python 3 compatible. +**Note**: The `oauth2` Python package is badly misnamed, and actually provides OAuth 1.0a support. Also note that packages required for both OAuth 1.0a, and OAuth 2.0 are not yet Python 3 compatible. ## Installation diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index bbb9b73c..bc31d234 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -175,13 +175,13 @@ We've now got a few snippet instances to play with. Let's take a look at serial serializer.data # {'pk': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'} -At this point we've translated the model instance into python native datatypes. To finalize the serialization process we render the data into `json`. +At this point we've translated the model instance into Python native datatypes. To finalize the serialization process we render the data into `json`. content = JSONRenderer().render(serializer.data) 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... import StringIO -- cgit v1.2.3 From 34f34f40b89080936ac8881ba9e77836a2641153 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 14 Jun 2013 12:43:34 +0100 Subject: Adding coveralls to travis builds --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6a453241..7f382db0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,7 @@ env: install: - pip install $DJANGO + - pip install coveralls - pip install defusedxml==0.3 - "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" @@ -23,7 +24,10 @@ install: - export PYTHONPATH=. script: - - python rest_framework/runtests/runtests.py + - coverage run --omit="rest_framework/tests/*,rest_framework/runtests/*,rest_framework/compat.py" rest_framework/runtests/runtests.py + +after_success: + - coveralls matrix: exclude: -- cgit v1.2.3 From 6cc4fe56373ba844a02173ea2efdf288d8fbac03 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 14 Jun 2013 12:55:48 +0100 Subject: Drop coveralls as seems to be screwing with things --- .travis.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7f382db0..6a453241 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,6 @@ env: install: - pip install $DJANGO - - pip install coveralls - pip install defusedxml==0.3 - "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" @@ -24,10 +23,7 @@ install: - export PYTHONPATH=. script: - - coverage run --omit="rest_framework/tests/*,rest_framework/runtests/*,rest_framework/compat.py" rest_framework/runtests/runtests.py - -after_success: - - coveralls + - python rest_framework/runtests/runtests.py matrix: exclude: -- cgit v1.2.3 From df957c8625c79e36c33f314c943a2c593f3a2701 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 14 Jun 2013 14:18:40 +0100 Subject: Fix and tests for ScopedRateThrottle. Closes #935 --- rest_framework/tests/test_throttling.py | 109 +++++++++++++++++++++++++++++++- rest_framework/throttling.py | 17 ++++- 2 files changed, 121 insertions(+), 5 deletions(-) diff --git a/rest_framework/tests/test_throttling.py b/rest_framework/tests/test_throttling.py index da400b2f..d35d3709 100644 --- a/rest_framework/tests/test_throttling.py +++ b/rest_framework/tests/test_throttling.py @@ -7,7 +7,7 @@ from django.contrib.auth.models import User from django.core.cache import cache from django.test.client import RequestFactory from rest_framework.views import APIView -from rest_framework.throttling import UserRateThrottle +from rest_framework.throttling import UserRateThrottle, ScopedRateThrottle from rest_framework.response import Response @@ -36,8 +36,6 @@ class MockView_MinuteThrottling(APIView): class ThrottlingTests(TestCase): - urls = 'rest_framework.tests.test_throttling' - def setUp(self): """ Reset the cache so that no throttles will be active @@ -141,3 +139,108 @@ class ThrottlingTests(TestCase): (60, None), (80, None) )) + + +class ScopedRateThrottleTests(TestCase): + """ + Tests for ScopedRateThrottle. + """ + + def setUp(self): + class XYScopedRateThrottle(ScopedRateThrottle): + TIMER_SECONDS = 0 + THROTTLE_RATES = {'x': '3/min', 'y': '1/min'} + timer = lambda self: self.TIMER_SECONDS + + class XView(APIView): + throttle_classes = (XYScopedRateThrottle,) + throttle_scope = 'x' + + def get(self, request): + return Response('x') + + class YView(APIView): + throttle_classes = (XYScopedRateThrottle,) + throttle_scope = 'y' + + def get(self, request): + return Response('y') + + class UnscopedView(APIView): + throttle_classes = (XYScopedRateThrottle,) + + def get(self, request): + return Response('y') + + self.throttle_class = XYScopedRateThrottle + self.factory = RequestFactory() + self.x_view = XView.as_view() + self.y_view = YView.as_view() + self.unscoped_view = UnscopedView.as_view() + + def increment_timer(self, seconds=1): + self.throttle_class.TIMER_SECONDS += seconds + + def test_scoped_rate_throttle(self): + request = self.factory.get('/') + + # Should be able to hit x view 3 times per minute. + response = self.x_view(request) + self.assertEqual(200, response.status_code) + + self.increment_timer() + response = self.x_view(request) + self.assertEqual(200, response.status_code) + + self.increment_timer() + response = self.x_view(request) + self.assertEqual(200, response.status_code) + + self.increment_timer() + response = self.x_view(request) + self.assertEqual(429, response.status_code) + + # Should be able to hit y view 1 time per minute. + self.increment_timer() + response = self.y_view(request) + self.assertEqual(200, response.status_code) + + self.increment_timer() + response = self.y_view(request) + self.assertEqual(429, response.status_code) + + # Ensure throttles properly reset by advancing the rest of the minute + self.increment_timer(55) + + # Should still be able to hit x view 3 times per minute. + response = self.x_view(request) + self.assertEqual(200, response.status_code) + + self.increment_timer() + response = self.x_view(request) + self.assertEqual(200, response.status_code) + + self.increment_timer() + response = self.x_view(request) + self.assertEqual(200, response.status_code) + + self.increment_timer() + response = self.x_view(request) + self.assertEqual(429, response.status_code) + + # Should still be able to hit y view 1 time per minute. + self.increment_timer() + response = self.y_view(request) + self.assertEqual(200, response.status_code) + + self.increment_timer() + response = self.y_view(request) + self.assertEqual(429, response.status_code) + + def test_unscoped_view_not_throttled(self): + request = self.factory.get('/') + + for idx in range(10): + self.increment_timer() + response = self.unscoped_view(request) + self.assertEqual(200, response.status_code) diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 9d89d1cb..ec9c80ff 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -40,9 +40,9 @@ class SimpleRateThrottle(BaseThrottle): """ timer = time.time - settings = api_settings cache_format = 'throtte_%(scope)s_%(ident)s' scope = None + THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES def __init__(self): if not getattr(self, 'rate', None): @@ -68,7 +68,7 @@ class SimpleRateThrottle(BaseThrottle): raise ImproperlyConfigured(msg) try: - return self.settings.DEFAULT_THROTTLE_RATES[self.scope] + return self.THROTTLE_RATES[self.scope] except KeyError: msg = "No default throttle rate set for '%s' scope" % self.scope raise ImproperlyConfigured(msg) @@ -187,6 +187,19 @@ class ScopedRateThrottle(SimpleRateThrottle): """ scope_attr = 'throttle_scope' + def __init__(self): + pass + + def allow_request(self, request, view): + self.scope = getattr(view, self.scope_attr, None) + + if not self.scope: + return True + + self.rate = self.get_rate() + self.num_requests, self.duration = self.parse_rate(self.rate) + return super(ScopedRateThrottle, self).allow_request(request, view) + def get_cache_key(self, request, view): """ If `view.throttle_scope` is not set, don't apply this throttle. -- cgit v1.2.3 From 52298480c2966b0a155a1e564fd2f1f594a50cae Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 14 Jun 2013 15:39:56 +0100 Subject: Clean up --- rest_framework/throttling.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index ec9c80ff..f6bb1cc8 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -188,16 +188,24 @@ class ScopedRateThrottle(SimpleRateThrottle): scope_attr = 'throttle_scope' def __init__(self): + # Override the usual SimpleRateThrottle, because we can't determine + # the rate until called by the view. pass def allow_request(self, request, view): + # We can only determine the scope once we're called by the view. self.scope = getattr(view, self.scope_attr, None) + # If a view does not have a `throttle_scope` always allow the request if not self.scope: return True + # Determine the allowed request rate as we normally would during + # the `__init__` call. self.rate = self.get_rate() self.num_requests, self.duration = self.parse_rate(self.rate) + + # We can now proceed as normal. return super(ScopedRateThrottle, self).allow_request(request, view) def get_cache_key(self, request, view): @@ -207,18 +215,12 @@ class ScopedRateThrottle(SimpleRateThrottle): Otherwise generate the unique cache key by concatenating the user id with the '.throttle_scope` property of the view. """ - scope = getattr(view, self.scope_attr, None) - - if not scope: - # Only throttle views if `.throttle_scope` is set on the view. - return None - if request.user.is_authenticated(): ident = request.user.id else: ident = request.META.get('REMOTE_ADDR', None) return self.cache_format % { - 'scope': scope, + 'scope': self.scope, 'ident': ident } -- cgit v1.2.3 From ef38886fe8379c8bea318f7a6aa000a3ae85db51 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 14 Jun 2013 15:40:20 +0100 Subject: Update release notes --- docs/topics/release-notes.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 9ac51f42..f49dd5c8 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -40,6 +40,14 @@ You can determine your currently installed version using `pip freeze`: ## 2.3.x series +### Master + +* Added `trailing_slash` option to routers. +* Support wider range of default serializer validation when used with custom model fields. +* Bugfix: Return error correctly when OAuth non-existent consumer occurs. +* Bugfix: Allow `FileUploadParser` to correctly filename if provided as URL kwarg. +* Bugfix: Fix `ScopedRateThrottle`. + ### 2.3.5 **Date**: 3rd June 2013 -- cgit v1.2.3 From c875a27edfb5769de95ecd6252fa2a9c3a2f6792 Mon Sep 17 00:00:00 2001 From: bigsassy Date: Sun, 16 Jun 2013 18:32:33 -0300 Subject: Update 1-serialization.md Fixed typo in documentation (Testarea to Textarea)--- 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 bc31d234..2b214d6a 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -146,7 +146,7 @@ The first thing we need to get started on our Web API is provide a way of serial 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. -Notice that we can also use various attributes that would typically be used on form fields, such as `widget=widgets.Testarea`. 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. +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. We can actually also save ourselves some time by using the `ModelSerializer` class, as we'll see later, but for now we'll keep our serializer definition explicit. -- cgit v1.2.3 From aa706f581c84209d83acf04ccd8854e205057e50 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 17 Jun 2013 09:27:12 +0100 Subject: Add Django OAuth Toolkit to docs --- docs/api-guide/authentication.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 09491f02..8cf995b3 100755 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -303,6 +303,10 @@ The command line to test the authentication looks like: curl -H "Authorization: Bearer " http://localhost:8000/api/ +### Alternative OAuth 2 implementations + +Note that [Django OAuth Toolkit][django-oauth-toolkit] is an alternative external package that also includes OAuth 2.0 support for REST framework. + --- # Custom authentication @@ -347,6 +351,10 @@ The following third party packages are also available. HTTP digest authentication is a widely implemented scheme that was intended to replace HTTP basic authentication, and which provides a simple encrypted authentication mechanism. [Juan Riaza][juanriaza] maintains the [djangorestframework-digestauth][djangorestframework-digestauth] package which provides HTTP digest authentication support for REST framework. +## Django OAuth Toolkit + +The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support, and works with Python 2.7 and Python 3.3+. The package is maintained by [Evonove][evonove] and uses the excelllent [OAuthLib][oauthlib]. The package is well documented, and comes as a recommended alternative for OAuth 2.0 support. + [cite]: http://jacobian.org/writing/rest-worst-practices/ [http401]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2 [http403]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4 @@ -365,3 +373,6 @@ HTTP digest authentication is a widely implemented scheme that was intended to r [django-oauth2-provider]: https://github.com/caffeinehit/django-oauth2-provider [django-oauth2-provider-docs]: https://django-oauth2-provider.readthedocs.org/en/latest/ [rfc6749]: http://tools.ietf.org/html/rfc6749 +[django-oauth-toolkit]: https://github.com/evonove/django-oauth-toolkit +[evonove]: https://github.com/evonove/ +[oauthlib]: https://github.com/idan/oauthlib -- cgit v1.2.3 From 4ad10949687c271fed930cd1142aa23cd8c20b2b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 17 Jun 2013 15:09:36 +0100 Subject: HyperlinkedModelSerializer supports overriding of 'url' field. Closes #936 --- rest_framework/serializers.py | 43 ++++++++++------------ .../tests/test_hyperlinkedserializers.py | 27 ++++++++++++++ tmp.html | 0 3 files changed, 47 insertions(+), 23 deletions(-) create mode 100644 tmp.html diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 11ead02e..5a8fd89f 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -904,34 +904,20 @@ class HyperlinkedModelSerializer(ModelSerializer): _default_view_name = '%(model_name)s-detail' _hyperlink_field_class = HyperlinkedRelatedField - # Just a placeholder to ensure 'url' is the first field - # The field itself is actually created on initialization, - # when the view_name and lookup_field arguments are available. - url = Field() - - def __init__(self, *args, **kwargs): - super(HyperlinkedModelSerializer, self).__init__(*args, **kwargs) + def get_default_fields(self): + fields = super(HyperlinkedModelSerializer, self).get_default_fields() if self.opts.view_name is None: self.opts.view_name = self._get_default_view_name(self.opts.model) - url_field = HyperlinkedIdentityField( - view_name=self.opts.view_name, - lookup_field=self.opts.lookup_field - ) - url_field.initialize(self, 'url') - self.fields['url'] = url_field + if 'url' not in fields: + url_field = HyperlinkedIdentityField( + view_name=self.opts.view_name, + lookup_field=self.opts.lookup_field + ) + fields.insert(0, 'url', url_field) - def _get_default_view_name(self, model): - """ - Return the view name to use if 'view_name' is not specified in 'Meta' - """ - model_meta = model._meta - format_kwargs = { - 'app_label': model_meta.app_label, - 'model_name': model_meta.object_name.lower() - } - return self._default_view_name % format_kwargs + return fields def get_pk_field(self, model_field): if self.opts.fields and model_field.name in self.opts.fields: @@ -966,3 +952,14 @@ class HyperlinkedModelSerializer(ModelSerializer): return data.get('url', None) except AttributeError: return None + + def _get_default_view_name(self, model): + """ + Return the view name to use if 'view_name' is not specified in 'Meta' + """ + model_meta = model._meta + format_kwargs = { + 'app_label': model_meta.app_label, + 'model_name': model_meta.object_name.lower() + } + return self._default_view_name % format_kwargs diff --git a/rest_framework/tests/test_hyperlinkedserializers.py b/rest_framework/tests/test_hyperlinkedserializers.py index 1894ddb2..129600cb 100644 --- a/rest_framework/tests/test_hyperlinkedserializers.py +++ b/rest_framework/tests/test_hyperlinkedserializers.py @@ -301,3 +301,30 @@ class TestOptionalRelationHyperlinkedView(TestCase): data=json.dumps(self.data), content_type='application/json') self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class TestOverriddenURLField(TestCase): + def setUp(self): + class OverriddenURLSerializer(serializers.HyperlinkedModelSerializer): + url = serializers.SerializerMethodField('get_url') + + class Meta: + model = BlogPost + fields = ('title', 'url') + + def get_url(self, obj): + return 'foo bar' + + self.Serializer = OverriddenURLSerializer + self.obj = BlogPost.objects.create(title='New blog post') + + def test_overridden_url_field(self): + """ + The 'url' field should respect overriding. + Regression test for #936. + """ + serializer = self.Serializer(self.obj) + self.assertEqual( + serializer.data, + {'title': 'New blog post', 'url': 'foo bar'} + ) diff --git a/tmp.html b/tmp.html new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3 From da62987b8225c23513056430e222d40c46466d8e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 17 Jun 2013 15:10:53 +0100 Subject: Remove erronous checkin --- tmp.html | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tmp.html diff --git a/tmp.html b/tmp.html deleted file mode 100644 index e69de29b..00000000 -- cgit v1.2.3 From 6d2ca75d8e2403a17fa7cb2d112f5241a0247226 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 18 Jun 2013 11:10:56 +0100 Subject: Don't raise AttributeError on views with no model or queryset, when using DjangoModelPermissions --- rest_framework/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index 45fcfd66..1036663e 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -128,7 +128,7 @@ class DjangoModelPermissions(BasePermission): # Workaround to ensure DjangoModelPermissions are not applied # to the root view when using DefaultRouter. - if model_cls is None and getattr(view, '_ignore_model_permissions'): + if model_cls is None and getattr(view, '_ignore_model_permissions', False): return True assert model_cls, ('Cannot apply DjangoModelPermissions on a view that' -- cgit v1.2.3 From f3529f1f4a3b01c2821278da3bafbc04c1c00553 Mon Sep 17 00:00:00 2001 From: Philip Douglas Date: Fri, 21 Jun 2013 16:26:28 +0100 Subject: Correct docs' incorrect usage of action decorator If you don't call it, it doesn't work. --- docs/api-guide/viewsets.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 79257e2a..25d11bfb 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -27,7 +27,7 @@ Let's define a simple viewset that can be used to list or retrieve all the users queryset = User.objects.all() serializer = UserSerializer(queryset, many=True) return Response(serializer.data) - + def retrieve(self, request, pk=None): queryset = User.objects.all() user = get_object_or_404(queryset, pk=pk) @@ -69,7 +69,7 @@ The default routers included with REST framework will provide routes for a stand """ Example empty viewset demonstrating the standard actions that will be handled by a router class. - + If you're using format suffixes, make sure to also include the `format=None` keyword argument for each action. """ @@ -103,12 +103,12 @@ For example: class UserViewSet(viewsets.ModelViewSet): """ - A viewset that provides the standard actions + A viewset that provides the standard actions """ queryset = User.objects.all() serializer_class = UserSerializer - - @action + + @action() def set_password(self, request, pk=None): user = self.get_object() serializer = PasswordSerializer(data=request.DATA) @@ -197,7 +197,7 @@ As with `ModelViewSet`, you'll normally need to provide at least the `queryset` Again, as with `ModelViewSet`, you can use any of the standard attributes and method overrides available to `GenericAPIView`. -# Custom ViewSet base classes +# Custom ViewSet base classes You may need to provide custom `ViewSet` classes that do not have the full set of `ModelViewSet` actions, or that customize the behavior in some other way. @@ -211,7 +211,7 @@ To create a base viewset class that provides `create`, `list` and `retrieve` ope viewsets.GenericViewSet): """ A viewset that provides `retrieve`, `update`, and `list` actions. - + To use it, override the class and set the `.queryset` and `.serializer_class` attributes. """ -- cgit v1.2.3 From fa9f5fb8dcf6d51b2db70d4e2a991779b056d1d4 Mon Sep 17 00:00:00 2001 From: Philip Douglas Date: Fri, 21 Jun 2013 16:28:17 +0100 Subject: Allow uppercase methods in action decorator. Previously, using uppercase for the method argument would silently fail to route those methods. --- rest_framework/routers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rest_framework/routers.py b/rest_framework/routers.py index f70c2cdb..ae64cc3b 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -136,6 +136,7 @@ class SimpleRouter(BaseRouter): attr = getattr(viewset, methodname) httpmethods = getattr(attr, 'bind_to_methods', None) if httpmethods: + httpmethods = [method.lower() for method in httpmethods] dynamic_routes.append((httpmethods, methodname)) ret = [] -- cgit v1.2.3 From 3d4bb4b5533fa281c2f11c12ceb0a9ae61aa0d54 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 22:03:07 +0100 Subject: Ensure action kwargs properly handdled. Refs #940. --- htmlcov/coverage_html.js | 372 ++++ htmlcov/index.html | 404 +++++ htmlcov/jquery-1.4.3.min.js | 166 ++ htmlcov/jquery.hotkeys.js | 99 + htmlcov/jquery.isonscreen.js | 53 + htmlcov/jquery.tablesorter.min.js | 2 + htmlcov/keybd_closed.png | Bin 0 -> 264 bytes htmlcov/keybd_open.png | Bin 0 -> 267 bytes htmlcov/rest_framework___init__.html | 99 + htmlcov/rest_framework_authentication.html | 767 ++++++++ htmlcov/rest_framework_authtoken___init__.html | 81 + htmlcov/rest_framework_authtoken_models.html | 151 ++ htmlcov/rest_framework_authtoken_serializers.html | 129 ++ htmlcov/rest_framework_authtoken_views.html | 133 ++ htmlcov/rest_framework_decorators.html | 339 ++++ htmlcov/rest_framework_exceptions.html | 257 +++ htmlcov/rest_framework_fields.html | 1991 ++++++++++++++++++++ htmlcov/rest_framework_filters.html | 367 ++++ htmlcov/rest_framework_generics.html | 1079 +++++++++++ htmlcov/rest_framework_mixins.html | 449 +++++ htmlcov/rest_framework_models.html | 83 + htmlcov/rest_framework_negotiation.html | 259 +++ htmlcov/rest_framework_pagination.html | 269 +++ htmlcov/rest_framework_parsers.html | 671 +++++++ htmlcov/rest_framework_permissions.html | 429 +++++ htmlcov/rest_framework_relations.html | 1347 ++++++++++++++ htmlcov/rest_framework_renderers.html | 1227 +++++++++++++ htmlcov/rest_framework_request.html | 819 +++++++++ htmlcov/rest_framework_response.html | 249 +++ htmlcov/rest_framework_reverse.html | 127 ++ htmlcov/rest_framework_routers.html | 595 ++++++ htmlcov/rest_framework_serializers.html | 2011 +++++++++++++++++++++ htmlcov/rest_framework_settings.html | 465 +++++ htmlcov/rest_framework_status.html | 187 ++ htmlcov/rest_framework_throttling.html | 533 ++++++ htmlcov/rest_framework_urlpatterns.html | 205 +++ htmlcov/rest_framework_urls.html | 129 ++ htmlcov/rest_framework_utils___init__.html | 81 + htmlcov/rest_framework_utils_breadcrumbs.html | 189 ++ htmlcov/rest_framework_utils_encoders.html | 275 +++ htmlcov/rest_framework_utils_formatting.html | 241 +++ htmlcov/rest_framework_utils_mediatypes.html | 257 +++ htmlcov/rest_framework_views.html | 793 ++++++++ htmlcov/rest_framework_viewsets.html | 359 ++++ htmlcov/status.dat | 1258 +++++++++++++ htmlcov/style.css | 275 +++ rest_framework/tests/test_routers.py | 35 +- 47 files changed, 20303 insertions(+), 3 deletions(-) create mode 100644 htmlcov/coverage_html.js create mode 100644 htmlcov/index.html create mode 100644 htmlcov/jquery-1.4.3.min.js create mode 100644 htmlcov/jquery.hotkeys.js create mode 100644 htmlcov/jquery.isonscreen.js create mode 100644 htmlcov/jquery.tablesorter.min.js create mode 100644 htmlcov/keybd_closed.png create mode 100644 htmlcov/keybd_open.png create mode 100644 htmlcov/rest_framework___init__.html create mode 100644 htmlcov/rest_framework_authentication.html create mode 100644 htmlcov/rest_framework_authtoken___init__.html create mode 100644 htmlcov/rest_framework_authtoken_models.html create mode 100644 htmlcov/rest_framework_authtoken_serializers.html create mode 100644 htmlcov/rest_framework_authtoken_views.html create mode 100644 htmlcov/rest_framework_decorators.html create mode 100644 htmlcov/rest_framework_exceptions.html create mode 100644 htmlcov/rest_framework_fields.html create mode 100644 htmlcov/rest_framework_filters.html create mode 100644 htmlcov/rest_framework_generics.html create mode 100644 htmlcov/rest_framework_mixins.html create mode 100644 htmlcov/rest_framework_models.html create mode 100644 htmlcov/rest_framework_negotiation.html create mode 100644 htmlcov/rest_framework_pagination.html create mode 100644 htmlcov/rest_framework_parsers.html create mode 100644 htmlcov/rest_framework_permissions.html create mode 100644 htmlcov/rest_framework_relations.html create mode 100644 htmlcov/rest_framework_renderers.html create mode 100644 htmlcov/rest_framework_request.html create mode 100644 htmlcov/rest_framework_response.html create mode 100644 htmlcov/rest_framework_reverse.html create mode 100644 htmlcov/rest_framework_routers.html create mode 100644 htmlcov/rest_framework_serializers.html create mode 100644 htmlcov/rest_framework_settings.html create mode 100644 htmlcov/rest_framework_status.html create mode 100644 htmlcov/rest_framework_throttling.html create mode 100644 htmlcov/rest_framework_urlpatterns.html create mode 100644 htmlcov/rest_framework_urls.html create mode 100644 htmlcov/rest_framework_utils___init__.html create mode 100644 htmlcov/rest_framework_utils_breadcrumbs.html create mode 100644 htmlcov/rest_framework_utils_encoders.html create mode 100644 htmlcov/rest_framework_utils_formatting.html create mode 100644 htmlcov/rest_framework_utils_mediatypes.html create mode 100644 htmlcov/rest_framework_views.html create mode 100644 htmlcov/rest_framework_viewsets.html create mode 100644 htmlcov/status.dat create mode 100644 htmlcov/style.css diff --git a/htmlcov/coverage_html.js b/htmlcov/coverage_html.js new file mode 100644 index 00000000..da3e22c8 --- /dev/null +++ b/htmlcov/coverage_html.js @@ -0,0 +1,372 @@ +// Coverage.py HTML report browser code. +/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */ +/*global coverage: true, document, window, $ */ + +coverage = {}; + +// Find all the elements with shortkey_* class, and use them to assign a shotrtcut key. +coverage.assign_shortkeys = function () { + $("*[class*='shortkey_']").each(function (i, e) { + $.each($(e).attr("class").split(" "), function (i, c) { + if (/^shortkey_/.test(c)) { + $(document).bind('keydown', c.substr(9), function () { + $(e).click(); + }); + } + }); + }); +}; + +// Create the events for the help panel. +coverage.wire_up_help_panel = function () { + $("#keyboard_icon").click(function () { + // Show the help panel, and position it so the keyboard icon in the + // panel is in the same place as the keyboard icon in the header. + $(".help_panel").show(); + var koff = $("#keyboard_icon").offset(); + var poff = $("#panel_icon").position(); + $(".help_panel").offset({ + top: koff.top-poff.top, + left: koff.left-poff.left + }); + }); + $("#panel_icon").click(function () { + $(".help_panel").hide(); + }); +}; + +// Loaded on index.html +coverage.index_ready = function ($) { + // Look for a cookie containing previous sort settings: + var sort_list = []; + var cookie_name = "COVERAGE_INDEX_SORT"; + var i; + + // This almost makes it worth installing the jQuery cookie plugin: + if (document.cookie.indexOf(cookie_name) > -1) { + var cookies = document.cookie.split(";"); + for (i = 0; i < cookies.length; i++) { + var parts = cookies[i].split("="); + + if ($.trim(parts[0]) === cookie_name && parts[1]) { + sort_list = eval("[[" + parts[1] + "]]"); + break; + } + } + } + + // Create a new widget which exists only to save and restore + // the sort order: + $.tablesorter.addWidget({ + id: "persistentSort", + + // Format is called by the widget before displaying: + format: function (table) { + if (table.config.sortList.length === 0 && sort_list.length > 0) { + // This table hasn't been sorted before - we'll use + // our stored settings: + $(table).trigger('sorton', [sort_list]); + } + else { + // This is not the first load - something has + // already defined sorting so we'll just update + // our stored value to match: + sort_list = table.config.sortList; + } + } + }); + + // Configure our tablesorter to handle the variable number of + // columns produced depending on report options: + var headers = []; + var col_count = $("table.index > thead > tr > th").length; + + headers[0] = { sorter: 'text' }; + for (i = 1; i < col_count-1; i++) { + headers[i] = { sorter: 'digit' }; + } + headers[col_count-1] = { sorter: 'percent' }; + + // Enable the table sorter: + $("table.index").tablesorter({ + widgets: ['persistentSort'], + headers: headers + }); + + coverage.assign_shortkeys(); + coverage.wire_up_help_panel(); + + // Watch for page unload events so we can save the final sort settings: + $(window).unload(function () { + document.cookie = cookie_name + "=" + sort_list.toString() + "; path=/"; + }); +}; + +// -- pyfile stuff -- + +coverage.pyfile_ready = function ($) { + // If we're directed to a particular line number, highlight the line. + var frag = location.hash; + if (frag.length > 2 && frag[1] === 'n') { + $(frag).addClass('highlight'); + coverage.set_sel(parseInt(frag.substr(2), 10)); + } + else { + coverage.set_sel(0); + } + + $(document) + .bind('keydown', 'j', coverage.to_next_chunk_nicely) + .bind('keydown', 'k', coverage.to_prev_chunk_nicely) + .bind('keydown', '0', coverage.to_top) + .bind('keydown', '1', coverage.to_first_chunk) + ; + + coverage.assign_shortkeys(); + coverage.wire_up_help_panel(); +}; + +coverage.toggle_lines = function (btn, cls) { + btn = $(btn); + var hide = "hide_"+cls; + if (btn.hasClass(hide)) { + $("#source ."+cls).removeClass(hide); + btn.removeClass(hide); + } + else { + $("#source ."+cls).addClass(hide); + btn.addClass(hide); + } +}; + +// Return the nth line div. +coverage.line_elt = function (n) { + return $("#t" + n); +}; + +// Return the nth line number div. +coverage.num_elt = function (n) { + return $("#n" + n); +}; + +// Return the container of all the code. +coverage.code_container = function () { + return $(".linenos"); +}; + +// Set the selection. b and e are line numbers. +coverage.set_sel = function (b, e) { + // The first line selected. + coverage.sel_begin = b; + // The next line not selected. + coverage.sel_end = (e === undefined) ? b+1 : e; +}; + +coverage.to_top = function () { + coverage.set_sel(0, 1); + coverage.scroll_window(0); +}; + +coverage.to_first_chunk = function () { + coverage.set_sel(0, 1); + coverage.to_next_chunk(); +}; + +coverage.is_transparent = function (color) { + // Different browsers return different colors for "none". + return color === "transparent" || color === "rgba(0, 0, 0, 0)"; +}; + +coverage.to_next_chunk = function () { + var c = coverage; + + // Find the start of the next colored chunk. + var probe = c.sel_end; + while (true) { + var probe_line = c.line_elt(probe); + if (probe_line.length === 0) { + return; + } + var color = probe_line.css("background-color"); + if (!c.is_transparent(color)) { + break; + } + probe++; + } + + // There's a next chunk, `probe` points to it. + var begin = probe; + + // Find the end of this chunk. + var next_color = color; + while (next_color === color) { + probe++; + probe_line = c.line_elt(probe); + next_color = probe_line.css("background-color"); + } + c.set_sel(begin, probe); + c.show_selection(); +}; + +coverage.to_prev_chunk = function () { + var c = coverage; + + // Find the end of the prev colored chunk. + var probe = c.sel_begin-1; + var probe_line = c.line_elt(probe); + if (probe_line.length === 0) { + return; + } + var color = probe_line.css("background-color"); + while (probe > 0 && c.is_transparent(color)) { + probe--; + probe_line = c.line_elt(probe); + if (probe_line.length === 0) { + return; + } + color = probe_line.css("background-color"); + } + + // There's a prev chunk, `probe` points to its last line. + var end = probe+1; + + // Find the beginning of this chunk. + var prev_color = color; + while (prev_color === color) { + probe--; + probe_line = c.line_elt(probe); + prev_color = probe_line.css("background-color"); + } + c.set_sel(probe+1, end); + c.show_selection(); +}; + +// Return the line number of the line nearest pixel position pos +coverage.line_at_pos = function (pos) { + var l1 = coverage.line_elt(1), + l2 = coverage.line_elt(2), + result; + if (l1.length && l2.length) { + var l1_top = l1.offset().top, + line_height = l2.offset().top - l1_top, + nlines = (pos - l1_top) / line_height; + if (nlines < 1) { + result = 1; + } + else { + result = Math.ceil(nlines); + } + } + else { + result = 1; + } + return result; +}; + +// Returns 0, 1, or 2: how many of the two ends of the selection are on +// the screen right now? +coverage.selection_ends_on_screen = function () { + if (coverage.sel_begin === 0) { + return 0; + } + + var top = coverage.line_elt(coverage.sel_begin); + var next = coverage.line_elt(coverage.sel_end-1); + + return ( + (top.isOnScreen() ? 1 : 0) + + (next.isOnScreen() ? 1 : 0) + ); +}; + +coverage.to_next_chunk_nicely = function () { + coverage.finish_scrolling(); + if (coverage.selection_ends_on_screen() === 0) { + // The selection is entirely off the screen: select the top line on + // the screen. + var win = $(window); + coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop())); + } + coverage.to_next_chunk(); +}; + +coverage.to_prev_chunk_nicely = function () { + coverage.finish_scrolling(); + if (coverage.selection_ends_on_screen() === 0) { + var win = $(window); + coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop() + win.height())); + } + coverage.to_prev_chunk(); +}; + +// Select line number lineno, or if it is in a colored chunk, select the +// entire chunk +coverage.select_line_or_chunk = function (lineno) { + var c = coverage; + var probe_line = c.line_elt(lineno); + if (probe_line.length === 0) { + return; + } + var the_color = probe_line.css("background-color"); + if (!c.is_transparent(the_color)) { + // The line is in a highlighted chunk. + // Search backward for the first line. + var probe = lineno; + var color = the_color; + while (probe > 0 && color === the_color) { + probe--; + probe_line = c.line_elt(probe); + if (probe_line.length === 0) { + break; + } + color = probe_line.css("background-color"); + } + var begin = probe + 1; + + // Search forward for the last line. + probe = lineno; + color = the_color; + while (color === the_color) { + probe++; + probe_line = c.line_elt(probe); + color = probe_line.css("background-color"); + } + + coverage.set_sel(begin, probe); + } + else { + coverage.set_sel(lineno); + } +}; + +coverage.show_selection = function () { + var c = coverage; + + // Highlight the lines in the chunk + c.code_container().find(".highlight").removeClass("highlight"); + for (var probe = c.sel_begin; probe > 0 && probe < c.sel_end; probe++) { + c.num_elt(probe).addClass("highlight"); + } + + c.scroll_to_selection(); +}; + +coverage.scroll_to_selection = function () { + // Scroll the page if the chunk isn't fully visible. + if (coverage.selection_ends_on_screen() < 2) { + // Need to move the page. The html,body trick makes it scroll in all + // browsers, got it from http://stackoverflow.com/questions/3042651 + var top = coverage.line_elt(coverage.sel_begin); + var top_pos = parseInt(top.offset().top, 10); + coverage.scroll_window(top_pos - 30); + } +}; + +coverage.scroll_window = function (to_pos) { + $("html,body").animate({scrollTop: to_pos}, 200); +}; + +coverage.finish_scrolling = function () { + $("html,body").stop(true, true); +}; + diff --git a/htmlcov/index.html b/htmlcov/index.html new file mode 100644 index 00000000..98345165 --- /dev/null +++ b/htmlcov/index.html @@ -0,0 +1,404 @@ + + + + + Coverage report + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ n + s + m + x + + c   change column sorting +

+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Modulestatementsmissingexcludedcoverage
Total3621406089%
rest_framework/__init__400100%
rest_framework/authentication16933080%
rest_framework/authtoken/__init__000100%
rest_framework/authtoken/models211095%
rest_framework/authtoken/serializers172088%
rest_framework/authtoken/views2100100%
rest_framework/decorators6000100%
rest_framework/exceptions512096%
rest_framework/fields59480087%
rest_framework/filters776092%
rest_framework/generics19634083%
rest_framework/mixins977093%
rest_framework/models000100%
rest_framework/negotiation414090%
rest_framework/pagination4300100%
rest_framework/parsers15313092%
rest_framework/permissions6312081%
rest_framework/relations36588076%
rest_framework/renderers28223092%
rest_framework/request1618095%
rest_framework/response421098%
rest_framework/reverse123075%
rest_framework/routers1087094%
rest_framework/serializers46427094%
rest_framework/settings442095%
rest_framework/status4600100%
rest_framework/throttling9017081%
rest_framework/urlpatterns314087%
rest_framework/urls400100%
rest_framework/utils/__init__000100%
rest_framework/utils/breadcrumbs2700100%
rest_framework/utils/encoders7019073%
rest_framework/utils/formatting391097%
rest_framework/utils/mediatypes4410077%
rest_framework/views14600100%
rest_framework/viewsets392095%
+
+ + + + + diff --git a/htmlcov/jquery-1.4.3.min.js b/htmlcov/jquery-1.4.3.min.js new file mode 100644 index 00000000..c941a5f7 --- /dev/null +++ b/htmlcov/jquery-1.4.3.min.js @@ -0,0 +1,166 @@ +/*! + * jQuery JavaScript Library v1.4.3 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Thu Oct 14 23:10:06 2010 -0400 + */ +(function(E,A){function U(){return false}function ba(){return true}function ja(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function Ga(a){var b,d,e=[],f=[],h,k,l,n,s,v,B,D;k=c.data(this,this.nodeType?"events":"__events__");if(typeof k==="function")k=k.events;if(!(a.liveFired===this||!k||!k.live||a.button&&a.type==="click")){if(a.namespace)D=RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)");a.liveFired=this;var H=k.live.slice(0);for(n=0;nd)break;a.currentTarget=f.elem;a.data=f.handleObj.data; +a.handleObj=f.handleObj;D=f.handleObj.origHandler.apply(f.elem,arguments);if(D===false||a.isPropagationStopped()){d=f.level;if(D===false)b=false}}return b}}function Y(a,b){return(a&&a!=="*"?a+".":"")+b.replace(Ha,"`").replace(Ia,"&")}function ka(a,b,d){if(c.isFunction(b))return c.grep(a,function(f,h){return!!b.call(f,h,f)===d});else if(b.nodeType)return c.grep(a,function(f){return f===b===d});else if(typeof b==="string"){var e=c.grep(a,function(f){return f.nodeType===1});if(Ja.test(b))return c.filter(b, +e,!d);else b=c.filter(b,e)}return c.grep(a,function(f){return c.inArray(f,b)>=0===d})}function la(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var e=c.data(a[d++]),f=c.data(this,e);if(e=e&&e.events){delete f.handle;f.events={};for(var h in e)for(var k in e[h])c.event.add(this,h,e[h][k],e[h][k].data)}}})}function Ka(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)} +function ma(a,b,d){var e=b==="width"?a.offsetWidth:a.offsetHeight;if(d==="border")return e;c.each(b==="width"?La:Ma,function(){d||(e-=parseFloat(c.css(a,"padding"+this))||0);if(d==="margin")e+=parseFloat(c.css(a,"margin"+this))||0;else e-=parseFloat(c.css(a,"border"+this+"Width"))||0});return e}function ca(a,b,d,e){if(c.isArray(b)&&b.length)c.each(b,function(f,h){d||Na.test(a)?e(a,h):ca(a+"["+(typeof h==="object"||c.isArray(h)?f:"")+"]",h,d,e)});else if(!d&&b!=null&&typeof b==="object")c.isEmptyObject(b)? +e(a,""):c.each(b,function(f,h){ca(a+"["+f+"]",h,d,e)});else e(a,b)}function S(a,b){var d={};c.each(na.concat.apply([],na.slice(0,b)),function(){d[this]=a});return d}function oa(a){if(!da[a]){var b=c("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d==="")d="block";da[a]=d}return da[a]}function ea(a){return c.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var u=E.document,c=function(){function a(){if(!b.isReady){try{u.documentElement.doScroll("left")}catch(i){setTimeout(a, +1);return}b.ready()}}var b=function(i,r){return new b.fn.init(i,r)},d=E.jQuery,e=E.$,f,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,k=/\S/,l=/^\s+/,n=/\s+$/,s=/\W/,v=/\d/,B=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,D=/^[\],:{}\s]*$/,H=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,w=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,G=/(?:^|:|,)(?:\s*\[)+/g,M=/(webkit)[ \/]([\w.]+)/,g=/(opera)(?:.*version)?[ \/]([\w.]+)/,j=/(msie) ([\w.]+)/,o=/(mozilla)(?:.*? rv:([\w.]+))?/,m=navigator.userAgent,p=false, +q=[],t,x=Object.prototype.toString,C=Object.prototype.hasOwnProperty,P=Array.prototype.push,N=Array.prototype.slice,R=String.prototype.trim,Q=Array.prototype.indexOf,L={};b.fn=b.prototype={init:function(i,r){var y,z,F;if(!i)return this;if(i.nodeType){this.context=this[0]=i;this.length=1;return this}if(i==="body"&&!r&&u.body){this.context=u;this[0]=u.body;this.selector="body";this.length=1;return this}if(typeof i==="string")if((y=h.exec(i))&&(y[1]||!r))if(y[1]){F=r?r.ownerDocument||r:u;if(z=B.exec(i))if(b.isPlainObject(r)){i= +[u.createElement(z[1])];b.fn.attr.call(i,r,true)}else i=[F.createElement(z[1])];else{z=b.buildFragment([y[1]],[F]);i=(z.cacheable?z.fragment.cloneNode(true):z.fragment).childNodes}return b.merge(this,i)}else{if((z=u.getElementById(y[2]))&&z.parentNode){if(z.id!==y[2])return f.find(i);this.length=1;this[0]=z}this.context=u;this.selector=i;return this}else if(!r&&!s.test(i)){this.selector=i;this.context=u;i=u.getElementsByTagName(i);return b.merge(this,i)}else return!r||r.jquery?(r||f).find(i):b(r).find(i); +else if(b.isFunction(i))return f.ready(i);if(i.selector!==A){this.selector=i.selector;this.context=i.context}return b.makeArray(i,this)},selector:"",jquery:"1.4.3",length:0,size:function(){return this.length},toArray:function(){return N.call(this,0)},get:function(i){return i==null?this.toArray():i<0?this.slice(i)[0]:this[i]},pushStack:function(i,r,y){var z=b();b.isArray(i)?P.apply(z,i):b.merge(z,i);z.prevObject=this;z.context=this.context;if(r==="find")z.selector=this.selector+(this.selector?" ": +"")+y;else if(r)z.selector=this.selector+"."+r+"("+y+")";return z},each:function(i,r){return b.each(this,i,r)},ready:function(i){b.bindReady();if(b.isReady)i.call(u,b);else q&&q.push(i);return this},eq:function(i){return i===-1?this.slice(i):this.slice(i,+i+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(N.apply(this,arguments),"slice",N.call(arguments).join(","))},map:function(i){return this.pushStack(b.map(this,function(r,y){return i.call(r, +y,r)}))},end:function(){return this.prevObject||b(null)},push:P,sort:[].sort,splice:[].splice};b.fn.init.prototype=b.fn;b.extend=b.fn.extend=function(){var i=arguments[0]||{},r=1,y=arguments.length,z=false,F,I,K,J,fa;if(typeof i==="boolean"){z=i;i=arguments[1]||{};r=2}if(typeof i!=="object"&&!b.isFunction(i))i={};if(y===r){i=this;--r}for(;r0)){if(q){for(var r=0;i=q[r++];)i.call(u,b);q=null}b.fn.triggerHandler&&b(u).triggerHandler("ready")}}},bindReady:function(){if(!p){p=true;if(u.readyState==="complete")return setTimeout(b.ready, +1);if(u.addEventListener){u.addEventListener("DOMContentLoaded",t,false);E.addEventListener("load",b.ready,false)}else if(u.attachEvent){u.attachEvent("onreadystatechange",t);E.attachEvent("onload",b.ready);var i=false;try{i=E.frameElement==null}catch(r){}u.documentElement.doScroll&&i&&a()}}},isFunction:function(i){return b.type(i)==="function"},isArray:Array.isArray||function(i){return b.type(i)==="array"},isWindow:function(i){return i&&typeof i==="object"&&"setInterval"in i},isNaN:function(i){return i== +null||!v.test(i)||isNaN(i)},type:function(i){return i==null?String(i):L[x.call(i)]||"object"},isPlainObject:function(i){if(!i||b.type(i)!=="object"||i.nodeType||b.isWindow(i))return false;if(i.constructor&&!C.call(i,"constructor")&&!C.call(i.constructor.prototype,"isPrototypeOf"))return false;for(var r in i);return r===A||C.call(i,r)},isEmptyObject:function(i){for(var r in i)return false;return true},error:function(i){throw i;},parseJSON:function(i){if(typeof i!=="string"||!i)return null;i=b.trim(i); +if(D.test(i.replace(H,"@").replace(w,"]").replace(G,"")))return E.JSON&&E.JSON.parse?E.JSON.parse(i):(new Function("return "+i))();else b.error("Invalid JSON: "+i)},noop:function(){},globalEval:function(i){if(i&&k.test(i)){var r=u.getElementsByTagName("head")[0]||u.documentElement,y=u.createElement("script");y.type="text/javascript";if(b.support.scriptEval)y.appendChild(u.createTextNode(i));else y.text=i;r.insertBefore(y,r.firstChild);r.removeChild(y)}},nodeName:function(i,r){return i.nodeName&&i.nodeName.toUpperCase()=== +r.toUpperCase()},each:function(i,r,y){var z,F=0,I=i.length,K=I===A||b.isFunction(i);if(y)if(K)for(z in i){if(r.apply(i[z],y)===false)break}else for(;F";a=u.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var s=u.createElement("div"); +s.style.width=s.style.paddingLeft="1px";u.body.appendChild(s);c.boxModel=c.support.boxModel=s.offsetWidth===2;if("zoom"in s.style){s.style.display="inline";s.style.zoom=1;c.support.inlineBlockNeedsLayout=s.offsetWidth===2;s.style.display="";s.innerHTML="
";c.support.shrinkWrapBlocks=s.offsetWidth!==2}s.innerHTML="
t
";var v=s.getElementsByTagName("td");c.support.reliableHiddenOffsets=v[0].offsetHeight=== +0;v[0].style.display="";v[1].style.display="none";c.support.reliableHiddenOffsets=c.support.reliableHiddenOffsets&&v[0].offsetHeight===0;s.innerHTML="";u.body.removeChild(s).style.display="none"});a=function(s){var v=u.createElement("div");s="on"+s;var B=s in v;if(!B){v.setAttribute(s,"return;");B=typeof v[s]==="function"}return B};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=f=h=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength", +cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var pa={},Oa=/^(?:\{.*\}|\[.*\])$/;c.extend({cache:{},uuid:0,expando:"jQuery"+c.now(),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},data:function(a,b,d){if(c.acceptData(a)){a=a==E?pa:a;var e=a.nodeType,f=e?a[c.expando]:null,h=c.cache;if(!(e&&!f&&typeof b==="string"&&d===A)){if(e)f||(a[c.expando]=f=++c.uuid);else h=a;if(typeof b==="object")if(e)h[f]= +c.extend(h[f],b);else c.extend(h,b);else if(e&&!h[f])h[f]={};a=e?h[f]:h;if(d!==A)a[b]=d;return typeof b==="string"?a[b]:a}}},removeData:function(a,b){if(c.acceptData(a)){a=a==E?pa:a;var d=a.nodeType,e=d?a[c.expando]:a,f=c.cache,h=d?f[e]:e;if(b){if(h){delete h[b];d&&c.isEmptyObject(h)&&c.removeData(a)}}else if(d&&c.support.deleteExpando)delete a[c.expando];else if(a.removeAttribute)a.removeAttribute(c.expando);else if(d)delete f[e];else for(var k in a)delete a[k]}},acceptData:function(a){if(a.nodeName){var b= +c.noData[a.nodeName.toLowerCase()];if(b)return!(b===true||a.getAttribute("classid")!==b)}return true}});c.fn.extend({data:function(a,b){if(typeof a==="undefined")return this.length?c.data(this[0]):null;else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===A){var e=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(e===A&&this.length){e=c.data(this[0],a);if(e===A&&this[0].nodeType===1){e=this[0].getAttribute("data-"+a);if(typeof e=== +"string")try{e=e==="true"?true:e==="false"?false:e==="null"?null:!c.isNaN(e)?parseFloat(e):Oa.test(e)?c.parseJSON(e):e}catch(f){}else e=A}}return e===A&&d[1]?this.data(d[0]):e}else return this.each(function(){var h=c(this),k=[d[0],b];h.triggerHandler("setData"+d[1]+"!",k);c.data(this,a,b);h.triggerHandler("changeData"+d[1]+"!",k)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var e=c.data(a,b);if(!d)return e|| +[];if(!e||c.isArray(d))e=c.data(a,b,c.makeArray(d));else e.push(d);return e}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),e=d.shift();if(e==="inprogress")e=d.shift();if(e){b==="fx"&&d.unshift("inprogress");e.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===A)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this, +a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var qa=/[\n\t]/g,ga=/\s+/,Pa=/\r/g,Qa=/^(?:href|src|style)$/,Ra=/^(?:button|input)$/i,Sa=/^(?:button|input|object|select|textarea)$/i,Ta=/^a(?:rea)?$/i,ra=/^(?:radio|checkbox)$/i;c.fn.extend({attr:function(a,b){return c.access(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this, +a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(s){var v=c(this);v.addClass(a.call(this,s,v.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ga),d=0,e=this.length;d-1)return true;return false}, +val:function(a){if(!arguments.length){var b=this[0];if(b){if(c.nodeName(b,"option")){var d=b.attributes.value;return!d||d.specified?b.value:b.text}if(c.nodeName(b,"select")){var e=b.selectedIndex;d=[];var f=b.options;b=b.type==="select-one";if(e<0)return null;var h=b?e:0;for(e=b?e+1:f.length;h=0;else if(c.nodeName(this,"select")){var B=c.makeArray(v);c("option",this).each(function(){this.selected= +c.inArray(c(this).val(),B)>=0});if(!B.length)this.selectedIndex=-1}else this.value=v}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,e){if(!a||a.nodeType===3||a.nodeType===8)return A;if(e&&b in c.attrFn)return c(a)[b](d);e=a.nodeType!==1||!c.isXMLDoc(a);var f=d!==A;b=e&&c.props[b]||b;if(a.nodeType===1){var h=Qa.test(b);if((b in a||a[b]!==A)&&e&&!h){if(f){b==="type"&&Ra.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); +if(d===null)a.nodeType===1&&a.removeAttribute(b);else a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:Sa.test(a.nodeName)||Ta.test(a.nodeName)&&a.href?0:A;return a[b]}if(!c.support.style&&e&&b==="style"){if(f)a.style.cssText=""+d;return a.style.cssText}f&&a.setAttribute(b,""+d);if(!a.attributes[b]&&a.hasAttribute&&!a.hasAttribute(b))return A;a=!c.support.hrefNormalized&&e&& +h?a.getAttribute(b,2):a.getAttribute(b);return a===null?A:a}}});var X=/\.(.*)$/,ha=/^(?:textarea|input|select)$/i,Ha=/\./g,Ia=/ /g,Ua=/[^\w\s.|`]/g,Va=function(a){return a.replace(Ua,"\\$&")},sa={focusin:0,focusout:0};c.event={add:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(c.isWindow(a)&&a!==E&&!a.frameElement)a=E;if(d===false)d=U;var f,h;if(d.handler){f=d;d=f.handler}if(!d.guid)d.guid=c.guid++;if(h=c.data(a)){var k=a.nodeType?"events":"__events__",l=h[k],n=h.handle;if(typeof l=== +"function"){n=l.handle;l=l.events}else if(!l){a.nodeType||(h[k]=h=function(){});h.events=l={}}if(!n)h.handle=n=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(n.elem,arguments):A};n.elem=a;b=b.split(" ");for(var s=0,v;k=b[s++];){h=f?c.extend({},f):{handler:d,data:e};if(k.indexOf(".")>-1){v=k.split(".");k=v.shift();h.namespace=v.slice(0).sort().join(".")}else{v=[];h.namespace=""}h.type=k;if(!h.guid)h.guid=d.guid;var B=l[k],D=c.event.special[k]||{};if(!B){B=l[k]=[]; +if(!D.setup||D.setup.call(a,e,v,n)===false)if(a.addEventListener)a.addEventListener(k,n,false);else a.attachEvent&&a.attachEvent("on"+k,n)}if(D.add){D.add.call(a,h);if(!h.handler.guid)h.handler.guid=d.guid}B.push(h);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(d===false)d=U;var f,h,k=0,l,n,s,v,B,D,H=a.nodeType?"events":"__events__",w=c.data(a),G=w&&w[H];if(w&&G){if(typeof G==="function"){w=G;G=G.events}if(b&&b.type){d=b.handler;b=b.type}if(!b|| +typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(f in G)c.event.remove(a,f+b)}else{for(b=b.split(" ");f=b[k++];){v=f;l=f.indexOf(".")<0;n=[];if(!l){n=f.split(".");f=n.shift();s=RegExp("(^|\\.)"+c.map(n.slice(0).sort(),Va).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(B=G[f])if(d){v=c.event.special[f]||{};for(h=e||0;h=0){a.type= +f=f.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[f]&&c.each(c.cache,function(){this.events&&this.events[f]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return A;a.result=A;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(e=d.nodeType?c.data(d,"handle"):(c.data(d,"__events__")||{}).handle)&&e.apply(d,b);e=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+f]&&d["on"+f].apply(d,b)=== +false){a.result=false;a.preventDefault()}}catch(h){}if(!a.isPropagationStopped()&&e)c.event.trigger(a,b,e,true);else if(!a.isDefaultPrevented()){e=a.target;var k,l=f.replace(X,""),n=c.nodeName(e,"a")&&l==="click",s=c.event.special[l]||{};if((!s._default||s._default.call(d,a)===false)&&!n&&!(e&&e.nodeName&&c.noData[e.nodeName.toLowerCase()])){try{if(e[l]){if(k=e["on"+l])e["on"+l]=null;c.event.triggered=true;e[l]()}}catch(v){}if(k)e["on"+l]=k;c.event.triggered=false}}},handle:function(a){var b,d,e; +d=[];var f,h=c.makeArray(arguments);a=h[0]=c.event.fix(a||E.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;if(!b){e=a.type.split(".");a.type=e.shift();d=e.slice(0).sort();e=RegExp("(^|\\.)"+d.join("\\.(?:.*\\.)?")+"(\\.|$)")}a.namespace=a.namespace||d.join(".");f=c.data(this,this.nodeType?"events":"__events__");if(typeof f==="function")f=f.events;d=(f||{})[a.type];if(f&&d){d=d.slice(0);f=0;for(var k=d.length;f-1?c.map(a.options,function(e){return e.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},Z=function(a,b){var d=a.target,e,f;if(!(!ha.test(d.nodeName)||d.readOnly)){e=c.data(d,"_change_data");f=va(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",f);if(!(e===A||f===e))if(e!=null||f){a.type="change";a.liveFired= +A;return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:Z,beforedeactivate:Z,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return Z.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return Z.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,"_change_data",va(a))}},setup:function(){if(this.type=== +"file")return false;for(var a in V)c.event.add(this,a+".specialChange",V[a]);return ha.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return ha.test(this.nodeName)}};V=c.event.special.change.filters;V.focus=V.beforeactivate}u.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(e){e=c.event.fix(e);e.type=b;return c.event.trigger(e,null,e.target)}c.event.special[b]={setup:function(){sa[b]++===0&&u.addEventListener(a,d,true)},teardown:function(){--sa[b]=== +0&&u.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,e,f){if(typeof d==="object"){for(var h in d)this[b](h,e,d[h],f);return this}if(c.isFunction(e)||e===false){f=e;e=A}var k=b==="one"?c.proxy(f,function(n){c(this).unbind(n,k);return f.apply(this,arguments)}):f;if(d==="unload"&&b!=="one")this.one(d,e,f);else{h=0;for(var l=this.length;h0?this.bind(b,d,e):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});E.attachEvent&&!E.addEventListener&&c(E).bind("unload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}}); +(function(){function a(g,j,o,m,p,q){p=0;for(var t=m.length;p0){C=x;break}}x=x[g]}m[p]=C}}}var d=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,h=false,k=true;[0,0].sort(function(){k=false;return 0});var l=function(g,j,o,m){o=o||[];var p=j=j||u;if(j.nodeType!==1&&j.nodeType!==9)return[];if(!g||typeof g!=="string")return o;var q=[],t,x,C,P,N=true,R=l.isXML(j),Q=g,L;do{d.exec("");if(t=d.exec(Q)){Q=t[3];q.push(t[1]);if(t[2]){P=t[3]; +break}}}while(t);if(q.length>1&&s.exec(g))if(q.length===2&&n.relative[q[0]])x=M(q[0]+q[1],j);else for(x=n.relative[q[0]]?[j]:l(q.shift(),j);q.length;){g=q.shift();if(n.relative[g])g+=q.shift();x=M(g,x)}else{if(!m&&q.length>1&&j.nodeType===9&&!R&&n.match.ID.test(q[0])&&!n.match.ID.test(q[q.length-1])){t=l.find(q.shift(),j,R);j=t.expr?l.filter(t.expr,t.set)[0]:t.set[0]}if(j){t=m?{expr:q.pop(),set:D(m)}:l.find(q.pop(),q.length===1&&(q[0]==="~"||q[0]==="+")&&j.parentNode?j.parentNode:j,R);x=t.expr?l.filter(t.expr, +t.set):t.set;if(q.length>0)C=D(x);else N=false;for(;q.length;){t=L=q.pop();if(n.relative[L])t=q.pop();else L="";if(t==null)t=j;n.relative[L](C,t,R)}}else C=[]}C||(C=x);C||l.error(L||g);if(f.call(C)==="[object Array]")if(N)if(j&&j.nodeType===1)for(g=0;C[g]!=null;g++){if(C[g]&&(C[g]===true||C[g].nodeType===1&&l.contains(j,C[g])))o.push(x[g])}else for(g=0;C[g]!=null;g++)C[g]&&C[g].nodeType===1&&o.push(x[g]);else o.push.apply(o,C);else D(C,o);if(P){l(P,p,o,m);l.uniqueSort(o)}return o};l.uniqueSort=function(g){if(w){h= +k;g.sort(w);if(h)for(var j=1;j0};l.find=function(g,j,o){var m;if(!g)return[];for(var p=0,q=n.order.length;p":function(g,j){var o=typeof j==="string",m,p=0,q=g.length;if(o&&!/\W/.test(j))for(j=j.toLowerCase();p=0))o||m.push(t);else if(o)j[q]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},CHILD:function(g){if(g[1]==="nth"){var j=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=j[1]+(j[2]||1)-0;g[3]=j[3]-0}g[0]=e++;return g},ATTR:function(g,j,o, +m,p,q){j=g[1].replace(/\\/g,"");if(!q&&n.attrMap[j])g[1]=n.attrMap[j];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,j,o,m,p){if(g[1]==="not")if((d.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=l(g[3],null,null,j);else{g=l.filter(g[3],j,o,true^p);o||m.push.apply(m,g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled=== +true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,j,o){return!!l(o[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"=== +g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},setFilters:{first:function(g,j){return j===0},last:function(g,j,o,m){return j===m.length-1},even:function(g,j){return j%2===0},odd:function(g,j){return j%2===1},lt:function(g,j,o){return jo[3]-0},nth:function(g,j,o){return o[3]- +0===j},eq:function(g,j,o){return o[3]-0===j}},filter:{PSEUDO:function(g,j,o,m){var p=j[1],q=n.filters[p];if(q)return q(g,o,j,m);else if(p==="contains")return(g.textContent||g.innerText||l.getText([g])||"").indexOf(j[3])>=0;else if(p==="not"){j=j[3];o=0;for(m=j.length;o=0}},ID:function(g,j){return g.nodeType===1&&g.getAttribute("id")===j},TAG:function(g,j){return j==="*"&&g.nodeType===1||g.nodeName.toLowerCase()=== +j},CLASS:function(g,j){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(j)>-1},ATTR:function(g,j){var o=j[1];o=n.attrHandle[o]?n.attrHandle[o](g):g[o]!=null?g[o]:g.getAttribute(o);var m=o+"",p=j[2],q=j[4];return o==null?p==="!=":p==="="?m===q:p==="*="?m.indexOf(q)>=0:p==="~="?(" "+m+" ").indexOf(q)>=0:!q?m&&o!==false:p==="!="?m!==q:p==="^="?m.indexOf(q)===0:p==="$="?m.substr(m.length-q.length)===q:p==="|="?m===q||m.substr(0,q.length+1)===q+"-":false},POS:function(g,j,o,m){var p=n.setFilters[j[2]]; +if(p)return p(g,o,j,m)}}},s=n.match.POS,v=function(g,j){return"\\"+(j-0+1)},B;for(B in n.match){n.match[B]=RegExp(n.match[B].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[B]=RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[B].source.replace(/\\(\d+)/g,v))}var D=function(g,j){g=Array.prototype.slice.call(g,0);if(j){j.push.apply(j,g);return j}return g};try{Array.prototype.slice.call(u.documentElement.childNodes,0)}catch(H){D=function(g,j){var o=j||[],m=0;if(f.call(g)==="[object Array]")Array.prototype.push.apply(o, +g);else if(typeof g.length==="number")for(var p=g.length;m";var o=u.documentElement;o.insertBefore(g,o.firstChild);if(u.getElementById(j)){n.find.ID=function(m,p,q){if(typeof p.getElementById!=="undefined"&&!q)return(p=p.getElementById(m[1]))?p.id===m[1]||typeof p.getAttributeNode!=="undefined"&&p.getAttributeNode("id").nodeValue===m[1]?[p]:A:[]};n.filter.ID=function(m,p){var q=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&q&&q.nodeValue===p}}o.removeChild(g); +o=g=null})();(function(){var g=u.createElement("div");g.appendChild(u.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(j,o){var m=o.getElementsByTagName(j[1]);if(j[1]==="*"){for(var p=[],q=0;m[q];q++)m[q].nodeType===1&&p.push(m[q]);m=p}return m};g.innerHTML="";if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(j){return j.getAttribute("href",2)};g=null})();u.querySelectorAll&& +function(){var g=l,j=u.createElement("div");j.innerHTML="

";if(!(j.querySelectorAll&&j.querySelectorAll(".TEST").length===0)){l=function(m,p,q,t){p=p||u;if(!t&&!l.isXML(p))if(p.nodeType===9)try{return D(p.querySelectorAll(m),q)}catch(x){}else if(p.nodeType===1&&p.nodeName.toLowerCase()!=="object"){var C=p.id,P=p.id="__sizzle__";try{return D(p.querySelectorAll("#"+P+" "+m),q)}catch(N){}finally{if(C)p.id=C;else p.removeAttribute("id")}}return g(m,p,q,t)};for(var o in g)l[o]=g[o]; +j=null}}();(function(){var g=u.documentElement,j=g.matchesSelector||g.mozMatchesSelector||g.webkitMatchesSelector||g.msMatchesSelector,o=false;try{j.call(u.documentElement,":sizzle")}catch(m){o=true}if(j)l.matchesSelector=function(p,q){try{if(o||!n.match.PSEUDO.test(q))return j.call(p,q)}catch(t){}return l(q,null,null,[p]).length>0}})();(function(){var g=u.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length=== +0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(j,o,m){if(typeof o.getElementsByClassName!=="undefined"&&!m)return o.getElementsByClassName(j[1])};g=null}}})();l.contains=u.documentElement.contains?function(g,j){return g!==j&&(g.contains?g.contains(j):true)}:function(g,j){return!!(g.compareDocumentPosition(j)&16)};l.isXML=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false};var M=function(g, +j){for(var o=[],m="",p,q=j.nodeType?[j]:j;p=n.match.PSEUDO.exec(g);){m+=p[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;p=0;for(var t=q.length;p0)for(var h=d;h0},closest:function(a, +b){var d=[],e,f,h=this[0];if(c.isArray(a)){var k={},l,n=1;if(h&&a.length){e=0;for(f=a.length;e-1:c(h).is(e))d.push({selector:l,elem:h,level:n})}h=h.parentNode;n++}}return d}k=$a.test(a)?c(a,b||this.context):null;e=0;for(f=this.length;e-1:c.find.matchesSelector(h,a)){d.push(h);break}else{h=h.parentNode;if(!h|| +!h.ownerDocument||h===b)break}d=d.length>1?c.unique(d):d;return this.pushStack(d,"closest",a)},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var d=typeof a==="string"?c(a,b||this.context):c.makeArray(a),e=c.merge(this.get(),d);return this.pushStack(!d[0]||!d[0].parentNode||d[0].parentNode.nodeType===11||!e[0]||!e[0].parentNode||e[0].parentNode.nodeType===11?e:c.unique(e))},andSelf:function(){return this.add(this.prevObject)}}); +c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling", +d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,e){var f=c.map(this,b,d);Wa.test(a)||(e=d);if(e&&typeof e==="string")f=c.filter(e,f);f=this.length>1?c.unique(f):f;if((this.length>1||Ya.test(e))&&Xa.test(a))f=f.reverse();return this.pushStack(f,a,Za.call(arguments).join(","))}}); +c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return b.length===1?c.find.matchesSelector(b[0],a)?[b[0]]:[]:c.find.matches(a,b)},dir:function(a,b,d){var e=[];for(a=a[b];a&&a.nodeType!==9&&(d===A||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&e.push(a);a=a[b]}return e},nth:function(a,b,d){b=b||1;for(var e=0;a;a=a[d])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var xa=/ jQuery\d+="(?:\d+|null)"/g, +$=/^\s+/,ya=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,za=/<([\w:]+)/,ab=/\s]+\/)>/g,O={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"], +area:[1,"",""],_default:[0,"",""]};O.optgroup=O.option;O.tbody=O.tfoot=O.colgroup=O.caption=O.thead;O.th=O.td;if(!c.support.htmlSerialize)O._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==A)return this.empty().append((this[0]&&this[0].ownerDocument||u).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this, +d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})}, +unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a= +c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,e;(e=this[d])!=null;d++)if(!a||c.filter(a,[e]).length){if(!b&&e.nodeType===1){c.cleanData(e.getElementsByTagName("*")); +c.cleanData([e])}e.parentNode&&e.parentNode.removeChild(e)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,e=this.ownerDocument;if(!d){d=e.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(xa,"").replace(cb,'="$1">').replace($, +"")],e)[0]}else return this.cloneNode(true)});if(a===true){la(this,b);la(this.find("*"),b.find("*"))}return b},html:function(a){if(a===A)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(xa,""):null;else if(typeof a==="string"&&!Aa.test(a)&&(c.support.leadingWhitespace||!$.test(a))&&!O[(za.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ya,"<$1>");try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?l.cloneNode(true):l)}k.length&&c.each(k,Ka)}return this}});c.buildFragment=function(a,b,d){var e,f,h;b=b&&b[0]?b[0].ownerDocument||b[0]:u;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===u&&!Aa.test(a[0])&&(c.support.checkClone|| +!Ba.test(a[0]))){f=true;if(h=c.fragments[a[0]])if(h!==1)e=h}if(!e){e=b.createDocumentFragment();c.clean(a,b,e,d)}if(f)c.fragments[a[0]]=h?e:1;return{fragment:e,cacheable:f}};c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var e=[];d=c(d);var f=this.length===1&&this[0].parentNode;if(f&&f.nodeType===11&&f.childNodes.length===1&&d.length===1){d[b](this[0]);return this}else{f=0;for(var h= +d.length;f0?this.clone(true):this).get();c(d[f])[b](k);e=e.concat(k)}return this.pushStack(e,a,d.selector)}}});c.extend({clean:function(a,b,d,e){b=b||u;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||u;for(var f=[],h=0,k;(k=a[h])!=null;h++){if(typeof k==="number")k+="";if(k){if(typeof k==="string"&&!bb.test(k))k=b.createTextNode(k);else if(typeof k==="string"){k=k.replace(ya,"<$1>");var l=(za.exec(k)||["",""])[1].toLowerCase(),n=O[l]||O._default, +s=n[0],v=b.createElement("div");for(v.innerHTML=n[1]+k+n[2];s--;)v=v.lastChild;if(!c.support.tbody){s=ab.test(k);l=l==="table"&&!s?v.firstChild&&v.firstChild.childNodes:n[1]===""&&!s?v.childNodes:[];for(n=l.length-1;n>=0;--n)c.nodeName(l[n],"tbody")&&!l[n].childNodes.length&&l[n].parentNode.removeChild(l[n])}!c.support.leadingWhitespace&&$.test(k)&&v.insertBefore(b.createTextNode($.exec(k)[0]),v.firstChild);k=v.childNodes}if(k.nodeType)f.push(k);else f=c.merge(f,k)}}if(d)for(h=0;f[h];h++)if(e&& +c.nodeName(f[h],"script")&&(!f[h].type||f[h].type.toLowerCase()==="text/javascript"))e.push(f[h].parentNode?f[h].parentNode.removeChild(f[h]):f[h]);else{f[h].nodeType===1&&f.splice.apply(f,[h+1,0].concat(c.makeArray(f[h].getElementsByTagName("script"))));d.appendChild(f[h])}return f},cleanData:function(a){for(var b,d,e=c.cache,f=c.event.special,h=c.support.deleteExpando,k=0,l;(l=a[k])!=null;k++)if(!(l.nodeName&&c.noData[l.nodeName.toLowerCase()]))if(d=l[c.expando]){if((b=e[d])&&b.events)for(var n in b.events)f[n]? +c.event.remove(l,n):c.removeEvent(l,n,b.handle);if(h)delete l[c.expando];else l.removeAttribute&&l.removeAttribute(c.expando);delete e[d]}}});var Ca=/alpha\([^)]*\)/i,db=/opacity=([^)]*)/,eb=/-([a-z])/ig,fb=/([A-Z])/g,Da=/^-?\d+(?:px)?$/i,gb=/^-?\d/,hb={position:"absolute",visibility:"hidden",display:"block"},La=["Left","Right"],Ma=["Top","Bottom"],W,ib=u.defaultView&&u.defaultView.getComputedStyle,jb=function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){if(arguments.length===2&&b===A)return this; +return c.access(this,a,b,true,function(d,e,f){return f!==A?c.style(d,e,f):c.css(d,e)})};c.extend({cssHooks:{opacity:{get:function(a,b){if(b){var d=W(a,"opacity","opacity");return d===""?"1":d}else return a.style.opacity}}},cssNumber:{zIndex:true,fontWeight:true,opacity:true,zoom:true,lineHeight:true},cssProps:{"float":c.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,d,e){if(!(!a||a.nodeType===3||a.nodeType===8||!a.style)){var f,h=c.camelCase(b),k=a.style,l=c.cssHooks[h];b=c.cssProps[h]|| +h;if(d!==A){if(!(typeof d==="number"&&isNaN(d)||d==null)){if(typeof d==="number"&&!c.cssNumber[h])d+="px";if(!l||!("set"in l)||(d=l.set(a,d))!==A)try{k[b]=d}catch(n){}}}else{if(l&&"get"in l&&(f=l.get(a,false,e))!==A)return f;return k[b]}}},css:function(a,b,d){var e,f=c.camelCase(b),h=c.cssHooks[f];b=c.cssProps[f]||f;if(h&&"get"in h&&(e=h.get(a,true,d))!==A)return e;else if(W)return W(a,b,f)},swap:function(a,b,d){var e={},f;for(f in b){e[f]=a.style[f];a.style[f]=b[f]}d.call(a);for(f in b)a.style[f]= +e[f]},camelCase:function(a){return a.replace(eb,jb)}});c.curCSS=c.css;c.each(["height","width"],function(a,b){c.cssHooks[b]={get:function(d,e,f){var h;if(e){if(d.offsetWidth!==0)h=ma(d,b,f);else c.swap(d,hb,function(){h=ma(d,b,f)});return h+"px"}},set:function(d,e){if(Da.test(e)){e=parseFloat(e);if(e>=0)return e+"px"}else return e}}});if(!c.support.opacity)c.cssHooks.opacity={get:function(a,b){return db.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"": +b?"1":""},set:function(a,b){var d=a.style;d.zoom=1;var e=c.isNaN(b)?"":"alpha(opacity="+b*100+")",f=d.filter||"";d.filter=Ca.test(f)?f.replace(Ca,e):d.filter+" "+e}};if(ib)W=function(a,b,d){var e;d=d.replace(fb,"-$1").toLowerCase();if(!(b=a.ownerDocument.defaultView))return A;if(b=b.getComputedStyle(a,null)){e=b.getPropertyValue(d);if(e===""&&!c.contains(a.ownerDocument.documentElement,a))e=c.style(a,d)}return e};else if(u.documentElement.currentStyle)W=function(a,b){var d,e,f=a.currentStyle&&a.currentStyle[b], +h=a.style;if(!Da.test(f)&&gb.test(f)){d=h.left;e=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;h.left=b==="fontSize"?"1em":f||0;f=h.pixelLeft+"px";h.left=d;a.runtimeStyle.left=e}return f};if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=a.offsetHeight;return a.offsetWidth===0&&b===0||!c.support.reliableHiddenOffsets&&(a.style.display||c.css(a,"display"))==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var kb=c.now(),lb=/)<[^<]*)*<\/script>/gi, +mb=/^(?:select|textarea)/i,nb=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ob=/^(?:GET|HEAD|DELETE)$/,Na=/\[\]$/,T=/\=\?(&|$)/,ia=/\?/,pb=/([?&])_=[^&]*/,qb=/^(\w+:)?\/\/([^\/?#]+)/,rb=/%20/g,sb=/#.*$/,Ea=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!=="string"&&Ea)return Ea.apply(this,arguments);else if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var f=a.slice(e,a.length);a=a.slice(0,e)}e="GET";if(b)if(c.isFunction(b)){d= +b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);e="POST"}var h=this;c.ajax({url:a,type:e,dataType:"html",data:b,complete:function(k,l){if(l==="success"||l==="notmodified")h.html(f?c("
").append(k.responseText.replace(lb,"")).find(f):k.responseText);d&&h.each(d,[k.responseText,l,k])}});return this},serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&& +!this.disabled&&(this.checked||mb.test(this.nodeName)||nb.test(this.type))}).map(function(a,b){var d=c(this).val();return d==null?null:c.isArray(d)?c.map(d,function(e){return{name:b.name,value:e}}):{name:b.name,value:d}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:e})}, +getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:e})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return new E.XMLHttpRequest},accepts:{xml:"application/xml, text/xml",html:"text/html", +script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},ajax:function(a){var b=c.extend(true,{},c.ajaxSettings,a),d,e,f,h=b.type.toUpperCase(),k=ob.test(h);b.url=b.url.replace(sb,"");b.context=a&&a.context!=null?a.context:b;if(b.data&&b.processData&&typeof b.data!=="string")b.data=c.param(b.data,b.traditional);if(b.dataType==="jsonp"){if(h==="GET")T.test(b.url)||(b.url+=(ia.test(b.url)?"&":"?")+(b.jsonp||"callback")+"=?");else if(!b.data|| +!T.test(b.data))b.data=(b.data?b.data+"&":"")+(b.jsonp||"callback")+"=?";b.dataType="json"}if(b.dataType==="json"&&(b.data&&T.test(b.data)||T.test(b.url))){d=b.jsonpCallback||"jsonp"+kb++;if(b.data)b.data=(b.data+"").replace(T,"="+d+"$1");b.url=b.url.replace(T,"="+d+"$1");b.dataType="script";var l=E[d];E[d]=function(m){f=m;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);if(c.isFunction(l))l(m);else{E[d]=A;try{delete E[d]}catch(p){}}v&&v.removeChild(B)}}if(b.dataType==="script"&&b.cache===null)b.cache= +false;if(b.cache===false&&h==="GET"){var n=c.now(),s=b.url.replace(pb,"$1_="+n);b.url=s+(s===b.url?(ia.test(b.url)?"&":"?")+"_="+n:"")}if(b.data&&h==="GET")b.url+=(ia.test(b.url)?"&":"?")+b.data;b.global&&c.active++===0&&c.event.trigger("ajaxStart");n=(n=qb.exec(b.url))&&(n[1]&&n[1]!==location.protocol||n[2]!==location.host);if(b.dataType==="script"&&h==="GET"&&n){var v=u.getElementsByTagName("head")[0]||u.documentElement,B=u.createElement("script");if(b.scriptCharset)B.charset=b.scriptCharset;B.src= +b.url;if(!d){var D=false;B.onload=B.onreadystatechange=function(){if(!D&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){D=true;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);B.onload=B.onreadystatechange=null;v&&B.parentNode&&v.removeChild(B)}}}v.insertBefore(B,v.firstChild);return A}var H=false,w=b.xhr();if(w){b.username?w.open(h,b.url,b.async,b.username,b.password):w.open(h,b.url,b.async);try{if(b.data!=null&&!k||a&&a.contentType)w.setRequestHeader("Content-Type", +b.contentType);if(b.ifModified){c.lastModified[b.url]&&w.setRequestHeader("If-Modified-Since",c.lastModified[b.url]);c.etag[b.url]&&w.setRequestHeader("If-None-Match",c.etag[b.url])}n||w.setRequestHeader("X-Requested-With","XMLHttpRequest");w.setRequestHeader("Accept",b.dataType&&b.accepts[b.dataType]?b.accepts[b.dataType]+", */*; q=0.01":b.accepts._default)}catch(G){}if(b.beforeSend&&b.beforeSend.call(b.context,w,b)===false){b.global&&c.active--===1&&c.event.trigger("ajaxStop");w.abort();return false}b.global&& +c.triggerGlobal(b,"ajaxSend",[w,b]);var M=w.onreadystatechange=function(m){if(!w||w.readyState===0||m==="abort"){H||c.handleComplete(b,w,e,f);H=true;if(w)w.onreadystatechange=c.noop}else if(!H&&w&&(w.readyState===4||m==="timeout")){H=true;w.onreadystatechange=c.noop;e=m==="timeout"?"timeout":!c.httpSuccess(w)?"error":b.ifModified&&c.httpNotModified(w,b.url)?"notmodified":"success";var p;if(e==="success")try{f=c.httpData(w,b.dataType,b)}catch(q){e="parsererror";p=q}if(e==="success"||e==="notmodified")d|| +c.handleSuccess(b,w,e,f);else c.handleError(b,w,e,p);d||c.handleComplete(b,w,e,f);m==="timeout"&&w.abort();if(b.async)w=null}};try{var g=w.abort;w.abort=function(){w&&g.call&&g.call(w);M("abort")}}catch(j){}b.async&&b.timeout>0&&setTimeout(function(){w&&!H&&M("timeout")},b.timeout);try{w.send(k||b.data==null?null:b.data)}catch(o){c.handleError(b,w,null,o);c.handleComplete(b,w,e,f)}b.async||M();return w}},param:function(a,b){var d=[],e=function(h,k){k=c.isFunction(k)?k():k;d[d.length]=encodeURIComponent(h)+ +"="+encodeURIComponent(k)};if(b===A)b=c.ajaxSettings.traditional;if(c.isArray(a)||a.jquery)c.each(a,function(){e(this.name,this.value)});else for(var f in a)ca(f,a[f],b,e);return d.join("&").replace(rb,"+")}});c.extend({active:0,lastModified:{},etag:{},handleError:function(a,b,d,e){a.error&&a.error.call(a.context,b,d,e);a.global&&c.triggerGlobal(a,"ajaxError",[b,a,e])},handleSuccess:function(a,b,d,e){a.success&&a.success.call(a.context,e,d,b);a.global&&c.triggerGlobal(a,"ajaxSuccess",[b,a])},handleComplete:function(a, +b,d){a.complete&&a.complete.call(a.context,b,d);a.global&&c.triggerGlobal(a,"ajaxComplete",[b,a]);a.global&&c.active--===1&&c.event.trigger("ajaxStop")},triggerGlobal:function(a,b,d){(a.context&&a.context.url==null?c(a.context):c.event).trigger(b,d)},httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===1223}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),e=a.getResponseHeader("Etag"); +if(d)c.lastModified[b]=d;if(e)c.etag[b]=e;return a.status===304},httpData:function(a,b,d){var e=a.getResponseHeader("content-type")||"",f=b==="xml"||!b&&e.indexOf("xml")>=0;a=f?a.responseXML:a.responseText;f&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b==="json"||!b&&e.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&e.indexOf("javascript")>=0)c.globalEval(a);return a}});if(E.ActiveXObject)c.ajaxSettings.xhr= +function(){if(E.location.protocol!=="file:")try{return new E.XMLHttpRequest}catch(a){}try{return new E.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}};c.support.ajax=!!c.ajaxSettings.xhr();var da={},tb=/^(?:toggle|show|hide)$/,ub=/^([+\-]=)?([\d+.\-]+)(.*)$/,aa,na=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b,d){if(a||a===0)return this.animate(S("show",3),a,b,d);else{a= +0;for(b=this.length;a=0;e--)if(d[e].elem===this){b&&d[e](true);d.splice(e,1)}});b||this.dequeue();return this}});c.each({slideDown:S("show",1),slideUp:S("hide",1),slideToggle:S("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,e,f){return this.animate(b, +d,e,f)}});c.extend({speed:function(a,b,d){var e=a&&typeof a==="object"?c.extend({},a):{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};e.duration=c.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in c.fx.speeds?c.fx.speeds[e.duration]:c.fx.speeds._default;e.old=e.complete;e.complete=function(){e.queue!==false&&c(this).dequeue();c.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,d,e){return d+e*a},swing:function(a,b,d,e){return(-Math.cos(a* +Math.PI)/2+0.5)*e+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||c.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a=parseFloat(c.css(this.elem,this.prop));return a&&a>-1E4?a:0},custom:function(a,b,d){function e(h){return f.step(h)} +this.startTime=c.now();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;this.pos=this.state=0;var f=this;a=c.fx;e.elem=this.elem;if(e()&&c.timers.push(e)&&!aa)aa=setInterval(a.tick,a.interval)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true; +this.custom(this.cur(),0)},step:function(a){var b=c.now(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var e in this.options.curAnim)if(this.options.curAnim[e]!==true)d=false;if(d){if(this.options.overflow!=null&&!c.support.shrinkWrapBlocks){var f=this.elem,h=this.options;c.each(["","X","Y"],function(l,n){f.style["overflow"+n]=h.overflow[l]})}this.options.hide&&c(this.elem).hide();if(this.options.hide|| +this.options.show)for(var k in this.options.curAnim)c.style(this.elem,k,this.options.orig[k]);this.options.complete.call(this.elem)}return false}else{a=b-this.startTime;this.state=a/this.options.duration;b=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||b](this.state,a,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a= +c.timers,b=0;b-1;e={};var s={};if(n)s=f.position();k=n?s.top:parseInt(k,10)||0;l=n?s.left:parseInt(l,10)||0;if(c.isFunction(b))b=b.call(a,d,h);if(b.top!=null)e.top=b.top-h.top+k;if(b.left!=null)e.left=b.left-h.left+l;"using"in b?b.using.call(a, +e):f.css(e)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),e=Fa.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.css(a,"marginTop"))||0;d.left-=parseFloat(c.css(a,"marginLeft"))||0;e.top+=parseFloat(c.css(b[0],"borderTopWidth"))||0;e.left+=parseFloat(c.css(b[0],"borderLeftWidth"))||0;return{top:d.top-e.top,left:d.left-e.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||u.body;a&&!Fa.test(a.nodeName)&& +c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(e){var f=this[0],h;if(!f)return null;if(e!==A)return this.each(function(){if(h=ea(this))h.scrollTo(!a?e:c(h).scrollLeft(),a?e:c(h).scrollTop());else this[d]=e});else return(h=ea(f))?"pageXOffset"in h?h[a?"pageYOffset":"pageXOffset"]:c.support.boxModel&&h.document.documentElement[d]||h.document.body[d]:f[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase(); +c.fn["inner"+b]=function(){return this[0]?parseFloat(c.css(this[0],d,"padding")):null};c.fn["outer"+b]=function(e){return this[0]?parseFloat(c.css(this[0],d,e?"margin":"border")):null};c.fn[d]=function(e){var f=this[0];if(!f)return e==null?null:this;if(c.isFunction(e))return this.each(function(h){var k=c(this);k[d](e.call(this,h,k[d]()))});return c.isWindow(f)?f.document.compatMode==="CSS1Compat"&&f.document.documentElement["client"+b]||f.document.body["client"+b]:f.nodeType===9?Math.max(f.documentElement["client"+ +b],f.body["scroll"+b],f.documentElement["scroll"+b],f.body["offset"+b],f.documentElement["offset"+b]):e===A?parseFloat(c.css(f,d)):this.css(d,typeof e==="string"?e:e+"px")}})})(window); diff --git a/htmlcov/jquery.hotkeys.js b/htmlcov/jquery.hotkeys.js new file mode 100644 index 00000000..09b21e03 --- /dev/null +++ b/htmlcov/jquery.hotkeys.js @@ -0,0 +1,99 @@ +/* + * jQuery Hotkeys Plugin + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * + * Based upon the plugin by Tzury Bar Yochay: + * http://github.com/tzuryby/hotkeys + * + * Original idea by: + * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ +*/ + +(function(jQuery){ + + jQuery.hotkeys = { + version: "0.8", + + specialKeys: { + 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", + 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", + 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", + 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", + 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", + 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", + 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta" + }, + + shiftNums: { + "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", + "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", + ".": ">", "/": "?", "\\": "|" + } + }; + + function keyHandler( handleObj ) { + // Only care when a possible input has been specified + if ( typeof handleObj.data !== "string" ) { + return; + } + + var origHandler = handleObj.handler, + keys = handleObj.data.toLowerCase().split(" "); + + handleObj.handler = function( event ) { + // Don't fire in text-accepting inputs that we didn't directly bind to + if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || + event.target.type === "text") ) { + return; + } + + // Keypress represents characters, not special keys + var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], + character = String.fromCharCode( event.which ).toLowerCase(), + key, modif = "", possible = {}; + + // check combinations (alt|ctrl|shift+anything) + if ( event.altKey && special !== "alt" ) { + modif += "alt+"; + } + + if ( event.ctrlKey && special !== "ctrl" ) { + modif += "ctrl+"; + } + + // TODO: Need to make sure this works consistently across platforms + if ( event.metaKey && !event.ctrlKey && special !== "meta" ) { + modif += "meta+"; + } + + if ( event.shiftKey && special !== "shift" ) { + modif += "shift+"; + } + + if ( special ) { + possible[ modif + special ] = true; + + } else { + possible[ modif + character ] = true; + possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; + + // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" + if ( modif === "shift+" ) { + possible[ jQuery.hotkeys.shiftNums[ character ] ] = true; + } + } + + for ( var i = 0, l = keys.length; i < l; i++ ) { + if ( possible[ keys[i] ] ) { + return origHandler.apply( this, arguments ); + } + } + }; + } + + jQuery.each([ "keydown", "keyup", "keypress" ], function() { + jQuery.event.special[ this ] = { add: keyHandler }; + }); + +})( jQuery ); diff --git a/htmlcov/jquery.isonscreen.js b/htmlcov/jquery.isonscreen.js new file mode 100644 index 00000000..0182ebd2 --- /dev/null +++ b/htmlcov/jquery.isonscreen.js @@ -0,0 +1,53 @@ +/* Copyright (c) 2010 + * @author Laurence Wheway + * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) + * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. + * + * @version 1.2.0 + */ +(function($) { + jQuery.extend({ + isOnScreen: function(box, container) { + //ensure numbers come in as intgers (not strings) and remove 'px' is it's there + for(var i in box){box[i] = parseFloat(box[i])}; + for(var i in container){container[i] = parseFloat(container[i])}; + + if(!container){ + container = { + left: $(window).scrollLeft(), + top: $(window).scrollTop(), + width: $(window).width(), + height: $(window).height() + } + } + + if( box.left+box.width-container.left > 0 && + box.left < container.width+container.left && + box.top+box.height-container.top > 0 && + box.top < container.height+container.top + ) return true; + return false; + } + }) + + + jQuery.fn.isOnScreen = function (container) { + for(var i in container){container[i] = parseFloat(container[i])}; + + if(!container){ + container = { + left: $(window).scrollLeft(), + top: $(window).scrollTop(), + width: $(window).width(), + height: $(window).height() + } + } + + if( $(this).offset().left+$(this).width()-container.left > 0 && + $(this).offset().left < container.width+container.left && + $(this).offset().top+$(this).height()-container.top > 0 && + $(this).offset().top < container.height+container.top + ) return true; + return false; + } +})(jQuery); diff --git a/htmlcov/jquery.tablesorter.min.js b/htmlcov/jquery.tablesorter.min.js new file mode 100644 index 00000000..64c70071 --- /dev/null +++ b/htmlcov/jquery.tablesorter.min.js @@ -0,0 +1,2 @@ + +(function($){$.extend({tablesorter:new function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'.',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}var rows=table.tBodies[0].rows;if(table.tBodies[0].rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('
').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;ib)?1:0));};function sortTextDesc(a,b){return((ba)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){$this.trigger("sortStart");var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){var $cell=$(this);var i=this.column;this.order=this.count++%2;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i + + + + + + + Coverage for rest_framework/__init__: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+
+ + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+ +
+

__version__ = '2.3.5' 

+

 

+

VERSION = __version__  # synonym 

+

 

+

# Header encoding (see RFC5987) 

+

HTTP_HEADER_ENCODING = 'iso-8859-1' 

+

 

+

# Default datetime input and output formats 

+

ISO_8601 = 'iso-8601' 

+ +
+ + + + + + diff --git a/htmlcov/rest_framework_authentication.html b/htmlcov/rest_framework_authentication.html new file mode 100644 index 00000000..899d0677 --- /dev/null +++ b/htmlcov/rest_framework_authentication.html @@ -0,0 +1,767 @@ + + + + + + + + Coverage for rest_framework/authentication: 80% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+ +
+

""" 

+

Provides various authentication policies. 

+

""" 

+

from __future__ import unicode_literals 

+

import base64 

+

from datetime import datetime 

+

 

+

from django.contrib.auth import authenticate 

+

from django.core.exceptions import ImproperlyConfigured 

+

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 

+

from rest_framework.authtoken.models import Token 

+

 

+

 

+

def get_authorization_header(request): 

+

    """ 

+

    Return request's 'Authorization:' header, as a bytestring. 

+

 

+

    Hide some test client ickyness where the header can be unicode. 

+

    """ 

+

    auth = request.META.get('HTTP_AUTHORIZATION', b'') 

+

    if type(auth) == type(''): 

+

        # Work around django test client oddness 

+

        auth = auth.encode(HTTP_HEADER_ENCODING) 

+

    return auth 

+

 

+

 

+

class BaseAuthentication(object): 

+

    """ 

+

    All authentication classes should extend BaseAuthentication. 

+

    """ 

+

 

+

    def authenticate(self, request): 

+

        """ 

+

        Authenticate the request and return a two-tuple of (user, token). 

+

        """ 

+

        raise NotImplementedError(".authenticate() must be overridden.") 

+

 

+

    def authenticate_header(self, request): 

+

        """ 

+

        Return a string to be used as the value of the `WWW-Authenticate` 

+

        header in a `401 Unauthenticated` response, or `None` if the 

+

        authentication scheme should return `403 Permission Denied` responses. 

+

        """ 

+

        pass 

+

 

+

 

+

class BasicAuthentication(BaseAuthentication): 

+

    """ 

+

    HTTP Basic authentication against username/password. 

+

    """ 

+

    www_authenticate_realm = 'api' 

+

 

+

    def authenticate(self, request): 

+

        """ 

+

        Returns a `User` if a correct username and password have been supplied 

+

        using HTTP Basic authentication.  Otherwise returns `None`. 

+

        """ 

+

        auth = get_authorization_header(request).split() 

+

 

+

        if not auth or auth[0].lower() != b'basic': 

+

            return None 

+

 

+

        if len(auth) == 1: 

+

            msg = 'Invalid basic header. No credentials provided.' 

+

            raise exceptions.AuthenticationFailed(msg) 

+

        elif len(auth) > 2: 

+

            msg = 'Invalid basic header. Credentials string should not contain spaces.' 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        try: 

+

            auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':') 

+

        except (TypeError, UnicodeDecodeError): 

+

            msg = 'Invalid basic header. Credentials not correctly base64 encoded' 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        userid, password = auth_parts[0], auth_parts[2] 

+

        return self.authenticate_credentials(userid, password) 

+

 

+

    def authenticate_credentials(self, userid, password): 

+

        """ 

+

        Authenticate the userid and password against username and password. 

+

        """ 

+

        user = authenticate(username=userid, password=password) 

+

        if user is None or not user.is_active: 

+

            raise exceptions.AuthenticationFailed('Invalid username/password') 

+

        return (user, None) 

+

 

+

    def authenticate_header(self, request): 

+

        return 'Basic realm="%s"' % self.www_authenticate_realm 

+

 

+

 

+

class SessionAuthentication(BaseAuthentication): 

+

    """ 

+

    Use Django's session framework for authentication. 

+

    """ 

+

 

+

    def authenticate(self, request): 

+

        """ 

+

        Returns a `User` if the request session currently has a logged in user. 

+

        Otherwise returns `None`. 

+

        """ 

+

 

+

        # Get the underlying HttpRequest object 

+

        http_request = request._request 

+

        user = getattr(http_request, 'user', None) 

+

 

+

        # Unauthenticated, CSRF validation not required 

+

        if not user or not user.is_active: 

+

            return None 

+

 

+

        # Enforce CSRF validation for session based authentication. 

+

        class CSRFCheck(CsrfViewMiddleware): 

+

            def _reject(self, request, reason): 

+

                # Return the failure reason instead of an HttpResponse 

+

                return reason 

+

 

+

        reason = CSRFCheck().process_view(http_request, None, (), {}) 

+

        if reason: 

+

            # CSRF failed, bail with explicit error message 

+

            raise exceptions.AuthenticationFailed('CSRF Failed: %s' % reason) 

+

 

+

        # CSRF passed with authenticated user 

+

        return (user, None) 

+

 

+

 

+

class TokenAuthentication(BaseAuthentication): 

+

    """ 

+

    Simple token based authentication. 

+

 

+

    Clients should authenticate by passing the token key in the "Authorization" 

+

    HTTP header, prepended with the string "Token ".  For example: 

+

 

+

        Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a 

+

    """ 

+

 

+

    model = Token 

+

    """ 

+

    A custom token model may be used, but must have the following properties. 

+

 

+

    * key -- The string identifying the token 

+

    * user -- The user to which the token belongs 

+

    """ 

+

 

+

    def authenticate(self, request): 

+

        auth = get_authorization_header(request).split() 

+

 

+

        if not auth or auth[0].lower() != b'token': 

+

            return None 

+

 

+

        if len(auth) == 1: 

+

            msg = 'Invalid token header. No credentials provided.' 

+

            raise exceptions.AuthenticationFailed(msg) 

+

        elif len(auth) > 2: 

+

            msg = 'Invalid token header. Token string should not contain spaces.' 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        return self.authenticate_credentials(auth[1]) 

+

 

+

    def authenticate_credentials(self, key): 

+

        try: 

+

            token = self.model.objects.get(key=key) 

+

        except self.model.DoesNotExist: 

+

            raise exceptions.AuthenticationFailed('Invalid token') 

+

 

+

        if not token.user.is_active: 

+

            raise exceptions.AuthenticationFailed('User inactive or deleted') 

+

 

+

        return (token.user, token) 

+

 

+

    def authenticate_header(self, request): 

+

        return 'Token' 

+

 

+

 

+

class OAuthAuthentication(BaseAuthentication): 

+

    """ 

+

    OAuth 1.0a authentication backend using `django-oauth-plus` and `oauth2`. 

+

 

+

    Note: The `oauth2` package actually provides oauth1.0a support.  Urg. 

+

          We import it from the `compat` module as `oauth`. 

+

    """ 

+

    www_authenticate_realm = 'api' 

+

 

+

    def __init__(self, *args, **kwargs): 

+

        super(OAuthAuthentication, self).__init__(*args, **kwargs) 

+

 

+

        if oauth is None: 

+

            raise ImproperlyConfigured( 

+

                "The 'oauth2' package could not be imported." 

+

                "It is required for use with the 'OAuthAuthentication' class.") 

+

 

+

        if oauth_provider is None: 

+

            raise ImproperlyConfigured( 

+

                "The 'django-oauth-plus' package could not be imported." 

+

                "It is required for use with the 'OAuthAuthentication' class.") 

+

 

+

    def authenticate(self, request): 

+

        """ 

+

        Returns two-tuple of (user, token) if authentication succeeds, 

+

        or None otherwise. 

+

        """ 

+

        try: 

+

            oauth_request = oauth_provider.utils.get_oauth_request(request) 

+

        except oauth.Error as err: 

+

            raise exceptions.AuthenticationFailed(err.message) 

+

 

+

        if not oauth_request: 

+

            return None 

+

 

+

        oauth_params = oauth_provider.consts.OAUTH_PARAMETERS_NAMES 

+

 

+

        found = any(param for param in oauth_params if param in oauth_request) 

+

        missing = list(param for param in oauth_params if param not in oauth_request) 

+

 

+

        if not found: 

+

            # OAuth authentication was not attempted. 

+

            return None 

+

 

+

        if missing: 

+

            # OAuth was attempted but missing parameters. 

+

            msg = 'Missing parameters: %s' % (', '.join(missing)) 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        if not self.check_nonce(request, oauth_request): 

+

            msg = 'Nonce check failed' 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        try: 

+

            consumer_key = oauth_request.get_parameter('oauth_consumer_key') 

+

            consumer = oauth_provider_store.get_consumer(request, oauth_request, consumer_key) 

+

        except oauth_provider.store.InvalidConsumerError: 

+

            msg = 'Invalid consumer token: %s' % oauth_request.get_parameter('oauth_consumer_key') 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        if consumer.status != oauth_provider.consts.ACCEPTED: 

+

            msg = 'Invalid consumer key status: %s' % consumer.get_status_display() 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        try: 

+

            token_param = oauth_request.get_parameter('oauth_token') 

+

            token = oauth_provider_store.get_access_token(request, oauth_request, consumer, token_param) 

+

        except oauth_provider.store.InvalidTokenError: 

+

            msg = 'Invalid access token: %s' % oauth_request.get_parameter('oauth_token') 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        try: 

+

            self.validate_token(request, consumer, token) 

+

        except oauth.Error as err: 

+

            raise exceptions.AuthenticationFailed(err.message) 

+

 

+

        user = token.user 

+

 

+

        if not user.is_active: 

+

            msg = 'User inactive or deleted: %s' % user.username 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        return (token.user, token) 

+

 

+

    def authenticate_header(self, request): 

+

        """ 

+

        If permission is denied, return a '401 Unauthorized' response, 

+

        with an appropraite 'WWW-Authenticate' header. 

+

        """ 

+

        return 'OAuth realm="%s"' % self.www_authenticate_realm 

+

 

+

    def validate_token(self, request, consumer, token): 

+

        """ 

+

        Check the token and raise an `oauth.Error` exception if invalid. 

+

        """ 

+

        oauth_server, oauth_request = oauth_provider.utils.initialize_server_request(request) 

+

        oauth_server.verify_request(oauth_request, consumer, token) 

+

 

+

    def check_nonce(self, request, oauth_request): 

+

        """ 

+

        Checks nonce of request, and return True if valid. 

+

        """ 

+

        return oauth_provider_store.check_nonce(request, oauth_request, oauth_request['oauth_nonce']) 

+

 

+

 

+

class OAuth2Authentication(BaseAuthentication): 

+

    """ 

+

    OAuth 2 authentication backend using `django-oauth2-provider` 

+

    """ 

+

    www_authenticate_realm = 'api' 

+

 

+

    def __init__(self, *args, **kwargs): 

+

        super(OAuth2Authentication, self).__init__(*args, **kwargs) 

+

 

+

        if oauth2_provider is None: 

+

            raise ImproperlyConfigured( 

+

                "The 'django-oauth2-provider' package could not be imported. " 

+

                "It is required for use with the 'OAuth2Authentication' class.") 

+

 

+

    def authenticate(self, request): 

+

        """ 

+

        Returns two-tuple of (user, token) if authentication succeeds, 

+

        or None otherwise. 

+

        """ 

+

 

+

        auth = get_authorization_header(request).split() 

+

 

+

        if not auth or auth[0].lower() != b'bearer': 

+

            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, auth[1]) 

+

 

+

    def authenticate_credentials(self, request, access_token): 

+

        """ 

+

        Authenticate the request, given the access token. 

+

        """ 

+

 

+

        try: 

+

            token = oauth2_provider.models.AccessToken.objects.select_related('user') 

+

            # TODO: Change to timezone aware datetime when oauth2_provider add 

+

            # support to it. 

+

            token = token.get(token=access_token, expires__gt=datetime.now()) 

+

        except oauth2_provider.models.AccessToken.DoesNotExist: 

+

            raise exceptions.AuthenticationFailed('Invalid token') 

+

 

+

        user = token.user 

+

 

+

        if not user.is_active: 

+

            msg = 'User inactive or deleted: %s' % user.username 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        return (user, token) 

+

 

+

    def authenticate_header(self, request): 

+

        """ 

+

        Bearer is the only finalized type currently 

+

 

+

        Check details on the `OAuth2Authentication.authenticate` method 

+

        """ 

+

        return 'Bearer realm="%s"' % self.www_authenticate_realm 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_authtoken___init__.html b/htmlcov/rest_framework_authtoken___init__.html new file mode 100644 index 00000000..f7257493 --- /dev/null +++ b/htmlcov/rest_framework_authtoken___init__.html @@ -0,0 +1,81 @@ + + + + + + + + Coverage for rest_framework/authtoken/__init__: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+ + + +
+
+ + + + + diff --git a/htmlcov/rest_framework_authtoken_models.html b/htmlcov/rest_framework_authtoken_models.html new file mode 100644 index 00000000..27d2fff1 --- /dev/null +++ b/htmlcov/rest_framework_authtoken_models.html @@ -0,0 +1,151 @@ + + + + + + + + Coverage for rest_framework/authtoken/models: 95% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+ +
+

import uuid 

+

import hmac 

+

from hashlib import sha1 

+

from rest_framework.compat import User 

+

from django.conf import settings 

+

from django.db import models 

+

 

+

 

+

class Token(models.Model): 

+

    """ 

+

    The default authorization token model. 

+

    """ 

+

    key = models.CharField(max_length=40, primary_key=True) 

+

    user = models.OneToOneField(User, related_name='auth_token') 

+

    created = models.DateTimeField(auto_now_add=True) 

+

 

+

    class Meta: 

+

        # Work around for a bug in Django: 

+

        # https://code.djangoproject.com/ticket/19422 

+

        # 

+

        # Also see corresponding ticket: 

+

        # https://github.com/tomchristie/django-rest-framework/issues/705 

+

        abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS 

+

 

+

    def save(self, *args, **kwargs): 

+

        if not self.key: 

+

            self.key = self.generate_key() 

+

        return super(Token, self).save(*args, **kwargs) 

+

 

+

    def generate_key(self): 

+

        unique = uuid.uuid4() 

+

        return hmac.new(unique.bytes, digestmod=sha1).hexdigest() 

+

 

+

    def __unicode__(self): 

+

        return self.key 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_authtoken_serializers.html b/htmlcov/rest_framework_authtoken_serializers.html new file mode 100644 index 00000000..8997d9a7 --- /dev/null +++ b/htmlcov/rest_framework_authtoken_serializers.html @@ -0,0 +1,129 @@ + + + + + + + + Coverage for rest_framework/authtoken/serializers: 88% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+ +
+

from django.contrib.auth import authenticate 

+

from rest_framework import serializers 

+

 

+

 

+

class AuthTokenSerializer(serializers.Serializer): 

+

    username = serializers.CharField() 

+

    password = serializers.CharField() 

+

 

+

    def validate(self, attrs): 

+

        username = attrs.get('username') 

+

        password = attrs.get('password') 

+

 

+

        if username and password: 

+

            user = authenticate(username=username, password=password) 

+

 

+

            if user: 

+

                if not user.is_active: 

+

                    raise serializers.ValidationError('User account is disabled.') 

+

                attrs['user'] = user 

+

                return attrs 

+

            else: 

+

                raise serializers.ValidationError('Unable to login with provided credentials.') 

+

        else: 

+

            raise serializers.ValidationError('Must include "username" and "password"') 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_authtoken_views.html b/htmlcov/rest_framework_authtoken_views.html new file mode 100644 index 00000000..d13746ea --- /dev/null +++ b/htmlcov/rest_framework_authtoken_views.html @@ -0,0 +1,133 @@ + + + + + + + + Coverage for rest_framework/authtoken/views: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+ +
+

from rest_framework.views import APIView 

+

from rest_framework import status 

+

from rest_framework import parsers 

+

from rest_framework import renderers 

+

from rest_framework.response import Response 

+

from rest_framework.authtoken.models import Token 

+

from rest_framework.authtoken.serializers import AuthTokenSerializer 

+

 

+

 

+

class ObtainAuthToken(APIView): 

+

    throttle_classes = () 

+

    permission_classes = () 

+

    parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,) 

+

    renderer_classes = (renderers.JSONRenderer,) 

+

    serializer_class = AuthTokenSerializer 

+

    model = Token 

+

 

+

    def post(self, request): 

+

        serializer = self.serializer_class(data=request.DATA) 

+

        if serializer.is_valid(): 

+

            token, created = Token.objects.get_or_create(user=serializer.object['user']) 

+

            return Response({'token': token.key}) 

+

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 

+

 

+

 

+

obtain_auth_token = ObtainAuthToken.as_view() 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_decorators.html b/htmlcov/rest_framework_decorators.html new file mode 100644 index 00000000..6ad6f6b5 --- /dev/null +++ b/htmlcov/rest_framework_decorators.html @@ -0,0 +1,339 @@ + + + + + + + + Coverage for rest_framework/decorators: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+ +
+

""" 

+

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 

+

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 

+

 

+

 

+

def api_view(http_method_names): 

+

 

+

    """ 

+

    Decorator that converts a function-based view into an APIView subclass. 

+

    Takes a list of allowed methods for the view as an argument. 

+

    """ 

+

 

+

    def decorator(func): 

+

 

+

        WrappedAPIView = type( 

+

            six.PY3 and 'WrappedAPIView' or b'WrappedAPIView', 

+

            (APIView,), 

+

            {'__doc__': func.__doc__} 

+

        ) 

+

 

+

        # Note, the above allows us to set the docstring. 

+

        # It is the equivalent of: 

+

        # 

+

        #     class WrappedAPIView(APIView): 

+

        #         pass 

+

        #     WrappedAPIView.__doc__ = func.doc    <--- Not possible to do this 

+

 

+

        # api_view applied without (method_names) 

+

        assert not(isinstance(http_method_names, types.FunctionType)), \ 

+

            '@api_view missing list of allowed HTTP methods' 

+

 

+

        # api_view applied with eg. string instead of list of strings 

+

        assert isinstance(http_method_names, (list, tuple)), \ 

+

            '@api_view expected a list of strings, received %s' % type(http_method_names).__name__ 

+

 

+

        allowed_methods = set(http_method_names) | set(('options',)) 

+

        WrappedAPIView.http_method_names = [method.lower() for method in allowed_methods] 

+

 

+

        def handler(self, *args, **kwargs): 

+

            return func(*args, **kwargs) 

+

 

+

        for method in http_method_names: 

+

            setattr(WrappedAPIView, method.lower(), handler) 

+

 

+

        WrappedAPIView.__name__ = func.__name__ 

+

 

+

        WrappedAPIView.renderer_classes = getattr(func, 'renderer_classes', 

+

                                                  APIView.renderer_classes) 

+

 

+

        WrappedAPIView.parser_classes = getattr(func, 'parser_classes', 

+

                                                APIView.parser_classes) 

+

 

+

        WrappedAPIView.authentication_classes = getattr(func, 'authentication_classes', 

+

                                                        APIView.authentication_classes) 

+

 

+

        WrappedAPIView.throttle_classes = getattr(func, 'throttle_classes', 

+

                                                  APIView.throttle_classes) 

+

 

+

        WrappedAPIView.permission_classes = getattr(func, 'permission_classes', 

+

                                                    APIView.permission_classes) 

+

 

+

        return WrappedAPIView.as_view() 

+

    return decorator 

+

 

+

 

+

def renderer_classes(renderer_classes): 

+

    def decorator(func): 

+

        func.renderer_classes = renderer_classes 

+

        return func 

+

    return decorator 

+

 

+

 

+

def parser_classes(parser_classes): 

+

    def decorator(func): 

+

        func.parser_classes = parser_classes 

+

        return func 

+

    return decorator 

+

 

+

 

+

def authentication_classes(authentication_classes): 

+

    def decorator(func): 

+

        func.authentication_classes = authentication_classes 

+

        return func 

+

    return decorator 

+

 

+

 

+

def throttle_classes(throttle_classes): 

+

    def decorator(func): 

+

        func.throttle_classes = throttle_classes 

+

        return func 

+

    return decorator 

+

 

+

 

+

def permission_classes(permission_classes): 

+

    def decorator(func): 

+

        func.permission_classes = permission_classes 

+

        return func 

+

    return decorator 

+

 

+

 

+

def 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.kwargs = kwargs 

+

        return func 

+

    return decorator 

+

 

+

 

+

def 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.kwargs = kwargs 

+

        return func 

+

    return decorator 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_exceptions.html b/htmlcov/rest_framework_exceptions.html new file mode 100644 index 00000000..d975a848 --- /dev/null +++ b/htmlcov/rest_framework_exceptions.html @@ -0,0 +1,257 @@ + + + + + + + + Coverage for rest_framework/exceptions: 96% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+ +
+

""" 

+

Handled exceptions raised by REST framework. 

+

 

+

In addition Django's built in 403 and 404 exceptions are handled. 

+

(`django.http.Http404` and `django.core.exceptions.PermissionDenied`) 

+

""" 

+

from __future__ import unicode_literals 

+

from rest_framework import status 

+

 

+

 

+

class APIException(Exception): 

+

    """ 

+

    Base class for REST framework exceptions. 

+

    Subclasses should provide `.status_code` and `.detail` properties. 

+

    """ 

+

    pass 

+

 

+

 

+

class ParseError(APIException): 

+

    status_code = status.HTTP_400_BAD_REQUEST 

+

    default_detail = 'Malformed request.' 

+

 

+

    def __init__(self, detail=None): 

+

        self.detail = detail or self.default_detail 

+

 

+

 

+

class AuthenticationFailed(APIException): 

+

    status_code = status.HTTP_401_UNAUTHORIZED 

+

    default_detail = 'Incorrect authentication credentials.' 

+

 

+

    def __init__(self, detail=None): 

+

        self.detail = detail or self.default_detail 

+

 

+

 

+

class NotAuthenticated(APIException): 

+

    status_code = status.HTTP_401_UNAUTHORIZED 

+

    default_detail = 'Authentication credentials were not provided.' 

+

 

+

    def __init__(self, detail=None): 

+

        self.detail = detail or self.default_detail 

+

 

+

 

+

class PermissionDenied(APIException): 

+

    status_code = status.HTTP_403_FORBIDDEN 

+

    default_detail = 'You do not have permission to perform this action.' 

+

 

+

    def __init__(self, detail=None): 

+

        self.detail = detail or self.default_detail 

+

 

+

 

+

class MethodNotAllowed(APIException): 

+

    status_code = status.HTTP_405_METHOD_NOT_ALLOWED 

+

    default_detail = "Method '%s' not allowed." 

+

 

+

    def __init__(self, method, detail=None): 

+

        self.detail = (detail or self.default_detail) % method 

+

 

+

 

+

class NotAcceptable(APIException): 

+

    status_code = status.HTTP_406_NOT_ACCEPTABLE 

+

    default_detail = "Could not satisfy the request's Accept header" 

+

 

+

    def __init__(self, detail=None, available_renderers=None): 

+

        self.detail = detail or self.default_detail 

+

        self.available_renderers = available_renderers 

+

 

+

 

+

class UnsupportedMediaType(APIException): 

+

    status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE 

+

    default_detail = "Unsupported media type '%s' in request." 

+

 

+

    def __init__(self, media_type, detail=None): 

+

        self.detail = (detail or self.default_detail) % media_type 

+

 

+

 

+

class Throttled(APIException): 

+

    status_code = status.HTTP_429_TOO_MANY_REQUESTS 

+

    default_detail = "Request was throttled." 

+

    extra_detail = "Expected available in %d second%s." 

+

 

+

    def __init__(self, wait=None, detail=None): 

+

        import math 

+

        self.wait = wait and math.ceil(wait) or None 

+

        if wait is not None: 

+

            format = detail or self.default_detail + self.extra_detail 

+

            self.detail = format % (self.wait, self.wait != 1 and 's' or '') 

+

        else: 

+

            self.detail = detail or self.default_detail 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_fields.html b/htmlcov/rest_framework_fields.html new file mode 100644 index 00000000..cf2731d2 --- /dev/null +++ b/htmlcov/rest_framework_fields.html @@ -0,0 +1,1991 @@ + + + + + + + + Coverage for rest_framework/fields: 87% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+

344

+

345

+

346

+

347

+

348

+

349

+

350

+

351

+

352

+

353

+

354

+

355

+

356

+

357

+

358

+

359

+

360

+

361

+

362

+

363

+

364

+

365

+

366

+

367

+

368

+

369

+

370

+

371

+

372

+

373

+

374

+

375

+

376

+

377

+

378

+

379

+

380

+

381

+

382

+

383

+

384

+

385

+

386

+

387

+

388

+

389

+

390

+

391

+

392

+

393

+

394

+

395

+

396

+

397

+

398

+

399

+

400

+

401

+

402

+

403

+

404

+

405

+

406

+

407

+

408

+

409

+

410

+

411

+

412

+

413

+

414

+

415

+

416

+

417

+

418

+

419

+

420

+

421

+

422

+

423

+

424

+

425

+

426

+

427

+

428

+

429

+

430

+

431

+

432

+

433

+

434

+

435

+

436

+

437

+

438

+

439

+

440

+

441

+

442

+

443

+

444

+

445

+

446

+

447

+

448

+

449

+

450

+

451

+

452

+

453

+

454

+

455

+

456

+

457

+

458

+

459

+

460

+

461

+

462

+

463

+

464

+

465

+

466

+

467

+

468

+

469

+

470

+

471

+

472

+

473

+

474

+

475

+

476

+

477

+

478

+

479

+

480

+

481

+

482

+

483

+

484

+

485

+

486

+

487

+

488

+

489

+

490

+

491

+

492

+

493

+

494

+

495

+

496

+

497

+

498

+

499

+

500

+

501

+

502

+

503

+

504

+

505

+

506

+

507

+

508

+

509

+

510

+

511

+

512

+

513

+

514

+

515

+

516

+

517

+

518

+

519

+

520

+

521

+

522

+

523

+

524

+

525

+

526

+

527

+

528

+

529

+

530

+

531

+

532

+

533

+

534

+

535

+

536

+

537

+

538

+

539

+

540

+

541

+

542

+

543

+

544

+

545

+

546

+

547

+

548

+

549

+

550

+

551

+

552

+

553

+

554

+

555

+

556

+

557

+

558

+

559

+

560

+

561

+

562

+

563

+

564

+

565

+

566

+

567

+

568

+

569

+

570

+

571

+

572

+

573

+

574

+

575

+

576

+

577

+

578

+

579

+

580

+

581

+

582

+

583

+

584

+

585

+

586

+

587

+

588

+

589

+

590

+

591

+

592

+

593

+

594

+

595

+

596

+

597

+

598

+

599

+

600

+

601

+

602

+

603

+

604

+

605

+

606

+

607

+

608

+

609

+

610

+

611

+

612

+

613

+

614

+

615

+

616

+

617

+

618

+

619

+

620

+

621

+

622

+

623

+

624

+

625

+

626

+

627

+

628

+

629

+

630

+

631

+

632

+

633

+

634

+

635

+

636

+

637

+

638

+

639

+

640

+

641

+

642

+

643

+

644

+

645

+

646

+

647

+

648

+

649

+

650

+

651

+

652

+

653

+

654

+

655

+

656

+

657

+

658

+

659

+

660

+

661

+

662

+

663

+

664

+

665

+

666

+

667

+

668

+

669

+

670

+

671

+

672

+

673

+

674

+

675

+

676

+

677

+

678

+

679

+

680

+

681

+

682

+

683

+

684

+

685

+

686

+

687

+

688

+

689

+

690

+

691

+

692

+

693

+

694

+

695

+

696

+

697

+

698

+

699

+

700

+

701

+

702

+

703

+

704

+

705

+

706

+

707

+

708

+

709

+

710

+

711

+

712

+

713

+

714

+

715

+

716

+

717

+

718

+

719

+

720

+

721

+

722

+

723

+

724

+

725

+

726

+

727

+

728

+

729

+

730

+

731

+

732

+

733

+

734

+

735

+

736

+

737

+

738

+

739

+

740

+

741

+

742

+

743

+

744

+

745

+

746

+

747

+

748

+

749

+

750

+

751

+

752

+

753

+

754

+

755

+

756

+

757

+

758

+

759

+

760

+

761

+

762

+

763

+

764

+

765

+

766

+

767

+

768

+

769

+

770

+

771

+

772

+

773

+

774

+

775

+

776

+

777

+

778

+

779

+

780

+

781

+

782

+

783

+

784

+

785

+

786

+

787

+

788

+

789

+

790

+

791

+

792

+

793

+

794

+

795

+

796

+

797

+

798

+

799

+

800

+

801

+

802

+

803

+

804

+

805

+

806

+

807

+

808

+

809

+

810

+

811

+

812

+

813

+

814

+

815

+

816

+

817

+

818

+

819

+

820

+

821

+

822

+

823

+

824

+

825

+

826

+

827

+

828

+

829

+

830

+

831

+

832

+

833

+

834

+

835

+

836

+

837

+

838

+

839

+

840

+

841

+

842

+

843

+

844

+

845

+

846

+

847

+

848

+

849

+

850

+

851

+

852

+

853

+

854

+

855

+

856

+

857

+

858

+

859

+

860

+

861

+

862

+

863

+

864

+

865

+

866

+

867

+

868

+

869

+

870

+

871

+

872

+

873

+

874

+

875

+

876

+

877

+

878

+

879

+

880

+

881

+

882

+

883

+

884

+

885

+

886

+

887

+

888

+

889

+

890

+

891

+

892

+

893

+

894

+

895

+

896

+

897

+

898

+

899

+

900

+

901

+

902

+

903

+

904

+

905

+

906

+

907

+

908

+

909

+

910

+

911

+

912

+

913

+

914

+

915

+

916

+

917

+

918

+

919

+

920

+

921

+

922

+

923

+

924

+

925

+

926

+

927

+

928

+

929

+

930

+

931

+

932

+

933

+

934

+

935

+

936

+

937

+

938

+

939

+

940

+

941

+

942

+

943

+

944

+

945

+

946

+

947

+

948

+

949

+

950

+

951

+

952

+

953

+

954

+

955

+ +
+

""" 

+

Serializer fields perform validation on incoming data. 

+

 

+

They are very similar to Django's form fields. 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

import copy 

+

import datetime 

+

import inspect 

+

import re 

+

import warnings 

+

from decimal import Decimal, DecimalException 

+

from django import forms 

+

from django.core import validators 

+

from django.core.exceptions import ValidationError 

+

from django.conf import settings 

+

from django.db.models.fields import BLANK_CHOICE_DASH 

+

from django.forms import widgets 

+

from django.utils.encoding import is_protected_type 

+

from django.utils.translation import ugettext_lazy as _ 

+

from django.utils.datastructures import SortedDict 

+

from rest_framework import ISO_8601 

+

from rest_framework.compat import ( 

+

    timezone, parse_date, parse_datetime, parse_time, BytesIO, six, smart_text, 

+

    force_text, is_non_str_iterable 

+

) 

+

from rest_framework.settings import api_settings 

+

 

+

 

+

def is_simple_callable(obj): 

+

    """ 

+

    True if the object is a callable that takes no arguments. 

+

    """ 

+

    function = inspect.isfunction(obj) 

+

    method = inspect.ismethod(obj) 

+

 

+

    if not (function or method): 

+

        return False 

+

 

+

    args, _, _, defaults = inspect.getargspec(obj) 

+

    len_args = len(args) if function else len(args) - 1 

+

    len_defaults = len(defaults) if defaults else 0 

+

    return len_args <= len_defaults 

+

 

+

 

+

def get_component(obj, attr_name): 

+

    """ 

+

    Given an object, and an attribute name, 

+

    return that attribute on the object. 

+

    """ 

+

    if isinstance(obj, dict): 

+

        val = obj.get(attr_name) 

+

    else: 

+

        val = getattr(obj, attr_name) 

+

 

+

    if is_simple_callable(val): 

+

        return val() 

+

    return val 

+

 

+

 

+

def readable_datetime_formats(formats): 

+

    format = ', '.join(formats).replace(ISO_8601, 

+

             'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HHMM|-HHMM|Z]') 

+

    return humanize_strptime(format) 

+

 

+

 

+

def readable_date_formats(formats): 

+

    format = ', '.join(formats).replace(ISO_8601, 'YYYY[-MM[-DD]]') 

+

    return humanize_strptime(format) 

+

 

+

 

+

def readable_time_formats(formats): 

+

    format = ', '.join(formats).replace(ISO_8601, 'hh:mm[:ss[.uuuuuu]]') 

+

    return humanize_strptime(format) 

+

 

+

 

+

def humanize_strptime(format_string): 

+

    # Note that we're missing some of the locale specific mappings that 

+

    # don't really make sense. 

+

    mapping = { 

+

        "%Y": "YYYY", 

+

        "%y": "YY", 

+

        "%m": "MM", 

+

        "%b": "[Jan-Dec]", 

+

        "%B": "[January-December]", 

+

        "%d": "DD", 

+

        "%H": "hh", 

+

        "%I": "hh",  # Requires '%p' to differentiate from '%H'. 

+

        "%M": "mm", 

+

        "%S": "ss", 

+

        "%f": "uuuuuu", 

+

        "%a": "[Mon-Sun]", 

+

        "%A": "[Monday-Sunday]", 

+

        "%p": "[AM|PM]", 

+

        "%z": "[+HHMM|-HHMM]" 

+

    } 

+

    for key, val in mapping.items(): 

+

        format_string = format_string.replace(key, val) 

+

    return format_string 

+

 

+

 

+

class Field(object): 

+

    read_only = True 

+

    creation_counter = 0 

+

    empty = '' 

+

    type_name = None 

+

    partial = False 

+

    use_files = False 

+

    form_field_class = forms.CharField 

+

    type_label = 'field' 

+

 

+

    def __init__(self, source=None, label=None, help_text=None): 

+

        self.parent = None 

+

 

+

        self.creation_counter = Field.creation_counter 

+

        Field.creation_counter += 1 

+

 

+

        self.source = source 

+

 

+

        if label is not None: 

+

            self.label = smart_text(label) 

+

 

+

        if help_text is not None: 

+

            self.help_text = smart_text(help_text) 

+

 

+

    def initialize(self, parent, field_name): 

+

        """ 

+

        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. 

+

        """ 

+

        self.parent = parent 

+

        self.root = parent.root or parent 

+

        self.context = self.root.context 

+

        self.partial = self.root.partial 

+

        if self.partial: 

+

            self.required = False 

+

 

+

    def field_from_native(self, data, files, field_name, into): 

+

        """ 

+

        Given a dictionary and a field name, updates the dictionary `into`, 

+

        with the field and it's deserialized value. 

+

        """ 

+

        return 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        """ 

+

        Given and object and a field name, returns the value that should be 

+

        serialized for that field. 

+

        """ 

+

        if obj is None: 

+

            return self.empty 

+

 

+

        if self.source == '*': 

+

            return self.to_native(obj) 

+

 

+

        source = self.source or field_name 

+

        value = obj 

+

 

+

        for component in source.split('.'): 

+

            value = get_component(value, component) 

+

            if value is None: 

+

                break 

+

 

+

        return self.to_native(value) 

+

 

+

    def to_native(self, value): 

+

        """ 

+

        Converts the field's value into it's simple representation. 

+

        """ 

+

        if is_simple_callable(value): 

+

            value = value() 

+

 

+

        if is_protected_type(value): 

+

            return value 

+

        elif (is_non_str_iterable(value) and 

+

              not isinstance(value, (dict, six.string_types))): 

+

            return [self.to_native(item) for item in value] 

+

        elif isinstance(value, dict): 

+

            # Make sure we preserve field ordering, if it exists 

+

            ret = SortedDict() 

+

            for key, val in value.items(): 

+

                ret[key] = self.to_native(val) 

+

            return ret 

+

        return force_text(value) 

+

 

+

    def attributes(self): 

+

        """ 

+

        Returns a dictionary of attributes to be used when serializing to xml. 

+

        """ 

+

        if self.type_name: 

+

            return {'type': self.type_name} 

+

        return {} 

+

 

+

    def metadata(self): 

+

        metadata = SortedDict() 

+

        metadata['type'] = self.type_label 

+

        metadata['required'] = getattr(self, 'required', False) 

+

        optional_attrs = ['read_only', 'label', 'help_text', 

+

                          'min_length', 'max_length'] 

+

        for attr in optional_attrs: 

+

            value = getattr(self, attr, None) 

+

            if value is not None and value != '': 

+

                metadata[attr] = force_text(value, strings_only=True) 

+

        return metadata 

+

 

+

 

+

class WritableField(Field): 

+

    """ 

+

    Base for read/write fields. 

+

    """ 

+

    default_validators = [] 

+

    default_error_messages = { 

+

        'required': _('This field is required.'), 

+

        'invalid': _('Invalid value.'), 

+

    } 

+

    widget = widgets.TextInput 

+

    default = None 

+

 

+

    def __init__(self, source=None, label=None, help_text=None, 

+

                 read_only=False, required=None, 

+

                 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 

+

        if required is None: 

+

            self.required = not(read_only) 

+

        else: 

+

            assert not (read_only and required), "Cannot set required=True and read_only=True" 

+

            self.required = required 

+

 

+

        messages = {} 

+

        for c in reversed(self.__class__.__mro__): 

+

            messages.update(getattr(c, 'default_error_messages', {})) 

+

        messages.update(error_messages or {}) 

+

        self.error_messages = messages 

+

 

+

        self.validators = self.default_validators + validators 

+

        self.default = default if default is not None else self.default 

+

 

+

        # Widgets are ony used for HTML forms. 

+

        widget = widget or self.widget 

+

        if isinstance(widget, type): 

+

            widget = widget() 

+

        self.widget = widget 

+

 

+

    def __deepcopy__(self, memo): 

+

        result = copy.copy(self) 

+

        memo[id(self)] = result 

+

        result.validators = self.validators[:] 

+

        return result 

+

 

+

    def validate(self, value): 

+

        if value in validators.EMPTY_VALUES and self.required: 

+

            raise ValidationError(self.error_messages['required']) 

+

 

+

    def run_validators(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return 

+

        errors = [] 

+

        for v in self.validators: 

+

            try: 

+

                v(value) 

+

            except ValidationError as e: 

+

                if hasattr(e, 'code') and e.code in self.error_messages: 

+

                    message = self.error_messages[e.code] 

+

                    if e.params: 

+

                        message = message % e.params 

+

                    errors.append(message) 

+

                else: 

+

                    errors.extend(e.messages) 

+

        if errors: 

+

            raise ValidationError(errors) 

+

 

+

    def field_from_native(self, data, files, field_name, into): 

+

        """ 

+

        Given a dictionary and a field name, updates the dictionary `into`, 

+

        with the field and it's deserialized value. 

+

        """ 

+

        if self.read_only: 

+

            return 

+

 

+

        try: 

+

            if self.use_files: 

+

                files = files or {} 

+

                native = files[field_name] 

+

            else: 

+

                native = data[field_name] 

+

        except KeyError: 

+

            if self.default is not None and not self.partial: 

+

                # Note: partial updates shouldn't set defaults 

+

                if is_simple_callable(self.default): 

+

                    native = self.default() 

+

                else: 

+

                    native = self.default 

+

            else: 

+

                if self.required: 

+

                    raise ValidationError(self.error_messages['required']) 

+

                return 

+

 

+

        value = self.from_native(native) 

+

        if self.source == '*': 

+

            if value: 

+

                into.update(value) 

+

        else: 

+

            self.validate(value) 

+

            self.run_validators(value) 

+

            into[self.source or field_name] = value 

+

 

+

    def from_native(self, value): 

+

        """ 

+

        Reverts a simple representation back to the field's value. 

+

        """ 

+

        return value 

+

 

+

 

+

class ModelField(WritableField): 

+

    """ 

+

    A generic field that can be used against an arbitrary model field. 

+

    """ 

+

    def __init__(self, *args, **kwargs): 

+

        try: 

+

            self.model_field = kwargs.pop('model_field') 

+

        except KeyError: 

+

            raise ValueError("ModelField requires 'model_field' kwarg") 

+

 

+

        self.min_length = kwargs.pop('min_length', 

+

                                     getattr(self.model_field, 'min_length', None)) 

+

        self.max_length = kwargs.pop('max_length', 

+

                                     getattr(self.model_field, 'max_length', None)) 

+

        self.min_value = kwargs.pop('min_value', 

+

                                    getattr(self.model_field, 'min_value', None)) 

+

        self.max_value = kwargs.pop('max_value', 

+

                                    getattr(self.model_field, 'max_value', None)) 

+

 

+

        super(ModelField, self).__init__(*args, **kwargs) 

+

 

+

        if self.min_length is not None: 

+

            self.validators.append(validators.MinLengthValidator(self.min_length)) 

+

        if self.max_length is not None: 

+

            self.validators.append(validators.MaxLengthValidator(self.max_length)) 

+

        if self.min_value is not None: 

+

            self.validators.append(validators.MinValueValidator(self.min_value)) 

+

        if self.max_value is not None: 

+

            self.validators.append(validators.MaxValueValidator(self.max_value)) 

+

 

+

    def from_native(self, value): 

+

        rel = getattr(self.model_field, "rel", None) 

+

        if rel is not None: 

+

            return rel.to._meta.get_field(rel.field_name).to_python(value) 

+

        else: 

+

            return self.model_field.to_python(value) 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        value = self.model_field._get_val_from_obj(obj) 

+

        if is_protected_type(value): 

+

            return value 

+

        return self.model_field.value_to_string(obj) 

+

 

+

    def attributes(self): 

+

        return { 

+

            "type": self.model_field.get_internal_type() 

+

        } 

+

 

+

 

+

##### Typed Fields ##### 

+

 

+

class BooleanField(WritableField): 

+

    type_name = 'BooleanField' 

+

    type_label = 'boolean' 

+

    form_field_class = forms.BooleanField 

+

    widget = widgets.CheckboxInput 

+

    default_error_messages = { 

+

        'invalid': _("'%s' value must be either True or False."), 

+

    } 

+

    empty = False 

+

 

+

    # Note: we set default to `False` in order to fill in missing value not 

+

    # supplied by html form.  TODO: Fix so that only html form input gets 

+

    # this behavior. 

+

    default = False 

+

 

+

    def from_native(self, value): 

+

        if value in ('true', 't', 'True', '1'): 

+

            return True 

+

        if value in ('false', 'f', 'False', '0'): 

+

            return False 

+

        return bool(value) 

+

 

+

 

+

class CharField(WritableField): 

+

    type_name = 'CharField' 

+

    type_label = 'string' 

+

    form_field_class = forms.CharField 

+

 

+

    def __init__(self, max_length=None, min_length=None, *args, **kwargs): 

+

        self.max_length, self.min_length = max_length, min_length 

+

        super(CharField, self).__init__(*args, **kwargs) 

+

        if min_length is not None: 

+

            self.validators.append(validators.MinLengthValidator(min_length)) 

+

        if max_length is not None: 

+

            self.validators.append(validators.MaxLengthValidator(max_length)) 

+

 

+

    def from_native(self, value): 

+

        if isinstance(value, six.string_types) or value is None: 

+

            return value 

+

        return smart_text(value) 

+

 

+

 

+

class URLField(CharField): 

+

    type_name = 'URLField' 

+

    type_label = 'url' 

+

 

+

    def __init__(self, **kwargs): 

+

        kwargs['validators'] = [validators.URLValidator()] 

+

        super(URLField, self).__init__(**kwargs) 

+

 

+

 

+

class SlugField(CharField): 

+

    type_name = 'SlugField' 

+

    type_label = 'slug' 

+

    form_field_class = forms.SlugField 

+

 

+

    default_error_messages = { 

+

        'invalid': _("Enter a valid 'slug' consisting of letters, numbers," 

+

                     " underscores or hyphens."), 

+

    } 

+

    default_validators = [validators.validate_slug] 

+

 

+

    def __init__(self, *args, **kwargs): 

+

        super(SlugField, self).__init__(*args, **kwargs) 

+

 

+

 

+

class ChoiceField(WritableField): 

+

    type_name = 'ChoiceField' 

+

    type_label = 'multiple choice' 

+

    form_field_class = forms.ChoiceField 

+

    widget = widgets.Select 

+

    default_error_messages = { 

+

        'invalid_choice': _('Select a valid choice. %(value)s is not one of ' 

+

                            'the available choices.'), 

+

    } 

+

 

+

    def __init__(self, choices=(), *args, **kwargs): 

+

        super(ChoiceField, self).__init__(*args, **kwargs) 

+

        self.choices = choices 

+

        if not self.required: 

+

            self.choices = BLANK_CHOICE_DASH + self.choices 

+

 

+

    def _get_choices(self): 

+

        return self._choices 

+

 

+

    def _set_choices(self, value): 

+

        # Setting choices also sets the choices on the widget. 

+

        # choices can be any iterable, but we call list() on it because 

+

        # it will be consumed more than once. 

+

        self._choices = self.widget.choices = list(value) 

+

 

+

    choices = property(_get_choices, _set_choices) 

+

 

+

    def validate(self, value): 

+

        """ 

+

        Validates that the input is in self.choices. 

+

        """ 

+

        super(ChoiceField, self).validate(value) 

+

        if value and not self.valid_value(value): 

+

            raise ValidationError(self.error_messages['invalid_choice'] % {'value': value}) 

+

 

+

    def valid_value(self, value): 

+

        """ 

+

        Check to see if the provided value is a valid choice. 

+

        """ 

+

        for k, v in self.choices: 

+

            if isinstance(v, (list, tuple)): 

+

                # This is an optgroup, so look inside the group for options 

+

                for k2, v2 in v: 

+

                    if value == smart_text(k2): 

+

                        return True 

+

            else: 

+

                if value == smart_text(k) or value == k: 

+

                    return True 

+

        return False 

+

 

+

 

+

class EmailField(CharField): 

+

    type_name = 'EmailField' 

+

    type_label = 'email' 

+

    form_field_class = forms.EmailField 

+

 

+

    default_error_messages = { 

+

        'invalid': _('Enter a valid e-mail address.'), 

+

    } 

+

    default_validators = [validators.validate_email] 

+

 

+

    def from_native(self, value): 

+

        ret = super(EmailField, self).from_native(value) 

+

        if ret is None: 

+

            return None 

+

        return ret.strip() 

+

 

+

 

+

class RegexField(CharField): 

+

    type_name = 'RegexField' 

+

    type_label = 'regex' 

+

    form_field_class = forms.RegexField 

+

 

+

    def __init__(self, regex, max_length=None, min_length=None, *args, **kwargs): 

+

        super(RegexField, self).__init__(max_length, min_length, *args, **kwargs) 

+

        self.regex = regex 

+

 

+

    def _get_regex(self): 

+

        return self._regex 

+

 

+

    def _set_regex(self, regex): 

+

        if isinstance(regex, six.string_types): 

+

            regex = re.compile(regex) 

+

        self._regex = regex 

+

        if hasattr(self, '_regex_validator') and self._regex_validator in self.validators: 

+

            self.validators.remove(self._regex_validator) 

+

        self._regex_validator = validators.RegexValidator(regex=regex) 

+

        self.validators.append(self._regex_validator) 

+

 

+

    regex = property(_get_regex, _set_regex) 

+

 

+

 

+

class DateField(WritableField): 

+

    type_name = 'DateField' 

+

    type_label = 'date' 

+

    widget = widgets.DateInput 

+

    form_field_class = forms.DateField 

+

 

+

    default_error_messages = { 

+

        'invalid': _("Date has wrong format. Use one of these formats instead: %s"), 

+

    } 

+

    empty = None 

+

    input_formats = api_settings.DATE_INPUT_FORMATS 

+

    format = api_settings.DATE_FORMAT 

+

 

+

    def __init__(self, input_formats=None, format=None, *args, **kwargs): 

+

        self.input_formats = input_formats if input_formats is not None else self.input_formats 

+

        self.format = format if format is not None else self.format 

+

        super(DateField, self).__init__(*args, **kwargs) 

+

 

+

    def from_native(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        if isinstance(value, datetime.datetime): 

+

            if timezone and settings.USE_TZ and timezone.is_aware(value): 

+

                # Convert aware datetimes to the default time zone 

+

                # before casting them to dates (#17742). 

+

                default_timezone = timezone.get_default_timezone() 

+

                value = timezone.make_naive(value, default_timezone) 

+

            return value.date() 

+

        if isinstance(value, datetime.date): 

+

            return value 

+

 

+

        for format in self.input_formats: 

+

            if format.lower() == ISO_8601: 

+

                try: 

+

                    parsed = parse_date(value) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    if parsed is not None: 

+

                        return parsed 

+

            else: 

+

                try: 

+

                    parsed = datetime.datetime.strptime(value, format) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    return parsed.date() 

+

 

+

        msg = self.error_messages['invalid'] % readable_date_formats(self.input_formats) 

+

        raise ValidationError(msg) 

+

 

+

    def to_native(self, value): 

+

        if value is None or self.format is None: 

+

            return value 

+

 

+

        if isinstance(value, datetime.datetime): 

+

            value = value.date() 

+

 

+

        if self.format.lower() == ISO_8601: 

+

            return value.isoformat() 

+

        return value.strftime(self.format) 

+

 

+

 

+

class DateTimeField(WritableField): 

+

    type_name = 'DateTimeField' 

+

    type_label = 'datetime' 

+

    widget = widgets.DateTimeInput 

+

    form_field_class = forms.DateTimeField 

+

 

+

    default_error_messages = { 

+

        'invalid': _("Datetime has wrong format. Use one of these formats instead: %s"), 

+

    } 

+

    empty = None 

+

    input_formats = api_settings.DATETIME_INPUT_FORMATS 

+

    format = api_settings.DATETIME_FORMAT 

+

 

+

    def __init__(self, input_formats=None, format=None, *args, **kwargs): 

+

        self.input_formats = input_formats if input_formats is not None else self.input_formats 

+

        self.format = format if format is not None else self.format 

+

        super(DateTimeField, self).__init__(*args, **kwargs) 

+

 

+

    def from_native(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        if isinstance(value, datetime.datetime): 

+

            return value 

+

        if isinstance(value, datetime.date): 

+

            value = datetime.datetime(value.year, value.month, value.day) 

+

            if settings.USE_TZ: 

+

                # For backwards compatibility, interpret naive datetimes in 

+

                # local time. This won't work during DST change, but we can't 

+

                # do much about it, so we let the exceptions percolate up the 

+

                # call stack. 

+

                warnings.warn("DateTimeField received a naive datetime (%s)" 

+

                              " while time zone support is active." % value, 

+

                              RuntimeWarning) 

+

                default_timezone = timezone.get_default_timezone() 

+

                value = timezone.make_aware(value, default_timezone) 

+

            return value 

+

 

+

        for format in self.input_formats: 

+

            if format.lower() == ISO_8601: 

+

                try: 

+

                    parsed = parse_datetime(value) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    if parsed is not None: 

+

                        return parsed 

+

            else: 

+

                try: 

+

                    parsed = datetime.datetime.strptime(value, format) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    return parsed 

+

 

+

        msg = self.error_messages['invalid'] % readable_datetime_formats(self.input_formats) 

+

        raise ValidationError(msg) 

+

 

+

    def to_native(self, value): 

+

        if value is None or self.format is None: 

+

            return value 

+

 

+

        if self.format.lower() == ISO_8601: 

+

            ret = value.isoformat() 

+

            if ret.endswith('+00:00'): 

+

                ret = ret[:-6] + 'Z' 

+

            return ret 

+

        return value.strftime(self.format) 

+

 

+

 

+

class TimeField(WritableField): 

+

    type_name = 'TimeField' 

+

    type_label = 'time' 

+

    widget = widgets.TimeInput 

+

    form_field_class = forms.TimeField 

+

 

+

    default_error_messages = { 

+

        'invalid': _("Time has wrong format. Use one of these formats instead: %s"), 

+

    } 

+

    empty = None 

+

    input_formats = api_settings.TIME_INPUT_FORMATS 

+

    format = api_settings.TIME_FORMAT 

+

 

+

    def __init__(self, input_formats=None, format=None, *args, **kwargs): 

+

        self.input_formats = input_formats if input_formats is not None else self.input_formats 

+

        self.format = format if format is not None else self.format 

+

        super(TimeField, self).__init__(*args, **kwargs) 

+

 

+

    def from_native(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        if isinstance(value, datetime.time): 

+

            return value 

+

 

+

        for format in self.input_formats: 

+

            if format.lower() == ISO_8601: 

+

                try: 

+

                    parsed = parse_time(value) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    if parsed is not None: 

+

                        return parsed 

+

            else: 

+

                try: 

+

                    parsed = datetime.datetime.strptime(value, format) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    return parsed.time() 

+

 

+

        msg = self.error_messages['invalid'] % readable_time_formats(self.input_formats) 

+

        raise ValidationError(msg) 

+

 

+

    def to_native(self, value): 

+

        if value is None or self.format is None: 

+

            return value 

+

 

+

        if isinstance(value, datetime.datetime): 

+

            value = value.time() 

+

 

+

        if self.format.lower() == ISO_8601: 

+

            return value.isoformat() 

+

        return value.strftime(self.format) 

+

 

+

 

+

class IntegerField(WritableField): 

+

    type_name = 'IntegerField' 

+

    type_label = 'integer' 

+

    form_field_class = forms.IntegerField 

+

 

+

    default_error_messages = { 

+

        'invalid': _('Enter a whole number.'), 

+

        'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), 

+

        'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), 

+

    } 

+

 

+

    def __init__(self, max_value=None, min_value=None, *args, **kwargs): 

+

        self.max_value, self.min_value = max_value, min_value 

+

        super(IntegerField, self).__init__(*args, **kwargs) 

+

 

+

        if max_value is not None: 

+

            self.validators.append(validators.MaxValueValidator(max_value)) 

+

        if min_value is not None: 

+

            self.validators.append(validators.MinValueValidator(min_value)) 

+

 

+

    def from_native(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        try: 

+

            value = int(str(value)) 

+

        except (ValueError, TypeError): 

+

            raise ValidationError(self.error_messages['invalid']) 

+

        return value 

+

 

+

 

+

class FloatField(WritableField): 

+

    type_name = 'FloatField' 

+

    type_label = 'float' 

+

    form_field_class = forms.FloatField 

+

 

+

    default_error_messages = { 

+

        'invalid': _("'%s' value must be a float."), 

+

    } 

+

 

+

    def from_native(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        try: 

+

            return float(value) 

+

        except (TypeError, ValueError): 

+

            msg = self.error_messages['invalid'] % value 

+

            raise ValidationError(msg) 

+

 

+

 

+

class DecimalField(WritableField): 

+

    type_name = 'DecimalField' 

+

    type_label = 'decimal' 

+

    form_field_class = forms.DecimalField 

+

 

+

    default_error_messages = { 

+

        'invalid': _('Enter a number.'), 

+

        'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), 

+

        'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), 

+

        'max_digits': _('Ensure that there are no more than %s digits in total.'), 

+

        'max_decimal_places': _('Ensure that there are no more than %s decimal places.'), 

+

        'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.') 

+

    } 

+

 

+

    def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs): 

+

        self.max_value, self.min_value = max_value, min_value 

+

        self.max_digits, self.decimal_places = max_digits, decimal_places 

+

        super(DecimalField, self).__init__(*args, **kwargs) 

+

 

+

        if max_value is not None: 

+

            self.validators.append(validators.MaxValueValidator(max_value)) 

+

        if min_value is not None: 

+

            self.validators.append(validators.MinValueValidator(min_value)) 

+

 

+

    def from_native(self, value): 

+

        """ 

+

        Validates that the input is a decimal number. Returns a Decimal 

+

        instance. Returns None for empty values. Ensures that there are no more 

+

        than max_digits in the number, and no more than decimal_places digits 

+

        after the decimal point. 

+

        """ 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

        value = smart_text(value).strip() 

+

        try: 

+

            value = Decimal(value) 

+

        except DecimalException: 

+

            raise ValidationError(self.error_messages['invalid']) 

+

        return value 

+

 

+

    def validate(self, value): 

+

        super(DecimalField, self).validate(value) 

+

        if value in validators.EMPTY_VALUES: 

+

            return 

+

        # Check for NaN, Inf and -Inf values. We can't compare directly for NaN, 

+

        # since it is never equal to itself. However, NaN is the only value that 

+

        # isn't equal to itself, so we can use this to identify NaN 

+

        if value != value or value == Decimal("Inf") or value == Decimal("-Inf"): 

+

            raise ValidationError(self.error_messages['invalid']) 

+

        sign, digittuple, exponent = value.as_tuple() 

+

        decimals = abs(exponent) 

+

        # digittuple doesn't include any leading zeros. 

+

        digits = len(digittuple) 

+

        if decimals > digits: 

+

            # We have leading zeros up to or past the decimal point.  Count 

+

            # everything past the decimal point as a digit.  We do not count 

+

            # 0 before the decimal point as a digit since that would mean 

+

            # we would not allow max_digits = decimal_places. 

+

            digits = decimals 

+

        whole_digits = digits - decimals 

+

 

+

        if self.max_digits is not None and digits > self.max_digits: 

+

            raise ValidationError(self.error_messages['max_digits'] % self.max_digits) 

+

        if self.decimal_places is not None and decimals > self.decimal_places: 

+

            raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places) 

+

        if self.max_digits is not None and self.decimal_places is not None and whole_digits > (self.max_digits - self.decimal_places): 

+

            raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places)) 

+

        return value 

+

 

+

 

+

class FileField(WritableField): 

+

    use_files = True 

+

    type_name = 'FileField' 

+

    type_label = 'file upload' 

+

    form_field_class = forms.FileField 

+

    widget = widgets.FileInput 

+

 

+

    default_error_messages = { 

+

        'invalid': _("No file was submitted. Check the encoding type on the form."), 

+

        'missing': _("No file was submitted."), 

+

        'empty': _("The submitted file is empty."), 

+

        'max_length': _('Ensure this filename has at most %(max)d characters (it has %(length)d).'), 

+

        'contradiction': _('Please either submit a file or check the clear checkbox, not both.') 

+

    } 

+

 

+

    def __init__(self, *args, **kwargs): 

+

        self.max_length = kwargs.pop('max_length', None) 

+

        self.allow_empty_file = kwargs.pop('allow_empty_file', False) 

+

        super(FileField, self).__init__(*args, **kwargs) 

+

 

+

    def from_native(self, data): 

+

        if data in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        # UploadedFile objects should have name and size attributes. 

+

        try: 

+

            file_name = data.name 

+

            file_size = data.size 

+

        except AttributeError: 

+

            raise ValidationError(self.error_messages['invalid']) 

+

 

+

        if self.max_length is not None and len(file_name) > self.max_length: 

+

            error_values = {'max': self.max_length, 'length': len(file_name)} 

+

            raise ValidationError(self.error_messages['max_length'] % error_values) 

+

        if not file_name: 

+

            raise ValidationError(self.error_messages['invalid']) 

+

        if not self.allow_empty_file and not file_size: 

+

            raise ValidationError(self.error_messages['empty']) 

+

 

+

        return data 

+

 

+

    def to_native(self, value): 

+

        return value.name 

+

 

+

 

+

class ImageField(FileField): 

+

    use_files = True 

+

    type_name = 'ImageField' 

+

    type_label = 'image upload' 

+

    form_field_class = forms.ImageField 

+

 

+

    default_error_messages = { 

+

        'invalid_image': _("Upload a valid image. The file you uploaded was " 

+

                           "either not an image or a corrupted image."), 

+

    } 

+

 

+

    def from_native(self, data): 

+

        """ 

+

        Checks that the file-upload field data contains a valid image (GIF, JPG, 

+

        PNG, possibly others -- whatever the Python Imaging Library supports). 

+

        """ 

+

        f = super(ImageField, self).from_native(data) 

+

        if f is None: 

+

            return None 

+

 

+

        from compat import Image 

+

        assert Image is not None, 'PIL must be installed for ImageField support' 

+

 

+

        # We need to get a file object for PIL. We might have a path or we might 

+

        # have to read the data into memory. 

+

        if hasattr(data, 'temporary_file_path'): 

+

            file = data.temporary_file_path() 

+

        else: 

+

            if hasattr(data, 'read'): 

+

                file = BytesIO(data.read()) 

+

            else: 

+

                file = BytesIO(data['content']) 

+

 

+

        try: 

+

            # load() could spot a truncated JPEG, but it loads the entire 

+

            # image in memory, which is a DoS vector. See #3848 and #18520. 

+

            # verify() must be called immediately after the constructor. 

+

            Image.open(file).verify() 

+

        except ImportError: 

+

            # Under PyPy, it is possible to import PIL. However, the underlying 

+

            # _imaging C module isn't available, so an ImportError will be 

+

            # raised. Catch and re-raise. 

+

            raise 

+

        except Exception:  # Python Imaging Library doesn't recognize it as an image 

+

            raise ValidationError(self.error_messages['invalid_image']) 

+

        if hasattr(f, 'seek') and callable(f.seek): 

+

            f.seek(0) 

+

        return f 

+

 

+

 

+

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): 

+

        self.method_name = method_name 

+

        super(SerializerMethodField, self).__init__() 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        value = getattr(self.parent, self.method_name)(obj) 

+

        return self.to_native(value) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_filters.html b/htmlcov/rest_framework_filters.html new file mode 100644 index 00000000..28b6eaae --- /dev/null +++ b/htmlcov/rest_framework_filters.html @@ -0,0 +1,367 @@ + + + + + + + + Coverage for rest_framework/filters: 92% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+ +
+

""" 

+

Provides generic filtering backends that can be used to filter the results 

+

returned by list views. 

+

""" 

+

from __future__ import unicode_literals 

+

from django.db import models 

+

from rest_framework.compat import django_filters, six 

+

from functools import reduce 

+

import operator 

+

 

+

FilterSet = django_filters and django_filters.FilterSet or None 

+

 

+

 

+

class BaseFilterBackend(object): 

+

    """ 

+

    A base class from which all filter backend classes should inherit. 

+

    """ 

+

 

+

    def filter_queryset(self, request, queryset, view): 

+

        """ 

+

        Return a filtered queryset. 

+

        """ 

+

        raise NotImplementedError(".filter_queryset() must be overridden.") 

+

 

+

 

+

class DjangoFilterBackend(BaseFilterBackend): 

+

    """ 

+

    A filter backend that uses django-filter. 

+

    """ 

+

    default_filter_set = FilterSet 

+

 

+

    def __init__(self): 

+

        assert django_filters, 'Using DjangoFilterBackend, but django-filter is not installed' 

+

 

+

    def get_filter_class(self, view, queryset=None): 

+

        """ 

+

        Return the django-filters `FilterSet` used to filter the queryset. 

+

        """ 

+

        filter_class = getattr(view, 'filter_class', None) 

+

        filter_fields = getattr(view, 'filter_fields', None) 

+

 

+

        if filter_class: 

+

            filter_model = filter_class.Meta.model 

+

 

+

            assert issubclass(filter_model, queryset.model), \ 

+

                'FilterSet model %s does not match queryset model %s' % \ 

+

                (filter_model, queryset.model) 

+

 

+

            return filter_class 

+

 

+

        if filter_fields: 

+

            class AutoFilterSet(self.default_filter_set): 

+

                class Meta: 

+

                    model = queryset.model 

+

                    fields = filter_fields 

+

            return AutoFilterSet 

+

 

+

        return None 

+

 

+

    def filter_queryset(self, request, queryset, view): 

+

        filter_class = self.get_filter_class(view, queryset) 

+

 

+

        if filter_class: 

+

            return filter_class(request.QUERY_PARAMS, queryset=queryset).qs 

+

 

+

        return queryset 

+

 

+

 

+

class SearchFilter(BaseFilterBackend): 

+

    search_param = 'search'  # The URL query parameter used for the search. 

+

 

+

    def get_search_terms(self, request): 

+

        """ 

+

        Search terms are set by a ?search=... query parameter, 

+

        and may be comma and/or whitespace delimited. 

+

        """ 

+

        params = request.QUERY_PARAMS.get(self.search_param, '') 

+

        return params.replace(',', ' ').split() 

+

 

+

    def construct_search(self, field_name): 

+

        if field_name.startswith('^'): 

+

            return "%s__istartswith" % field_name[1:] 

+

        elif field_name.startswith('='): 

+

            return "%s__iexact" % field_name[1:] 

+

        elif field_name.startswith('@'): 

+

            return "%s__search" % field_name[1:] 

+

        else: 

+

            return "%s__icontains" % field_name 

+

 

+

    def filter_queryset(self, request, queryset, view): 

+

        search_fields = getattr(view, 'search_fields', None) 

+

 

+

        if not search_fields: 

+

            return queryset 

+

 

+

        orm_lookups = [self.construct_search(str(search_field)) 

+

                       for search_field in search_fields] 

+

 

+

        for search_term in self.get_search_terms(request): 

+

            or_queries = [models.Q(**{orm_lookup: search_term}) 

+

                          for orm_lookup in orm_lookups] 

+

            queryset = queryset.filter(reduce(operator.or_, or_queries)) 

+

 

+

        return queryset 

+

 

+

 

+

class OrderingFilter(BaseFilterBackend): 

+

    ordering_param = 'ordering'  # The URL query parameter used for the ordering. 

+

 

+

    def get_ordering(self, request): 

+

        """ 

+

        Search terms are set by a ?search=... query parameter, 

+

        and may be comma and/or whitespace delimited. 

+

        """ 

+

        params = request.QUERY_PARAMS.get(self.ordering_param) 

+

        if params: 

+

            return [param.strip() for param in params.split(',')] 

+

 

+

    def get_default_ordering(self, view): 

+

        ordering = getattr(view, 'ordering', None) 

+

        if isinstance(ordering, six.string_types): 

+

            return (ordering,) 

+

        return ordering 

+

 

+

    def remove_invalid_fields(self, queryset, ordering): 

+

        field_names = [field.name for field in queryset.model._meta.fields] 

+

        return [term for term in ordering if term.lstrip('-') in field_names] 

+

 

+

    def filter_queryset(self, request, queryset, view): 

+

        ordering = self.get_ordering(request) 

+

 

+

        if ordering: 

+

            # Skip any incorrect parameters 

+

            ordering = self.remove_invalid_fields(queryset, ordering) 

+

 

+

        if not ordering: 

+

            # Use 'ordering' attribtue by default 

+

            ordering = self.get_default_ordering(view) 

+

 

+

        if ordering: 

+

            return queryset.order_by(*ordering) 

+

 

+

        return queryset 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_generics.html b/htmlcov/rest_framework_generics.html new file mode 100644 index 00000000..5f5851cb --- /dev/null +++ b/htmlcov/rest_framework_generics.html @@ -0,0 +1,1079 @@ + + + + + + + + Coverage for rest_framework/generics: 83% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+

344

+

345

+

346

+

347

+

348

+

349

+

350

+

351

+

352

+

353

+

354

+

355

+

356

+

357

+

358

+

359

+

360

+

361

+

362

+

363

+

364

+

365

+

366

+

367

+

368

+

369

+

370

+

371

+

372

+

373

+

374

+

375

+

376

+

377

+

378

+

379

+

380

+

381

+

382

+

383

+

384

+

385

+

386

+

387

+

388

+

389

+

390

+

391

+

392

+

393

+

394

+

395

+

396

+

397

+

398

+

399

+

400

+

401

+

402

+

403

+

404

+

405

+

406

+

407

+

408

+

409

+

410

+

411

+

412

+

413

+

414

+

415

+

416

+

417

+

418

+

419

+

420

+

421

+

422

+

423

+

424

+

425

+

426

+

427

+

428

+

429

+

430

+

431

+

432

+

433

+

434

+

435

+

436

+

437

+

438

+

439

+

440

+

441

+

442

+

443

+

444

+

445

+

446

+

447

+

448

+

449

+

450

+

451

+

452

+

453

+

454

+

455

+

456

+

457

+

458

+

459

+

460

+

461

+

462

+

463

+

464

+

465

+

466

+

467

+

468

+

469

+

470

+

471

+

472

+

473

+

474

+

475

+

476

+

477

+

478

+

479

+

480

+

481

+

482

+

483

+

484

+

485

+

486

+

487

+

488

+

489

+

490

+

491

+

492

+

493

+

494

+

495

+

496

+

497

+

498

+

499

+ +
+

""" 

+

Generic views that provide commonly needed behaviour. 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

from django.core.exceptions import ImproperlyConfigured, PermissionDenied 

+

from django.core.paginator import Paginator, InvalidPage 

+

from django.http import Http404 

+

from django.shortcuts import get_object_or_404 as _get_object_or_404 

+

from django.utils.translation import ugettext as _ 

+

from rest_framework import views, mixins, exceptions 

+

from rest_framework.request import clone_request 

+

from rest_framework.settings import api_settings 

+

import warnings 

+

 

+

 

+

def get_object_or_404(queryset, **filter_kwargs): 

+

    """ 

+

    Same as Django's standard shortcut, but make sure to raise 404 

+

    if the filter_kwargs don't match the required types. 

+

    """ 

+

    try: 

+

        return _get_object_or_404(queryset, **filter_kwargs) 

+

    except (TypeError, ValueError): 

+

        raise Http404 

+

 

+

 

+

class GenericAPIView(views.APIView): 

+

    """ 

+

    Base class for all other generic views. 

+

    """ 

+

 

+

    # You'll need to either set these attributes, 

+

    # or override `get_queryset()`/`get_serializer_class()`. 

+

    queryset = None 

+

    serializer_class = None 

+

 

+

    # 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. 

+

    model = None 

+

 

+

    # If you want to use object lookups other than pk, set this attribute. 

+

    # For more complex lookup requirements override `get_object()`. 

+

    lookup_field = 'pk' 

+

 

+

    # Pagination settings 

+

    paginate_by = api_settings.PAGINATE_BY 

+

    paginate_by_param = api_settings.PAGINATE_BY_PARAM 

+

    pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS 

+

    page_kwarg = 'page' 

+

 

+

    # The filter backend classes to use for queryset filtering 

+

    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS 

+

 

+

    # The following attributes may be subject to change, 

+

    # and should be considered private API. 

+

    model_serializer_class = api_settings.DEFAULT_MODEL_SERIALIZER_CLASS 

+

    paginator_class = Paginator 

+

 

+

    ###################################### 

+

    # These are pending deprecation... 

+

 

+

    pk_url_kwarg = 'pk' 

+

    slug_url_kwarg = 'slug' 

+

    slug_field = 'slug' 

+

    allow_empty = True 

+

    filter_backend = api_settings.FILTER_BACKEND 

+

 

+

    def get_serializer_context(self): 

+

        """ 

+

        Extra context provided to the serializer class. 

+

        """ 

+

        return { 

+

            'request': self.request, 

+

            'format': self.format_kwarg, 

+

            'view': self 

+

        } 

+

 

+

    def get_serializer(self, instance=None, data=None, 

+

                       files=None, many=False, partial=False): 

+

        """ 

+

        Return the serializer instance that should be used for validating and 

+

        deserializing input, and for serializing output. 

+

        """ 

+

        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) 

+

 

+

    def get_pagination_serializer(self, page): 

+

        """ 

+

        Return a serializer instance to use with paginated data. 

+

        """ 

+

        class SerializerClass(self.pagination_serializer_class): 

+

            class Meta: 

+

                object_serializer_class = self.get_serializer_class() 

+

 

+

        pagination_serializer_class = SerializerClass 

+

        context = self.get_serializer_context() 

+

        return pagination_serializer_class(instance=page, context=context) 

+

 

+

    def paginate_queryset(self, queryset, page_size=None): 

+

        """ 

+

        Paginate a queryset if required, either returning a page object, 

+

        or `None` if pagination is not configured for this view. 

+

        """ 

+

        deprecated_style = False 

+

        if page_size is not None: 

+

            warnings.warn('The `page_size` parameter to `paginate_queryset()` ' 

+

                          'is due to be 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) 

+

            deprecated_style = True 

+

        else: 

+

            # Determine the required page size. 

+

            # If pagination is not configured, simply return None. 

+

            page_size = self.get_paginate_by() 

+

            if not page_size: 

+

                return None 

+

 

+

        if not self.allow_empty: 

+

            warnings.warn( 

+

                'The `allow_empty` parameter is due to be deprecated. ' 

+

                'To use `allow_empty=False` style behavior, You should override ' 

+

                '`get_queryset()` and explicitly raise a 404 on empty querysets.', 

+

                PendingDeprecationWarning, stacklevel=2 

+

            ) 

+

 

+

        paginator = self.paginator_class(queryset, page_size, 

+

                                         allow_empty_first_page=self.allow_empty) 

+

        page_kwarg = self.kwargs.get(self.page_kwarg) 

+

        page_query_param = self.request.QUERY_PARAMS.get(self.page_kwarg) 

+

        page = page_kwarg or page_query_param or 1 

+

        try: 

+

            page_number = int(page) 

+

        except ValueError: 

+

            if page == 'last': 

+

                page_number = paginator.num_pages 

+

            else: 

+

                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) 

+

            }) 

+

 

+

        if deprecated_style: 

+

            return (paginator, page, page.object_list, page.has_other_pages()) 

+

        return page 

+

 

+

    def filter_queryset(self, queryset): 

+

        """ 

+

        Given a queryset, filter it with whichever filter backend is in use. 

+

 

+

        You are unlikely to want to override this method, although you may need 

+

        to call it either from a list view, or from a custom `get_object` 

+

        method if you want to apply the configured filtering backend to the 

+

        default queryset. 

+

        """ 

+

        filter_backends = self.filter_backends or [] 

+

        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` ' 

+

                'attribute and `DEFAULT_FILTER_BACKENDS` setting, that take ' 

+

                'a *list* of filter backend classes.', 

+

                PendingDeprecationWarning, stacklevel=2 

+

            ) 

+

            filter_backends = [self.filter_backend] 

+

 

+

        for backend in filter_backends: 

+

            queryset = backend().filter_queryset(self.request, queryset, self) 

+

        return queryset 

+

 

+

    ######################## 

+

    ### The following methods provide default implementations 

+

    ### that you may want to override for more complex cases. 

+

 

+

    def get_paginate_by(self, queryset=None): 

+

        """ 

+

        Return the size of pages to use with pagination. 

+

 

+

        If `PAGINATE_BY_PARAM` is set it will attempt to get the page size 

+

        from a named query parameter in the url, eg. ?page_size=100 

+

 

+

        Otherwise defaults to using `self.paginate_by`. 

+

        """ 

+

        if queryset is not None: 

+

            warnings.warn('The `queryset` parameter to `get_paginate_by()` ' 

+

                          'is due to be deprecated.', 

+

                          PendingDeprecationWarning, stacklevel=2) 

+

 

+

        if self.paginate_by_param: 

+

            query_params = self.request.QUERY_PARAMS 

+

            try: 

+

                return int(query_params[self.paginate_by_param]) 

+

            except (KeyError, ValueError): 

+

                pass 

+

 

+

        return self.paginate_by 

+

 

+

    def get_serializer_class(self): 

+

        """ 

+

        Return the class to use for the serializer. 

+

        Defaults to using `self.serializer_class`. 

+

 

+

        You may want to override this if you need to provide different 

+

        serializations depending on the incoming request. 

+

 

+

        (Eg. admins get full serialization, others get basic serialization) 

+

        """ 

+

        serializer_class = self.serializer_class 

+

        if serializer_class is not None: 

+

            return serializer_class 

+

 

+

        assert self.model is not None, \ 

+

            "'%s' should either include a 'serializer_class' attribute, " \ 

+

            "or use the 'model' attribute as a shortcut for " \ 

+

            "automatically generating a serializer class." \ 

+

            % self.__class__.__name__ 

+

 

+

        class DefaultSerializer(self.model_serializer_class): 

+

            class Meta: 

+

                model = self.model 

+

        return DefaultSerializer 

+

 

+

    def get_queryset(self): 

+

        """ 

+

        Get the list of items for this view. 

+

        This must be an iterable, and may be a queryset. 

+

        Defaults to using `self.queryset`. 

+

 

+

        You may want to override this if you need to provide different 

+

        querysets depending on the incoming request. 

+

 

+

        (Eg. return a list of items that is specific to the user) 

+

        """ 

+

        if self.queryset is not None: 

+

            return self.queryset._clone() 

+

 

+

        if self.model is not None: 

+

            return self.model._default_manager.all() 

+

 

+

        raise ImproperlyConfigured("'%s' must define 'queryset' or 'model'" 

+

                                    % self.__class__.__name__) 

+

 

+

    def get_object(self, queryset=None): 

+

        """ 

+

        Returns the object the view is displaying. 

+

 

+

        You may want to override this if you need to provide non-standard 

+

        queryset lookups.  Eg if objects are referenced using multiple 

+

        keyword arguments in the url conf. 

+

        """ 

+

        # Determine the base queryset to use. 

+

        if queryset is None: 

+

            queryset = self.filter_queryset(self.get_queryset()) 

+

        else: 

+

            pass  # Deprecation warning 

+

 

+

        # Perform the lookup filtering. 

+

        pk = self.kwargs.get(self.pk_url_kwarg, None) 

+

        slug = self.kwargs.get(self.slug_url_kwarg, None) 

+

        lookup = self.kwargs.get(self.lookup_field, None) 

+

 

+

        if lookup is not None: 

+

            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. ' 

+

                'Use the `lookup_field` attribute instead', 

+

                PendingDeprecationWarning 

+

            ) 

+

            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. ' 

+

                'Use the `lookup_field` attribute instead', 

+

                PendingDeprecationWarning 

+

            ) 

+

            filter_kwargs = {self.slug_field: slug} 

+

        else: 

+

            raise ImproperlyConfigured( 

+

                'Expected view %s to be called with a URL keyword argument ' 

+

                'named "%s". Fix your URL conf, or set the `.lookup_field` ' 

+

                'attribute on the view correctly.' % 

+

                (self.__class__.__name__, self.lookup_field) 

+

            ) 

+

 

+

        obj = get_object_or_404(queryset, **filter_kwargs) 

+

 

+

        # May raise a permission denied 

+

        self.check_object_permissions(self.request, obj) 

+

 

+

        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. 

+

 

+

    def pre_save(self, obj): 

+

        """ 

+

        Placeholder method for calling before saving an object. 

+

 

+

        May be used to set attributes on the object that are implicit 

+

        in either the request, or the url. 

+

        """ 

+

        pass 

+

 

+

    def post_save(self, obj, created=False): 

+

        """ 

+

        Placeholder method for calling after saving an object. 

+

        """ 

+

        pass 

+

 

+

    def metadata(self, request): 

+

        """ 

+

        Return a dictionary of metadata about the view. 

+

        Used to return responses for OPTIONS requests. 

+

 

+

        We override the default behavior, and add some extra information 

+

        about the required request body for POST and PUT operations. 

+

        """ 

+

        ret = super(GenericAPIView, self).metadata(request) 

+

 

+

        actions = {} 

+

        for method in ('PUT', 'POST'): 

+

            if method not in self.allowed_methods: 

+

                continue 

+

 

+

            cloned_request = clone_request(request, method) 

+

            try: 

+

                # Test global permissions 

+

                self.check_permissions(cloned_request) 

+

                # Test object permissions 

+

                if method == 'PUT': 

+

                    self.get_object() 

+

            except (exceptions.APIException, PermissionDenied, Http404): 

+

                pass 

+

            else: 

+

                # If user has appropriate permissions for the view, include 

+

                # appropriate metadata about the fields that should be supplied. 

+

                serializer = self.get_serializer() 

+

                actions[method] = serializer.metadata() 

+

 

+

        if actions: 

+

            ret['actions'] = actions 

+

 

+

        return ret 

+

 

+

 

+

########################################################## 

+

### Concrete view classes that provide method handlers ### 

+

### by composing the mixin classes with the base view. ### 

+

########################################################## 

+

 

+

class CreateAPIView(mixins.CreateModelMixin, 

+

                    GenericAPIView): 

+

 

+

    """ 

+

    Concrete view for creating a model instance. 

+

    """ 

+

    def post(self, request, *args, **kwargs): 

+

        return self.create(request, *args, **kwargs) 

+

 

+

 

+

class ListAPIView(mixins.ListModelMixin, 

+

                  GenericAPIView): 

+

    """ 

+

    Concrete view for listing a queryset. 

+

    """ 

+

    def get(self, request, *args, **kwargs): 

+

        return self.list(request, *args, **kwargs) 

+

 

+

 

+

class RetrieveAPIView(mixins.RetrieveModelMixin, 

+

                      GenericAPIView): 

+

    """ 

+

    Concrete view for retrieving a model instance. 

+

    """ 

+

    def get(self, request, *args, **kwargs): 

+

        return self.retrieve(request, *args, **kwargs) 

+

 

+

 

+

class DestroyAPIView(mixins.DestroyModelMixin, 

+

                     GenericAPIView): 

+

 

+

    """ 

+

    Concrete view for deleting a model instance. 

+

    """ 

+

    def delete(self, request, *args, **kwargs): 

+

        return self.destroy(request, *args, **kwargs) 

+

 

+

 

+

class UpdateAPIView(mixins.UpdateModelMixin, 

+

                    GenericAPIView): 

+

 

+

    """ 

+

    Concrete view for updating a model instance. 

+

    """ 

+

    def put(self, request, *args, **kwargs): 

+

        return self.update(request, *args, **kwargs) 

+

 

+

    def patch(self, request, *args, **kwargs): 

+

        return self.partial_update(request, *args, **kwargs) 

+

 

+

 

+

class ListCreateAPIView(mixins.ListModelMixin, 

+

                        mixins.CreateModelMixin, 

+

                        GenericAPIView): 

+

    """ 

+

    Concrete view for listing a queryset or creating a model instance. 

+

    """ 

+

    def get(self, request, *args, **kwargs): 

+

        return self.list(request, *args, **kwargs) 

+

 

+

    def post(self, request, *args, **kwargs): 

+

        return self.create(request, *args, **kwargs) 

+

 

+

 

+

class RetrieveUpdateAPIView(mixins.RetrieveModelMixin, 

+

                            mixins.UpdateModelMixin, 

+

                            GenericAPIView): 

+

    """ 

+

    Concrete view for retrieving, updating a model instance. 

+

    """ 

+

    def get(self, request, *args, **kwargs): 

+

        return self.retrieve(request, *args, **kwargs) 

+

 

+

    def put(self, request, *args, **kwargs): 

+

        return self.update(request, *args, **kwargs) 

+

 

+

    def patch(self, request, *args, **kwargs): 

+

        return self.partial_update(request, *args, **kwargs) 

+

 

+

 

+

class RetrieveDestroyAPIView(mixins.RetrieveModelMixin, 

+

                             mixins.DestroyModelMixin, 

+

                             GenericAPIView): 

+

    """ 

+

    Concrete view for retrieving or deleting a model instance. 

+

    """ 

+

    def get(self, request, *args, **kwargs): 

+

        return self.retrieve(request, *args, **kwargs) 

+

 

+

    def delete(self, request, *args, **kwargs): 

+

        return self.destroy(request, *args, **kwargs) 

+

 

+

 

+

class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, 

+

                                   mixins.UpdateModelMixin, 

+

                                   mixins.DestroyModelMixin, 

+

                                   GenericAPIView): 

+

    """ 

+

    Concrete view for retrieving, updating or deleting a model instance. 

+

    """ 

+

    def get(self, request, *args, **kwargs): 

+

        return self.retrieve(request, *args, **kwargs) 

+

 

+

    def put(self, request, *args, **kwargs): 

+

        return self.update(request, *args, **kwargs) 

+

 

+

    def patch(self, request, *args, **kwargs): 

+

        return self.partial_update(request, *args, **kwargs) 

+

 

+

    def delete(self, request, *args, **kwargs): 

+

        return self.destroy(request, *args, **kwargs) 

+

 

+

 

+

########################## 

+

### Deprecated classes ### 

+

########################## 

+

 

+

class MultipleObjectAPIView(GenericAPIView): 

+

    def __init__(self, *args, **kwargs): 

+

        warnings.warn( 

+

            'Subclassing `MultipleObjectAPIView` is due to be deprecated. ' 

+

            'You should simply subclass `GenericAPIView` instead.', 

+

            PendingDeprecationWarning, stacklevel=2 

+

        ) 

+

        super(MultipleObjectAPIView, self).__init__(*args, **kwargs) 

+

 

+

 

+

class SingleObjectAPIView(GenericAPIView): 

+

    def __init__(self, *args, **kwargs): 

+

        warnings.warn( 

+

            'Subclassing `SingleObjectAPIView` is due to be deprecated. ' 

+

            'You should simply subclass `GenericAPIView` instead.', 

+

            PendingDeprecationWarning, stacklevel=2 

+

        ) 

+

        super(SingleObjectAPIView, self).__init__(*args, **kwargs) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_mixins.html b/htmlcov/rest_framework_mixins.html new file mode 100644 index 00000000..fa62f2ae --- /dev/null +++ b/htmlcov/rest_framework_mixins.html @@ -0,0 +1,449 @@ + + + + + + + + Coverage for rest_framework/mixins: 93% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+ +
+

""" 

+

Basic building blocks for generic class based views. 

+

 

+

We don't bind behaviour to http method handlers yet, 

+

which allows mixin classes to be composed in interesting ways. 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

from django.http import Http404 

+

from rest_framework import status 

+

from rest_framework.response import Response 

+

from rest_framework.request import clone_request 

+

import warnings 

+

 

+

 

+

def _get_validation_exclusions(obj, pk=None, slug_field=None, lookup_field=None): 

+

    """ 

+

    Given a model instance, and an optional pk and slug field, 

+

    return the full list of all other field names on that model. 

+

 

+

    For use when performing full_clean on a model instance, 

+

    so we only clean the required fields. 

+

    """ 

+

    include = [] 

+

 

+

    if pk: 

+

        # Pending deprecation 

+

        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 

+

        include.append(slug_field) 

+

 

+

    if lookup_field and lookup_field != 'pk': 

+

        include.append(lookup_field) 

+

 

+

    return [field.name for field in obj._meta.fields if field.name not in include] 

+

 

+

 

+

class CreateModelMixin(object): 

+

    """ 

+

    Create a model instance. 

+

    """ 

+

    def create(self, request, *args, **kwargs): 

+

        serializer = self.get_serializer(data=request.DATA, files=request.FILES) 

+

 

+

        if serializer.is_valid(): 

+

            self.pre_save(serializer.object) 

+

            self.object = serializer.save(force_insert=True) 

+

            self.post_save(self.object, created=True) 

+

            headers = self.get_success_headers(serializer.data) 

+

            return Response(serializer.data, status=status.HTTP_201_CREATED, 

+

                            headers=headers) 

+

 

+

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 

+

 

+

    def get_success_headers(self, data): 

+

        try: 

+

            return {'Location': data['url']} 

+

        except (TypeError, KeyError): 

+

            return {} 

+

 

+

 

+

class ListModelMixin(object): 

+

    """ 

+

    List a queryset. 

+

    """ 

+

    empty_error = "Empty list and '%(class_name)s.allow_empty' is False." 

+

 

+

    def list(self, request, *args, **kwargs): 

+

        self.object_list = self.filter_queryset(self.get_queryset()) 

+

 

+

        # Default is to allow empty querysets.  This can be altered by setting 

+

        # `.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. ' 

+

                'To use `allow_empty=False` style behavior, You should override ' 

+

                '`get_queryset()` and explicitly raise a 404 on empty querysets.', 

+

                PendingDeprecationWarning 

+

            ) 

+

            class_name = self.__class__.__name__ 

+

            error_msg = self.empty_error % {'class_name': class_name} 

+

            raise Http404(error_msg) 

+

 

+

        # Switch between paginated or standard style responses 

+

        page = self.paginate_queryset(self.object_list) 

+

        if page is not None: 

+

            serializer = self.get_pagination_serializer(page) 

+

        else: 

+

            serializer = self.get_serializer(self.object_list, many=True) 

+

 

+

        return Response(serializer.data) 

+

 

+

 

+

class RetrieveModelMixin(object): 

+

    """ 

+

    Retrieve a model instance. 

+

    """ 

+

    def retrieve(self, request, *args, **kwargs): 

+

        self.object = self.get_object() 

+

        serializer = self.get_serializer(self.object) 

+

        return Response(serializer.data) 

+

 

+

 

+

class UpdateModelMixin(object): 

+

    """ 

+

    Update a model instance. 

+

    """ 

+

    def update(self, request, *args, **kwargs): 

+

        partial = kwargs.pop('partial', False) 

+

        self.object = self.get_object_or_none() 

+

 

+

        if self.object is None: 

+

            created = True 

+

            save_kwargs = {'force_insert': True} 

+

            success_status_code = status.HTTP_201_CREATED 

+

        else: 

+

            created = False 

+

            save_kwargs = {'force_update': True} 

+

            success_status_code = status.HTTP_200_OK 

+

 

+

        serializer = self.get_serializer(self.object, data=request.DATA, 

+

                                         files=request.FILES, partial=partial) 

+

 

+

        if serializer.is_valid(): 

+

            self.pre_save(serializer.object) 

+

            self.object = serializer.save(**save_kwargs) 

+

            self.post_save(self.object, created=created) 

+

            return Response(serializer.data, status=success_status_code) 

+

 

+

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 

+

 

+

    def partial_update(self, request, *args, **kwargs): 

+

        kwargs['partial'] = True 

+

        return self.update(request, *args, **kwargs) 

+

 

+

    def get_object_or_none(self): 

+

        try: 

+

            return self.get_object() 

+

        except Http404: 

+

            # If this is a PUT-as-create operation, we need to ensure that 

+

            # we have relevant permissions, as if this was a POST request. 

+

            # This will either raise a PermissionDenied exception, 

+

            # or simply return None 

+

            self.check_permissions(clone_request(self.request, 'POST')) 

+

 

+

    def pre_save(self, obj): 

+

        """ 

+

        Set any attributes on the object that are implicit in the request. 

+

        """ 

+

        # pk and/or slug attributes are implicit in the URL. 

+

        lookup = self.kwargs.get(self.lookup_field, None) 

+

        pk = self.kwargs.get(self.pk_url_kwarg, None) 

+

        slug = self.kwargs.get(self.slug_url_kwarg, None) 

+

        slug_field = slug and self.slug_field or None 

+

 

+

        if lookup: 

+

            setattr(obj, self.lookup_field, lookup) 

+

 

+

        if pk: 

+

            setattr(obj, 'pk', pk) 

+

 

+

        if slug: 

+

            setattr(obj, slug_field, slug) 

+

 

+

        # Ensure we clean the attributes so that we don't eg return integer 

+

        # pk using a string representation, as provided by the url conf kwarg. 

+

        if hasattr(obj, 'full_clean'): 

+

            exclude = _get_validation_exclusions(obj, pk, slug_field, self.lookup_field) 

+

            obj.full_clean(exclude) 

+

 

+

 

+

class DestroyModelMixin(object): 

+

    """ 

+

    Destroy a model instance. 

+

    """ 

+

    def destroy(self, request, *args, **kwargs): 

+

        obj = self.get_object() 

+

        obj.delete() 

+

        return Response(status=status.HTTP_204_NO_CONTENT) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_models.html b/htmlcov/rest_framework_models.html new file mode 100644 index 00000000..6786c620 --- /dev/null +++ b/htmlcov/rest_framework_models.html @@ -0,0 +1,83 @@ + + + + + + + + Coverage for rest_framework/models: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+ +
+

# Just to keep things like ./manage.py test happy 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_negotiation.html b/htmlcov/rest_framework_negotiation.html new file mode 100644 index 00000000..7ed526c9 --- /dev/null +++ b/htmlcov/rest_framework_negotiation.html @@ -0,0 +1,259 @@ + + + + + + + + Coverage for rest_framework/negotiation: 90% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+ +
+

""" 

+

Content negotiation deals with selecting an appropriate renderer given the 

+

incoming request.  Typically this will be based on the request's Accept header. 

+

""" 

+

from __future__ import unicode_literals 

+

from django.http import Http404 

+

from rest_framework import exceptions 

+

from rest_framework.settings import api_settings 

+

from rest_framework.utils.mediatypes import order_by_precedence, media_type_matches 

+

from rest_framework.utils.mediatypes import _MediaType 

+

 

+

 

+

class BaseContentNegotiation(object): 

+

    def select_parser(self, request, parsers): 

+

        raise NotImplementedError('.select_parser() must be implemented') 

+

 

+

    def select_renderer(self, request, renderers, format_suffix=None): 

+

        raise NotImplementedError('.select_renderer() must be implemented') 

+

 

+

 

+

class DefaultContentNegotiation(BaseContentNegotiation): 

+

    settings = api_settings 

+

 

+

    def select_parser(self, request, parsers): 

+

        """ 

+

        Given a list of parsers and a media type, return the appropriate 

+

        parser to handle the incoming request. 

+

        """ 

+

        for parser in parsers: 

+

            if media_type_matches(parser.media_type, request.content_type): 

+

                return parser 

+

        return None 

+

 

+

    def select_renderer(self, request, renderers, format_suffix=None): 

+

        """ 

+

        Given a request and a list of renderers, return a two-tuple of: 

+

        (renderer, media type). 

+

        """ 

+

        # Allow URL style format override.  eg. "?format=json 

+

        format_query_param = self.settings.URL_FORMAT_OVERRIDE 

+

        format = format_suffix or request.QUERY_PARAMS.get(format_query_param) 

+

 

+

        if format: 

+

            renderers = self.filter_renderers(renderers, format) 

+

 

+

        accepts = self.get_accept_list(request) 

+

 

+

        # Check the acceptable media types against each renderer, 

+

        # attempting more specific media types first 

+

        # NB. The inner loop here isn't as bad as it first looks :) 

+

        #     Worst case is we're looping over len(accept_list) * len(self.renderers) 

+

        for media_type_set in order_by_precedence(accepts): 

+

            for renderer in renderers: 

+

                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): 

+

                            # Eg client requests '*/*' 

+

                            # Accepted media type is 'application/json' 

+

                            return renderer, renderer.media_type 

+

                        else: 

+

                            # Eg client requests 'application/json; indent=8' 

+

                            # Accepted media type is 'application/json; indent=8' 

+

                            return renderer, media_type 

+

 

+

        raise exceptions.NotAcceptable(available_renderers=renderers) 

+

 

+

    def filter_renderers(self, renderers, format): 

+

        """ 

+

        If there is a '.json' style format suffix, filter the renderers 

+

        so that we only negotiation against those that accept that format. 

+

        """ 

+

        renderers = [renderer for renderer in renderers 

+

                     if renderer.format == format] 

+

        if not renderers: 

+

            raise Http404 

+

        return renderers 

+

 

+

    def get_accept_list(self, request): 

+

        """ 

+

        Given the incoming request, return a tokenised list of media 

+

        type strings. 

+

 

+

        Allows URL style accept override.  eg. "?accept=application/json" 

+

        """ 

+

        header = request.META.get('HTTP_ACCEPT', '*/*') 

+

        header = request.QUERY_PARAMS.get(self.settings.URL_ACCEPT_OVERRIDE, header) 

+

        return [token.strip() for token in header.split(',')] 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_pagination.html b/htmlcov/rest_framework_pagination.html new file mode 100644 index 00000000..5a3f76d8 --- /dev/null +++ b/htmlcov/rest_framework_pagination.html @@ -0,0 +1,269 @@ + + + + + + + + Coverage for rest_framework/pagination: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+ +
+

""" 

+

Pagination serializers determine the structure of the output that should 

+

be used for paginated responses. 

+

""" 

+

from __future__ import unicode_literals 

+

from rest_framework import serializers 

+

from rest_framework.templatetags.rest_framework import replace_query_param 

+

 

+

 

+

class NextPageField(serializers.Field): 

+

    """ 

+

    Field that returns a link to the next page in paginated results. 

+

    """ 

+

    page_field = 'page' 

+

 

+

    def to_native(self, value): 

+

        if not value.has_next(): 

+

            return None 

+

        page = value.next_page_number() 

+

        request = self.context.get('request') 

+

        url = request and request.build_absolute_uri() or '' 

+

        return replace_query_param(url, self.page_field, page) 

+

 

+

 

+

class PreviousPageField(serializers.Field): 

+

    """ 

+

    Field that returns a link to the previous page in paginated results. 

+

    """ 

+

    page_field = 'page' 

+

 

+

    def to_native(self, value): 

+

        if not value.has_previous(): 

+

            return None 

+

        page = value.previous_page_number() 

+

        request = self.context.get('request') 

+

        url = request and request.build_absolute_uri() or '' 

+

        return replace_query_param(url, self.page_field, page) 

+

 

+

 

+

class DefaultObjectSerializer(serializers.Field): 

+

    """ 

+

    If no object serializer is specified, then this serializer will be applied 

+

    as the default. 

+

    """ 

+

 

+

    def __init__(self, source=None, context=None): 

+

        # Note: Swallow context kwarg - only required for eg. ModelSerializer. 

+

        super(DefaultObjectSerializer, self).__init__(source=source) 

+

 

+

 

+

class PaginationSerializerOptions(serializers.SerializerOptions): 

+

    """ 

+

    An object that stores the options that may be provided to a 

+

    pagination serializer by using the inner `Meta` class. 

+

 

+

    Accessible on the instance as `serializer.opts`. 

+

    """ 

+

    def __init__(self, meta): 

+

        super(PaginationSerializerOptions, self).__init__(meta) 

+

        self.object_serializer_class = getattr(meta, 'object_serializer_class', 

+

                                               DefaultObjectSerializer) 

+

 

+

 

+

class BasePaginationSerializer(serializers.Serializer): 

+

    """ 

+

    A base class for pagination serializers to inherit from, 

+

    to make implementing custom serializers more easy. 

+

    """ 

+

    _options_class = PaginationSerializerOptions 

+

    results_field = 'results' 

+

 

+

    def __init__(self, *args, **kwargs): 

+

        """ 

+

        Override init to add in the object serializer field on-the-fly. 

+

        """ 

+

        super(BasePaginationSerializer, self).__init__(*args, **kwargs) 

+

        results_field = self.results_field 

+

        object_serializer = self.opts.object_serializer_class 

+

 

+

        if 'context' in kwargs: 

+

            context_kwarg = {'context': kwargs['context']} 

+

        else: 

+

            context_kwarg = {} 

+

 

+

        self.fields[results_field] = object_serializer(source='object_list', **context_kwarg) 

+

 

+

 

+

class PaginationSerializer(BasePaginationSerializer): 

+

    """ 

+

    A default implementation of a pagination serializer. 

+

    """ 

+

    count = serializers.Field(source='paginator.count') 

+

    next = NextPageField(source='*') 

+

    previous = PreviousPageField(source='*') 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_parsers.html b/htmlcov/rest_framework_parsers.html new file mode 100644 index 00000000..92f1db62 --- /dev/null +++ b/htmlcov/rest_framework_parsers.html @@ -0,0 +1,671 @@ + + + + + + + + Coverage for rest_framework/parsers: 92% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+ +
+

""" 

+

Parsers are used to parse the content of incoming HTTP requests. 

+

 

+

They give us a generic way of being able to handle various media types 

+

on the request, such as form content or json encoded data. 

+

""" 

+

from __future__ import unicode_literals 

+

from django.conf import settings 

+

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 yaml, etree 

+

from rest_framework.exceptions import ParseError 

+

from rest_framework.compat import six 

+

import json 

+

import datetime 

+

import decimal 

+

 

+

 

+

class DataAndFiles(object): 

+

    def __init__(self, data, files): 

+

        self.data = data 

+

        self.files = files 

+

 

+

 

+

class BaseParser(object): 

+

    """ 

+

    All parsers should extend `BaseParser`, specifying a `media_type` 

+

    attribute, and overriding the `.parse()` method. 

+

    """ 

+

 

+

    media_type = None 

+

 

+

    def parse(self, stream, media_type=None, parser_context=None): 

+

        """ 

+

        Given a stream to read from, return the parsed representation. 

+

        Should return parsed data, or a `DataAndFiles` object consisting of the 

+

        parsed data and files. 

+

        """ 

+

        raise NotImplementedError(".parse() must be overridden.") 

+

 

+

 

+

class JSONParser(BaseParser): 

+

    """ 

+

    Parses JSON-serialized data. 

+

    """ 

+

 

+

    media_type = 'application/json' 

+

 

+

    def parse(self, stream, media_type=None, parser_context=None): 

+

        """ 

+

        Returns a 2-tuple of `(data, files)`. 

+

 

+

        `data` will be an object which is the parsed content of the response. 

+

        `files` will always be `None`. 

+

        """ 

+

        parser_context = parser_context or {} 

+

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

+

 

+

        try: 

+

            data = stream.read().decode(encoding) 

+

            return json.loads(data) 

+

        except ValueError as exc: 

+

            raise ParseError('JSON parse error - %s' % six.text_type(exc)) 

+

 

+

 

+

class YAMLParser(BaseParser): 

+

    """ 

+

    Parses YAML-serialized data. 

+

    """ 

+

 

+

    media_type = 'application/yaml' 

+

 

+

    def parse(self, stream, media_type=None, parser_context=None): 

+

        """ 

+

        Returns a 2-tuple of `(data, files)`. 

+

 

+

        `data` will be an object which is the parsed content of the response. 

+

        `files` will always be `None`. 

+

        """ 

+

        assert yaml, 'YAMLParser requires pyyaml to be installed' 

+

 

+

        parser_context = parser_context or {} 

+

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

+

 

+

        try: 

+

            data = stream.read().decode(encoding) 

+

            return yaml.safe_load(data) 

+

        except (ValueError, yaml.parser.ParserError) as exc: 

+

            raise ParseError('YAML parse error - %s' % six.u(exc)) 

+

 

+

 

+

class FormParser(BaseParser): 

+

    """ 

+

    Parser for form data. 

+

    """ 

+

 

+

    media_type = 'application/x-www-form-urlencoded' 

+

 

+

    def parse(self, stream, media_type=None, parser_context=None): 

+

        """ 

+

        Returns a 2-tuple of `(data, files)`. 

+

 

+

        `data` will be a :class:`QueryDict` containing all the form parameters. 

+

        `files` will always be :const:`None`. 

+

        """ 

+

        parser_context = parser_context or {} 

+

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

+

        data = QueryDict(stream.read(), encoding=encoding) 

+

        return data 

+

 

+

 

+

class MultiPartParser(BaseParser): 

+

    """ 

+

    Parser for multipart form data, which may include file data. 

+

    """ 

+

 

+

    media_type = 'multipart/form-data' 

+

 

+

    def parse(self, stream, media_type=None, parser_context=None): 

+

        """ 

+

        Returns a DataAndFiles object. 

+

 

+

        `.data` will be a `QueryDict` containing all the form parameters. 

+

        `.files` will be a `QueryDict` containing all the form files. 

+

        """ 

+

        parser_context = parser_context or {} 

+

        request = parser_context['request'] 

+

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

+

        meta = request.META 

+

        upload_handlers = request.upload_handlers 

+

 

+

        try: 

+

            parser = DjangoMultiPartParser(meta, stream, upload_handlers, encoding) 

+

            data, files = parser.parse() 

+

            return DataAndFiles(data, files) 

+

        except MultiPartParserError as exc: 

+

            raise ParseError('Multipart form parse error - %s' % six.u(exc)) 

+

 

+

 

+

class XMLParser(BaseParser): 

+

    """ 

+

    XML parser. 

+

    """ 

+

 

+

    media_type = 'application/xml' 

+

 

+

    def parse(self, stream, media_type=None, parser_context=None): 

+

        assert etree, 'XMLParser requires defusedxml to be installed' 

+

 

+

        parser_context = parser_context or {} 

+

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

+

        parser = etree.DefusedXMLParser(encoding=encoding) 

+

        try: 

+

            tree = etree.parse(stream, parser=parser, forbid_dtd=True) 

+

        except (etree.ParseError, ValueError) as exc: 

+

            raise ParseError('XML parse error - %s' % six.u(exc)) 

+

        data = self._xml_convert(tree.getroot()) 

+

 

+

        return data 

+

 

+

    def _xml_convert(self, element): 

+

        """ 

+

        convert the xml `element` into the corresponding python object 

+

        """ 

+

 

+

        children = list(element) 

+

 

+

        if len(children) == 0: 

+

            return self._type_convert(element.text) 

+

        else: 

+

            # if the fist child tag is list-item means all children are list-item 

+

            if children[0].tag == "list-item": 

+

                data = [] 

+

                for child in children: 

+

                    data.append(self._xml_convert(child)) 

+

            else: 

+

                data = {} 

+

                for child in children: 

+

                    data[child.tag] = self._xml_convert(child) 

+

 

+

            return data 

+

 

+

    def _type_convert(self, value): 

+

        """ 

+

        Converts the value returned by the XMl parse into the equivalent 

+

        Python type 

+

        """ 

+

        if value is None: 

+

            return value 

+

 

+

        try: 

+

            return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S') 

+

        except ValueError: 

+

            pass 

+

 

+

        try: 

+

            return int(value) 

+

        except ValueError: 

+

            pass 

+

 

+

        try: 

+

            return decimal.Decimal(value) 

+

        except decimal.InvalidOperation: 

+

            pass 

+

 

+

        return value 

+

 

+

 

+

class FileUploadParser(BaseParser): 

+

    """ 

+

    Parser for file upload data. 

+

    """ 

+

    media_type = '*/*' 

+

 

+

    def parse(self, stream, media_type=None, parser_context=None): 

+

        """ 

+

        Returns a DataAndFiles object. 

+

 

+

        `.data` will be None (we expect request body to be a file content). 

+

        `.files` will be a `QueryDict` containing one 'file' element. 

+

        """ 

+

 

+

        parser_context = parser_context or {} 

+

        request = parser_context['request'] 

+

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

+

        meta = request.META 

+

        upload_handlers = request.upload_handlers 

+

        filename = self.get_filename(stream, media_type, parser_context) 

+

 

+

        # Note that this code is extracted from Django's handling of 

+

        # file uploads in MultiPartParser. 

+

        content_type = meta.get('HTTP_CONTENT_TYPE', 

+

                                meta.get('CONTENT_TYPE', '')) 

+

        try: 

+

            content_length = int(meta.get('HTTP_CONTENT_LENGTH', 

+

                                          meta.get('CONTENT_LENGTH', 0))) 

+

        except (ValueError, TypeError): 

+

            content_length = None 

+

 

+

        # See if the handler will want to take care of the parsing. 

+

        for handler in upload_handlers: 

+

            result = handler.handle_raw_input(None, 

+

                                              meta, 

+

                                              content_length, 

+

                                              None, 

+

                                              encoding) 

+

            if result is not None: 

+

                return DataAndFiles(None, {'file': result[1]}) 

+

 

+

        # This is the standard case. 

+

        possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size] 

+

        chunk_size = min([2 ** 31 - 4] + possible_sizes) 

+

        chunks = ChunkIter(stream, chunk_size) 

+

        counters = [0] * len(upload_handlers) 

+

 

+

        for handler in upload_handlers: 

+

            try: 

+

                handler.new_file(None, filename, content_type, 

+

                                 content_length, encoding) 

+

            except StopFutureHandlers: 

+

                break 

+

 

+

        for chunk in chunks: 

+

            for i, handler in enumerate(upload_handlers): 

+

                chunk_length = len(chunk) 

+

                chunk = handler.receive_data_chunk(chunk, counters[i]) 

+

                counters[i] += chunk_length 

+

                if chunk is None: 

+

                    break 

+

 

+

        for i, handler in enumerate(upload_handlers): 

+

            file_obj = handler.file_complete(counters[i]) 

+

            if file_obj: 

+

                return DataAndFiles(None, {'file': file_obj}) 

+

        raise ParseError("FileUpload parse error - " 

+

                         "none of upload handlers can handle the stream") 

+

 

+

    def get_filename(self, stream, media_type, parser_context): 

+

        """ 

+

        Detects the uploaded file name. First searches a 'filename' url kwarg. 

+

        Then tries to parse Content-Disposition header. 

+

        """ 

+

        try: 

+

            return parser_context['kwargs']['filename'] 

+

        except KeyError: 

+

            pass 

+

 

+

        try: 

+

            meta = parser_context['request'].META 

+

            disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION']) 

+

            return disposition[1]['filename'] 

+

        except (AttributeError, KeyError): 

+

            pass 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_permissions.html b/htmlcov/rest_framework_permissions.html new file mode 100644 index 00000000..20a29522 --- /dev/null +++ b/htmlcov/rest_framework_permissions.html @@ -0,0 +1,429 @@ + + + + + + + + Coverage for rest_framework/permissions: 81% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+ +
+

""" 

+

Provides a set of pluggable permission policies. 

+

""" 

+

from __future__ import unicode_literals 

+

import inspect 

+

import warnings 

+

 

+

SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS'] 

+

 

+

from rest_framework.compat import oauth2_provider_scope, oauth2_constants 

+

 

+

 

+

class BasePermission(object): 

+

    """ 

+

    A base class from which all permission classes should inherit. 

+

    """ 

+

 

+

    def has_permission(self, request, view): 

+

        """ 

+

        Return `True` if permission is granted, `False` otherwise. 

+

        """ 

+

        return True 

+

 

+

    def has_object_permission(self, request, view, obj): 

+

        """ 

+

        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 

+

 

+

 

+

class AllowAny(BasePermission): 

+

    """ 

+

    Allow any access. 

+

    This isn't strictly required, since you could use an empty 

+

    permission_classes list, but it's useful because it makes the intention 

+

    more explicit. 

+

    """ 

+

    def has_permission(self, request, view): 

+

        return True 

+

 

+

 

+

class IsAuthenticated(BasePermission): 

+

    """ 

+

    Allows access only to authenticated users. 

+

    """ 

+

 

+

    def has_permission(self, request, view): 

+

        if request.user and request.user.is_authenticated(): 

+

            return True 

+

        return False 

+

 

+

 

+

class IsAdminUser(BasePermission): 

+

    """ 

+

    Allows access only to admin users. 

+

    """ 

+

 

+

    def has_permission(self, request, view): 

+

        if request.user and request.user.is_staff: 

+

            return True 

+

        return False 

+

 

+

 

+

class IsAuthenticatedOrReadOnly(BasePermission): 

+

    """ 

+

    The request is authenticated as a user, or is a read-only request. 

+

    """ 

+

 

+

    def has_permission(self, request, view): 

+

        if (request.method in SAFE_METHODS or 

+

            request.user and 

+

            request.user.is_authenticated()): 

+

            return True 

+

        return False 

+

 

+

 

+

class DjangoModelPermissions(BasePermission): 

+

    """ 

+

    The request is authenticated using `django.contrib.auth` permissions. 

+

    See: https://docs.djangoproject.com/en/dev/topics/auth/#permissions 

+

 

+

    It ensures that the user is authenticated, and has the appropriate 

+

    `add`/`change`/`delete` permissions on the model. 

+

 

+

    This permission can only be applied against view classes that 

+

    provide a `.model` or `.queryset` attribute. 

+

    """ 

+

 

+

    # Map methods into required permission codes. 

+

    # Override this if you need to also provide 'view' permissions, 

+

    # or if you want to provide custom permission codes. 

+

    perms_map = { 

+

        'GET': [], 

+

        'OPTIONS': [], 

+

        'HEAD': [], 

+

        'POST': ['%(app_label)s.add_%(model_name)s'], 

+

        'PUT': ['%(app_label)s.change_%(model_name)s'], 

+

        'PATCH': ['%(app_label)s.change_%(model_name)s'], 

+

        'DELETE': ['%(app_label)s.delete_%(model_name)s'], 

+

    } 

+

 

+

    authenticated_users_only = True 

+

 

+

    def get_required_permissions(self, method, model_cls): 

+

        """ 

+

        Given a model and an HTTP method, return the list of permission 

+

        codes that the user is required to have. 

+

        """ 

+

        kwargs = { 

+

            'app_label': model_cls._meta.app_label, 

+

            'model_name': model_cls._meta.module_name 

+

        } 

+

        return [perm % kwargs for perm in self.perms_map[method]] 

+

 

+

    def has_permission(self, request, view): 

+

        model_cls = getattr(view, 'model', None) 

+

        queryset = getattr(view, 'queryset', None) 

+

 

+

        if model_cls is None and queryset is not None: 

+

            model_cls = queryset.model 

+

 

+

        # Workaround to ensure DjangoModelPermissions are not applied 

+

        # to the root view when using DefaultRouter. 

+

        if model_cls is None and getattr(view, '_ignore_model_permissions', False): 

+

            return True 

+

 

+

        assert model_cls, ('Cannot apply DjangoModelPermissions on a view that' 

+

                           ' does not have `.model` or `.queryset` property.') 

+

 

+

        perms = self.get_required_permissions(request.method, model_cls) 

+

 

+

        if (request.user and 

+

            (request.user.is_authenticated() or not self.authenticated_users_only) and 

+

            request.user.has_perms(perms)): 

+

            return True 

+

        return False 

+

 

+

 

+

class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions): 

+

    """ 

+

    Similar to DjangoModelPermissions, except that anonymous users are 

+

    allowed read-only access. 

+

    """ 

+

    authenticated_users_only = False 

+

 

+

 

+

class TokenHasReadWriteScope(BasePermission): 

+

    """ 

+

    The request is authenticated as a user and the token used has the right scope 

+

    """ 

+

 

+

    def has_permission(self, request, view): 

+

        token = request.auth 

+

        read_only = request.method in SAFE_METHODS 

+

 

+

        if not token: 

+

            return False 

+

 

+

        if hasattr(token, 'resource'):  # OAuth 1 

+

            return read_only or not request.auth.resource.is_readonly 

+

        elif hasattr(token, 'scope'):  # OAuth 2 

+

            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.') 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_relations.html b/htmlcov/rest_framework_relations.html new file mode 100644 index 00000000..29ad3cf6 --- /dev/null +++ b/htmlcov/rest_framework_relations.html @@ -0,0 +1,1347 @@ + + + + + + + + Coverage for rest_framework/relations: 76% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+

344

+

345

+

346

+

347

+

348

+

349

+

350

+

351

+

352

+

353

+

354

+

355

+

356

+

357

+

358

+

359

+

360

+

361

+

362

+

363

+

364

+

365

+

366

+

367

+

368

+

369

+

370

+

371

+

372

+

373

+

374

+

375

+

376

+

377

+

378

+

379

+

380

+

381

+

382

+

383

+

384

+

385

+

386

+

387

+

388

+

389

+

390

+

391

+

392

+

393

+

394

+

395

+

396

+

397

+

398

+

399

+

400

+

401

+

402

+

403

+

404

+

405

+

406

+

407

+

408

+

409

+

410

+

411

+

412

+

413

+

414

+

415

+

416

+

417

+

418

+

419

+

420

+

421

+

422

+

423

+

424

+

425

+

426

+

427

+

428

+

429

+

430

+

431

+

432

+

433

+

434

+

435

+

436

+

437

+

438

+

439

+

440

+

441

+

442

+

443

+

444

+

445

+

446

+

447

+

448

+

449

+

450

+

451

+

452

+

453

+

454

+

455

+

456

+

457

+

458

+

459

+

460

+

461

+

462

+

463

+

464

+

465

+

466

+

467

+

468

+

469

+

470

+

471

+

472

+

473

+

474

+

475

+

476

+

477

+

478

+

479

+

480

+

481

+

482

+

483

+

484

+

485

+

486

+

487

+

488

+

489

+

490

+

491

+

492

+

493

+

494

+

495

+

496

+

497

+

498

+

499

+

500

+

501

+

502

+

503

+

504

+

505

+

506

+

507

+

508

+

509

+

510

+

511

+

512

+

513

+

514

+

515

+

516

+

517

+

518

+

519

+

520

+

521

+

522

+

523

+

524

+

525

+

526

+

527

+

528

+

529

+

530

+

531

+

532

+

533

+

534

+

535

+

536

+

537

+

538

+

539

+

540

+

541

+

542

+

543

+

544

+

545

+

546

+

547

+

548

+

549

+

550

+

551

+

552

+

553

+

554

+

555

+

556

+

557

+

558

+

559

+

560

+

561

+

562

+

563

+

564

+

565

+

566

+

567

+

568

+

569

+

570

+

571

+

572

+

573

+

574

+

575

+

576

+

577

+

578

+

579

+

580

+

581

+

582

+

583

+

584

+

585

+

586

+

587

+

588

+

589

+

590

+

591

+

592

+

593

+

594

+

595

+

596

+

597

+

598

+

599

+

600

+

601

+

602

+

603

+

604

+

605

+

606

+

607

+

608

+

609

+

610

+

611

+

612

+

613

+

614

+

615

+

616

+

617

+

618

+

619

+

620

+

621

+

622

+

623

+

624

+

625

+

626

+

627

+

628

+

629

+

630

+

631

+

632

+

633

+ +
+

""" 

+

Serializer fields that deal with relationships. 

+

 

+

These fields allow you to specify the style that should be used to represent 

+

model relationships, including hyperlinks, primary keys, or slugs. 

+

""" 

+

from __future__ import unicode_literals 

+

from django.core.exceptions import ObjectDoesNotExist, ValidationError 

+

from django.core.urlresolvers import resolve, get_script_prefix, NoReverseMatch 

+

from django import forms 

+

from django.db.models.fields import BLANK_CHOICE_DASH 

+

from django.forms import widgets 

+

from django.forms.models import ModelChoiceIterator 

+

from django.utils.translation import ugettext_lazy as _ 

+

from rest_framework.fields import Field, WritableField, get_component, is_simple_callable 

+

from rest_framework.reverse import reverse 

+

from rest_framework.compat import urlparse 

+

from rest_framework.compat import smart_text 

+

import warnings 

+

 

+

 

+

##### Relational fields ##### 

+

 

+

 

+

# Not actually Writable, but subclasses may need to be. 

+

class RelatedField(WritableField): 

+

    """ 

+

    Base class for related model fields. 

+

 

+

    This represents a relationship using the unicode representation of the target. 

+

    """ 

+

    widget = widgets.Select 

+

    many_widget = widgets.SelectMultiple 

+

    form_field_class = forms.ChoiceField 

+

    many_form_field_class = forms.MultipleChoiceField 

+

 

+

    cache_choices = False 

+

    empty_label = None 

+

    read_only = True 

+

    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: 

+

            self.widget = self.many_widget 

+

            self.form_field_class = self.many_form_field_class 

+

 

+

        kwargs['read_only'] = kwargs.pop('read_only', self.read_only) 

+

        super(RelatedField, self).__init__(*args, **kwargs) 

+

 

+

        if not self.required: 

+

            self.empty_label = BLANK_CHOICE_DASH[0][1] 

+

 

+

        self.queryset = queryset 

+

 

+

    def initialize(self, parent, field_name): 

+

        super(RelatedField, self).initialize(parent, field_name) 

+

        if self.queryset is None and not self.read_only: 

+

            try: 

+

                manager = getattr(self.parent.opts.model, self.source or field_name) 

+

                if hasattr(manager, 'related'):  # Forward 

+

                    self.queryset = manager.related.model._default_manager.all() 

+

                else:  # Reverse 

+

                    self.queryset = manager.field.rel.to._default_manager.all() 

+

            except Exception: 

+

                msg = ('Serializer related fields must include a `queryset`' + 

+

                       ' argument or set `read_only=True') 

+

                raise Exception(msg) 

+

 

+

    ### We need this stuff to make form choices work... 

+

 

+

    def prepare_value(self, obj): 

+

        return self.to_native(obj) 

+

 

+

    def label_from_instance(self, obj): 

+

        """ 

+

        Return a readable representation for use with eg. select widgets. 

+

        """ 

+

        desc = smart_text(obj) 

+

        ident = smart_text(self.to_native(obj)) 

+

        if desc == ident: 

+

            return desc 

+

        return "%s - %s" % (desc, ident) 

+

 

+

    def _get_queryset(self): 

+

        return self._queryset 

+

 

+

    def _set_queryset(self, queryset): 

+

        self._queryset = queryset 

+

        self.widget.choices = self.choices 

+

 

+

    queryset = property(_get_queryset, _set_queryset) 

+

 

+

    def _get_choices(self): 

+

        # If self._choices is set, then somebody must have manually set 

+

        # the property self.choices. In this case, just return self._choices. 

+

        if hasattr(self, '_choices'): 

+

            return self._choices 

+

 

+

        # Otherwise, execute the QuerySet in self.queryset to determine the 

+

        # choices dynamically. Return a fresh ModelChoiceIterator that has not been 

+

        # consumed. Note that we're instantiating a new ModelChoiceIterator *each* 

+

        # time _get_choices() is called (and, thus, each time self.choices is 

+

        # accessed) so that we can ensure the QuerySet has not been consumed. This 

+

        # construct might look complicated but it allows for lazy evaluation of 

+

        # the queryset. 

+

        return ModelChoiceIterator(self) 

+

 

+

    def _set_choices(self, value): 

+

        # Setting choices also sets the choices on the widget. 

+

        # choices can be any iterable, but we call list() on it because 

+

        # it will be consumed more than once. 

+

        self._choices = self.widget.choices = list(value) 

+

 

+

    choices = property(_get_choices, _set_choices) 

+

 

+

    ### Regular serializer stuff... 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        try: 

+

            if self.source == '*': 

+

                return self.to_native(obj) 

+

 

+

            source = self.source or field_name 

+

            value = obj 

+

 

+

            for component in source.split('.'): 

+

                value = get_component(value, component) 

+

                if value is None: 

+

                    break 

+

        except ObjectDoesNotExist: 

+

            return None 

+

 

+

        if value is None: 

+

            return None 

+

 

+

        if self.many: 

+

            if is_simple_callable(getattr(value, 'all', None)): 

+

                return [self.to_native(item) for item in value.all()] 

+

            else: 

+

                # Also support non-queryset iterables. 

+

                # This allows us to also support plain lists of related items. 

+

                return [self.to_native(item) for item in value] 

+

        return self.to_native(value) 

+

 

+

    def field_from_native(self, data, files, field_name, into): 

+

        if self.read_only: 

+

            return 

+

 

+

        try: 

+

            if self.many: 

+

                try: 

+

                    # Form data 

+

                    value = data.getlist(field_name) 

+

                    if value == [''] or value == []: 

+

                        raise KeyError 

+

                except AttributeError: 

+

                    # Non-form data 

+

                    value = data[field_name] 

+

            else: 

+

                value = data[field_name] 

+

        except KeyError: 

+

            if self.partial: 

+

                return 

+

            value = [] if self.many else None 

+

 

+

        if value in (None, '') and self.required: 

+

            raise ValidationError(self.error_messages['required']) 

+

        elif value in (None, ''): 

+

            into[(self.source or field_name)] = None 

+

        elif self.many: 

+

            into[(self.source or field_name)] = [self.from_native(item) for item in value] 

+

        else: 

+

            into[(self.source or field_name)] = self.from_native(value) 

+

 

+

 

+

### PrimaryKey relationships 

+

 

+

class PrimaryKeyRelatedField(RelatedField): 

+

    """ 

+

    Represents a relationship as a pk value. 

+

    """ 

+

    read_only = False 

+

 

+

    default_error_messages = { 

+

        'does_not_exist': _("Invalid pk '%s' - object does not exist."), 

+

        'incorrect_type': _('Incorrect type.  Expected pk value, received %s.'), 

+

    } 

+

 

+

    # TODO: Remove these field hacks... 

+

    def prepare_value(self, obj): 

+

        return self.to_native(obj.pk) 

+

 

+

    def label_from_instance(self, obj): 

+

        """ 

+

        Return a readable representation for use with eg. select widgets. 

+

        """ 

+

        desc = smart_text(obj) 

+

        ident = smart_text(self.to_native(obj.pk)) 

+

        if desc == ident: 

+

            return desc 

+

        return "%s - %s" % (desc, ident) 

+

 

+

    # TODO: Possibly change this to just take `obj`, through prob less performant 

+

    def to_native(self, pk): 

+

        return pk 

+

 

+

    def from_native(self, data): 

+

        if self.queryset is None: 

+

            raise Exception('Writable related fields must include a `queryset` argument') 

+

 

+

        try: 

+

            return self.queryset.get(pk=data) 

+

        except ObjectDoesNotExist: 

+

            msg = self.error_messages['does_not_exist'] % smart_text(data) 

+

            raise ValidationError(msg) 

+

        except (TypeError, ValueError): 

+

            received = type(data).__name__ 

+

            msg = self.error_messages['incorrect_type'] % received 

+

            raise ValidationError(msg) 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        if self.many: 

+

            # To-many relationship 

+

 

+

            queryset = None 

+

            if not self.source: 

+

                # Prefer obj.serializable_value for performance reasons 

+

                try: 

+

                    queryset = obj.serializable_value(field_name) 

+

                except AttributeError: 

+

                    pass 

+

            if queryset is None: 

+

                # RelatedManager (reverse relationship) 

+

                source = self.source or field_name 

+

                queryset = obj 

+

                for component in source.split('.'): 

+

                    queryset = get_component(queryset, component) 

+

 

+

            # Forward relationship 

+

            if is_simple_callable(getattr(queryset, 'all', None)): 

+

                return [self.to_native(item.pk) for item in queryset.all()] 

+

            else: 

+

                # Also support non-queryset iterables. 

+

                # This allows us to also support plain lists of related items. 

+

                return [self.to_native(item.pk) for item in queryset] 

+

 

+

        # To-one relationship 

+

        try: 

+

            # Prefer obj.serializable_value for performance reasons 

+

            pk = obj.serializable_value(self.source or field_name) 

+

        except AttributeError: 

+

            # RelatedObject (reverse relationship) 

+

            try: 

+

                pk = getattr(obj, self.source or field_name).pk 

+

            except ObjectDoesNotExist: 

+

                return None 

+

 

+

        # Forward relationship 

+

        return self.to_native(pk) 

+

 

+

 

+

### Slug relationships 

+

 

+

 

+

class SlugRelatedField(RelatedField): 

+

    """ 

+

    Represents a relationship using a unique field on the target. 

+

    """ 

+

    read_only = False 

+

 

+

    default_error_messages = { 

+

        'does_not_exist': _("Object with %s=%s does not exist."), 

+

        'invalid': _('Invalid value.'), 

+

    } 

+

 

+

    def __init__(self, *args, **kwargs): 

+

        self.slug_field = kwargs.pop('slug_field', None) 

+

        assert self.slug_field, 'slug_field is required' 

+

        super(SlugRelatedField, self).__init__(*args, **kwargs) 

+

 

+

    def to_native(self, obj): 

+

        return getattr(obj, self.slug_field) 

+

 

+

    def from_native(self, data): 

+

        if self.queryset is None: 

+

            raise Exception('Writable related fields must include a `queryset` argument') 

+

 

+

        try: 

+

            return self.queryset.get(**{self.slug_field: data}) 

+

        except ObjectDoesNotExist: 

+

            raise ValidationError(self.error_messages['does_not_exist'] % 

+

                                  (self.slug_field, smart_text(data))) 

+

        except (TypeError, ValueError): 

+

            msg = self.error_messages['invalid'] 

+

            raise ValidationError(msg) 

+

 

+

 

+

### Hyperlinked relationships 

+

 

+

class HyperlinkedRelatedField(RelatedField): 

+

    """ 

+

    Represents a relationship using hyperlinking. 

+

    """ 

+

    read_only = False 

+

    lookup_field = 'pk' 

+

 

+

    default_error_messages = { 

+

        'no_match': _('Invalid hyperlink - No URL match'), 

+

        'incorrect_match': _('Invalid hyperlink - Incorrect URL match'), 

+

        'configuration_error': _('Invalid hyperlink due to configuration error'), 

+

        'does_not_exist': _("Invalid hyperlink - object does not exist."), 

+

        'incorrect_type': _('Incorrect type.  Expected url string, received %s.'), 

+

    } 

+

 

+

    # These are all pending deprecation 

+

    pk_url_kwarg = 'pk' 

+

    slug_field = 'slug' 

+

    slug_url_kwarg = None  # Defaults to same as `slug_field` unless overridden 

+

 

+

    def __init__(self, *args, **kwargs): 

+

        try: 

+

            self.view_name = kwargs.pop('view_name') 

+

        except KeyError: 

+

            raise ValueError("Hyperlinked field requires 'view_name' kwarg") 

+

 

+

        self.lookup_field = kwargs.pop('lookup_field', self.lookup_field) 

+

        self.format = kwargs.pop('format', None) 

+

 

+

        # These are pending deprecation 

+

        if 'pk_url_kwarg' in kwargs: 

+

            msg = 'pk_url_kwarg is pending deprecation. Use lookup_field instead.' 

+

            warnings.warn(msg, PendingDeprecationWarning, 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) 

+

        if 'slug_field' in kwargs: 

+

            msg = 'slug_field is pending deprecation. Use lookup_field instead.' 

+

            warnings.warn(msg, PendingDeprecationWarning, 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) 

+

        default_slug_kwarg = self.slug_url_kwarg or self.slug_field 

+

        self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', default_slug_kwarg) 

+

 

+

        super(HyperlinkedRelatedField, self).__init__(*args, **kwargs) 

+

 

+

    def get_url(self, obj, view_name, request, format): 

+

        """ 

+

        Given an object, return the URL that hyperlinks to the object. 

+

 

+

        May raise a `NoReverseMatch` if the `view_name` and `lookup_field` 

+

        attributes are not configured to correctly match the URL conf. 

+

        """ 

+

        lookup_field = getattr(obj, self.lookup_field) 

+

        kwargs = {self.lookup_field: lookup_field} 

+

        try: 

+

            return reverse(view_name, kwargs=kwargs, request=request, format=format) 

+

        except NoReverseMatch: 

+

            pass 

+

 

+

        if self.pk_url_kwarg != 'pk': 

+

            # Only try pk if it has been explicitly set. 

+

            # Otherwise, the default `lookup_field = 'pk'` has us covered. 

+

            pk = obj.pk 

+

            kwargs = {self.pk_url_kwarg: pk} 

+

            try: 

+

                return reverse(view_name, kwargs=kwargs, request=request, format=format) 

+

            except NoReverseMatch: 

+

                pass 

+

 

+

        slug = getattr(obj, self.slug_field, None) 

+

        if slug is not None: 

+

            # Only try slug if it corresponds to an attribute on the object. 

+

            kwargs = {self.slug_url_kwarg: slug} 

+

            try: 

+

                ret = reverse(view_name, kwargs=kwargs, request=request, format=format) 

+

                if self.slug_field == 'slug' and self.slug_url_kwarg == 'slug': 

+

                    # 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.' \ 

+

                          'You should set `lookup_field=slug` on the HyperlinkedRelatedField.' 

+

                    warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

+

                return ret 

+

            except NoReverseMatch: 

+

                pass 

+

 

+

        raise NoReverseMatch() 

+

 

+

    def get_object(self, queryset, view_name, view_args, view_kwargs): 

+

        """ 

+

        Return the object corresponding to a matched URL. 

+

 

+

        Takes the matched URL conf arguments, and the queryset, and should 

+

        return an object instance, or raise an `ObjectDoesNotExist` exception. 

+

        """ 

+

        lookup = view_kwargs.get(self.lookup_field, None) 

+

        pk = view_kwargs.get(self.pk_url_kwarg, None) 

+

        slug = view_kwargs.get(self.slug_url_kwarg, None) 

+

 

+

        if lookup is not None: 

+

            filter_kwargs = {self.lookup_field: lookup} 

+

        elif pk is not None: 

+

            filter_kwargs = {'pk': pk} 

+

        elif slug is not None: 

+

            filter_kwargs = {self.slug_field: slug} 

+

        else: 

+

            raise ObjectDoesNotExist() 

+

 

+

        return queryset.get(**filter_kwargs) 

+

 

+

    def to_native(self, obj): 

+

        view_name = self.view_name 

+

        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) 

+

 

+

        # If the object has not yet been saved then we cannot hyperlink to it. 

+

        if getattr(obj, 'pk', None) is None: 

+

            return 

+

 

+

        # Return the hyperlink, or error if incorrectly configured. 

+

        try: 

+

            return self.get_url(obj, view_name, request, format) 

+

        except NoReverseMatch: 

+

            msg = ( 

+

                'Could not resolve URL for hyperlinked relationship using ' 

+

                'view name "%s". You may have failed to include the related ' 

+

                'model in your API, or incorrectly configured the ' 

+

                '`lookup_field` attribute on this field.' 

+

            ) 

+

            raise Exception(msg % view_name) 

+

 

+

    def from_native(self, value): 

+

        # Convert URL -> model instance pk 

+

        # TODO: Use values_list 

+

        queryset = self.queryset 

+

        if queryset is None: 

+

            raise Exception('Writable related fields must include a `queryset` argument') 

+

 

+

        try: 

+

            http_prefix = value.startswith(('http:', 'https:')) 

+

        except AttributeError: 

+

            msg = self.error_messages['incorrect_type'] 

+

            raise ValidationError(msg % type(value).__name__) 

+

 

+

        if http_prefix: 

+

            # If needed convert absolute URLs to relative path 

+

            value = urlparse.urlparse(value).path 

+

            prefix = get_script_prefix() 

+

            if value.startswith(prefix): 

+

                value = '/' + value[len(prefix):] 

+

 

+

        try: 

+

            match = resolve(value) 

+

        except Exception: 

+

            raise ValidationError(self.error_messages['no_match']) 

+

 

+

        if match.view_name != self.view_name: 

+

            raise ValidationError(self.error_messages['incorrect_match']) 

+

 

+

        try: 

+

            return self.get_object(queryset, match.view_name, 

+

                                   match.args, match.kwargs) 

+

        except (ObjectDoesNotExist, TypeError, ValueError): 

+

            raise ValidationError(self.error_messages['does_not_exist']) 

+

 

+

 

+

class HyperlinkedIdentityField(Field): 

+

    """ 

+

    Represents the instance, or a property on the instance, using hyperlinking. 

+

    """ 

+

    lookup_field = 'pk' 

+

    read_only = True 

+

 

+

    # These are all pending deprecation 

+

    pk_url_kwarg = 'pk' 

+

    slug_field = 'slug' 

+

    slug_url_kwarg = None  # Defaults to same as `slug_field` unless overridden 

+

 

+

    def __init__(self, *args, **kwargs): 

+

        try: 

+

            self.view_name = kwargs.pop('view_name') 

+

        except KeyError: 

+

            msg = "HyperlinkedIdentityField requires 'view_name' argument" 

+

            raise ValueError(msg) 

+

 

+

        self.format = kwargs.pop('format', None) 

+

        lookup_field = kwargs.pop('lookup_field', None) 

+

        self.lookup_field = lookup_field or self.lookup_field 

+

 

+

        # These are pending deprecation 

+

        if 'pk_url_kwarg' in kwargs: 

+

            msg = 'pk_url_kwarg is pending deprecation. Use lookup_field instead.' 

+

            warnings.warn(msg, PendingDeprecationWarning, 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) 

+

        if 'slug_field' in kwargs: 

+

            msg = 'slug_field is pending deprecation. Use lookup_field instead.' 

+

            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

+

 

+

        self.slug_field = kwargs.pop('slug_field', self.slug_field) 

+

        default_slug_kwarg = self.slug_url_kwarg or self.slug_field 

+

        self.pk_url_kwarg = kwargs.pop('pk_url_kwarg', self.pk_url_kwarg) 

+

        self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', default_slug_kwarg) 

+

 

+

        super(HyperlinkedIdentityField, self).__init__(*args, **kwargs) 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        request = self.context.get('request', None) 

+

        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) 

+

 

+

        # By default use whatever format is given for the current context 

+

        # unless the target is a different type to the source. 

+

        # 

+

        # Eg. Consider a HyperlinkedIdentityField pointing from a json 

+

        # representation to an html property of that representation... 

+

        # 

+

        # '/snippets/1/' should link to '/snippets/1/highlight/' 

+

        # ...but... 

+

        # '/snippets/1/.json' should link to '/snippets/1/highlight/.html' 

+

        if format and self.format and self.format != format: 

+

            format = self.format 

+

 

+

        # Return the hyperlink, or error if incorrectly configured. 

+

        try: 

+

            return self.get_url(obj, view_name, request, format) 

+

        except NoReverseMatch: 

+

            msg = ( 

+

                'Could not resolve URL for hyperlinked relationship using ' 

+

                'view name "%s". You may have failed to include the related ' 

+

                'model in your API, or incorrectly configured the ' 

+

                '`lookup_field` attribute on this field.' 

+

            ) 

+

            raise Exception(msg % view_name) 

+

 

+

    def get_url(self, obj, view_name, request, format): 

+

        """ 

+

        Given an object, return the URL that hyperlinks to the object. 

+

 

+

        May raise a `NoReverseMatch` if the `view_name` and `lookup_field` 

+

        attributes are not configured to correctly match the URL conf. 

+

        """ 

+

        lookup_field = getattr(obj, self.lookup_field) 

+

        kwargs = {self.lookup_field: lookup_field} 

+

        try: 

+

            return reverse(view_name, kwargs=kwargs, request=request, format=format) 

+

        except NoReverseMatch: 

+

            pass 

+

 

+

        if self.pk_url_kwarg != 'pk': 

+

            # Only try pk lookup if it has been explicitly set. 

+

            # Otherwise, the default `lookup_field = 'pk'` has us covered. 

+

            kwargs = {self.pk_url_kwarg: obj.pk} 

+

            try: 

+

                return reverse(view_name, kwargs=kwargs, request=request, format=format) 

+

            except NoReverseMatch: 

+

                pass 

+

 

+

        slug = getattr(obj, self.slug_field, None) 

+

        if slug: 

+

            # Only use slug lookup if a slug field exists on the model 

+

            kwargs = {self.slug_url_kwarg: slug} 

+

            try: 

+

                return reverse(view_name, kwargs=kwargs, request=request, format=format) 

+

            except NoReverseMatch: 

+

                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/htmlcov/rest_framework_renderers.html b/htmlcov/rest_framework_renderers.html new file mode 100644 index 00000000..58c71b85 --- /dev/null +++ b/htmlcov/rest_framework_renderers.html @@ -0,0 +1,1227 @@ + + + + + + + + Coverage for rest_framework/renderers: 92% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+

344

+

345

+

346

+

347

+

348

+

349

+

350

+

351

+

352

+

353

+

354

+

355

+

356

+

357

+

358

+

359

+

360

+

361

+

362

+

363

+

364

+

365

+

366

+

367

+

368

+

369

+

370

+

371

+

372

+

373

+

374

+

375

+

376

+

377

+

378

+

379

+

380

+

381

+

382

+

383

+

384

+

385

+

386

+

387

+

388

+

389

+

390

+

391

+

392

+

393

+

394

+

395

+

396

+

397

+

398

+

399

+

400

+

401

+

402

+

403

+

404

+

405

+

406

+

407

+

408

+

409

+

410

+

411

+

412

+

413

+

414

+

415

+

416

+

417

+

418

+

419

+

420

+

421

+

422

+

423

+

424

+

425

+

426

+

427

+

428

+

429

+

430

+

431

+

432

+

433

+

434

+

435

+

436

+

437

+

438

+

439

+

440

+

441

+

442

+

443

+

444

+

445

+

446

+

447

+

448

+

449

+

450

+

451

+

452

+

453

+

454

+

455

+

456

+

457

+

458

+

459

+

460

+

461

+

462

+

463

+

464

+

465

+

466

+

467

+

468

+

469

+

470

+

471

+

472

+

473

+

474

+

475

+

476

+

477

+

478

+

479

+

480

+

481

+

482

+

483

+

484

+

485

+

486

+

487

+

488

+

489

+

490

+

491

+

492

+

493

+

494

+

495

+

496

+

497

+

498

+

499

+

500

+

501

+

502

+

503

+

504

+

505

+

506

+

507

+

508

+

509

+

510

+

511

+

512

+

513

+

514

+

515

+

516

+

517

+

518

+

519

+

520

+

521

+

522

+

523

+

524

+

525

+

526

+

527

+

528

+

529

+

530

+

531

+

532

+

533

+

534

+

535

+

536

+

537

+

538

+

539

+

540

+

541

+

542

+

543

+

544

+

545

+

546

+

547

+

548

+

549

+

550

+

551

+

552

+

553

+

554

+

555

+

556

+

557

+

558

+

559

+

560

+

561

+

562

+

563

+

564

+

565

+

566

+

567

+

568

+

569

+

570

+

571

+

572

+

573

+ +
+

""" 

+

Renderers are used to serialize a response into specific media types. 

+

 

+

They give us a generic way of being able to handle various media types 

+

on the response, such as JSON encoded data or HTML output. 

+

 

+

REST framework also provides an HTML renderer the renders the browsable API. 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

import copy 

+

import json 

+

from django import forms 

+

from django.core.exceptions import ImproperlyConfigured 

+

from django.http.multipartparser import parse_header 

+

from django.template import RequestContext, loader, Template 

+

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.settings import api_settings 

+

from rest_framework.request import clone_request 

+

from rest_framework.utils import encoders 

+

from rest_framework.utils.breadcrumbs import get_breadcrumbs 

+

from rest_framework.utils.formatting import get_view_name, get_view_description 

+

from rest_framework import exceptions, parsers, status, VERSION 

+

 

+

 

+

class BaseRenderer(object): 

+

    """ 

+

    All renderers should extend this class, setting the `media_type` 

+

    and `format` attributes, and override the `.render()` method. 

+

    """ 

+

 

+

    media_type = None 

+

    format = None 

+

    charset = 'utf-8' 

+

 

+

    def render(self, data, accepted_media_type=None, renderer_context=None): 

+

        raise NotImplemented('Renderer class requires .render() to be implemented') 

+

 

+

 

+

class JSONRenderer(BaseRenderer): 

+

    """ 

+

    Renderer which serializes to JSON. 

+

    Applies JSON's backslash-u character escaping for non-ascii characters. 

+

    """ 

+

 

+

    media_type = 'application/json' 

+

    format = 'json' 

+

    encoder_class = encoders.JSONEncoder 

+

    ensure_ascii = True 

+

    charset = 'utf-8' 

+

    # Note that JSON encodings must be utf-8, utf-16 or utf-32. 

+

    # See: http://www.ietf.org/rfc/rfc4627.txt 

+

 

+

    def render(self, data, accepted_media_type=None, renderer_context=None): 

+

        """ 

+

        Render `data` into JSON. 

+

        """ 

+

        if data is None: 

+

            return '' 

+

 

+

        # 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) 

+

 

+

        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 

+

 

+

        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, 

+

        # and may (or may not) be unicode. 

+

        # On python 3.x json.dumps() returns unicode strings. 

+

        if isinstance(ret, six.text_type): 

+

            return bytes(ret.encode(self.charset)) 

+

        return ret 

+

 

+

 

+

class UnicodeJSONRenderer(JSONRenderer): 

+

    ensure_ascii = False 

+

    charset = 'utf-8' 

+

    """ 

+

    Renderer which serializes to JSON. 

+

    Does *not* apply JSON's character escaping for non-ascii characters. 

+

    """ 

+

 

+

 

+

class JSONPRenderer(JSONRenderer): 

+

    """ 

+

    Renderer which serializes to json, 

+

    wrapping the json output in a callback function. 

+

    """ 

+

 

+

    media_type = 'application/javascript' 

+

    format = 'jsonp' 

+

    callback_parameter = 'callback' 

+

    default_callback = 'callback' 

+

 

+

    def get_callback(self, renderer_context): 

+

        """ 

+

        Determine the name of the callback to wrap around the json output. 

+

        """ 

+

        request = renderer_context.get('request', None) 

+

        params = request and request.QUERY_PARAMS or {} 

+

        return params.get(self.callback_parameter, self.default_callback) 

+

 

+

    def render(self, data, accepted_media_type=None, renderer_context=None): 

+

        """ 

+

        Renders into jsonp, wrapping the json output in a callback function. 

+

 

+

        Clients may set the callback function name using a query parameter 

+

        on the URL, for example: ?callback=exampleCallbackName 

+

        """ 

+

        renderer_context = renderer_context or {} 

+

        callback = self.get_callback(renderer_context) 

+

        json = super(JSONPRenderer, self).render(data, accepted_media_type, 

+

                                                 renderer_context) 

+

        return callback.encode(self.charset) + b'(' + json + b');' 

+

 

+

 

+

class XMLRenderer(BaseRenderer): 

+

    """ 

+

    Renderer which serializes to XML. 

+

    """ 

+

 

+

    media_type = 'application/xml' 

+

    format = 'xml' 

+

    charset = 'utf-8' 

+

 

+

    def render(self, data, accepted_media_type=None, renderer_context=None): 

+

        """ 

+

        Renders *obj* into serialized XML. 

+

        """ 

+

        if data is None: 

+

            return '' 

+

 

+

        stream = StringIO() 

+

 

+

        xml = SimplerXMLGenerator(stream, self.charset) 

+

        xml.startDocument() 

+

        xml.startElement("root", {}) 

+

 

+

        self._to_xml(xml, data) 

+

 

+

        xml.endElement("root") 

+

        xml.endDocument() 

+

        return stream.getvalue() 

+

 

+

    def _to_xml(self, xml, data): 

+

        if isinstance(data, (list, tuple)): 

+

            for item in data: 

+

                xml.startElement("list-item", {}) 

+

                self._to_xml(xml, item) 

+

                xml.endElement("list-item") 

+

 

+

        elif isinstance(data, dict): 

+

            for key, value in six.iteritems(data): 

+

                xml.startElement(key, {}) 

+

                self._to_xml(xml, value) 

+

                xml.endElement(key) 

+

 

+

        elif data is None: 

+

            # Don't output any value 

+

            pass 

+

 

+

        else: 

+

            xml.characters(smart_text(data)) 

+

 

+

 

+

class YAMLRenderer(BaseRenderer): 

+

    """ 

+

    Renderer which serializes to YAML. 

+

    """ 

+

 

+

    media_type = 'application/yaml' 

+

    format = 'yaml' 

+

    encoder = encoders.SafeDumper 

+

    charset = 'utf-8' 

+

 

+

    def render(self, data, accepted_media_type=None, renderer_context=None): 

+

        """ 

+

        Renders *obj* into serialized YAML. 

+

        """ 

+

        assert yaml, 'YAMLRenderer requires pyyaml to be installed' 

+

 

+

        if data is None: 

+

            return '' 

+

 

+

        return yaml.dump(data, stream=None, encoding=self.charset, Dumper=self.encoder) 

+

 

+

 

+

class TemplateHTMLRenderer(BaseRenderer): 

+

    """ 

+

    An HTML renderer for use with templates. 

+

 

+

    The data supplied to the Response object should be a dictionary that will 

+

    be used as context for the template. 

+

 

+

    The template name is determined by (in order of preference): 

+

 

+

    1. An explicit `.template_name` attribute set on the response. 

+

    2. An explicit `.template_name` attribute set on this class. 

+

    3. The return result of calling `view.get_template_names()`. 

+

 

+

    For example: 

+

        data = {'users': User.objects.all()} 

+

        return Response(data, template_name='users.html') 

+

 

+

    For pre-rendered HTML, see StaticHTMLRenderer. 

+

    """ 

+

 

+

    media_type = 'text/html' 

+

    format = 'html' 

+

    template_name = None 

+

    exception_template_names = [ 

+

        '%(status_code)s.html', 

+

        'api_exception.html' 

+

    ] 

+

    charset = 'utf-8' 

+

 

+

    def render(self, data, accepted_media_type=None, renderer_context=None): 

+

        """ 

+

        Renders data to HTML, using Django's standard template rendering. 

+

 

+

        The template name is determined by (in order of preference): 

+

 

+

        1. An explicit .template_name set on the response. 

+

        2. An explicit .template_name set on this class. 

+

        3. The return result of calling view.get_template_names(). 

+

        """ 

+

        renderer_context = renderer_context or {} 

+

        view = renderer_context['view'] 

+

        request = renderer_context['request'] 

+

        response = renderer_context['response'] 

+

 

+

        if response.exception: 

+

            template = self.get_exception_template(response) 

+

        else: 

+

            template_names = self.get_template_names(response, view) 

+

            template = self.resolve_template(template_names) 

+

 

+

        context = self.resolve_context(data, request, response) 

+

        return template.render(context) 

+

 

+

    def resolve_template(self, template_names): 

+

        return loader.select_template(template_names) 

+

 

+

    def resolve_context(self, data, request, response): 

+

        if response.exception: 

+

            data['status_code'] = response.status_code 

+

        return RequestContext(request, data) 

+

 

+

    def get_template_names(self, response, view): 

+

        if response.template_name: 

+

            return [response.template_name] 

+

        elif self.template_name: 

+

            return [self.template_name] 

+

        elif hasattr(view, 'get_template_names'): 

+

            return view.get_template_names() 

+

        raise ImproperlyConfigured('Returned a template response with no template_name') 

+

 

+

    def get_exception_template(self, response): 

+

        template_names = [name % {'status_code': response.status_code} 

+

                          for name in self.exception_template_names] 

+

 

+

        try: 

+

            # Try to find an appropriate error template 

+

            return self.resolve_template(template_names) 

+

        except Exception: 

+

            # Fall back to using eg '404 Not Found' 

+

            return Template('%d %s' % (response.status_code, 

+

                                       response.status_text.title())) 

+

 

+

 

+

# Note, subclass TemplateHTMLRenderer simply for the exception behavior 

+

class StaticHTMLRenderer(TemplateHTMLRenderer): 

+

    """ 

+

    An HTML renderer class that simply returns pre-rendered HTML. 

+

 

+

    The data supplied to the Response object should be a string representing 

+

    the pre-rendered HTML content. 

+

 

+

    For example: 

+

        data = '<html><body>example</body></html>' 

+

        return Response(data) 

+

 

+

    For template rendered HTML, see TemplateHTMLRenderer. 

+

    """ 

+

    media_type = 'text/html' 

+

    format = 'html' 

+

    charset = 'utf-8' 

+

 

+

    def render(self, data, accepted_media_type=None, renderer_context=None): 

+

        renderer_context = renderer_context or {} 

+

        response = renderer_context['response'] 

+

 

+

        if response and response.exception: 

+

            request = renderer_context['request'] 

+

            template = self.get_exception_template(response) 

+

            context = self.resolve_context(data, request, response) 

+

            return template.render(context) 

+

 

+

        return data 

+

 

+

 

+

class BrowsableAPIRenderer(BaseRenderer): 

+

    """ 

+

    HTML renderer used to self-document the API. 

+

    """ 

+

    media_type = 'text/html' 

+

    format = 'api' 

+

    template = 'rest_framework/api.html' 

+

    charset = 'utf-8' 

+

 

+

    def get_default_renderer(self, view): 

+

        """ 

+

        Return an instance of the first valid renderer. 

+

        (Don't use another documenting renderer.) 

+

        """ 

+

        renderers = [renderer for renderer in view.renderer_classes 

+

                     if not issubclass(renderer, BrowsableAPIRenderer)] 

+

        if not renderers: 

+

            return None 

+

        return renderers[0]() 

+

 

+

    def get_content(self, renderer, data, 

+

                    accepted_media_type, renderer_context): 

+

        """ 

+

        Get the content as if it had been rendered by the default 

+

        non-documenting renderer. 

+

        """ 

+

        if not renderer: 

+

            return '[No renderers were found]' 

+

 

+

        renderer_context['indent'] = 4 

+

        content = renderer.render(data, accepted_media_type, renderer_context) 

+

 

+

        if renderer.charset is None: 

+

            return '[%d bytes of binary content]' % len(content) 

+

 

+

        return content 

+

 

+

    def show_form_for_method(self, view, method, request, obj): 

+

        """ 

+

        Returns True if a form should be shown for this method. 

+

        """ 

+

        if not method in view.allowed_methods: 

+

            return  # Not a valid method 

+

 

+

        if not api_settings.FORM_METHOD_OVERRIDE: 

+

            return  # Cannot use form overloading 

+

 

+

        try: 

+

            view.check_permissions(request) 

+

            if obj is not None: 

+

                view.check_object_permissions(request, obj) 

+

        except exceptions.APIException: 

+

            return False  # Doesn't have permissions 

+

        return True 

+

 

+

    def serializer_to_form_fields(self, serializer): 

+

        fields = {} 

+

        for k, v in serializer.get_fields().items(): 

+

            if getattr(v, 'read_only', True): 

+

                continue 

+

 

+

            kwargs = {} 

+

            kwargs['required'] = v.required 

+

 

+

            #if getattr(v, 'queryset', None): 

+

            #    kwargs['queryset'] = v.queryset 

+

 

+

            if getattr(v, 'choices', None) is not None: 

+

                kwargs['choices'] = v.choices 

+

 

+

            if getattr(v, 'regex', None) is not None: 

+

                kwargs['regex'] = v.regex 

+

 

+

            if getattr(v, 'widget', None): 

+

                widget = copy.deepcopy(v.widget) 

+

                kwargs['widget'] = widget 

+

 

+

            if getattr(v, 'default', None) is not None: 

+

                kwargs['initial'] = v.default 

+

 

+

            if getattr(v, 'label', None) is not None: 

+

                kwargs['label'] = v.label 

+

 

+

            if getattr(v, 'help_text', None) is not None: 

+

                kwargs['help_text'] = v.help_text 

+

 

+

            fields[k] = v.form_field_class(**kwargs) 

+

 

+

        return fields 

+

 

+

    def _get_form(self, view, method, request): 

+

        # We need to impersonate a request with the correct method, 

+

        # so that eg. any dynamic get_serializer_class methods return the 

+

        # correct form for each method. 

+

        restore = view.request 

+

        request = clone_request(request, method) 

+

        view.request = request 

+

        try: 

+

            return self.get_form(view, method, request) 

+

        finally: 

+

            view.request = restore 

+

 

+

    def _get_raw_data_form(self, view, method, request, media_types): 

+

        # We need to impersonate a request with the correct method, 

+

        # so that eg. any dynamic get_serializer_class methods return the 

+

        # correct form for each method. 

+

        restore = view.request 

+

        request = clone_request(request, method) 

+

        view.request = request 

+

        try: 

+

            return self.get_raw_data_form(view, method, request, media_types) 

+

        finally: 

+

            view.request = restore 

+

 

+

    def get_form(self, view, method, request): 

+

        """ 

+

        Get a form, possibly bound to either the input or output data. 

+

        In the absence on of the Resource having an associated form then 

+

        provide a form that can be used to submit arbitrary content. 

+

        """ 

+

        obj = getattr(view, 'object', None) 

+

        if not self.show_form_for_method(view, method, request, obj): 

+

            return 

+

 

+

        if method in ('DELETE', 'OPTIONS'): 

+

            return True  # Don't actually need to return a form 

+

 

+

        if not getattr(view, 'get_serializer', None) or not parsers.FormParser in view.parser_classes: 

+

            return 

+

 

+

        serializer = view.get_serializer(instance=obj) 

+

        fields = self.serializer_to_form_fields(serializer) 

+

 

+

        # Creating an on the fly form see: 

+

        # http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python 

+

        OnTheFlyForm = type(str("OnTheFlyForm"), (forms.Form,), fields) 

+

        data = (obj is not None) and serializer.data or None 

+

        form_instance = OnTheFlyForm(data) 

+

        return form_instance 

+

 

+

    def get_raw_data_form(self, view, method, request, media_types): 

+

        """ 

+

        Returns a form that allows for arbitrary content types to be tunneled 

+

        via standard HTML forms. 

+

        (Which are typically application/x-www-form-urlencoded) 

+

        """ 

+

 

+

        # If we're not using content overloading there's no point in supplying a generic form, 

+

        # as the view won't treat the form's value as the content of the request. 

+

        if not (api_settings.FORM_CONTENT_OVERRIDE 

+

                and api_settings.FORM_CONTENTTYPE_OVERRIDE): 

+

            return None 

+

 

+

        # Check permissions 

+

        obj = getattr(view, 'object', None) 

+

        if not self.show_form_for_method(view, method, request, obj): 

+

            return 

+

 

+

        content_type_field = api_settings.FORM_CONTENTTYPE_OVERRIDE 

+

        content_field = api_settings.FORM_CONTENT_OVERRIDE 

+

        choices = [(media_type, media_type) for media_type in media_types] 

+

        initial = media_types[0] 

+

 

+

        # NB. http://jacobian.org/writing/dynamic-form-generation/ 

+

        class GenericContentForm(forms.Form): 

+

            def __init__(self): 

+

                super(GenericContentForm, self).__init__() 

+

 

+

                self.fields[content_type_field] = forms.ChoiceField( 

+

                    label='Media type', 

+

                    choices=choices, 

+

                    initial=initial 

+

                ) 

+

                self.fields[content_field] = forms.CharField( 

+

                    label='Content', 

+

                    widget=forms.Textarea 

+

                ) 

+

 

+

        return GenericContentForm() 

+

 

+

    def get_name(self, view): 

+

        return get_view_name(view.__class__, getattr(view, 'suffix', None)) 

+

 

+

    def get_description(self, view): 

+

        return get_view_description(view.__class__, html=True) 

+

 

+

    def get_breadcrumbs(self, request): 

+

        return get_breadcrumbs(request.path) 

+

 

+

    def render(self, data, accepted_media_type=None, renderer_context=None): 

+

        """ 

+

        Render the HTML for the browsable API representation. 

+

        """ 

+

        accepted_media_type = accepted_media_type or '' 

+

        renderer_context = renderer_context or {} 

+

 

+

        view = renderer_context['view'] 

+

        request = renderer_context['request'] 

+

        response = renderer_context['response'] 

+

        media_types = [parser.media_type for parser in view.parser_classes] 

+

 

+

        renderer = self.get_default_renderer(view) 

+

        content = self.get_content(renderer, data, accepted_media_type, renderer_context) 

+

 

+

        put_form = self._get_form(view, 'PUT', request) 

+

        post_form = self._get_form(view, 'POST', request) 

+

        patch_form = self._get_form(view, 'PATCH', request) 

+

        delete_form = self._get_form(view, 'DELETE', request) 

+

        options_form = self._get_form(view, 'OPTIONS', request) 

+

 

+

        raw_data_put_form = self._get_raw_data_form(view, 'PUT', request, media_types) 

+

        raw_data_post_form = self._get_raw_data_form(view, 'POST', request, media_types) 

+

        raw_data_patch_form = self._get_raw_data_form(view, 'PATCH', request, media_types) 

+

        raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form 

+

 

+

        name = self.get_name(view) 

+

        description = self.get_description(view) 

+

        breadcrumb_list = self.get_breadcrumbs(request) 

+

 

+

        template = loader.get_template(self.template) 

+

        context = RequestContext(request, { 

+

            'content': content, 

+

            'view': view, 

+

            'request': request, 

+

            'response': response, 

+

            'description': description, 

+

            'name': name, 

+

            'version': VERSION, 

+

            'breadcrumblist': breadcrumb_list, 

+

            'allowed_methods': view.allowed_methods, 

+

            'available_formats': [renderer.format for renderer in view.renderer_classes], 

+

 

+

            'put_form': put_form, 

+

            'post_form': post_form, 

+

            'patch_form': patch_form, 

+

            'delete_form': delete_form, 

+

            'options_form': options_form, 

+

 

+

            'raw_data_put_form': raw_data_put_form, 

+

            'raw_data_post_form': raw_data_post_form, 

+

            'raw_data_patch_form': raw_data_patch_form, 

+

            'raw_data_put_or_patch_form': raw_data_put_or_patch_form, 

+

 

+

            'api_settings': api_settings 

+

        }) 

+

 

+

        ret = template.render(context) 

+

 

+

        # Munge DELETE Response code to allow us to return content 

+

        # (Do this *after* we've rendered the template so that we include 

+

        # the normal deletion response code in the output) 

+

        if response.status_code == status.HTTP_204_NO_CONTENT: 

+

            response.status_code = status.HTTP_200_OK 

+

 

+

        return ret 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_request.html b/htmlcov/rest_framework_request.html new file mode 100644 index 00000000..03f2c3e3 --- /dev/null +++ b/htmlcov/rest_framework_request.html @@ -0,0 +1,819 @@ + + + + + + + + Coverage for rest_framework/request: 95% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+

344

+

345

+

346

+

347

+

348

+

349

+

350

+

351

+

352

+

353

+

354

+

355

+

356

+

357

+

358

+

359

+

360

+

361

+

362

+

363

+

364

+

365

+

366

+

367

+

368

+

369

+ +
+

""" 

+

The Request class is used as a wrapper around the standard request object. 

+

 

+

The wrapped request then offers a richer API, in particular : 

+

 

+

    - content automatically parsed according to `Content-Type` header, 

+

      and available as `request.DATA` 

+

    - full support of PUT method, including support for file uploads 

+

    - form overloading of HTTP method, content type and content 

+

""" 

+

from __future__ import unicode_literals 

+

from django.conf import settings 

+

from django.http import QueryDict 

+

from django.http.multipartparser import parse_header 

+

from django.utils.datastructures import MultiValueDict 

+

from rest_framework import HTTP_HEADER_ENCODING 

+

from rest_framework import exceptions 

+

from rest_framework.compat import BytesIO 

+

from rest_framework.settings import api_settings 

+

 

+

 

+

def is_form_media_type(media_type): 

+

    """ 

+

    Return True if the media type is a valid form media type. 

+

    """ 

+

    base_media_type, params = parse_header(media_type.encode(HTTP_HEADER_ENCODING)) 

+

    return (base_media_type == 'application/x-www-form-urlencoded' or 

+

            base_media_type == 'multipart/form-data') 

+

 

+

 

+

class Empty(object): 

+

    """ 

+

    Placeholder for unset attributes. 

+

    Cannot use `None`, as that may be a valid value. 

+

    """ 

+

    pass 

+

 

+

 

+

def _hasattr(obj, name): 

+

    return not getattr(obj, name) is Empty 

+

 

+

 

+

def clone_request(request, method): 

+

    """ 

+

    Internal helper method to clone a request, replacing with a different 

+

    HTTP method.  Used for checking permissions against other methods. 

+

    """ 

+

    ret = Request(request=request._request, 

+

                  parsers=request.parsers, 

+

                  authenticators=request.authenticators, 

+

                  negotiator=request.negotiator, 

+

                  parser_context=request.parser_context) 

+

    ret._data = request._data 

+

    ret._files = request._files 

+

    ret._content_type = request._content_type 

+

    ret._stream = request._stream 

+

    ret._method = method 

+

    if hasattr(request, '_user'): 

+

        ret._user = request._user 

+

    if hasattr(request, '_auth'): 

+

        ret._auth = request._auth 

+

    if hasattr(request, '_authenticator'): 

+

        ret._authenticator = request._authenticator 

+

    return ret 

+

 

+

 

+

class Request(object): 

+

    """ 

+

    Wrapper allowing to enhance a standard `HttpRequest` instance. 

+

 

+

    Kwargs: 

+

        - request(HttpRequest). The original request instance. 

+

        - parsers_classes(list/tuple). The parsers to use for parsing the 

+

          request content. 

+

        - authentication_classes(list/tuple). The authentications used to try 

+

          authenticating the request's user. 

+

    """ 

+

 

+

    _METHOD_PARAM = api_settings.FORM_METHOD_OVERRIDE 

+

    _CONTENT_PARAM = api_settings.FORM_CONTENT_OVERRIDE 

+

    _CONTENTTYPE_PARAM = api_settings.FORM_CONTENTTYPE_OVERRIDE 

+

 

+

    def __init__(self, request, parsers=None, authenticators=None, 

+

                 negotiator=None, parser_context=None): 

+

        self._request = request 

+

        self.parsers = parsers or () 

+

        self.authenticators = authenticators or () 

+

        self.negotiator = negotiator or self._default_negotiator() 

+

        self.parser_context = parser_context 

+

        self._data = Empty 

+

        self._files = Empty 

+

        self._method = Empty 

+

        self._content_type = Empty 

+

        self._stream = Empty 

+

 

+

        if self.parser_context is None: 

+

            self.parser_context = {} 

+

        self.parser_context['request'] = self 

+

        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET 

+

 

+

    def _default_negotiator(self): 

+

        return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS() 

+

 

+

    @property 

+

    def method(self): 

+

        """ 

+

        Returns the HTTP method. 

+

 

+

        This allows the `method` to be overridden by using a hidden `form` 

+

        field on a form POST request. 

+

        """ 

+

        if not _hasattr(self, '_method'): 

+

            self._load_method_and_content_type() 

+

        return self._method 

+

 

+

    @property 

+

    def content_type(self): 

+

        """ 

+

        Returns the content type header. 

+

 

+

        This should be used instead of `request.META.get('HTTP_CONTENT_TYPE')`, 

+

        as it allows the content type to be overridden by using a hidden form 

+

        field on a form POST request. 

+

        """ 

+

        if not _hasattr(self, '_content_type'): 

+

            self._load_method_and_content_type() 

+

        return self._content_type 

+

 

+

    @property 

+

    def stream(self): 

+

        """ 

+

        Returns an object that may be used to stream the request content. 

+

        """ 

+

        if not _hasattr(self, '_stream'): 

+

            self._load_stream() 

+

        return self._stream 

+

 

+

    @property 

+

    def QUERY_PARAMS(self): 

+

        """ 

+

        More semantically correct name for request.GET. 

+

        """ 

+

        return self._request.GET 

+

 

+

    @property 

+

    def DATA(self): 

+

        """ 

+

        Parses the request body and returns the data. 

+

 

+

        Similar to usual behaviour of `request.POST`, except that it handles 

+

        arbitrary parsers, and also works on methods other than POST (eg PUT). 

+

        """ 

+

        if not _hasattr(self, '_data'): 

+

            self._load_data_and_files() 

+

        return self._data 

+

 

+

    @property 

+

    def FILES(self): 

+

        """ 

+

        Parses the request body and returns any files uploaded in the request. 

+

 

+

        Similar to usual behaviour of `request.FILES`, except that it handles 

+

        arbitrary parsers, and also works on methods other than POST (eg PUT). 

+

        """ 

+

        if not _hasattr(self, '_files'): 

+

            self._load_data_and_files() 

+

        return self._files 

+

 

+

    @property 

+

    def user(self): 

+

        """ 

+

        Returns the user associated with the current request, as authenticated 

+

        by the authentication classes provided to the request. 

+

        """ 

+

        if not hasattr(self, '_user'): 

+

            self._authenticate() 

+

        return self._user 

+

 

+

    @user.setter 

+

    def user(self, value): 

+

        """ 

+

        Sets the user on the current request. This is necessary to maintain 

+

        compatilbility with django.contrib.auth where the user proprety is 

+

        set in the login and logout functions. 

+

        """ 

+

        self._user = value 

+

 

+

    @property 

+

    def auth(self): 

+

        """ 

+

        Returns any non-user authentication information associated with the 

+

        request, such as an authentication token. 

+

        """ 

+

        if not hasattr(self, '_auth'): 

+

            self._authenticate() 

+

        return self._auth 

+

 

+

    @auth.setter 

+

    def auth(self, value): 

+

        """ 

+

        Sets any non-user authentication information associated with the 

+

        request, such as an authentication token. 

+

        """ 

+

        self._auth = value 

+

 

+

    @property 

+

    def successful_authenticator(self): 

+

        """ 

+

        Return the instance of the authentication instance class that was used 

+

        to authenticate the request, or `None`. 

+

        """ 

+

        if not hasattr(self, '_authenticator'): 

+

            self._authenticate() 

+

        return self._authenticator 

+

 

+

    def _load_data_and_files(self): 

+

        """ 

+

        Parses the request content into self.DATA and self.FILES. 

+

        """ 

+

        if not _hasattr(self, '_content_type'): 

+

            self._load_method_and_content_type() 

+

 

+

        if not _hasattr(self, '_data'): 

+

            self._data, self._files = self._parse() 

+

 

+

    def _load_method_and_content_type(self): 

+

        """ 

+

        Sets the method and content_type, and then check if they've 

+

        been overridden. 

+

        """ 

+

        self._content_type = self.META.get('HTTP_CONTENT_TYPE', 

+

                                           self.META.get('CONTENT_TYPE', '')) 

+

 

+

        self._perform_form_overloading() 

+

 

+

        if not _hasattr(self, '_method'): 

+

            self._method = self._request.method 

+

 

+

            if self._method == 'POST': 

+

                # Allow X-HTTP-METHOD-OVERRIDE header 

+

                self._method = self.META.get('HTTP_X_HTTP_METHOD_OVERRIDE', 

+

                                             self._method) 

+

 

+

    def _load_stream(self): 

+

        """ 

+

        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'))) 

+

        except (ValueError, TypeError): 

+

            content_length = 0 

+

 

+

        if content_length == 0: 

+

            self._stream = None 

+

        elif hasattr(self._request, 'read'): 

+

            self._stream = self._request 

+

        else: 

+

            self._stream = BytesIO(self.raw_post_data) 

+

 

+

    def _perform_form_overloading(self): 

+

        """ 

+

        If this is a form POST request, then we need to check if the method and 

+

        content/content_type have been overridden by setting them in hidden 

+

        form fields or not. 

+

        """ 

+

 

+

        USE_FORM_OVERLOADING = ( 

+

            self._METHOD_PARAM or 

+

            (self._CONTENT_PARAM and self._CONTENTTYPE_PARAM) 

+

        ) 

+

 

+

        # We only need to use form overloading on form POST requests. 

+

        if (not USE_FORM_OVERLOADING 

+

            or self._request.method != 'POST' 

+

            or not is_form_media_type(self._content_type)): 

+

            return 

+

 

+

        # At this point we're committed to parsing the request as form data. 

+

        self._data = self._request.POST 

+

        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): 

+

            self._method = self._data[self._METHOD_PARAM].upper() 

+

 

+

        # Content overloading - modify the content type, and force re-parse. 

+

        if (self._CONTENT_PARAM and 

+

            self._CONTENTTYPE_PARAM and 

+

            self._CONTENT_PARAM in self._data and 

+

            self._CONTENTTYPE_PARAM in self._data): 

+

            self._content_type = self._data[self._CONTENTTYPE_PARAM] 

+

            self._stream = BytesIO(self._data[self._CONTENT_PARAM].encode(HTTP_HEADER_ENCODING)) 

+

            self._data, self._files = (Empty, Empty) 

+

 

+

    def _parse(self): 

+

        """ 

+

        Parse the request content, returning a two-tuple of (data, files) 

+

 

+

        May raise an `UnsupportedMediaType`, or `ParseError` exception. 

+

        """ 

+

        stream = self.stream 

+

        media_type = self.content_type 

+

 

+

        if stream is None or media_type is None: 

+

            empty_data = QueryDict('', self._request._encoding) 

+

            empty_files = MultiValueDict() 

+

            return (empty_data, empty_files) 

+

 

+

        parser = self.negotiator.select_parser(self, self.parsers) 

+

 

+

        if not parser: 

+

            raise exceptions.UnsupportedMediaType(media_type) 

+

 

+

        parsed = parser.parse(stream, media_type, self.parser_context) 

+

 

+

        # Parser classes may return the raw data, or a 

+

        # DataAndFiles object.  Unpack the result as required. 

+

        try: 

+

            return (parsed.data, parsed.files) 

+

        except AttributeError: 

+

            empty_files = MultiValueDict() 

+

            return (parsed, empty_files) 

+

 

+

    def _authenticate(self): 

+

        """ 

+

        Attempt to authenticate the request using each authentication instance 

+

        in turn. 

+

        Returns a three-tuple of (authenticator, user, authtoken). 

+

        """ 

+

        for authenticator in self.authenticators: 

+

            try: 

+

                user_auth_tuple = authenticator.authenticate(self) 

+

            except exceptions.APIException: 

+

                self._not_authenticated() 

+

                raise 

+

 

+

            if not user_auth_tuple is None: 

+

                self._authenticator = authenticator 

+

                self._user, self._auth = user_auth_tuple 

+

                return 

+

 

+

        self._not_authenticated() 

+

 

+

    def _not_authenticated(self): 

+

        """ 

+

        Return a three-tuple of (authenticator, user, authtoken), representing 

+

        an unauthenticated request. 

+

 

+

        By default this will be (None, AnonymousUser, None). 

+

        """ 

+

        self._authenticator = None 

+

 

+

        if api_settings.UNAUTHENTICATED_USER: 

+

            self._user = api_settings.UNAUTHENTICATED_USER() 

+

        else: 

+

            self._user = None 

+

 

+

        if api_settings.UNAUTHENTICATED_TOKEN: 

+

            self._auth = api_settings.UNAUTHENTICATED_TOKEN() 

+

        else: 

+

            self._auth = None 

+

 

+

    def __getattr__(self, attr): 

+

        """ 

+

        Proxy other attributes to the underlying HttpRequest object. 

+

        """ 

+

        return getattr(self._request, attr) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_response.html b/htmlcov/rest_framework_response.html new file mode 100644 index 00000000..d297ecd0 --- /dev/null +++ b/htmlcov/rest_framework_response.html @@ -0,0 +1,249 @@ + + + + + + + + Coverage for rest_framework/response: 98% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+ +
+

""" 

+

The Response class in REST framework is similar to HTTPResponse, except that 

+

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 

+

from django.core.handlers.wsgi import STATUS_CODE_TEXT 

+

from django.template.response import SimpleTemplateResponse 

+

from rest_framework.compat import six 

+

 

+

 

+

class Response(SimpleTemplateResponse): 

+

    """ 

+

    An HttpResponse that allows its data to be rendered into 

+

    arbitrary media types. 

+

    """ 

+

 

+

    def __init__(self, data=None, status=200, 

+

                 template_name=None, headers=None, 

+

                 exception=False, content_type=None): 

+

        """ 

+

        Alters the init arguments slightly. 

+

        For example, drop 'template_name', and instead use 'data'. 

+

 

+

        Setting 'renderer' and 'media_type' will typically be deferred, 

+

        For example being set automatically by the `APIView`. 

+

        """ 

+

        super(Response, self).__init__(None, status=status) 

+

        self.data = data 

+

        self.template_name = template_name 

+

        self.exception = exception 

+

        self.content_type = content_type 

+

 

+

        if headers: 

+

            for name, value in six.iteritems(headers): 

+

                self[name] = value 

+

 

+

    @property 

+

    def rendered_content(self): 

+

        renderer = getattr(self, 'accepted_renderer', None) 

+

        media_type = getattr(self, 'accepted_media_type', None) 

+

        context = getattr(self, 'renderer_context', None) 

+

 

+

        assert renderer, ".accepted_renderer not set on Response" 

+

        assert media_type, ".accepted_media_type not set on Response" 

+

        assert context, ".renderer_context not set on Response" 

+

        context['response'] = self 

+

 

+

        charset = renderer.charset 

+

        content_type = self.content_type 

+

 

+

        if content_type is None and charset is not None: 

+

            content_type = "{0}; charset={1}".format(media_type, charset) 

+

        elif content_type is None: 

+

            content_type = media_type 

+

        self['Content-Type'] = content_type 

+

 

+

        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.' 

+

            return bytes(ret.encode(charset)) 

+

        return ret 

+

 

+

    @property 

+

    def status_text(self): 

+

        """ 

+

        Returns reason text corresponding to our HTTP response status code. 

+

        Provided for convenience. 

+

        """ 

+

        # TODO: Deprecate and use a template tag instead 

+

        # TODO: Status code text for RFC 6585 status codes 

+

        return STATUS_CODE_TEXT.get(self.status_code, '') 

+

 

+

    def __getstate__(self): 

+

        """ 

+

        Remove attributes from the response that shouldn't be cached 

+

        """ 

+

        state = super(Response, self).__getstate__() 

+

        for key in ('accepted_renderer', 'renderer_context', 'data'): 

+

            if key in state: 

+

                del state[key] 

+

        return state 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_reverse.html b/htmlcov/rest_framework_reverse.html new file mode 100644 index 00000000..4e7a8de2 --- /dev/null +++ b/htmlcov/rest_framework_reverse.html @@ -0,0 +1,127 @@ + + + + + + + + Coverage for rest_framework/reverse: 75% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+ +
+

""" 

+

Provide reverse functions that return fully qualified URLs 

+

""" 

+

from __future__ import unicode_literals 

+

from django.core.urlresolvers import reverse as django_reverse 

+

from django.utils.functional import lazy 

+

 

+

 

+

def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra): 

+

    """ 

+

    Same as `django.core.urlresolvers.reverse`, but optionally takes a request 

+

    and returns a fully qualified URL, using the request to get the base URL. 

+

    """ 

+

    if format is not None: 

+

        kwargs = kwargs or {} 

+

        kwargs['format'] = format 

+

    url = django_reverse(viewname, args=args, kwargs=kwargs, **extra) 

+

    if request: 

+

        return request.build_absolute_uri(url) 

+

    return url 

+

 

+

 

+

reverse_lazy = lazy(reverse, str) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_routers.html b/htmlcov/rest_framework_routers.html new file mode 100644 index 00000000..f08d5007 --- /dev/null +++ b/htmlcov/rest_framework_routers.html @@ -0,0 +1,595 @@ + + + + + + + + Coverage for rest_framework/routers: 94% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+ +
+

""" 

+

Routers provide a convenient and consistent way of automatically 

+

determining the URL conf for your API. 

+

 

+

They are used by simply instantiating a Router class, and then registering 

+

all the required ViewSets with that router. 

+

 

+

For example, you might have a `urls.py` that looks something like this: 

+

 

+

    router = routers.DefaultRouter() 

+

    router.register('users', UserViewSet, 'user') 

+

    router.register('accounts', AccountViewSet, 'account') 

+

 

+

    urlpatterns = router.urls 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

from collections import namedtuple 

+

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 

+

 

+

 

+

Route = namedtuple('Route', ['url', 'mapping', 'name', 'initkwargs']) 

+

 

+

 

+

def replace_methodname(format_string, methodname): 

+

    """ 

+

    Partially format a format_string, swapping out any 

+

    '{methodname}' or '{methodnamehyphen}' components. 

+

    """ 

+

    methodnamehyphen = methodname.replace('_', '-') 

+

    ret = format_string 

+

    ret = ret.replace('{methodname}', methodname) 

+

    ret = ret.replace('{methodnamehyphen}', methodnamehyphen) 

+

    return ret 

+

 

+

 

+

class BaseRouter(object): 

+

    def __init__(self): 

+

        self.registry = [] 

+

 

+

    def register(self, prefix, viewset, base_name=None): 

+

        if base_name is None: 

+

            base_name = self.get_default_base_name(viewset) 

+

        self.registry.append((prefix, viewset, base_name)) 

+

 

+

    def get_default_base_name(self, viewset): 

+

        """ 

+

        If `base_name` is not specified, attempt to automatically determine 

+

        it from the viewset. 

+

        """ 

+

        raise NotImplemented('get_default_base_name must be overridden') 

+

 

+

    def get_urls(self): 

+

        """ 

+

        Return a list of URL patterns, given the registered viewsets. 

+

        """ 

+

        raise NotImplemented('get_urls must be overridden') 

+

 

+

    @property 

+

    def urls(self): 

+

        if not hasattr(self, '_urls'): 

+

            self._urls = patterns('', *self.get_urls()) 

+

        return self._urls 

+

 

+

 

+

class SimpleRouter(BaseRouter): 

+

    routes = [ 

+

        # List route. 

+

        Route( 

+

            url=r'^{prefix}{trailing_slash}$', 

+

            mapping={ 

+

                'get': 'list', 

+

                'post': 'create' 

+

            }, 

+

            name='{basename}-list', 

+

            initkwargs={'suffix': 'List'} 

+

        ), 

+

        # Detail route. 

+

        Route( 

+

            url=r'^{prefix}/{lookup}{trailing_slash}$', 

+

            mapping={ 

+

                'get': 'retrieve', 

+

                'put': 'update', 

+

                'patch': 'partial_update', 

+

                'delete': 'destroy' 

+

            }, 

+

            name='{basename}-detail', 

+

            initkwargs={'suffix': 'Instance'} 

+

        ), 

+

        # Dynamically generated routes. 

+

        # Generated using @action or @link decorators on methods of the viewset. 

+

        Route( 

+

            url=r'^{prefix}/{lookup}/{methodname}{trailing_slash}$', 

+

            mapping={ 

+

                '{httpmethod}': '{methodname}', 

+

            }, 

+

            name='{basename}-{methodnamehyphen}', 

+

            initkwargs={} 

+

        ), 

+

    ] 

+

 

+

    def __init__(self, trailing_slash=True): 

+

        self.trailing_slash = trailing_slash and '/' or '' 

+

        super(SimpleRouter, self).__init__() 

+

 

+

    def get_default_base_name(self, viewset): 

+

        """ 

+

        If `base_name` is not specified, attempt to automatically determine 

+

        it from the viewset. 

+

        """ 

+

        model_cls = getattr(viewset, 'model', None) 

+

        queryset = getattr(viewset, 'queryset', None) 

+

        if model_cls is None and queryset is not None: 

+

            model_cls = queryset.model 

+

 

+

        assert model_cls, '`name` not argument not specified, and could ' \ 

+

            'not automatically determine the name from the viewset, as ' \ 

+

            'it does not have a `.model` or `.queryset` attribute.' 

+

 

+

        return model_cls._meta.object_name.lower() 

+

 

+

    def get_routes(self, viewset): 

+

        """ 

+

        Augment `self.routes` with any dynamically generated routes. 

+

 

+

        Returns a list of the Route namedtuple. 

+

        """ 

+

 

+

        # Determine any `@action` or `@link` decorated methods on the viewset 

+

        dynamic_routes = [] 

+

        for methodname in dir(viewset): 

+

            attr = getattr(viewset, methodname) 

+

            httpmethods = getattr(attr, 'bind_to_methods', None) 

+

            if httpmethods: 

+

                dynamic_routes.append((httpmethods, methodname)) 

+

 

+

        ret = [] 

+

        for route in self.routes: 

+

            if route.mapping == {'{httpmethod}': '{methodname}'}: 

+

                # 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( 

+

                        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) 

+

 

+

        return ret 

+

 

+

    def get_method_map(self, viewset, method_map): 

+

        """ 

+

        Given a viewset, and a mapping of http methods to actions, 

+

        return a new mapping which only includes any mappings that 

+

        are actually implemented by the viewset. 

+

        """ 

+

        bound_methods = {} 

+

        for method, action in method_map.items(): 

+

            if hasattr(viewset, action): 

+

                bound_methods[method] = action 

+

        return bound_methods 

+

 

+

    def get_lookup_regex(self, viewset): 

+

        """ 

+

        Given a viewset, return the portion of URL regex that is used 

+

        to match against a single instance. 

+

        """ 

+

        base_regex = '(?P<{lookup_field}>[^/]+)' 

+

        lookup_field = getattr(viewset, 'lookup_field', 'pk') 

+

        return base_regex.format(lookup_field=lookup_field) 

+

 

+

    def get_urls(self): 

+

        """ 

+

        Use the registered viewsets to generate a list of URL patterns. 

+

        """ 

+

        ret = [] 

+

 

+

        for prefix, viewset, basename in self.registry: 

+

            lookup = self.get_lookup_regex(viewset) 

+

            routes = self.get_routes(viewset) 

+

 

+

            for route in routes: 

+

 

+

                # Only actions which actually exist on the viewset will be bound 

+

                mapping = self.get_method_map(viewset, route.mapping) 

+

                if not mapping: 

+

                    continue 

+

 

+

                # Build the url pattern 

+

                regex = route.url.format( 

+

                    prefix=prefix, 

+

                    lookup=lookup, 

+

                    trailing_slash=self.trailing_slash 

+

                ) 

+

                view = viewset.as_view(mapping, **route.initkwargs) 

+

                name = route.name.format(basename=basename) 

+

                ret.append(url(regex, view, name=name)) 

+

 

+

        return ret 

+

 

+

 

+

class DefaultRouter(SimpleRouter): 

+

    """ 

+

    The default router extends the SimpleRouter, but also adds in a default 

+

    API root view, and adds format suffix patterns to the URLs. 

+

    """ 

+

    include_root_view = True 

+

    include_format_suffixes = True 

+

    root_view_name = 'api-root' 

+

 

+

    def get_api_root_view(self): 

+

        """ 

+

        Return a view to use as the API root. 

+

        """ 

+

        api_root_dict = {} 

+

        list_name = self.routes[0].name 

+

        for prefix, viewset, basename in self.registry: 

+

            api_root_dict[prefix] = list_name.format(basename=basename) 

+

 

+

        class APIRoot(views.APIView): 

+

            _ignore_model_permissions = True 

+

 

+

            def get(self, request, format=None): 

+

                ret = {} 

+

                for key, url_name in api_root_dict.items(): 

+

                    ret[key] = reverse(url_name, request=request, format=format) 

+

                return Response(ret) 

+

 

+

        return APIRoot.as_view() 

+

 

+

    def get_urls(self): 

+

        """ 

+

        Generate the list of URL patterns, including a default root view 

+

        for the API, and appending `.json` style format suffixes. 

+

        """ 

+

        urls = [] 

+

 

+

        if self.include_root_view: 

+

            root_url = url(r'^$', self.get_api_root_view(), name=self.root_view_name) 

+

            urls.append(root_url) 

+

 

+

        default_urls = super(DefaultRouter, self).get_urls() 

+

        urls.extend(default_urls) 

+

 

+

        if self.include_format_suffixes: 

+

            urls = format_suffix_patterns(urls) 

+

 

+

        return urls 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_serializers.html b/htmlcov/rest_framework_serializers.html new file mode 100644 index 00000000..79dc5647 --- /dev/null +++ b/htmlcov/rest_framework_serializers.html @@ -0,0 +1,2011 @@ + + + + + + + + Coverage for rest_framework/serializers: 94% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+

344

+

345

+

346

+

347

+

348

+

349

+

350

+

351

+

352

+

353

+

354

+

355

+

356

+

357

+

358

+

359

+

360

+

361

+

362

+

363

+

364

+

365

+

366

+

367

+

368

+

369

+

370

+

371

+

372

+

373

+

374

+

375

+

376

+

377

+

378

+

379

+

380

+

381

+

382

+

383

+

384

+

385

+

386

+

387

+

388

+

389

+

390

+

391

+

392

+

393

+

394

+

395

+

396

+

397

+

398

+

399

+

400

+

401

+

402

+

403

+

404

+

405

+

406

+

407

+

408

+

409

+

410

+

411

+

412

+

413

+

414

+

415

+

416

+

417

+

418

+

419

+

420

+

421

+

422

+

423

+

424

+

425

+

426

+

427

+

428

+

429

+

430

+

431

+

432

+

433

+

434

+

435

+

436

+

437

+

438

+

439

+

440

+

441

+

442

+

443

+

444

+

445

+

446

+

447

+

448

+

449

+

450

+

451

+

452

+

453

+

454

+

455

+

456

+

457

+

458

+

459

+

460

+

461

+

462

+

463

+

464

+

465

+

466

+

467

+

468

+

469

+

470

+

471

+

472

+

473

+

474

+

475

+

476

+

477

+

478

+

479

+

480

+

481

+

482

+

483

+

484

+

485

+

486

+

487

+

488

+

489

+

490

+

491

+

492

+

493

+

494

+

495

+

496

+

497

+

498

+

499

+

500

+

501

+

502

+

503

+

504

+

505

+

506

+

507

+

508

+

509

+

510

+

511

+

512

+

513

+

514

+

515

+

516

+

517

+

518

+

519

+

520

+

521

+

522

+

523

+

524

+

525

+

526

+

527

+

528

+

529

+

530

+

531

+

532

+

533

+

534

+

535

+

536

+

537

+

538

+

539

+

540

+

541

+

542

+

543

+

544

+

545

+

546

+

547

+

548

+

549

+

550

+

551

+

552

+

553

+

554

+

555

+

556

+

557

+

558

+

559

+

560

+

561

+

562

+

563

+

564

+

565

+

566

+

567

+

568

+

569

+

570

+

571

+

572

+

573

+

574

+

575

+

576

+

577

+

578

+

579

+

580

+

581

+

582

+

583

+

584

+

585

+

586

+

587

+

588

+

589

+

590

+

591

+

592

+

593

+

594

+

595

+

596

+

597

+

598

+

599

+

600

+

601

+

602

+

603

+

604

+

605

+

606

+

607

+

608

+

609

+

610

+

611

+

612

+

613

+

614

+

615

+

616

+

617

+

618

+

619

+

620

+

621

+

622

+

623

+

624

+

625

+

626

+

627

+

628

+

629

+

630

+

631

+

632

+

633

+

634

+

635

+

636

+

637

+

638

+

639

+

640

+

641

+

642

+

643

+

644

+

645

+

646

+

647

+

648

+

649

+

650

+

651

+

652

+

653

+

654

+

655

+

656

+

657

+

658

+

659

+

660

+

661

+

662

+

663

+

664

+

665

+

666

+

667

+

668

+

669

+

670

+

671

+

672

+

673

+

674

+

675

+

676

+

677

+

678

+

679

+

680

+

681

+

682

+

683

+

684

+

685

+

686

+

687

+

688

+

689

+

690

+

691

+

692

+

693

+

694

+

695

+

696

+

697

+

698

+

699

+

700

+

701

+

702

+

703

+

704

+

705

+

706

+

707

+

708

+

709

+

710

+

711

+

712

+

713

+

714

+

715

+

716

+

717

+

718

+

719

+

720

+

721

+

722

+

723

+

724

+

725

+

726

+

727

+

728

+

729

+

730

+

731

+

732

+

733

+

734

+

735

+

736

+

737

+

738

+

739

+

740

+

741

+

742

+

743

+

744

+

745

+

746

+

747

+

748

+

749

+

750

+

751

+

752

+

753

+

754

+

755

+

756

+

757

+

758

+

759

+

760

+

761

+

762

+

763

+

764

+

765

+

766

+

767

+

768

+

769

+

770

+

771

+

772

+

773

+

774

+

775

+

776

+

777

+

778

+

779

+

780

+

781

+

782

+

783

+

784

+

785

+

786

+

787

+

788

+

789

+

790

+

791

+

792

+

793

+

794

+

795

+

796

+

797

+

798

+

799

+

800

+

801

+

802

+

803

+

804

+

805

+

806

+

807

+

808

+

809

+

810

+

811

+

812

+

813

+

814

+

815

+

816

+

817

+

818

+

819

+

820

+

821

+

822

+

823

+

824

+

825

+

826

+

827

+

828

+

829

+

830

+

831

+

832

+

833

+

834

+

835

+

836

+

837

+

838

+

839

+

840

+

841

+

842

+

843

+

844

+

845

+

846

+

847

+

848

+

849

+

850

+

851

+

852

+

853

+

854

+

855

+

856

+

857

+

858

+

859

+

860

+

861

+

862

+

863

+

864

+

865

+

866

+

867

+

868

+

869

+

870

+

871

+

872

+

873

+

874

+

875

+

876

+

877

+

878

+

879

+

880

+

881

+

882

+

883

+

884

+

885

+

886

+

887

+

888

+

889

+

890

+

891

+

892

+

893

+

894

+

895

+

896

+

897

+

898

+

899

+

900

+

901

+

902

+

903

+

904

+

905

+

906

+

907

+

908

+

909

+

910

+

911

+

912

+

913

+

914

+

915

+

916

+

917

+

918

+

919

+

920

+

921

+

922

+

923

+

924

+

925

+

926

+

927

+

928

+

929

+

930

+

931

+

932

+

933

+

934

+

935

+

936

+

937

+

938

+

939

+

940

+

941

+

942

+

943

+

944

+

945

+

946

+

947

+

948

+

949

+

950

+

951

+

952

+

953

+

954

+

955

+

956

+

957

+

958

+

959

+

960

+

961

+

962

+

963

+

964

+

965

+ +
+

""" 

+

Serializers and ModelSerializers are similar to Forms and ModelForms. 

+

Unlike forms, they are not constrained to dealing with HTML output, and 

+

form encoded input. 

+

 

+

Serialization in REST framework is a two-phase process: 

+

 

+

1. Serializers marshal between complex types like model instances, and 

+

python primatives. 

+

2. The process of marshalling between python primatives and request and 

+

response content is handled by parsers and renderers. 

+

""" 

+

from __future__ import unicode_literals 

+

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 

+

from rest_framework.compat import get_concrete_model, six 

+

 

+

# Note: We do the following so that users of the framework can use this style: 

+

# 

+

#     example_field = serializers.CharField(...) 

+

# 

+

# 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 * 

+

 

+

 

+

class NestedValidationError(ValidationError): 

+

    """ 

+

    The default ValidationError behavior is to stringify each item in the list 

+

    if the messages are a list of error messages. 

+

 

+

    In the case of nested serializers, where the parent has many children, 

+

    then the child's `serializer.errors` will be a list of dicts.  In the case 

+

    of a single child, the `serializer.errors` will be a dict. 

+

 

+

    We need to override the default behavior to get properly nested error dicts. 

+

    """ 

+

 

+

    def __init__(self, message): 

+

        if isinstance(message, dict): 

+

            self.messages = [message] 

+

        else: 

+

            self.messages = message 

+

 

+

 

+

class DictWithMetadata(dict): 

+

    """ 

+

    A dict-like object, that can have additional properties attached. 

+

    """ 

+

    def __getstate__(self): 

+

        """ 

+

        Used by pickle (e.g., caching). 

+

        Overridden to remove the metadata from the dict, since it shouldn't be 

+

        pickled and may in some instances be unpickleable. 

+

        """ 

+

        return dict(self) 

+

 

+

 

+

class SortedDictWithMetadata(SortedDict): 

+

    """ 

+

    A sorted dict-like object, that can have additional properties attached. 

+

    """ 

+

    def __getstate__(self): 

+

        """ 

+

        Used by pickle (e.g., caching). 

+

        Overriden to remove the metadata from the dict, since it shouldn't be 

+

        pickle and may in some instances be unpickleable. 

+

        """ 

+

        return SortedDict(self).__dict__ 

+

 

+

 

+

def _is_protected_type(obj): 

+

    """ 

+

    True if the object is a native datatype that does not need to 

+

    be serialized further. 

+

    """ 

+

    return isinstance(obj, ( 

+

        types.NoneType, 

+

        int, long, 

+

        datetime.datetime, datetime.date, datetime.time, 

+

        float, Decimal, 

+

        basestring) 

+

    ) 

+

 

+

 

+

def _get_declared_fields(bases, attrs): 

+

    """ 

+

    Create a list of serializer field instances from the passed in 'attrs', 

+

    plus any fields on the base classes (in 'bases'). 

+

 

+

    Note that all fields from the base classes are used. 

+

    """ 

+

    fields = [(field_name, attrs.pop(field_name)) 

+

              for field_name, obj in list(six.iteritems(attrs)) 

+

              if isinstance(obj, Field)] 

+

    fields.sort(key=lambda x: x[1].creation_counter) 

+

 

+

    # If this class is subclassing another Serializer, add that Serializer's 

+

    # fields.  Note that we loop over the bases in *reverse*. This is necessary 

+

    # in order to maintain the correct order of fields. 

+

    for base in bases[::-1]: 

+

        if hasattr(base, 'base_fields'): 

+

            fields = list(base.base_fields.items()) + fields 

+

 

+

    return SortedDict(fields) 

+

 

+

 

+

class SerializerMetaclass(type): 

+

    def __new__(cls, name, bases, attrs): 

+

        attrs['base_fields'] = _get_declared_fields(bases, attrs) 

+

        return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs) 

+

 

+

 

+

class SerializerOptions(object): 

+

    """ 

+

    Meta class options for Serializer 

+

    """ 

+

    def __init__(self, meta): 

+

        self.depth = getattr(meta, 'depth', 0) 

+

        self.fields = getattr(meta, 'fields', ()) 

+

        self.exclude = getattr(meta, 'exclude', ()) 

+

 

+

 

+

class BaseSerializer(WritableField): 

+

    """ 

+

    This is the Serializer implementation. 

+

    We need to implement it as `BaseSerializer` due to metaclass magicks. 

+

    """ 

+

    class Meta(object): 

+

        pass 

+

 

+

    _options_class = SerializerOptions 

+

    _dict_class = SortedDictWithMetadata 

+

 

+

    def __init__(self, instance=None, data=None, files=None, 

+

                 context=None, partial=False, many=None, 

+

                 allow_add_remove=False, **kwargs): 

+

        super(BaseSerializer, self).__init__(**kwargs) 

+

        self.opts = self._options_class(self.Meta) 

+

        self.parent = None 

+

        self.root = None 

+

        self.partial = partial 

+

        self.many = many 

+

        self.allow_add_remove = allow_add_remove 

+

 

+

        self.context = context or {} 

+

 

+

        self.init_data = data 

+

        self.init_files = files 

+

        self.object = instance 

+

        self.fields = self.get_fields() 

+

 

+

        self._data = None 

+

        self._files = None 

+

        self._errors = None 

+

        self._deleted = None 

+

 

+

        if many and instance is not None and not hasattr(instance, '__iter__'): 

+

            raise ValueError('instance should be a queryset or other iterable with many=True') 

+

 

+

        if allow_add_remove and not many: 

+

            raise ValueError('allow_add_remove should only be used for bulk updates, but you have not set many=True') 

+

 

+

    ##### 

+

    # Methods to determine which fields to use when (de)serializing objects. 

+

 

+

    def get_default_fields(self): 

+

        """ 

+

        Return the complete set of default fields for the object, as a dict. 

+

        """ 

+

        return {} 

+

 

+

    def get_fields(self): 

+

        """ 

+

        Returns the complete set of fields for the object as a dict. 

+

 

+

        This will be the set of any explicitly declared fields, 

+

        plus the set of fields returned by get_default_fields(). 

+

        """ 

+

        ret = SortedDict() 

+

 

+

        # Get the explicitly declared fields 

+

        base_fields = copy.deepcopy(self.base_fields) 

+

        for key, field in base_fields.items(): 

+

            ret[key] = field 

+

 

+

        # Add in the default fields 

+

        default_fields = self.get_default_fields() 

+

        for key, val in default_fields.items(): 

+

            if key not in ret: 

+

                ret[key] = val 

+

 

+

        # If 'fields' is specified, use those fields, in that order. 

+

        if self.opts.fields: 

+

            assert isinstance(self.opts.fields, (list, tuple)), '`fields` must be a list or tuple' 

+

            new = SortedDict() 

+

            for key in self.opts.fields: 

+

                new[key] = ret[key] 

+

            ret = new 

+

 

+

        # Remove anything in 'exclude' 

+

        if self.opts.exclude: 

+

            assert isinstance(self.opts.exclude, (list, tuple)), '`exclude` must be a list or tuple' 

+

            for key in self.opts.exclude: 

+

                ret.pop(key, None) 

+

 

+

        for key, field in ret.items(): 

+

            field.initialize(parent=self, field_name=key) 

+

 

+

        return ret 

+

 

+

    ##### 

+

    # Methods to convert or revert from objects <--> primitive representations. 

+

 

+

    def get_field_key(self, field_name): 

+

        """ 

+

        Return the key that should be used for a given field. 

+

        """ 

+

        return field_name 

+

 

+

    def restore_fields(self, data, files): 

+

        """ 

+

        Core of deserialization, together with `restore_object`. 

+

        Converts a dictionary of data into a dictionary of deserialized fields. 

+

        """ 

+

        reverted_data = {} 

+

 

+

        if data is not None and not isinstance(data, dict): 

+

            self._errors['non_field_errors'] = ['Invalid data'] 

+

            return None 

+

 

+

        for field_name, field in self.fields.items(): 

+

            field.initialize(parent=self, field_name=field_name) 

+

            try: 

+

                field.field_from_native(data, files, field_name, reverted_data) 

+

            except ValidationError as err: 

+

                self._errors[field_name] = list(err.messages) 

+

 

+

        return reverted_data 

+

 

+

    def perform_validation(self, attrs): 

+

        """ 

+

        Run `validate_<fieldname>()` and `validate()` methods on the serializer 

+

        """ 

+

        for field_name, field in self.fields.items(): 

+

            if field_name in self._errors: 

+

                continue 

+

            try: 

+

                validate_method = getattr(self, 'validate_%s' % field_name, None) 

+

                if validate_method: 

+

                    source = field.source or field_name 

+

                    attrs = validate_method(attrs, source) 

+

            except ValidationError as err: 

+

                self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages) 

+

 

+

        # If there are already errors, we don't run .validate() because 

+

        # field-validation failed and thus `attrs` may not be complete. 

+

        # which in turn can cause inconsistent validation errors. 

+

        if not self._errors: 

+

            try: 

+

                attrs = self.validate(attrs) 

+

            except ValidationError as err: 

+

                if hasattr(err, 'message_dict'): 

+

                    for field_name, error_messages in err.message_dict.items(): 

+

                        self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) 

+

                elif hasattr(err, 'messages'): 

+

                    self._errors['non_field_errors'] = err.messages 

+

 

+

        return attrs 

+

 

+

    def validate(self, attrs): 

+

        """ 

+

        Stub method, to be overridden in Serializer subclasses 

+

        """ 

+

        return attrs 

+

 

+

    def restore_object(self, attrs, instance=None): 

+

        """ 

+

        Deserialize a dictionary of attributes into an object instance. 

+

        You should override this method to control how deserialized objects 

+

        are instantiated. 

+

        """ 

+

        if instance is not None: 

+

            instance.update(attrs) 

+

            return instance 

+

        return attrs 

+

 

+

    def to_native(self, obj): 

+

        """ 

+

        Serialize objects -> primitives. 

+

        """ 

+

        ret = self._dict_class() 

+

        ret.fields = {} 

+

 

+

        for field_name, field in self.fields.items(): 

+

            field.initialize(parent=self, field_name=field_name) 

+

            key = self.get_field_key(field_name) 

+

            value = field.field_to_native(obj, field_name) 

+

            ret[key] = value 

+

            ret.fields[key] = field 

+

        return ret 

+

 

+

    def from_native(self, data, files): 

+

        """ 

+

        Deserialize primitives -> objects. 

+

        """ 

+

        self._errors = {} 

+

        if data is not None or files is not None: 

+

            attrs = self.restore_fields(data, files) 

+

            if attrs is not None: 

+

                attrs = self.perform_validation(attrs) 

+

        else: 

+

            self._errors['non_field_errors'] = ['No input provided'] 

+

 

+

        if not self._errors: 

+

            return self.restore_object(attrs, instance=getattr(self, 'object', None)) 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        """ 

+

        Override default so that the serializer can be used as a nested field 

+

        across relationships. 

+

        """ 

+

        if self.source == '*': 

+

            return self.to_native(obj) 

+

 

+

        try: 

+

            source = self.source or field_name 

+

            value = obj 

+

 

+

            for component in source.split('.'): 

+

                value = get_component(value, component) 

+

                if value is None: 

+

                    break 

+

        except ObjectDoesNotExist: 

+

            return None 

+

 

+

        if is_simple_callable(getattr(value, 'all', None)): 

+

            return [self.to_native(item) for item in value.all()] 

+

 

+

        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: 

+

            return [self.to_native(item) for item in value] 

+

        return self.to_native(value) 

+

 

+

    def field_from_native(self, data, files, field_name, into): 

+

        """ 

+

        Override default so that the serializer can be used as a writable 

+

        nested field across relationships. 

+

        """ 

+

        if self.read_only: 

+

            return 

+

 

+

        try: 

+

            value = data[field_name] 

+

        except KeyError: 

+

            if self.default is not None and not self.partial: 

+

                # Note: partial updates shouldn't set defaults 

+

                value = copy.deepcopy(self.default) 

+

            else: 

+

                if self.required: 

+

                    raise ValidationError(self.error_messages['required']) 

+

                return 

+

 

+

        # Set the serializer object if it exists 

+

        obj = getattr(self.parent.object, field_name) if self.parent.object else None 

+

 

+

        if self.source == '*': 

+

            if value: 

+

                into.update(value) 

+

        else: 

+

            if value in (None, ''): 

+

                into[(self.source or field_name)] = None 

+

            else: 

+

                kwargs = { 

+

                    'instance': obj, 

+

                    'data': value, 

+

                    'context': self.context, 

+

                    'partial': self.partial, 

+

                    'many': self.many 

+

                } 

+

                serializer = self.__class__(**kwargs) 

+

 

+

                if serializer.is_valid(): 

+

                    into[self.source or field_name] = serializer.object 

+

                else: 

+

                    # Propagate errors up to our parent 

+

                    raise NestedValidationError(serializer.errors) 

+

 

+

    def get_identity(self, data): 

+

        """ 

+

        This hook is required for bulk update. 

+

        It is used to determine the canonical identity of a given object. 

+

 

+

        Note that the data has not been validated at this point, so we need 

+

        to make sure that we catch any cases of incorrect datatypes being 

+

        passed to this method. 

+

        """ 

+

        try: 

+

            return data.get('id', None) 

+

        except AttributeError: 

+

            return None 

+

 

+

    @property 

+

    def errors(self): 

+

        """ 

+

        Run deserialization and return error data, 

+

        setting self.object if no errors occurred. 

+

        """ 

+

        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: 

+

                ret = [] 

+

                errors = [] 

+

                update = self.object is not None 

+

 

+

                if update: 

+

                    # If this is a bulk update we need to map all the objects 

+

                    # to a canonical identity so we can determine which 

+

                    # individual object is being updated for each item in the 

+

                    # incoming data 

+

                    objects = self.object 

+

                    identities = [self.get_identity(self.to_native(obj)) for obj in objects] 

+

                    identity_to_objects = dict(zip(identities, objects)) 

+

 

+

                if hasattr(data, '__iter__') and not isinstance(data, (dict, six.text_type)): 

+

                    for item in data: 

+

                        if update: 

+

                            # Determine which object we're updating 

+

                            identity = self.get_identity(item) 

+

                            self.object = identity_to_objects.pop(identity, None) 

+

                            if self.object is None and not self.allow_add_remove: 

+

                                ret.append(None) 

+

                                errors.append({'non_field_errors': ['Cannot create a new item, only existing items may be updated.']}) 

+

                                continue 

+

 

+

                        ret.append(self.from_native(item, None)) 

+

                        errors.append(self._errors) 

+

 

+

                    if update: 

+

                        self._deleted = identity_to_objects.values() 

+

 

+

                    self._errors = any(errors) and errors or [] 

+

                else: 

+

                    self._errors = {'non_field_errors': ['Expected a list of items.']} 

+

            else: 

+

                ret = self.from_native(data, files) 

+

 

+

            if not self._errors: 

+

                self.object = ret 

+

 

+

        return self._errors 

+

 

+

    def is_valid(self): 

+

        return not self.errors 

+

 

+

    @property 

+

    def data(self): 

+

        """ 

+

        Returns the serialized data on the serializer. 

+

        """ 

+

        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: 

+

                self._data = [self.to_native(item) for item in obj] 

+

            else: 

+

                self._data = self.to_native(obj) 

+

 

+

        return self._data 

+

 

+

    def save_object(self, obj, **kwargs): 

+

        obj.save(**kwargs) 

+

 

+

    def delete_object(self, obj): 

+

        obj.delete() 

+

 

+

    def save(self, **kwargs): 

+

        """ 

+

        Save the deserialized object and return it. 

+

        """ 

+

        if isinstance(self.object, list): 

+

            [self.save_object(item, **kwargs) for item in self.object] 

+

        else: 

+

            self.save_object(self.object, **kwargs) 

+

 

+

        if self.allow_add_remove and self._deleted: 

+

            [self.delete_object(item) for item in self._deleted] 

+

 

+

        return self.object 

+

 

+

    def metadata(self): 

+

        """ 

+

        Return a dictionary of metadata about the fields on the serializer. 

+

        Useful for things like responding to OPTIONS requests, or generating 

+

        API schemas for auto-documentation. 

+

        """ 

+

        return SortedDict( 

+

            [(field_name, field.metadata()) 

+

            for field_name, field in six.iteritems(self.fields)] 

+

        ) 

+

 

+

 

+

class Serializer(six.with_metaclass(SerializerMetaclass, BaseSerializer)): 

+

    pass 

+

 

+

 

+

class ModelSerializerOptions(SerializerOptions): 

+

    """ 

+

    Meta class options for ModelSerializer 

+

    """ 

+

    def __init__(self, meta): 

+

        super(ModelSerializerOptions, self).__init__(meta) 

+

        self.model = getattr(meta, 'model', None) 

+

        self.read_only_fields = getattr(meta, 'read_only_fields', ()) 

+

 

+

 

+

class ModelSerializer(Serializer): 

+

    """ 

+

    A serializer that deals with model instances and querysets. 

+

    """ 

+

    _options_class = ModelSerializerOptions 

+

 

+

    field_mapping = { 

+

        models.AutoField: IntegerField, 

+

        models.FloatField: FloatField, 

+

        models.IntegerField: IntegerField, 

+

        models.PositiveIntegerField: IntegerField, 

+

        models.SmallIntegerField: IntegerField, 

+

        models.PositiveSmallIntegerField: IntegerField, 

+

        models.DateTimeField: DateTimeField, 

+

        models.DateField: DateField, 

+

        models.TimeField: TimeField, 

+

        models.DecimalField: DecimalField, 

+

        models.EmailField: EmailField, 

+

        models.CharField: CharField, 

+

        models.URLField: URLField, 

+

        models.SlugField: SlugField, 

+

        models.TextField: CharField, 

+

        models.CommaSeparatedIntegerField: CharField, 

+

        models.BooleanField: BooleanField, 

+

        models.FileField: FileField, 

+

        models.ImageField: ImageField, 

+

    } 

+

 

+

    def get_default_fields(self): 

+

        """ 

+

        Return all the fields that should be serialized for the model. 

+

        """ 

+

 

+

        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 

+

        ret = SortedDict() 

+

        nested = bool(self.opts.depth) 

+

 

+

        # Deal with adding the primary key field 

+

        pk_field = opts.pk 

+

        while pk_field.rel and pk_field.rel.parent_link: 

+

            # 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 

+

 

+

        # Deal with forward relationships 

+

        forward_rels = [field for field in opts.fields if field.serialize] 

+

        forward_rels += [field for field in opts.many_to_many if field.serialize] 

+

 

+

        for model_field in forward_rels: 

+

            has_through_model = False 

+

 

+

            if model_field.rel: 

+

                to_many = isinstance(model_field, 

+

                                     models.fields.related.ManyToManyField) 

+

                related_model = model_field.rel.to 

+

 

+

                if to_many and not model_field.rel.through._meta.auto_created: 

+

                    has_through_model = True 

+

 

+

            if model_field.rel and nested: 

+

                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. ' 

+

                        'Use `get_nested_field(model_field, related_model, ' 

+

                        'to_many) instead', 

+

                        PendingDeprecationWarning 

+

                    ) 

+

                    field = self.get_nested_field(model_field) 

+

                else: 

+

                    field = self.get_nested_field(model_field, related_model, to_many) 

+

            elif model_field.rel: 

+

                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. ' 

+

                        'Use `get_related_field(model_field, related_model, ' 

+

                        'to_many) instead', 

+

                        PendingDeprecationWarning 

+

                    ) 

+

                    field = self.get_related_field(model_field, to_many=to_many) 

+

                else: 

+

                    field = self.get_related_field(model_field, related_model, to_many) 

+

            else: 

+

                field = self.get_field(model_field) 

+

 

+

            if field: 

+

                if has_through_model: 

+

                    field.read_only = True 

+

 

+

                ret[model_field.name] = field 

+

 

+

        # Deal with reverse relationships 

+

        if not self.opts.fields: 

+

            reverse_rels = [] 

+

        else: 

+

            # Reverse relationships are only included if they are explicitly 

+

            # present in the `fields` option on the serializer 

+

            reverse_rels = opts.get_all_related_objects() 

+

            reverse_rels += opts.get_all_related_many_to_many_objects() 

+

 

+

        for relation in reverse_rels: 

+

            accessor_name = relation.get_accessor_name() 

+

            if not self.opts.fields or accessor_name not in self.opts.fields: 

+

                continue 

+

            related_model = relation.model 

+

            to_many = relation.field.rel.multiple 

+

            has_through_model = False 

+

            is_m2m = isinstance(relation.field, 

+

                                models.fields.related.ManyToManyField) 

+

 

+

            if is_m2m and not relation.field.rel.through._meta.auto_created: 

+

                has_through_model = True 

+

 

+

            if nested: 

+

                field = self.get_nested_field(None, related_model, to_many) 

+

            else: 

+

                field = self.get_related_field(None, related_model, to_many) 

+

 

+

            if field: 

+

                if has_through_model: 

+

                    field.read_only = True 

+

 

+

                ret[accessor_name] = field 

+

 

+

        # Add the `read_only` flag to any fields that have bee specified 

+

        # in the `read_only_fields` option 

+

        for field_name in self.opts.read_only_fields: 

+

            assert field_name not in self.base_fields.keys(), \ 

+

                "field '%s' on serializer '%s' specfied in " \ 

+

                "`read_only_fields`, but also added " \ 

+

                "as an explict field.  Remove it from `read_only_fields`." % \ 

+

                (field_name, self.__class__.__name__) 

+

            assert field_name in ret, \ 

+

                "Noexistant field '%s' specified in `read_only_fields` " \ 

+

                "on serializer '%s'." % \ 

+

                (self.__class__.__name__, field_name) 

+

            ret[field_name].read_only = True 

+

 

+

        return ret 

+

 

+

    def get_pk_field(self, model_field): 

+

        """ 

+

        Returns a default instance of the pk field. 

+

        """ 

+

        return self.get_field(model_field) 

+

 

+

    def get_nested_field(self, model_field, related_model, to_many): 

+

        """ 

+

        Creates a default instance of a nested relational field. 

+

 

+

        Note that model_field will be `None` for reverse relationships. 

+

        """ 

+

        class NestedModelSerializer(ModelSerializer): 

+

            class Meta: 

+

                model = related_model 

+

                depth = self.opts.depth - 1 

+

 

+

        return NestedModelSerializer(many=to_many) 

+

 

+

    def get_related_field(self, model_field, related_model, to_many): 

+

        """ 

+

        Creates a default instance of a flat relational field. 

+

 

+

        Note that model_field will be `None` for reverse relationships. 

+

        """ 

+

        # TODO: filter queryset using: 

+

        # .using(db).complex_filter(self.rel.limit_choices_to) 

+

 

+

        kwargs = { 

+

            'queryset': related_model._default_manager, 

+

            'many': to_many 

+

        } 

+

 

+

        if model_field: 

+

            kwargs['required'] = not(model_field.null or model_field.blank) 

+

 

+

        return PrimaryKeyRelatedField(**kwargs) 

+

 

+

    def get_field(self, model_field): 

+

        """ 

+

        Creates a default instance of a basic non-relational field. 

+

        """ 

+

        kwargs = {} 

+

 

+

        if model_field.null or model_field.blank: 

+

            kwargs['required'] = False 

+

 

+

        if isinstance(model_field, models.AutoField) or not model_field.editable: 

+

            kwargs['read_only'] = True 

+

 

+

        if model_field.has_default(): 

+

            kwargs['default'] = model_field.get_default() 

+

 

+

        if issubclass(model_field.__class__, models.TextField): 

+

            kwargs['widget'] = widgets.Textarea 

+

 

+

        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 

+

 

+

        # TODO: TypedChoiceField? 

+

        if model_field.flatchoices:  # This ModelField contains choices 

+

            kwargs['choices'] = model_field.flatchoices 

+

            return ChoiceField(**kwargs) 

+

 

+

        # put this below the ChoiceField because min_value isn't a valid initializer 

+

        if issubclass(model_field.__class__, models.PositiveIntegerField) or\ 

+

                issubclass(model_field.__class__, models.PositiveSmallIntegerField): 

+

            kwargs['min_value'] = 0 

+

 

+

        attribute_dict = { 

+

            models.CharField: ['max_length'], 

+

            models.CommaSeparatedIntegerField: ['max_length'], 

+

            models.DecimalField: ['max_digits', 'decimal_places'], 

+

            models.EmailField: ['max_length'], 

+

            models.FileField: ['max_length'], 

+

            models.ImageField: ['max_length'], 

+

            models.SlugField: ['max_length'], 

+

            models.URLField: ['max_length'], 

+

        } 

+

 

+

        if model_field.__class__ in attribute_dict: 

+

            attributes = attribute_dict[model_field.__class__] 

+

            for attribute in attributes: 

+

                kwargs.update({attribute: getattr(model_field, attribute)}) 

+

 

+

        try: 

+

            return self.field_mapping[model_field.__class__](**kwargs) 

+

        except KeyError: 

+

            return ModelField(model_field=model_field, **kwargs) 

+

 

+

    def get_validation_exclusions(self): 

+

        """ 

+

        Return a list of field names to exclude from model validation. 

+

        """ 

+

        cls = self.opts.model 

+

        opts = get_concrete_model(cls)._meta 

+

        exclusions = [field.name for field in opts.fields + opts.many_to_many] 

+

        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: 

+

                exclusions.remove(field_name) 

+

        return exclusions 

+

 

+

    def full_clean(self, instance): 

+

        """ 

+

        Perform Django's full_clean, and populate the `errors` dictionary 

+

        if any validation errors occur. 

+

 

+

        Note that we don't perform this inside the `.restore_object()` method, 

+

        so that subclasses can override `.restore_object()`, and still get 

+

        the full_clean validation checking. 

+

        """ 

+

        try: 

+

            instance.full_clean(exclude=self.get_validation_exclusions()) 

+

        except ValidationError as err: 

+

            self._errors = err.message_dict 

+

            return None 

+

        return instance 

+

 

+

    def restore_object(self, attrs, instance=None): 

+

        """ 

+

        Restore the model instance. 

+

        """ 

+

        m2m_data = {} 

+

        related_data = {} 

+

        meta = self.opts.model._meta 

+

 

+

        # Reverse fk or one-to-one relations 

+

        for (obj, model) in meta.get_all_related_objects_with_model(): 

+

            field_name = obj.field.related_query_name() 

+

            if field_name in attrs: 

+

                related_data[field_name] = attrs.pop(field_name) 

+

 

+

        # Reverse m2m relations 

+

        for (obj, model) in meta.get_all_related_m2m_objects_with_model(): 

+

            field_name = obj.field.related_query_name() 

+

            if field_name in attrs: 

+

                m2m_data[field_name] = attrs.pop(field_name) 

+

 

+

        # Forward m2m relations 

+

        for field in meta.many_to_many: 

+

            if field.name in attrs: 

+

                m2m_data[field.name] = attrs.pop(field.name) 

+

 

+

        # Update an existing instance... 

+

        if instance is not None: 

+

            for key, val in attrs.items(): 

+

                setattr(instance, key, val) 

+

 

+

        # ...or create a new instance 

+

        else: 

+

            instance = self.opts.model(**attrs) 

+

 

+

        # Any relations that cannot be set until we've 

+

        # saved the model get hidden away on these 

+

        # private attributes, so we can deal with them 

+

        # at the point of save. 

+

        instance._related_data = related_data 

+

        instance._m2m_data = m2m_data 

+

 

+

        return instance 

+

 

+

    def from_native(self, data, files): 

+

        """ 

+

        Override the default method to also include model field validation. 

+

        """ 

+

        instance = super(ModelSerializer, self).from_native(data, files) 

+

        if not self._errors: 

+

            return self.full_clean(instance) 

+

 

+

    def save_object(self, obj, **kwargs): 

+

        """ 

+

        Save the deserialized object and return it. 

+

        """ 

+

        obj.save(**kwargs) 

+

 

+

        if getattr(obj, '_m2m_data', None): 

+

            for accessor_name, object_list in obj._m2m_data.items(): 

+

                setattr(obj, accessor_name, object_list) 

+

            del(obj._m2m_data) 

+

 

+

        if getattr(obj, '_related_data', None): 

+

            for accessor_name, related in obj._related_data.items(): 

+

                setattr(obj, accessor_name, related) 

+

            del(obj._related_data) 

+

 

+

 

+

class HyperlinkedModelSerializerOptions(ModelSerializerOptions): 

+

    """ 

+

    Options for HyperlinkedModelSerializer 

+

    """ 

+

    def __init__(self, meta): 

+

        super(HyperlinkedModelSerializerOptions, self).__init__(meta) 

+

        self.view_name = getattr(meta, 'view_name', None) 

+

        self.lookup_field = getattr(meta, 'lookup_field', None) 

+

 

+

 

+

class HyperlinkedModelSerializer(ModelSerializer): 

+

    """ 

+

    A subclass of ModelSerializer that uses hyperlinked relationships, 

+

    instead of primary key relationships. 

+

    """ 

+

    _options_class = HyperlinkedModelSerializerOptions 

+

    _default_view_name = '%(model_name)s-detail' 

+

    _hyperlink_field_class = HyperlinkedRelatedField 

+

 

+

    def get_default_fields(self): 

+

        fields = super(HyperlinkedModelSerializer, self).get_default_fields() 

+

 

+

        if self.opts.view_name is None: 

+

            self.opts.view_name = self._get_default_view_name(self.opts.model) 

+

 

+

        if 'url' not in fields: 

+

            url_field = HyperlinkedIdentityField( 

+

                view_name=self.opts.view_name, 

+

                lookup_field=self.opts.lookup_field 

+

            ) 

+

            fields.insert(0, 'url', url_field) 

+

 

+

        return fields 

+

 

+

    def get_pk_field(self, model_field): 

+

        if self.opts.fields and model_field.name in self.opts.fields: 

+

            return self.get_field(model_field) 

+

 

+

    def get_related_field(self, model_field, related_model, to_many): 

+

        """ 

+

        Creates a default instance of a flat relational field. 

+

        """ 

+

        # TODO: filter queryset using: 

+

        # .using(db).complex_filter(self.rel.limit_choices_to) 

+

        kwargs = { 

+

            'queryset': related_model._default_manager, 

+

            'view_name': self._get_default_view_name(related_model), 

+

            'many': to_many 

+

        } 

+

 

+

        if model_field: 

+

            kwargs['required'] = not(model_field.null or model_field.blank) 

+

 

+

        if self.opts.lookup_field: 

+

            kwargs['lookup_field'] = self.opts.lookup_field 

+

 

+

        return self._hyperlink_field_class(**kwargs) 

+

 

+

    def get_identity(self, data): 

+

        """ 

+

        This hook is required for bulk update. 

+

        We need to override the default, to use the url as the identity. 

+

        """ 

+

        try: 

+

            return data.get('url', None) 

+

        except AttributeError: 

+

            return None 

+

 

+

    def _get_default_view_name(self, model): 

+

        """ 

+

        Return the view name to use if 'view_name' is not specified in 'Meta' 

+

        """ 

+

        model_meta = model._meta 

+

        format_kwargs = { 

+

            'app_label': model_meta.app_label, 

+

            'model_name': model_meta.object_name.lower() 

+

        } 

+

        return self._default_view_name % format_kwargs 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_settings.html b/htmlcov/rest_framework_settings.html new file mode 100644 index 00000000..ae47b5bc --- /dev/null +++ b/htmlcov/rest_framework_settings.html @@ -0,0 +1,465 @@ + + + + + + + + Coverage for rest_framework/settings: 95% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+ +
+

""" 

+

Settings for REST framework are all namespaced in the REST_FRAMEWORK setting. 

+

For example your project's `settings.py` file might look like this: 

+

 

+

REST_FRAMEWORK = { 

+

    'DEFAULT_RENDERER_CLASSES': ( 

+

        'rest_framework.renderers.JSONRenderer', 

+

        'rest_framework.renderers.YAMLRenderer', 

+

    ) 

+

    'DEFAULT_PARSER_CLASSES': ( 

+

        'rest_framework.parsers.JSONParser', 

+

        'rest_framework.parsers.YAMLParser', 

+

    ) 

+

} 

+

 

+

This module provides the `api_setting` object, that is used to access 

+

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 rest_framework import ISO_8601 

+

from rest_framework.compat import six 

+

 

+

 

+

USER_SETTINGS = getattr(settings, 'REST_FRAMEWORK', None) 

+

 

+

DEFAULTS = { 

+

    # Base API policies 

+

    'DEFAULT_RENDERER_CLASSES': ( 

+

        'rest_framework.renderers.JSONRenderer', 

+

        'rest_framework.renderers.BrowsableAPIRenderer', 

+

    ), 

+

    'DEFAULT_PARSER_CLASSES': ( 

+

        'rest_framework.parsers.JSONParser', 

+

        'rest_framework.parsers.FormParser', 

+

        'rest_framework.parsers.MultiPartParser' 

+

    ), 

+

    'DEFAULT_AUTHENTICATION_CLASSES': ( 

+

        'rest_framework.authentication.SessionAuthentication', 

+

        'rest_framework.authentication.BasicAuthentication' 

+

    ), 

+

    'DEFAULT_PERMISSION_CLASSES': ( 

+

        'rest_framework.permissions.AllowAny', 

+

    ), 

+

    '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_FILTER_BACKENDS': (), 

+

 

+

    # Throttling 

+

    'DEFAULT_THROTTLE_RATES': { 

+

        'user': None, 

+

        'anon': None, 

+

    }, 

+

 

+

    # Pagination 

+

    'PAGINATE_BY': None, 

+

    'PAGINATE_BY_PARAM': None, 

+

 

+

    # Authentication 

+

    'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser', 

+

    'UNAUTHENTICATED_TOKEN': None, 

+

 

+

    # Browser enhancements 

+

    'FORM_METHOD_OVERRIDE': '_method', 

+

    'FORM_CONTENT_OVERRIDE': '_content', 

+

    'FORM_CONTENTTYPE_OVERRIDE': '_content_type', 

+

    'URL_ACCEPT_OVERRIDE': 'accept', 

+

    'URL_FORMAT_OVERRIDE': 'format', 

+

 

+

    'FORMAT_SUFFIX_KWARG': 'format', 

+

 

+

    # Input and output formats 

+

    'DATE_INPUT_FORMATS': ( 

+

        ISO_8601, 

+

    ), 

+

    'DATE_FORMAT': None, 

+

 

+

    'DATETIME_INPUT_FORMATS': ( 

+

        ISO_8601, 

+

    ), 

+

    'DATETIME_FORMAT': None, 

+

 

+

    'TIME_INPUT_FORMATS': ( 

+

        ISO_8601, 

+

    ), 

+

    'TIME_FORMAT': None, 

+

 

+

    # Pending deprecation 

+

    'FILTER_BACKEND': None, 

+

} 

+

 

+

 

+

# List of settings that may be in string import notation. 

+

IMPORT_STRINGS = ( 

+

    'DEFAULT_RENDERER_CLASSES', 

+

    'DEFAULT_PARSER_CLASSES', 

+

    'DEFAULT_AUTHENTICATION_CLASSES', 

+

    'DEFAULT_PERMISSION_CLASSES', 

+

    'DEFAULT_THROTTLE_CLASSES', 

+

    'DEFAULT_CONTENT_NEGOTIATION_CLASS', 

+

    'DEFAULT_MODEL_SERIALIZER_CLASS', 

+

    'DEFAULT_PAGINATION_SERIALIZER_CLASS', 

+

    'DEFAULT_FILTER_BACKENDS', 

+

    'FILTER_BACKEND', 

+

    'UNAUTHENTICATED_USER', 

+

    'UNAUTHENTICATED_TOKEN', 

+

) 

+

 

+

 

+

def perform_import(val, setting_name): 

+

    """ 

+

    If the given setting is a string import notation, 

+

    then perform the necessary import or imports. 

+

    """ 

+

    if isinstance(val, six.string_types): 

+

        return import_from_string(val, setting_name) 

+

    elif isinstance(val, (list, tuple)): 

+

        return [import_from_string(item, setting_name) for item in val] 

+

    return val 

+

 

+

 

+

def import_from_string(val, setting_name): 

+

    """ 

+

    Attempt to import a class from a string representation. 

+

    """ 

+

    try: 

+

        # Nod to tastypie's use of importlib. 

+

        parts = val.split('.') 

+

        module_path, class_name = '.'.join(parts[:-1]), parts[-1] 

+

        module = importlib.import_module(module_path) 

+

        return getattr(module, class_name) 

+

    except ImportError as e: 

+

        msg = "Could not import '%s' for API setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e) 

+

        raise ImportError(msg) 

+

 

+

 

+

class APISettings(object): 

+

    """ 

+

    A settings object, that allows API settings to be accessed as properties. 

+

    For example: 

+

 

+

        from rest_framework.settings import api_settings 

+

        print api_settings.DEFAULT_RENDERER_CLASSES 

+

 

+

    Any setting with string import paths will be automatically resolved 

+

    and return the class, rather than the string literal. 

+

    """ 

+

    def __init__(self, user_settings=None, defaults=None, import_strings=None): 

+

        self.user_settings = user_settings or {} 

+

        self.defaults = defaults or {} 

+

        self.import_strings = import_strings or () 

+

 

+

    def __getattr__(self, attr): 

+

        if attr not in self.defaults.keys(): 

+

            raise AttributeError("Invalid API setting: '%s'" % attr) 

+

 

+

        try: 

+

            # Check if present in user settings 

+

            val = self.user_settings[attr] 

+

        except KeyError: 

+

            # Fall back to defaults 

+

            val = self.defaults[attr] 

+

 

+

        # Coerce import strings into classes 

+

        if val and attr in self.import_strings: 

+

            val = perform_import(val, attr) 

+

 

+

        self.validate_setting(attr, val) 

+

 

+

        # Cache the result 

+

        setattr(self, attr, val) 

+

        return val 

+

 

+

    def validate_setting(self, attr, val): 

+

        if attr == 'FILTER_BACKEND' and val is not None: 

+

            # Make sure we can initialize the class 

+

            val() 

+

 

+

api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_status.html b/htmlcov/rest_framework_status.html new file mode 100644 index 00000000..85f919f6 --- /dev/null +++ b/htmlcov/rest_framework_status.html @@ -0,0 +1,187 @@ + + + + + + + + Coverage for rest_framework/status: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+ +
+

""" 

+

Descriptive HTTP status codes, for code readability. 

+

 

+

See RFC 2616 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 

+

And RFC 6585 - http://tools.ietf.org/html/rfc6585 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

HTTP_100_CONTINUE = 100 

+

HTTP_101_SWITCHING_PROTOCOLS = 101 

+

HTTP_200_OK = 200 

+

HTTP_201_CREATED = 201 

+

HTTP_202_ACCEPTED = 202 

+

HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203 

+

HTTP_204_NO_CONTENT = 204 

+

HTTP_205_RESET_CONTENT = 205 

+

HTTP_206_PARTIAL_CONTENT = 206 

+

HTTP_300_MULTIPLE_CHOICES = 300 

+

HTTP_301_MOVED_PERMANENTLY = 301 

+

HTTP_302_FOUND = 302 

+

HTTP_303_SEE_OTHER = 303 

+

HTTP_304_NOT_MODIFIED = 304 

+

HTTP_305_USE_PROXY = 305 

+

HTTP_306_RESERVED = 306 

+

HTTP_307_TEMPORARY_REDIRECT = 307 

+

HTTP_400_BAD_REQUEST = 400 

+

HTTP_401_UNAUTHORIZED = 401 

+

HTTP_402_PAYMENT_REQUIRED = 402 

+

HTTP_403_FORBIDDEN = 403 

+

HTTP_404_NOT_FOUND = 404 

+

HTTP_405_METHOD_NOT_ALLOWED = 405 

+

HTTP_406_NOT_ACCEPTABLE = 406 

+

HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407 

+

HTTP_408_REQUEST_TIMEOUT = 408 

+

HTTP_409_CONFLICT = 409 

+

HTTP_410_GONE = 410 

+

HTTP_411_LENGTH_REQUIRED = 411 

+

HTTP_412_PRECONDITION_FAILED = 412 

+

HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413 

+

HTTP_414_REQUEST_URI_TOO_LONG = 414 

+

HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415 

+

HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416 

+

HTTP_417_EXPECTATION_FAILED = 417 

+

HTTP_428_PRECONDITION_REQUIRED = 428 

+

HTTP_429_TOO_MANY_REQUESTS = 429 

+

HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431 

+

HTTP_500_INTERNAL_SERVER_ERROR = 500 

+

HTTP_501_NOT_IMPLEMENTED = 501 

+

HTTP_502_BAD_GATEWAY = 502 

+

HTTP_503_SERVICE_UNAVAILABLE = 503 

+

HTTP_504_GATEWAY_TIMEOUT = 504 

+

HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505 

+

HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_throttling.html b/htmlcov/rest_framework_throttling.html new file mode 100644 index 00000000..778b0293 --- /dev/null +++ b/htmlcov/rest_framework_throttling.html @@ -0,0 +1,533 @@ + + + + + + + + Coverage for rest_framework/throttling: 81% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+ +
+

""" 

+

Provides various throttling policies. 

+

""" 

+

from __future__ import unicode_literals 

+

from django.core.cache import cache 

+

from django.core.exceptions import ImproperlyConfigured 

+

from rest_framework.settings import api_settings 

+

import time 

+

 

+

 

+

class BaseThrottle(object): 

+

    """ 

+

    Rate throttling of requests. 

+

    """ 

+

    def allow_request(self, request, view): 

+

        """ 

+

        Return `True` if the request should be allowed, `False` otherwise. 

+

        """ 

+

        raise NotImplementedError('.allow_request() must be overridden') 

+

 

+

    def wait(self): 

+

        """ 

+

        Optionally, return a recommended number of seconds to wait before 

+

        the next request. 

+

        """ 

+

        return None 

+

 

+

 

+

class SimpleRateThrottle(BaseThrottle): 

+

    """ 

+

    A simple cache implementation, that only requires `.get_cache_key()` 

+

    to be overridden. 

+

 

+

    The rate (requests / seconds) is set by a `throttle` attribute on the View 

+

    class.  The attribute is a string of the form 'number_of_requests/period'. 

+

 

+

    Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day') 

+

 

+

    Previous request information used for throttling is stored in the cache. 

+

    """ 

+

 

+

    timer = time.time 

+

    cache_format = 'throtte_%(scope)s_%(ident)s' 

+

    scope = None 

+

    THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES 

+

 

+

    def __init__(self): 

+

        if not getattr(self, 'rate', None): 

+

            self.rate = self.get_rate() 

+

        self.num_requests, self.duration = self.parse_rate(self.rate) 

+

 

+

    def get_cache_key(self, request, view): 

+

        """ 

+

        Should return a unique cache-key which can be used for throttling. 

+

        Must be overridden. 

+

 

+

        May return `None` if the request should not be throttled. 

+

        """ 

+

        raise NotImplementedError('.get_cache_key() must be overridden') 

+

 

+

    def get_rate(self): 

+

        """ 

+

        Determine the string representation of the allowed request rate. 

+

        """ 

+

        if not getattr(self, 'scope', None): 

+

            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" % 

+

                   self.__class__.__name__) 

+

            raise ImproperlyConfigured(msg) 

+

 

+

        try: 

+

            return self.THROTTLE_RATES[self.scope] 

+

        except KeyError: 

+

            msg = "No default throttle rate set for '%s' scope" % self.scope 

+

            raise ImproperlyConfigured(msg) 

+

 

+

    def parse_rate(self, rate): 

+

        """ 

+

        Given the request rate string, return a two tuple of: 

+

        <allowed number of requests>, <period of time in seconds> 

+

        """ 

+

        if rate is None: 

+

            return (None, None) 

+

        num, period = rate.split('/') 

+

        num_requests = int(num) 

+

        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]] 

+

        return (num_requests, duration) 

+

 

+

    def allow_request(self, request, view): 

+

        """ 

+

        Implement the check to see if the request should be throttled. 

+

 

+

        On success calls `throttle_success`. 

+

        On failure calls `throttle_failure`. 

+

        """ 

+

        if self.rate is None: 

+

            return True 

+

 

+

        self.key = self.get_cache_key(request, view) 

+

        self.history = cache.get(self.key, []) 

+

        self.now = self.timer() 

+

 

+

        # Drop any requests from the history which have now passed the 

+

        # throttle duration 

+

        while self.history and self.history[-1] <= self.now - self.duration: 

+

            self.history.pop() 

+

        if len(self.history) >= self.num_requests: 

+

            return self.throttle_failure() 

+

        return self.throttle_success() 

+

 

+

    def throttle_success(self): 

+

        """ 

+

        Inserts the current request's timestamp along with the key 

+

        into the cache. 

+

        """ 

+

        self.history.insert(0, self.now) 

+

        cache.set(self.key, self.history, self.duration) 

+

        return True 

+

 

+

    def throttle_failure(self): 

+

        """ 

+

        Called when a request to the API has failed due to throttling. 

+

        """ 

+

        return False 

+

 

+

    def wait(self): 

+

        """ 

+

        Returns the recommended next request time in seconds. 

+

        """ 

+

        if self.history: 

+

            remaining_duration = self.duration - (self.now - self.history[-1]) 

+

        else: 

+

            remaining_duration = self.duration 

+

 

+

        available_requests = self.num_requests - len(self.history) + 1 

+

 

+

        return remaining_duration / float(available_requests) 

+

 

+

 

+

class AnonRateThrottle(SimpleRateThrottle): 

+

    """ 

+

    Limits the rate of API calls that may be made by a anonymous users. 

+

 

+

    The IP address of the request will be used as the unique cache key. 

+

    """ 

+

    scope = 'anon' 

+

 

+

    def get_cache_key(self, request, view): 

+

        if request.user.is_authenticated(): 

+

            return None  # Only throttle unauthenticated requests. 

+

 

+

        ident = request.META.get('REMOTE_ADDR', None) 

+

 

+

        return self.cache_format % { 

+

            'scope': self.scope, 

+

            'ident': ident 

+

        } 

+

 

+

 

+

class UserRateThrottle(SimpleRateThrottle): 

+

    """ 

+

    Limits the rate of API calls that may be made by a given user. 

+

 

+

    The user id will be used as a unique cache key if the user is 

+

    authenticated.  For anonymous requests, the IP address of the request will 

+

    be used. 

+

    """ 

+

    scope = 'user' 

+

 

+

    def get_cache_key(self, request, view): 

+

        if request.user.is_authenticated(): 

+

            ident = request.user.id 

+

        else: 

+

            ident = request.META.get('REMOTE_ADDR', None) 

+

 

+

        return self.cache_format % { 

+

            'scope': self.scope, 

+

            'ident': ident 

+

        } 

+

 

+

 

+

class ScopedRateThrottle(SimpleRateThrottle): 

+

    """ 

+

    Limits the rate of API calls by different amounts for various parts of 

+

    the API.  Any view that has the `throttle_scope` property set will be 

+

    throttled.  The unique cache key will be generated by concatenating the 

+

    user id of the request, and the scope of the view being accessed. 

+

    """ 

+

    scope_attr = 'throttle_scope' 

+

 

+

    def __init__(self): 

+

        # Override the usual SimpleRateThrottle, because we can't determine 

+

        # the rate until called by the view. 

+

        pass 

+

 

+

    def allow_request(self, request, view): 

+

        # We can only determine the scope once we're called by the view. 

+

        self.scope = getattr(view, self.scope_attr, None) 

+

 

+

        # If a view does not have a `throttle_scope` always allow the request 

+

        if not self.scope: 

+

            return True 

+

 

+

        # Determine the allowed request rate as we normally would during 

+

        # the `__init__` call. 

+

        self.rate = self.get_rate() 

+

        self.num_requests, self.duration = self.parse_rate(self.rate) 

+

 

+

        # We can now proceed as normal. 

+

        return super(ScopedRateThrottle, self).allow_request(request, view) 

+

 

+

    def get_cache_key(self, request, view): 

+

        """ 

+

        If `view.throttle_scope` is not set, don't apply this throttle. 

+

 

+

        Otherwise generate the unique cache key by concatenating the user id 

+

        with the '.throttle_scope` property of the view. 

+

        """ 

+

        if request.user.is_authenticated(): 

+

            ident = request.user.id 

+

        else: 

+

            ident = request.META.get('REMOTE_ADDR', None) 

+

 

+

        return self.cache_format % { 

+

            'scope': self.scope, 

+

            'ident': ident 

+

        } 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_urlpatterns.html b/htmlcov/rest_framework_urlpatterns.html new file mode 100644 index 00000000..4c824a77 --- /dev/null +++ b/htmlcov/rest_framework_urlpatterns.html @@ -0,0 +1,205 @@ + + + + + + + + Coverage for rest_framework/urlpatterns: 87% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+ +
+

from __future__ import unicode_literals 

+

from django.core.urlresolvers import RegexURLResolver 

+

from rest_framework.compat import url, include 

+

from rest_framework.settings import api_settings 

+

 

+

 

+

def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required): 

+

    ret = [] 

+

    for urlpattern in urlpatterns: 

+

        if isinstance(urlpattern, RegexURLResolver): 

+

            # Set of included URL patterns 

+

            regex = urlpattern.regex.pattern 

+

            namespace = urlpattern.namespace 

+

            app_name = urlpattern.app_name 

+

            kwargs = urlpattern.default_kwargs 

+

            # Add in the included patterns, after applying the suffixes 

+

            patterns = apply_suffix_patterns(urlpattern.url_patterns, 

+

                                             suffix_pattern, 

+

                                             suffix_required) 

+

            ret.append(url(regex, include(patterns, namespace, app_name), kwargs)) 

+

 

+

        else: 

+

            # Regular URL pattern 

+

            regex = urlpattern.regex.pattern.rstrip('$') + suffix_pattern 

+

            view = urlpattern._callback or urlpattern._callback_str 

+

            kwargs = urlpattern.default_args 

+

            name = urlpattern.name 

+

            # Add in both the existing and the new urlpattern 

+

            if not suffix_required: 

+

                ret.append(urlpattern) 

+

            ret.append(url(regex, view, kwargs, name)) 

+

 

+

    return ret 

+

 

+

 

+

def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None): 

+

    """ 

+

    Supplement existing urlpatterns with corresponding patterns that also 

+

    include a '.format' suffix.  Retains urlpattern ordering. 

+

 

+

    urlpatterns: 

+

        A list of URL patterns. 

+

 

+

    suffix_required: 

+

        If `True`, only suffixed URLs will be generated, and non-suffixed 

+

        URLs will not be used.  Defaults to `False`. 

+

 

+

    allowed: 

+

        An optional tuple/list of allowed suffixes.  eg ['json', 'api'] 

+

        Defaults to `None`, which allows any suffix. 

+

    """ 

+

    suffix_kwarg = api_settings.FORMAT_SUFFIX_KWARG 

+

    if allowed: 

+

        if len(allowed) == 1: 

+

            allowed_pattern = allowed[0] 

+

        else: 

+

            allowed_pattern = '(%s)' % '|'.join(allowed) 

+

        suffix_pattern = r'\.(?P<%s>%s)$' % (suffix_kwarg, allowed_pattern) 

+

    else: 

+

        suffix_pattern = r'\.(?P<%s>[a-z]+)$' % suffix_kwarg 

+

 

+

    return apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_urls.html b/htmlcov/rest_framework_urls.html new file mode 100644 index 00000000..7720a6d4 --- /dev/null +++ b/htmlcov/rest_framework_urls.html @@ -0,0 +1,129 @@ + + + + + + + + Coverage for rest_framework/urls: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+ +
+

""" 

+

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`. 

+

 

+

    urlpatterns = patterns('', 

+

        ... 

+

        url(r'^auth', include('rest_framework.urls', namespace='rest_framework')) 

+

    ) 

+

""" 

+

from __future__ import unicode_literals 

+

from rest_framework.compat import patterns, url 

+

 

+

 

+

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'), 

+

) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_utils___init__.html b/htmlcov/rest_framework_utils___init__.html new file mode 100644 index 00000000..99eb18c4 --- /dev/null +++ b/htmlcov/rest_framework_utils___init__.html @@ -0,0 +1,81 @@ + + + + + + + + Coverage for rest_framework/utils/__init__: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+ + + +
+
+ + + + + diff --git a/htmlcov/rest_framework_utils_breadcrumbs.html b/htmlcov/rest_framework_utils_breadcrumbs.html new file mode 100644 index 00000000..14fb8955 --- /dev/null +++ b/htmlcov/rest_framework_utils_breadcrumbs.html @@ -0,0 +1,189 @@ + + + + + + + + Coverage for rest_framework/utils/breadcrumbs: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+ +
+

from __future__ import unicode_literals 

+

from django.core.urlresolvers import resolve, get_script_prefix 

+

from rest_framework.utils.formatting import get_view_name 

+

 

+

 

+

def get_breadcrumbs(url): 

+

    """ 

+

    Given a url returns a list of breadcrumbs, which are each a 

+

    tuple of (name, url). 

+

    """ 

+

 

+

    from rest_framework.views import APIView 

+

 

+

    def breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen): 

+

        """ 

+

        Add tuples of (name, url) to the breadcrumbs list, 

+

        progressively chomping off parts of the url. 

+

        """ 

+

 

+

        try: 

+

            (view, unused_args, unused_kwargs) = resolve(url) 

+

        except Exception: 

+

            pass 

+

        else: 

+

            # Check if this is a REST framework view, 

+

            # and if so add it to the breadcrumbs 

+

            cls = getattr(view, 'cls', None) 

+

            if cls is not None and issubclass(cls, APIView): 

+

                # Don't list the same view twice in a row. 

+

                # Probably an optional trailing slash. 

+

                if not seen or seen[-1] != view: 

+

                    suffix = getattr(view, 'suffix', None) 

+

                    name = get_view_name(view.cls, suffix) 

+

                    breadcrumbs_list.insert(0, (name, prefix + url)) 

+

                    seen.append(view) 

+

 

+

        if url == '': 

+

            # All done 

+

            return breadcrumbs_list 

+

 

+

        elif url.endswith('/'): 

+

            # Drop trailing slash off the end and continue to try to 

+

            # resolve more breadcrumbs 

+

            url = url.rstrip('/') 

+

            return breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen) 

+

 

+

        # Drop trailing non-slash off the end and continue to try to 

+

        # resolve more breadcrumbs 

+

        url = url[:url.rfind('/') + 1] 

+

        return breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen) 

+

 

+

    prefix = get_script_prefix().rstrip('/') 

+

    url = url[len(prefix):] 

+

    return breadcrumbs_recursive(url, [], prefix, []) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_utils_encoders.html b/htmlcov/rest_framework_utils_encoders.html new file mode 100644 index 00000000..9f0ca343 --- /dev/null +++ b/htmlcov/rest_framework_utils_encoders.html @@ -0,0 +1,275 @@ + + + + + + + + Coverage for rest_framework/utils/encoders: 73% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+ +
+

""" 

+

Helper classes for parsers. 

+

""" 

+

from __future__ import unicode_literals 

+

from django.utils.datastructures import SortedDict 

+

from django.utils.functional import Promise 

+

from rest_framework.compat import timezone, force_text 

+

from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata 

+

import datetime 

+

import decimal 

+

import types 

+

import json 

+

 

+

 

+

class JSONEncoder(json.JSONEncoder): 

+

    """ 

+

    JSONEncoder subclass that knows how to encode date/time/timedelta, 

+

    decimal types, and generators. 

+

    """ 

+

    def default(self, o): 

+

        # For Date Time string spec, see ECMA 262 

+

        # http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 

+

        if isinstance(o, Promise): 

+

            return force_text(o) 

+

        elif isinstance(o, datetime.datetime): 

+

            r = o.isoformat() 

+

            if o.microsecond: 

+

                r = r[:23] + r[26:] 

+

            if r.endswith('+00:00'): 

+

                r = r[:-6] + 'Z' 

+

            return r 

+

        elif isinstance(o, datetime.date): 

+

            return o.isoformat() 

+

        elif isinstance(o, datetime.time): 

+

            if timezone and timezone.is_aware(o): 

+

                raise ValueError("JSON can't represent timezone-aware times.") 

+

            r = o.isoformat() 

+

            if o.microsecond: 

+

                r = r[:12] 

+

            return r 

+

        elif isinstance(o, datetime.timedelta): 

+

            return str(o.total_seconds()) 

+

        elif isinstance(o, decimal.Decimal): 

+

            return str(o) 

+

        elif hasattr(o, '__iter__'): 

+

            return [i for i in o] 

+

        return super(JSONEncoder, self).default(o) 

+

 

+

 

+

try: 

+

    import yaml 

+

except ImportError: 

+

    SafeDumper = None 

+

else: 

+

    # Adapted from http://pyyaml.org/attachment/ticket/161/use_ordered_dict.py 

+

    class SafeDumper(yaml.SafeDumper): 

+

        """ 

+

        Handles decimals as strings. 

+

        Handles SortedDicts as usual dicts, but preserves field order, rather 

+

        than the usual behaviour of sorting the keys. 

+

        """ 

+

        def represent_decimal(self, data): 

+

            return self.represent_scalar('tag:yaml.org,2002:str', str(data)) 

+

 

+

        def represent_mapping(self, tag, mapping, flow_style=None): 

+

            value = [] 

+

            node = yaml.MappingNode(tag, value, flow_style=flow_style) 

+

            if self.alias_key is not None: 

+

                self.represented_objects[self.alias_key] = node 

+

            best_style = True 

+

            if hasattr(mapping, 'items'): 

+

                mapping = list(mapping.items()) 

+

                if not isinstance(mapping, SortedDict): 

+

                    mapping.sort() 

+

            for item_key, item_value in mapping: 

+

                node_key = self.represent_data(item_key) 

+

                node_value = self.represent_data(item_value) 

+

                if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style): 

+

                    best_style = False 

+

                if not (isinstance(node_value, yaml.ScalarNode) and not node_value.style): 

+

                    best_style = False 

+

                value.append((node_key, node_value)) 

+

            if flow_style is None: 

+

                if self.default_flow_style is not None: 

+

                    node.flow_style = self.default_flow_style 

+

                else: 

+

                    node.flow_style = best_style 

+

            return node 

+

 

+

    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/htmlcov/rest_framework_utils_formatting.html b/htmlcov/rest_framework_utils_formatting.html new file mode 100644 index 00000000..54e1570f --- /dev/null +++ b/htmlcov/rest_framework_utils_formatting.html @@ -0,0 +1,241 @@ + + + + + + + + Coverage for rest_framework/utils/formatting: 97% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+ +
+

""" 

+

Utility functions to return a formatted name and description for a given view. 

+

""" 

+

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 

+

import re 

+

 

+

 

+

def _remove_trailing_string(content, trailing): 

+

    """ 

+

    Strip trailing component `trailing` from `content` if it exists. 

+

    Used when generating names from view classes. 

+

    """ 

+

    if content.endswith(trailing) and content != trailing: 

+

        return content[:-len(trailing)] 

+

    return content 

+

 

+

 

+

def _remove_leading_indent(content): 

+

    """ 

+

    Remove leading indent from a block of text. 

+

    Used when generating descriptions from docstrings. 

+

    """ 

+

    whitespace_counts = [len(line) - len(line.lstrip(' ')) 

+

                         for line in content.splitlines()[1:] if line.lstrip()] 

+

 

+

    # unindent the content if needed 

+

    if whitespace_counts: 

+

        whitespace_pattern = '^' + (' ' * min(whitespace_counts)) 

+

        content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content) 

+

    content = content.strip('\n') 

+

    return content 

+

 

+

 

+

def _camelcase_to_spaces(content): 

+

    """ 

+

    Translate 'CamelCaseNames' to 'Camel Case Names'. 

+

    Used when generating names from view classes. 

+

    """ 

+

    camelcase_boundry = '(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))' 

+

    content = re.sub(camelcase_boundry, ' \\1', content).strip() 

+

    return ' '.join(content.split('_')).title() 

+

 

+

 

+

def get_view_name(cls, suffix=None): 

+

    """ 

+

    Return a formatted name for an `APIView` class or `@api_view` function. 

+

    """ 

+

    name = cls.__name__ 

+

    name = _remove_trailing_string(name, 'View') 

+

    name = _remove_trailing_string(name, 'ViewSet') 

+

    name = _camelcase_to_spaces(name) 

+

    if suffix: 

+

        name += ' ' + suffix 

+

    return name 

+

 

+

 

+

def get_view_description(cls, html=False): 

+

    """ 

+

    Return a description for an `APIView` class or `@api_view` function. 

+

    """ 

+

    description = cls.__doc__ or '' 

+

    description = _remove_leading_indent(description) 

+

    if html: 

+

        return markup_description(description) 

+

    return description 

+

 

+

 

+

def markup_description(description): 

+

    """ 

+

    Apply HTML markup to the given description. 

+

    """ 

+

    if apply_markdown: 

+

        description = apply_markdown(description) 

+

    else: 

+

        description = escape(description).replace('\n', '<br />') 

+

    return mark_safe(description) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_utils_mediatypes.html b/htmlcov/rest_framework_utils_mediatypes.html new file mode 100644 index 00000000..2ce44ab5 --- /dev/null +++ b/htmlcov/rest_framework_utils_mediatypes.html @@ -0,0 +1,257 @@ + + + + + + + + Coverage for rest_framework/utils/mediatypes: 77% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+ +
+

""" 

+

Handling of media types, as found in HTTP Content-Type and Accept headers. 

+

 

+

See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 

+

""" 

+

from __future__ import unicode_literals 

+

from django.http.multipartparser import parse_header 

+

from rest_framework import HTTP_HEADER_ENCODING 

+

 

+

 

+

def media_type_matches(lhs, rhs): 

+

    """ 

+

    Returns ``True`` if the media type in the first argument <= the 

+

    media type in the second argument.  The media types are strings 

+

    as described by the HTTP spec. 

+

 

+

    Valid media type strings include: 

+

 

+

    'application/json; indent=4' 

+

    'application/json' 

+

    'text/*' 

+

    '*/*' 

+

    """ 

+

    lhs = _MediaType(lhs) 

+

    rhs = _MediaType(rhs) 

+

    return lhs.match(rhs) 

+

 

+

 

+

def order_by_precedence(media_type_lst): 

+

    """ 

+

    Returns a list of sets of media type strings, ordered by precedence. 

+

    Precedence is determined by how specific a media type is: 

+

 

+

    3. 'type/subtype; param=val' 

+

    2. 'type/subtype' 

+

    1. 'type/*' 

+

    0. '*/*' 

+

    """ 

+

    ret = [set(), set(), set(), set()] 

+

    for media_type in media_type_lst: 

+

        precedence = _MediaType(media_type).precedence 

+

        ret[3 - precedence].add(media_type) 

+

    return [media_types for media_types in ret if media_types] 

+

 

+

 

+

class _MediaType(object): 

+

    def __init__(self, media_type_str): 

+

        if media_type_str is None: 

+

            media_type_str = '' 

+

        self.orig = media_type_str 

+

        self.full_type, self.params = parse_header(media_type_str.encode(HTTP_HEADER_ENCODING)) 

+

        self.main_type, sep, self.sub_type = self.full_type.partition('/') 

+

 

+

    def match(self, other): 

+

        """Return true if this MediaType satisfies the given MediaType.""" 

+

        for key in self.params.keys(): 

+

            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: 

+

            return False 

+

 

+

        if self.main_type != '*' and other.main_type != '*' and other.main_type != self.main_type: 

+

            return False 

+

 

+

        return True 

+

 

+

    @property 

+

    def precedence(self): 

+

        """ 

+

        Return a precedence level from 0-3 for the media type given how specific it is. 

+

        """ 

+

        if self.main_type == '*': 

+

            return 0 

+

        elif self.sub_type == '*': 

+

            return 1 

+

        elif not self.params or self.params.keys() == ['q']: 

+

            return 2 

+

        return 3 

+

 

+

    def __str__(self): 

+

        return unicode(self).encode('utf-8') 

+

 

+

    def __unicode__(self): 

+

        ret = "%s/%s" % (self.main_type, self.sub_type) 

+

        for key, val in self.params.items(): 

+

            ret += "; %s=%s" % (key, val) 

+

        return ret 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_views.html b/htmlcov/rest_framework_views.html new file mode 100644 index 00000000..f836e71f --- /dev/null +++ b/htmlcov/rest_framework_views.html @@ -0,0 +1,793 @@ + + + + + + + + Coverage for rest_framework/views: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+

344

+

345

+

346

+

347

+

348

+

349

+

350

+

351

+

352

+

353

+

354

+

355

+

356

+ +
+

""" 

+

Provides an APIView class that is the base of all views in REST framework. 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

from django.core.exceptions import PermissionDenied 

+

from django.http import Http404, HttpResponse 

+

from django.utils.datastructures import SortedDict 

+

from django.views.decorators.csrf import csrf_exempt 

+

from rest_framework import status, exceptions 

+

from rest_framework.compat import View 

+

from rest_framework.request import Request 

+

from rest_framework.response import Response 

+

from rest_framework.settings import api_settings 

+

from rest_framework.utils.formatting import get_view_name, get_view_description 

+

 

+

 

+

class APIView(View): 

+

    settings = api_settings 

+

 

+

    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES 

+

    parser_classes = api_settings.DEFAULT_PARSER_CLASSES 

+

    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES 

+

    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES 

+

    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES 

+

    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS 

+

 

+

    @classmethod 

+

    def as_view(cls, **initkwargs): 

+

        """ 

+

        Store the original class on the view function. 

+

 

+

        This allows us to discover information about the view when we do URL 

+

        reverse lookups.  Used for breadcrumb generation. 

+

        """ 

+

        view = super(APIView, cls).as_view(**initkwargs) 

+

        view.cls = cls 

+

        return view 

+

 

+

    @property 

+

    def allowed_methods(self): 

+

        """ 

+

        Wrap Django's private `_allowed_methods` interface in a public property. 

+

        """ 

+

        return self._allowed_methods() 

+

 

+

    @property 

+

    def default_response_headers(self): 

+

        # TODO: deprecate? 

+

        # TODO: Only vary by accept if multiple renderers 

+

        return { 

+

            'Allow': ', '.join(self.allowed_methods), 

+

            'Vary': 'Accept' 

+

        } 

+

 

+

    def http_method_not_allowed(self, request, *args, **kwargs): 

+

        """ 

+

        If `request.method` does not correspond to a handler method, 

+

        determine what kind of exception to raise. 

+

        """ 

+

        raise exceptions.MethodNotAllowed(request.method) 

+

 

+

    def permission_denied(self, request): 

+

        """ 

+

        If request is not permitted, determine what kind of exception to raise. 

+

        """ 

+

        if not self.request.successful_authenticator: 

+

            raise exceptions.NotAuthenticated() 

+

        raise exceptions.PermissionDenied() 

+

 

+

    def throttled(self, request, wait): 

+

        """ 

+

        If request is throttled, determine what kind of exception to raise. 

+

        """ 

+

        raise exceptions.Throttled(wait) 

+

 

+

    def get_authenticate_header(self, request): 

+

        """ 

+

        If a request is unauthenticated, determine the WWW-Authenticate 

+

        header to use for 401 responses, if any. 

+

        """ 

+

        authenticators = self.get_authenticators() 

+

        if authenticators: 

+

            return authenticators[0].authenticate_header(request) 

+

 

+

    def get_parser_context(self, http_request): 

+

        """ 

+

        Returns a dict that is passed through to Parser.parse(), 

+

        as the `parser_context` keyword argument. 

+

        """ 

+

        # Note: Additionally `request` will also be added to the context 

+

        #       by the Request object. 

+

        return { 

+

            'view': self, 

+

            'args': getattr(self, 'args', ()), 

+

            'kwargs': getattr(self, 'kwargs', {}) 

+

        } 

+

 

+

    def get_renderer_context(self): 

+

        """ 

+

        Returns a dict that is passed through to Renderer.render(), 

+

        as the `renderer_context` keyword argument. 

+

        """ 

+

        # Note: Additionally 'response' will also be added to the context, 

+

        #       by the Response object. 

+

        return { 

+

            'view': self, 

+

            'args': getattr(self, 'args', ()), 

+

            'kwargs': getattr(self, 'kwargs', {}), 

+

            'request': getattr(self, 'request', None) 

+

        } 

+

 

+

    # API policy instantiation methods 

+

 

+

    def get_format_suffix(self, **kwargs): 

+

        """ 

+

        Determine if the request includes a '.json' style format suffix 

+

        """ 

+

        if self.settings.FORMAT_SUFFIX_KWARG: 

+

            return kwargs.get(self.settings.FORMAT_SUFFIX_KWARG) 

+

 

+

    def get_renderers(self): 

+

        """ 

+

        Instantiates and returns the list of renderers that this view can use. 

+

        """ 

+

        return [renderer() for renderer in self.renderer_classes] 

+

 

+

    def get_parsers(self): 

+

        """ 

+

        Instantiates and returns the list of parsers that this view can use. 

+

        """ 

+

        return [parser() for parser in self.parser_classes] 

+

 

+

    def get_authenticators(self): 

+

        """ 

+

        Instantiates and returns the list of authenticators that this view can use. 

+

        """ 

+

        return [auth() for auth in self.authentication_classes] 

+

 

+

    def get_permissions(self): 

+

        """ 

+

        Instantiates and returns the list of permissions that this view requires. 

+

        """ 

+

        return [permission() for permission in self.permission_classes] 

+

 

+

    def get_throttles(self): 

+

        """ 

+

        Instantiates and returns the list of throttles that this view uses. 

+

        """ 

+

        return [throttle() for throttle in self.throttle_classes] 

+

 

+

    def get_content_negotiator(self): 

+

        """ 

+

        Instantiate and return the content negotiation class to use. 

+

        """ 

+

        if not getattr(self, '_negotiator', None): 

+

            self._negotiator = self.content_negotiation_class() 

+

        return self._negotiator 

+

 

+

    # API policy implementation methods 

+

 

+

    def perform_content_negotiation(self, request, force=False): 

+

        """ 

+

        Determine which renderer and media type to use render the response. 

+

        """ 

+

        renderers = self.get_renderers() 

+

        conneg = self.get_content_negotiator() 

+

 

+

        try: 

+

            return conneg.select_renderer(request, renderers, self.format_kwarg) 

+

        except Exception: 

+

            if force: 

+

                return (renderers[0], renderers[0].media_type) 

+

            raise 

+

 

+

    def perform_authentication(self, request): 

+

        """ 

+

        Perform authentication on the incoming request. 

+

 

+

        Note that if you override this and simply 'pass', then authentication 

+

        will instead be performed lazily, the first time either 

+

        `request.user` or `request.auth` is accessed. 

+

        """ 

+

        request.user 

+

 

+

    def check_permissions(self, request): 

+

        """ 

+

        Check if the request should be permitted. 

+

        Raises an appropriate exception if the request is not permitted. 

+

        """ 

+

        for permission in self.get_permissions(): 

+

            if not permission.has_permission(request, self): 

+

                self.permission_denied(request) 

+

 

+

    def check_object_permissions(self, request, obj): 

+

        """ 

+

        Check if the request should be permitted for a given object. 

+

        Raises an appropriate exception if the request is not permitted. 

+

        """ 

+

        for permission in self.get_permissions(): 

+

            if not permission.has_object_permission(request, self, obj): 

+

                self.permission_denied(request) 

+

 

+

    def check_throttles(self, request): 

+

        """ 

+

        Check if request should be throttled. 

+

        Raises an appropriate exception if the request is throttled. 

+

        """ 

+

        for throttle in self.get_throttles(): 

+

            if not throttle.allow_request(request, self): 

+

                self.throttled(request, throttle.wait()) 

+

 

+

    # Dispatch methods 

+

 

+

    def initialize_request(self, request, *args, **kargs): 

+

        """ 

+

        Returns the initial request object. 

+

        """ 

+

        parser_context = self.get_parser_context(request) 

+

 

+

        return Request(request, 

+

                       parsers=self.get_parsers(), 

+

                       authenticators=self.get_authenticators(), 

+

                       negotiator=self.get_content_negotiator(), 

+

                       parser_context=parser_context) 

+

 

+

    def initial(self, request, *args, **kwargs): 

+

        """ 

+

        Runs anything that needs to occur prior to calling the method handler. 

+

        """ 

+

        self.format_kwarg = self.get_format_suffix(**kwargs) 

+

 

+

        # Ensure that the incoming request is permitted 

+

        self.perform_authentication(request) 

+

        self.check_permissions(request) 

+

        self.check_throttles(request) 

+

 

+

        # Perform content negotiation and store the accepted info on the request 

+

        neg = self.perform_content_negotiation(request) 

+

        request.accepted_renderer, request.accepted_media_type = neg 

+

 

+

    def finalize_response(self, request, response, *args, **kwargs): 

+

        """ 

+

        Returns the final response object. 

+

        """ 

+

        # Make the error obvious if a proper response is not returned 

+

        assert isinstance(response, HttpResponse), ( 

+

            'Expected a `Response` to be returned from the view, ' 

+

            'but received a `%s`' % type(response) 

+

        ) 

+

 

+

        if isinstance(response, Response): 

+

            if not getattr(request, 'accepted_renderer', None): 

+

                neg = self.perform_content_negotiation(request, force=True) 

+

                request.accepted_renderer, request.accepted_media_type = neg 

+

 

+

            response.accepted_renderer = request.accepted_renderer 

+

            response.accepted_media_type = request.accepted_media_type 

+

            response.renderer_context = self.get_renderer_context() 

+

 

+

        for key, value in self.headers.items(): 

+

            response[key] = value 

+

 

+

        return response 

+

 

+

    def handle_exception(self, exc): 

+

        """ 

+

        Handle any exception that occurs, by returning an appropriate response, 

+

        or re-raising the error. 

+

        """ 

+

        if isinstance(exc, exceptions.Throttled): 

+

            # Throttle wait header 

+

            self.headers['X-Throttle-Wait-Seconds'] = '%d' % exc.wait 

+

 

+

        if isinstance(exc, (exceptions.NotAuthenticated, 

+

                            exceptions.AuthenticationFailed)): 

+

            # WWW-Authenticate header for 401 responses, else coerce to 403 

+

            auth_header = self.get_authenticate_header(self.request) 

+

 

+

            if auth_header: 

+

                self.headers['WWW-Authenticate'] = auth_header 

+

            else: 

+

                exc.status_code = status.HTTP_403_FORBIDDEN 

+

 

+

        if isinstance(exc, exceptions.APIException): 

+

            return Response({'detail': exc.detail}, 

+

                            status=exc.status_code, 

+

                            exception=True) 

+

        elif isinstance(exc, Http404): 

+

            return Response({'detail': 'Not found'}, 

+

                            status=status.HTTP_404_NOT_FOUND, 

+

                            exception=True) 

+

        elif isinstance(exc, PermissionDenied): 

+

            return Response({'detail': 'Permission denied'}, 

+

                            status=status.HTTP_403_FORBIDDEN, 

+

                            exception=True) 

+

        raise 

+

 

+

    # Note: session based authentication is explicitly CSRF validated, 

+

    # all other authentication is CSRF exempt. 

+

    @csrf_exempt 

+

    def dispatch(self, request, *args, **kwargs): 

+

        """ 

+

        `.dispatch()` is pretty much the same as Django's regular dispatch, 

+

        but with extra hooks for startup, finalize, and exception handling. 

+

        """ 

+

        self.args = args 

+

        self.kwargs = kwargs 

+

        request = self.initialize_request(request, *args, **kwargs) 

+

        self.request = request 

+

        self.headers = self.default_response_headers  # deprecate? 

+

 

+

        try: 

+

            self.initial(request, *args, **kwargs) 

+

 

+

            # Get the appropriate handler method 

+

            if request.method.lower() in self.http_method_names: 

+

                handler = getattr(self, request.method.lower(), 

+

                                  self.http_method_not_allowed) 

+

            else: 

+

                handler = self.http_method_not_allowed 

+

 

+

            response = handler(request, *args, **kwargs) 

+

 

+

        except Exception as exc: 

+

            response = self.handle_exception(exc) 

+

 

+

        self.response = self.finalize_response(request, response, *args, **kwargs) 

+

        return self.response 

+

 

+

    def options(self, request, *args, **kwargs): 

+

        """ 

+

        Handler method for HTTP 'OPTIONS' request. 

+

        We may as well implement this as Django will otherwise provide 

+

        a less useful default implementation. 

+

        """ 

+

        return Response(self.metadata(request), status=status.HTTP_200_OK) 

+

 

+

    def metadata(self, request): 

+

        """ 

+

        Return a dictionary of metadata about the view. 

+

        Used to return responses for OPTIONS requests. 

+

        """ 

+

 

+

        # This is used by ViewSets to disambiguate instance vs list views 

+

        view_name_suffix = getattr(self, 'suffix', None) 

+

 

+

        # By default we can't provide any form-like information, however the 

+

        # generic views override this implementation and add additional 

+

        # information for POST and PUT methods, based on the serializer. 

+

        ret = SortedDict() 

+

        ret['name'] = get_view_name(self.__class__, view_name_suffix) 

+

        ret['description'] = get_view_description(self.__class__) 

+

        ret['renders'] = [renderer.media_type for renderer in self.renderer_classes] 

+

        ret['parses'] = [parser.media_type for parser in self.parser_classes] 

+

        return ret 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_viewsets.html b/htmlcov/rest_framework_viewsets.html new file mode 100644 index 00000000..8264ddc0 --- /dev/null +++ b/htmlcov/rest_framework_viewsets.html @@ -0,0 +1,359 @@ + + + + + + + + Coverage for rest_framework/viewsets: 95% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+ +
+

""" 

+

ViewSets are essentially just a type of class based view, that doesn't provide 

+

any method handlers, such as `get()`, `post()`, etc... but instead has actions, 

+

such as `list()`, `retrieve()`, `create()`, etc... 

+

 

+

Actions are only bound to methods at the point of instantiating the views. 

+

 

+

    user_list = UserViewSet.as_view({'get': 'list'}) 

+

    user_detail = UserViewSet.as_view({'get': 'retrieve'}) 

+

 

+

Typically, rather than instantiate views from viewsets directly, you'll 

+

regsiter the viewset with a router and let the URL conf be determined 

+

automatically. 

+

 

+

    router = DefaultRouter() 

+

    router.register(r'users', UserViewSet, 'user') 

+

    urlpatterns = router.urls 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

from functools import update_wrapper 

+

from django.utils.decorators import classonlymethod 

+

from rest_framework import views, generics, mixins 

+

 

+

 

+

class ViewSetMixin(object): 

+

    """ 

+

    This is the magic. 

+

 

+

    Overrides `.as_view()` so that it takes an `actions` keyword that performs 

+

    the binding of HTTP methods to actions on the Resource. 

+

 

+

    For example, to create a concrete view binding the 'GET' and 'POST' methods 

+

    to the 'list' and 'create' actions... 

+

 

+

    view = MyViewSet.as_view({'get': 'list', 'post': 'create'}) 

+

    """ 

+

 

+

    @classonlymethod 

+

    def as_view(cls, actions=None, **initkwargs): 

+

        """ 

+

        Because of the way class based views create a closure around the 

+

        instantiated view, we need to totally reimplement `.as_view`, 

+

        and slightly modify the view function that is created and returned. 

+

        """ 

+

        # The suffix initkwarg is reserved for identifing the viewset type 

+

        # eg. 'List' or 'Instance'. 

+

        cls.suffix = None 

+

 

+

        # 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) 

+

            # We also store the mapping of request methods to actions, 

+

            # so that we can later set the action attribute. 

+

            # eg. `self.action = 'list'` on an incoming GET request. 

+

            self.action_map = actions 

+

 

+

            # Bind methods to actions 

+

            # This is the bit that's different to a standard view 

+

            for method, action in actions.items(): 

+

                handler = getattr(self, action) 

+

                setattr(self, method, handler) 

+

 

+

            # Patch this in as it's otherwise only present from 1.5 onwards 

+

            if hasattr(self, 'get') and not hasattr(self, 'head'): 

+

                self.head = self.get 

+

 

+

            # And continue as usual 

+

            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=()) 

+

 

+

        # We need to set these on the view function, so that breadcrumb 

+

        # generation can pick out these bits of information from a 

+

        # resolved URL. 

+

        view.cls = cls 

+

        view.suffix = initkwargs.get('suffix', None) 

+

        return view 

+

 

+

    def initialize_request(self, request, *args, **kargs): 

+

        """ 

+

        Set the `.action` attribute on the view, 

+

        depending on the request method. 

+

        """ 

+

        request = super(ViewSetMixin, self).initialize_request(request, *args, **kargs) 

+

        self.action = self.action_map.get(request.method.lower()) 

+

        return request 

+

 

+

 

+

class ViewSet(ViewSetMixin, views.APIView): 

+

    """ 

+

    The base ViewSet class does not provide any actions by default. 

+

    """ 

+

    pass 

+

 

+

 

+

class GenericViewSet(ViewSetMixin, generics.GenericAPIView): 

+

    """ 

+

    The GenericViewSet class does not provide any actions by default, 

+

    but does include the base set of generic view behavior, such as 

+

    the `get_object` and `get_queryset` methods. 

+

    """ 

+

    pass 

+

 

+

 

+

class ReadOnlyModelViewSet(mixins.RetrieveModelMixin, 

+

                           mixins.ListModelMixin, 

+

                           GenericViewSet): 

+

    """ 

+

    A viewset that provides default `list()` and `retrieve()` actions. 

+

    """ 

+

    pass 

+

 

+

 

+

class ModelViewSet(mixins.CreateModelMixin, 

+

                    mixins.RetrieveModelMixin, 

+

                    mixins.UpdateModelMixin, 

+

                    mixins.DestroyModelMixin, 

+

                    mixins.ListModelMixin, 

+

                    GenericViewSet): 

+

    """ 

+

    A viewset that provides default `create()`, `retrieve()`, `update()`, 

+

    `partial_update()`, `destroy()` and `list()` actions. 

+

    """ 

+

    pass 

+ +
+
+ + + + + diff --git a/htmlcov/status.dat b/htmlcov/status.dat new file mode 100644 index 00000000..9e448e37 --- /dev/null +++ b/htmlcov/status.dat @@ -0,0 +1,1258 @@ +(dp1 +S'files' +p2 +(dp3 +S'rest_framework_utils_encoders' +p4 +(dp5 +S'index' +p6 +(dp7 +S'par' +p8 +I0 +sS'html_filename' +p9 +S'rest_framework_utils_encoders.html' +p10 +sS'name' +p11 +S'rest_framework/utils/encoders' +p12 +sS'nums' +p13 +ccopy_reg +_reconstructor +p14 +(ccoverage.results +Numbers +p15 +c__builtin__ +object +p16 +NtRp17 +(dp18 +S'n_files' +p19 +I1 +sS'n_branches' +p20 +I0 +sS'n_statements' +p21 +I70 +sS'n_excluded' +p22 +I0 +sS'n_missing' +p23 +I19 +sS'n_missing_branches' +p24 +I0 +sbssS'hash' +p25 +S'\x9d|\xea|-p\x0c#\xef\x82\x8c\x91\xf1\xcd}f' +p26 +ssS'rest_framework___init__' +p27 +(dp28 +g6 +(dp29 +g8 +I0 +sg9 +S'rest_framework___init__.html' +p30 +sg11 +S'rest_framework/__init__' +p31 +sg13 +g14 +(g15 +g16 +NtRp32 +(dp33 +g19 +I1 +sg20 +I0 +sg21 +I4 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'\xbc\xf1f\xd9[\xd4\xcf\x9cQ\x94H\xfd3)\xea[' +p34 +ssS'rest_framework_urlpatterns' +p35 +(dp36 +g6 +(dp37 +g8 +I0 +sg9 +S'rest_framework_urlpatterns.html' +p38 +sg11 +S'rest_framework/urlpatterns' +p39 +sg13 +g14 +(g15 +g16 +NtRp40 +(dp41 +g19 +I1 +sg20 +I0 +sg21 +I31 +sg22 +I0 +sg23 +I4 +sg24 +I0 +sbssg25 +S"\x84\xb0-\xc6Y\x7f\xebA'\x8c5+\xcf\xf6\xcf\xda" +p42 +ssS'rest_framework_permissions' +p43 +(dp44 +g6 +(dp45 +g8 +I0 +sg9 +S'rest_framework_permissions.html' +p46 +sg11 +S'rest_framework/permissions' +p47 +sg13 +g14 +(g15 +g16 +NtRp48 +(dp49 +g19 +I1 +sg20 +I0 +sg21 +I63 +sg22 +I0 +sg23 +I12 +sg24 +I0 +sbssg25 +S",\xc4,\xda\x05\x86\x17\xe8u2~ls*'\xc1" +p50 +ssS'rest_framework_fields' +p51 +(dp52 +g6 +(dp53 +g8 +I0 +sg9 +S'rest_framework_fields.html' +p54 +sg11 +S'rest_framework/fields' +p55 +sg13 +g14 +(g15 +g16 +NtRp56 +(dp57 +g19 +I1 +sg20 +I0 +sg21 +I594 +sg22 +I0 +sg23 +I80 +sg24 +I0 +sbssg25 +S'\x08\x1b\xd2m\x91l\x14e\x97CDA\x1c&k\xf9' +p58 +ssS'rest_framework_models' +p59 +(dp60 +g6 +(dp61 +g8 +I0 +sg9 +S'rest_framework_models.html' +p62 +sg11 +S'rest_framework/models' +p63 +sg13 +g14 +(g15 +g16 +NtRp64 +(dp65 +g19 +I1 +sg20 +I0 +sg21 +I0 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S' E\xaf\xdd\xe7\xbb\xc4\x11z\xf8\x80\x18v.\xec\xf6' +p66 +ssS'rest_framework_utils_breadcrumbs' +p67 +(dp68 +g6 +(dp69 +g8 +I0 +sg9 +S'rest_framework_utils_breadcrumbs.html' +p70 +sg11 +S'rest_framework/utils/breadcrumbs' +p71 +sg13 +g14 +(g15 +g16 +NtRp72 +(dp73 +g19 +I1 +sg20 +I0 +sg21 +I27 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'V"\xf6\xbc\\m)\x12R4>c\xff\xea\xde\x8b' +p74 +ssS'rest_framework_urls' +p75 +(dp76 +g6 +(dp77 +g8 +I0 +sg9 +S'rest_framework_urls.html' +p78 +sg11 +S'rest_framework/urls' +p79 +sg13 +g14 +(g15 +g16 +NtRp80 +(dp81 +g19 +I1 +sg20 +I0 +sg21 +I4 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'\xba\x9b\xdaeu\x17\x8b\xe0e\xc6-\xc5R\xba\xa2\xd5' +p82 +ssS'rest_framework_serializers' +p83 +(dp84 +g6 +(dp85 +g8 +I0 +sg9 +S'rest_framework_serializers.html' +p86 +sg11 +S'rest_framework/serializers' +p87 +sg13 +g14 +(g15 +g16 +NtRp88 +(dp89 +g19 +I1 +sg20 +I0 +sg21 +I464 +sg22 +I0 +sg23 +I27 +sg24 +I0 +sbssg25 +S'O\\\xf6\x81y\x95\xae\x9a)\xe9~\xb8\xab\t\x88#' +p90 +ssS'rest_framework_exceptions' +p91 +(dp92 +g6 +(dp93 +g8 +I0 +sg9 +S'rest_framework_exceptions.html' +p94 +sg11 +S'rest_framework/exceptions' +p95 +sg13 +g14 +(g15 +g16 +NtRp96 +(dp97 +g19 +I1 +sg20 +I0 +sg21 +I51 +sg22 +I0 +sg23 +I2 +sg24 +I0 +sbssg25 +S'\xdd\xcaE\x12\x1f4V\xe6\x91\x11\xef:T\xe1r\xca' +p98 +ssS'rest_framework_status' +p99 +(dp100 +g6 +(dp101 +g8 +I0 +sg9 +S'rest_framework_status.html' +p102 +sg11 +S'rest_framework/status' +p103 +sg13 +g14 +(g15 +g16 +NtRp104 +(dp105 +g19 +I1 +sg20 +I0 +sg21 +I46 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'\x97z\xcd\xfd\xdc\x0c\xe3\xa9j\x04\xab\x13]\x98\xbf\x80' +p106 +ssS'rest_framework_relations' +p107 +(dp108 +g6 +(dp109 +g8 +I0 +sg9 +S'rest_framework_relations.html' +p110 +sg11 +S'rest_framework/relations' +p111 +sg13 +g14 +(g15 +g16 +NtRp112 +(dp113 +g19 +I1 +sg20 +I0 +sg21 +I365 +sg22 +I0 +sg23 +I88 +sg24 +I0 +sbssg25 +S'\xdb"\xfe\xc2\xb3\x8a\xe2(\xbeoNk\x1b\xd3H9' +p114 +ssS'rest_framework_authtoken_views' +p115 +(dp116 +g6 +(dp117 +g8 +I0 +sg9 +S'rest_framework_authtoken_views.html' +p118 +sg11 +S'rest_framework/authtoken/views' +p119 +sg13 +g14 +(g15 +g16 +NtRp120 +(dp121 +g19 +I1 +sg20 +I0 +sg21 +I21 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'\xb8A\x13\xee\xfc9\x8b\x1eY-\xad\x00\xa7\x9dH]' +p122 +ssS'rest_framework_mixins' +p123 +(dp124 +g6 +(dp125 +g8 +I0 +sg9 +S'rest_framework_mixins.html' +p126 +sg11 +S'rest_framework/mixins' +p127 +sg13 +g14 +(g15 +g16 +NtRp128 +(dp129 +g19 +I1 +sg20 +I0 +sg21 +I97 +sg22 +I0 +sg23 +I7 +sg24 +I0 +sbssg25 +S'\xcd\xe5\x9f\xc2\xbb\xd9\xcb\x14*\x88\x99\xe8\xdf\xd2\xa8\xd6' +p130 +ssS'rest_framework_views' +p131 +(dp132 +g6 +(dp133 +g8 +I0 +sg9 +S'rest_framework_views.html' +p134 +sg11 +S'rest_framework/views' +p135 +sg13 +g14 +(g15 +g16 +NtRp136 +(dp137 +g19 +I1 +sg20 +I0 +sg21 +I146 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'ZBo\x84oh^\x1f\x8c\x94Mp$\xf3\xd2\xa1' +p138 +ssS'rest_framework_generics' +p139 +(dp140 +g6 +(dp141 +g8 +I0 +sg9 +S'rest_framework_generics.html' +p142 +sg11 +S'rest_framework/generics' +p143 +sg13 +g14 +(g15 +g16 +NtRp144 +(dp145 +g19 +I1 +sg20 +I0 +sg21 +I196 +sg22 +I0 +sg23 +I34 +sg24 +I0 +sbssg25 +S'@\x1c\x97\x176\x18\x9c\xfc"| |\xb8^\xbb\x83' +p146 +ssS'rest_framework_utils___init__' +p147 +(dp148 +g6 +(dp149 +g8 +I0 +sg9 +S'rest_framework_utils___init__.html' +p150 +sg11 +S'rest_framework/utils/__init__' +p151 +sg13 +g14 +(g15 +g16 +NtRp152 +(dp153 +g19 +I1 +sg20 +I0 +sg21 +I0 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'\xb0\xc8pN\xaf>\xa0\xbaz\x144\xe0A9\xb8?' +p154 +ssS'rest_framework_renderers' +p155 +(dp156 +g6 +(dp157 +g8 +I0 +sg9 +S'rest_framework_renderers.html' +p158 +sg11 +S'rest_framework/renderers' +p159 +sg13 +g14 +(g15 +g16 +NtRp160 +(dp161 +g19 +I1 +sg20 +I0 +sg21 +I282 +sg22 +I0 +sg23 +I23 +sg24 +I0 +sbssg25 +S'\t\x11\xd4\xafO\xae\\*\x8d\xaf\xa4f\xde\x86\xe8N' +p162 +ssS'rest_framework_negotiation' +p163 +(dp164 +g6 +(dp165 +g8 +I0 +sg9 +S'rest_framework_negotiation.html' +p166 +sg11 +S'rest_framework/negotiation' +p167 +sg13 +g14 +(g15 +g16 +NtRp168 +(dp169 +g19 +I1 +sg20 +I0 +sg21 +I41 +sg22 +I0 +sg23 +I4 +sg24 +I0 +sbssg25 +S'\xd2\xa2\x94\xc8}y\xba\x9eZE\xe5M\xa5>\x9f\x8d' +p170 +ssS'rest_framework_throttling' +p171 +(dp172 +g6 +(dp173 +g8 +I0 +sg9 +S'rest_framework_throttling.html' +p174 +sg11 +S'rest_framework/throttling' +p175 +sg13 +g14 +(g15 +g16 +NtRp176 +(dp177 +g19 +I1 +sg20 +I0 +sg21 +I90 +sg22 +I0 +sg23 +I17 +sg24 +I0 +sbssg25 +S'a\xbcT\xe7\xff\x1an\xb5\x886\xa3\xa2e\x90PZ' +p178 +ssS'rest_framework_reverse' +p179 +(dp180 +g6 +(dp181 +g8 +I0 +sg9 +S'rest_framework_reverse.html' +p182 +sg11 +S'rest_framework/reverse' +p183 +sg13 +g14 +(g15 +g16 +NtRp184 +(dp185 +g19 +I1 +sg20 +I0 +sg21 +I12 +sg22 +I0 +sg23 +I3 +sg24 +I0 +sbssg25 +S"#\xe7D\x01\x10\xe8'1\x9c\xc9yX4\xb4\xef\x19" +p186 +ssS'rest_framework_request' +p187 +(dp188 +g6 +(dp189 +g8 +I0 +sg9 +S'rest_framework_request.html' +p190 +sg11 +S'rest_framework/request' +p191 +sg13 +g14 +(g15 +g16 +NtRp192 +(dp193 +g19 +I1 +sg20 +I0 +sg21 +I161 +sg22 +I0 +sg23 +I8 +sg24 +I0 +sbssg25 +S'C\xd4v\x9b\xf2Z\xe47\xe8\xc8\x03\xf4\xf8\xac\xefs' +p194 +ssS'rest_framework_parsers' +p195 +(dp196 +g6 +(dp197 +g8 +I0 +sg9 +S'rest_framework_parsers.html' +p198 +sg11 +S'rest_framework/parsers' +p199 +sg13 +g14 +(g15 +g16 +NtRp200 +(dp201 +g19 +I1 +sg20 +I0 +sg21 +I153 +sg22 +I0 +sg23 +I13 +sg24 +I0 +sbssg25 +S'\x11o\x05[\x99{\x9c\x8bj\xa8\xd0t\xe8\x16\\\xae' +p202 +ssS'rest_framework_settings' +p203 +(dp204 +g6 +(dp205 +g8 +I0 +sg9 +S'rest_framework_settings.html' +p206 +sg11 +S'rest_framework/settings' +p207 +sg13 +g14 +(g15 +g16 +NtRp208 +(dp209 +g19 +I1 +sg20 +I0 +sg21 +I44 +sg22 +I0 +sg23 +I2 +sg24 +I0 +sbssg25 +S'\n\xb8|\x03\xa7d|\xfc9\xda\xb5\xb9\x1a\x00@\xc3' +p210 +ssS'rest_framework_authtoken_models' +p211 +(dp212 +g6 +(dp213 +g8 +I0 +sg9 +S'rest_framework_authtoken_models.html' +p214 +sg11 +S'rest_framework/authtoken/models' +p215 +sg13 +g14 +(g15 +g16 +NtRp216 +(dp217 +g19 +I1 +sg20 +I0 +sg21 +I21 +sg22 +I0 +sg23 +I1 +sg24 +I0 +sbssg25 +S'U;\xc7\xf5{\xf6r\xc7]\x95\xffF\xde\x8caE' +p218 +ssS'rest_framework_decorators' +p219 +(dp220 +g6 +(dp221 +g8 +I0 +sg9 +S'rest_framework_decorators.html' +p222 +sg11 +S'rest_framework/decorators' +p223 +sg13 +g14 +(g15 +g16 +NtRp224 +(dp225 +g19 +I1 +sg20 +I0 +sg21 +I60 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S"\xd4\x88\xa2\x16\xf4#X\xb4X\xe97Lj\xeb\x16'" +p226 +ssS'rest_framework_authentication' +p227 +(dp228 +g6 +(dp229 +g8 +I0 +sg9 +S'rest_framework_authentication.html' +p230 +sg11 +S'rest_framework/authentication' +p231 +sg13 +g14 +(g15 +g16 +NtRp232 +(dp233 +g19 +I1 +sg20 +I0 +sg21 +I169 +sg22 +I0 +sg23 +I33 +sg24 +I0 +sbssg25 +S'^\x80:,\x1cL\xde\t\xc1\x93\xe0\x8b\x11\xf4\xb8\x06' +p234 +ssS'rest_framework_utils_formatting' +p235 +(dp236 +g6 +(dp237 +g8 +I0 +sg9 +S'rest_framework_utils_formatting.html' +p238 +sg11 +S'rest_framework/utils/formatting' +p239 +sg13 +g14 +(g15 +g16 +NtRp240 +(dp241 +g19 +I1 +sg20 +I0 +sg21 +I39 +sg22 +I0 +sg23 +I1 +sg24 +I0 +sbssg25 +S'\xdd\x05M\xeb\xfe\tl\xe6\xdd\xc5k\xae\xa8\xf9um' +p242 +ssS'rest_framework_pagination' +p243 +(dp244 +g6 +(dp245 +g8 +I0 +sg9 +S'rest_framework_pagination.html' +p246 +sg11 +S'rest_framework/pagination' +p247 +sg13 +g14 +(g15 +g16 +NtRp248 +(dp249 +g19 +I1 +sg20 +I0 +sg21 +I43 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'y\xa8f\rv\x8c\x9b\x9a:9\xdc\x89\t>\x0c' +p282 +ssS'rest_framework_viewsets' +p283 +(dp284 +g6 +(dp285 +g8 +I0 +sg9 +S'rest_framework_viewsets.html' +p286 +sg11 +S'rest_framework/viewsets' +p287 +sg13 +g14 +(g15 +g16 +NtRp288 +(dp289 +g19 +I1 +sg20 +I0 +sg21 +I39 +sg22 +I0 +sg23 +I2 +sg24 +I0 +sbssg25 +S'ic\x82\xc6e\x93$\x1b\x0c\x8bK\x10\x0f9\xe8\n' +p290 +ssS'rest_framework_authtoken___init__' +p291 +(dp292 +g6 +(dp293 +g8 +I0 +sg9 +S'rest_framework_authtoken___init__.html' +p294 +sg11 +S'rest_framework/authtoken/__init__' +p295 +sg13 +g14 +(g15 +g16 +NtRp296 +(dp297 +g19 +I1 +sg20 +I0 +sg21 +I0 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'\xb0\xc8pN\xaf>\xa0\xbaz\x144\xe0A9\xb8?' +p298 +ssS'rest_framework_routers' +p299 +(dp300 +g6 +(dp301 +g8 +I0 +sg9 +S'rest_framework_routers.html' +p302 +sg11 +S'rest_framework/routers' +p303 +sg13 +g14 +(g15 +g16 +NtRp304 +(dp305 +g19 +I1 +sg20 +I0 +sg21 +I108 +sg22 +I0 +sg23 +I7 +sg24 +I0 +sbssg25 +S'i\xa8[\x1f\x0f|\xd6\xa0R\x98\xa9\xecs\xe53\xb3' +p306 +sssS'version' +p307 +S'3.5.1' +p308 +sS'settings' +p309 +S'\xfe\xa4\x01e\x06\x8a\x97H\x97\xaf\xbf\xcd\xfez\xe4\xbf' +p310 +sS'format' +p311 +I1 +s. \ No newline at end of file diff --git a/htmlcov/style.css b/htmlcov/style.css new file mode 100644 index 00000000..c40357b8 --- /dev/null +++ b/htmlcov/style.css @@ -0,0 +1,275 @@ +/* CSS styles for Coverage. */ +/* Page-wide styles */ +html, body, h1, h2, h3, p, td, th { + margin: 0; + padding: 0; + border: 0; + outline: 0; + font-weight: inherit; + font-style: inherit; + font-size: 100%; + font-family: inherit; + vertical-align: baseline; + } + +/* Set baseline grid to 16 pt. */ +body { + font-family: georgia, serif; + font-size: 1em; + } + +html>body { + font-size: 16px; + } + +/* Set base font size to 12/16 */ +p { + font-size: .75em; /* 12/16 */ + line-height: 1.3333em; /* 16/12 */ + } + +table { + border-collapse: collapse; + } + +a.nav { + text-decoration: none; + color: inherit; + } +a.nav:hover { + text-decoration: underline; + color: inherit; + } + +/* Page structure */ +#header { + background: #f8f8f8; + width: 100%; + border-bottom: 1px solid #eee; + } + +#source { + padding: 1em; + font-family: "courier new", monospace; + } + +#indexfile #footer { + margin: 1em 3em; + } + +#pyfile #footer { + margin: 1em 1em; + } + +#footer .content { + padding: 0; + font-size: 85%; + font-family: verdana, sans-serif; + color: #666666; + font-style: italic; + } + +#index { + margin: 1em 0 0 3em; + } + +/* Header styles */ +#header .content { + padding: 1em 3em; + } + +h1 { + font-size: 1.25em; +} + +h2.stats { + margin-top: .5em; + font-size: 1em; +} +.stats span { + border: 1px solid; + padding: .1em .25em; + margin: 0 .1em; + cursor: pointer; + border-color: #999 #ccc #ccc #999; +} +.stats span.hide_run, .stats span.hide_exc, +.stats span.hide_mis, .stats span.hide_par, +.stats span.par.hide_run.hide_par { + border-color: #ccc #999 #999 #ccc; +} +.stats span.par.hide_run { + border-color: #999 #ccc #ccc #999; +} + +/* Help panel */ +#keyboard_icon { + float: right; + cursor: pointer; +} + +.help_panel { + position: absolute; + background: #ffc; + padding: .5em; + border: 1px solid #883; + display: none; +} + +#indexfile .help_panel { + width: 20em; height: 4em; +} + +#pyfile .help_panel { + width: 16em; height: 8em; +} + +.help_panel .legend { + font-style: italic; + margin-bottom: 1em; +} + +#panel_icon { + float: right; + cursor: pointer; +} + +.keyhelp { + margin: .75em; +} + +.keyhelp .key { + border: 1px solid black; + border-color: #888 #333 #333 #888; + padding: .1em .35em; + font-family: monospace; + font-weight: bold; + background: #eee; +} + +/* Source file styles */ +.linenos p { + text-align: right; + margin: 0; + padding: 0 .5em; + color: #999999; + font-family: verdana, sans-serif; + font-size: .625em; /* 10/16 */ + line-height: 1.6em; /* 16/10 */ + } +.linenos p.highlight { + background: #ffdd00; + } +.linenos p a { + text-decoration: none; + color: #999999; + } +.linenos p a:hover { + text-decoration: underline; + color: #999999; + } + +td.text { + width: 100%; + } +.text p { + margin: 0; + padding: 0 0 0 .5em; + border-left: 2px solid #ffffff; + white-space: nowrap; + } + +.text p.mis { + background: #ffdddd; + border-left: 2px solid #ff0000; + } +.text p.run, .text p.run.hide_par { + background: #ddffdd; + border-left: 2px solid #00ff00; + } +.text p.exc { + background: #eeeeee; + border-left: 2px solid #808080; + } +.text p.par, .text p.par.hide_run { + background: #ffffaa; + border-left: 2px solid #eeee99; + } +.text p.hide_run, .text p.hide_exc, .text p.hide_mis, .text p.hide_par, +.text p.hide_run.hide_par { + background: inherit; + } + +.text span.annotate { + font-family: georgia; + font-style: italic; + color: #666; + float: right; + padding-right: .5em; + } +.text p.hide_par span.annotate { + display: none; + } + +/* Syntax coloring */ +.text .com { + color: green; + font-style: italic; + line-height: 1px; + } +.text .key { + font-weight: bold; + line-height: 1px; + } +.text .str { + color: #000080; + } + +/* index styles */ +#index td, #index th { + text-align: right; + width: 5em; + padding: .25em .5em; + border-bottom: 1px solid #eee; + } +#index th { + font-style: italic; + color: #333; + border-bottom: 1px solid #ccc; + cursor: pointer; + } +#index th:hover { + background: #eee; + border-bottom: 1px solid #999; + } +#index td.left, #index th.left { + padding-left: 0; + } +#index td.right, #index th.right { + padding-right: 0; + } +#index th.headerSortDown, #index th.headerSortUp { + border-bottom: 1px solid #000; + } +#index td.name, #index th.name { + text-align: left; + width: auto; + } +#index td.name a { + text-decoration: none; + color: #000; + } +#index td.name a:hover { + text-decoration: underline; + color: #000; + } +#index tr.total { + } +#index tr.total td { + font-weight: bold; + border-top: 1px solid #ccc; + border-bottom: none; + } +#index tr.file:hover { + background: #eeeeee; + } diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py index 291142cf..fe0711fa 100644 --- a/rest_framework/tests/test_routers.py +++ b/rest_framework/tests/test_routers.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models from django.test import TestCase from django.test.client import RequestFactory -from rest_framework import serializers, viewsets +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.response import Response @@ -120,7 +120,7 @@ class TestCustomLookupFields(TestCase): ) -class TestTrailingSlash(TestCase): +class TestTrailingSlashIncluded(TestCase): def setUp(self): class NoteViewSet(viewsets.ModelViewSet): model = RouterTestModel @@ -135,7 +135,7 @@ class TestTrailingSlash(TestCase): self.assertEqual(expected[idx], self.urls[idx].regex.pattern) -class TestTrailingSlash(TestCase): +class TestTrailingSlashRemoved(TestCase): def setUp(self): class NoteViewSet(viewsets.ModelViewSet): model = RouterTestModel @@ -149,6 +149,7 @@ class TestTrailingSlash(TestCase): for idx in range(len(expected)): self.assertEqual(expected[idx], self.urls[idx].regex.pattern) + class TestNameableRoot(TestCase): def setUp(self): class NoteViewSet(viewsets.ModelViewSet): @@ -162,3 +163,31 @@ class TestNameableRoot(TestCase): expected = 'nameable-root' self.assertEqual(expected, self.urls[0].name) + +class TestActionKeywordArgs(TestCase): + """ + Ensure keyword arguments passed in the `@action` decorator + are properly handled. Refs #940. + """ + + def setUp(self): + class TestViewSet(viewsets.ModelViewSet): + permission_classes = [] + + @action(permission_classes=[permissions.AllowAny]) + def custom(self, request, *args, **kwargs): + return Response({ + 'permission_classes': self.permission_classes + }) + + self.router = SimpleRouter() + self.router.register(r'test', TestViewSet, base_name='test') + self.view = self.router.urls[-1].callback + + def test_action_kwargs(self): + request = factory.post('/test/0/custom/') + response = self.view(request) + self.assertEqual( + response.data, + {'permission_classes': [permissions.AllowAny]} + ) -- cgit v1.2.3 From f2e6af89755c34083acb1a5fcd843a480037293f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 22:04:38 +0100 Subject: Remove erronous htmlcov files --- htmlcov/coverage_html.js | 372 ---- htmlcov/index.html | 404 ----- htmlcov/jquery-1.4.3.min.js | 166 -- htmlcov/jquery.hotkeys.js | 99 - htmlcov/jquery.isonscreen.js | 53 - htmlcov/jquery.tablesorter.min.js | 2 - htmlcov/keybd_closed.png | Bin 264 -> 0 bytes htmlcov/keybd_open.png | Bin 267 -> 0 bytes htmlcov/rest_framework___init__.html | 99 - htmlcov/rest_framework_authentication.html | 767 -------- htmlcov/rest_framework_authtoken___init__.html | 81 - htmlcov/rest_framework_authtoken_models.html | 151 -- htmlcov/rest_framework_authtoken_serializers.html | 129 -- htmlcov/rest_framework_authtoken_views.html | 133 -- htmlcov/rest_framework_decorators.html | 339 ---- htmlcov/rest_framework_exceptions.html | 257 --- htmlcov/rest_framework_fields.html | 1991 -------------------- htmlcov/rest_framework_filters.html | 367 ---- htmlcov/rest_framework_generics.html | 1079 ----------- htmlcov/rest_framework_mixins.html | 449 ----- htmlcov/rest_framework_models.html | 83 - htmlcov/rest_framework_negotiation.html | 259 --- htmlcov/rest_framework_pagination.html | 269 --- htmlcov/rest_framework_parsers.html | 671 ------- htmlcov/rest_framework_permissions.html | 429 ----- htmlcov/rest_framework_relations.html | 1347 -------------- htmlcov/rest_framework_renderers.html | 1227 ------------- htmlcov/rest_framework_request.html | 819 --------- htmlcov/rest_framework_response.html | 249 --- htmlcov/rest_framework_reverse.html | 127 -- htmlcov/rest_framework_routers.html | 595 ------ htmlcov/rest_framework_serializers.html | 2011 --------------------- htmlcov/rest_framework_settings.html | 465 ----- htmlcov/rest_framework_status.html | 187 -- htmlcov/rest_framework_throttling.html | 533 ------ htmlcov/rest_framework_urlpatterns.html | 205 --- htmlcov/rest_framework_urls.html | 129 -- htmlcov/rest_framework_utils___init__.html | 81 - htmlcov/rest_framework_utils_breadcrumbs.html | 189 -- htmlcov/rest_framework_utils_encoders.html | 275 --- htmlcov/rest_framework_utils_formatting.html | 241 --- htmlcov/rest_framework_utils_mediatypes.html | 257 --- htmlcov/rest_framework_views.html | 793 -------- htmlcov/rest_framework_viewsets.html | 359 ---- htmlcov/status.dat | 1258 ------------- htmlcov/style.css | 275 --- 46 files changed, 20271 deletions(-) delete mode 100644 htmlcov/coverage_html.js delete mode 100644 htmlcov/index.html delete mode 100644 htmlcov/jquery-1.4.3.min.js delete mode 100644 htmlcov/jquery.hotkeys.js delete mode 100644 htmlcov/jquery.isonscreen.js delete mode 100644 htmlcov/jquery.tablesorter.min.js delete mode 100644 htmlcov/keybd_closed.png delete mode 100644 htmlcov/keybd_open.png delete mode 100644 htmlcov/rest_framework___init__.html delete mode 100644 htmlcov/rest_framework_authentication.html delete mode 100644 htmlcov/rest_framework_authtoken___init__.html delete mode 100644 htmlcov/rest_framework_authtoken_models.html delete mode 100644 htmlcov/rest_framework_authtoken_serializers.html delete mode 100644 htmlcov/rest_framework_authtoken_views.html delete mode 100644 htmlcov/rest_framework_decorators.html delete mode 100644 htmlcov/rest_framework_exceptions.html delete mode 100644 htmlcov/rest_framework_fields.html delete mode 100644 htmlcov/rest_framework_filters.html delete mode 100644 htmlcov/rest_framework_generics.html delete mode 100644 htmlcov/rest_framework_mixins.html delete mode 100644 htmlcov/rest_framework_models.html delete mode 100644 htmlcov/rest_framework_negotiation.html delete mode 100644 htmlcov/rest_framework_pagination.html delete mode 100644 htmlcov/rest_framework_parsers.html delete mode 100644 htmlcov/rest_framework_permissions.html delete mode 100644 htmlcov/rest_framework_relations.html delete mode 100644 htmlcov/rest_framework_renderers.html delete mode 100644 htmlcov/rest_framework_request.html delete mode 100644 htmlcov/rest_framework_response.html delete mode 100644 htmlcov/rest_framework_reverse.html delete mode 100644 htmlcov/rest_framework_routers.html delete mode 100644 htmlcov/rest_framework_serializers.html delete mode 100644 htmlcov/rest_framework_settings.html delete mode 100644 htmlcov/rest_framework_status.html delete mode 100644 htmlcov/rest_framework_throttling.html delete mode 100644 htmlcov/rest_framework_urlpatterns.html delete mode 100644 htmlcov/rest_framework_urls.html delete mode 100644 htmlcov/rest_framework_utils___init__.html delete mode 100644 htmlcov/rest_framework_utils_breadcrumbs.html delete mode 100644 htmlcov/rest_framework_utils_encoders.html delete mode 100644 htmlcov/rest_framework_utils_formatting.html delete mode 100644 htmlcov/rest_framework_utils_mediatypes.html delete mode 100644 htmlcov/rest_framework_views.html delete mode 100644 htmlcov/rest_framework_viewsets.html delete mode 100644 htmlcov/status.dat delete mode 100644 htmlcov/style.css diff --git a/htmlcov/coverage_html.js b/htmlcov/coverage_html.js deleted file mode 100644 index da3e22c8..00000000 --- a/htmlcov/coverage_html.js +++ /dev/null @@ -1,372 +0,0 @@ -// Coverage.py HTML report browser code. -/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */ -/*global coverage: true, document, window, $ */ - -coverage = {}; - -// Find all the elements with shortkey_* class, and use them to assign a shotrtcut key. -coverage.assign_shortkeys = function () { - $("*[class*='shortkey_']").each(function (i, e) { - $.each($(e).attr("class").split(" "), function (i, c) { - if (/^shortkey_/.test(c)) { - $(document).bind('keydown', c.substr(9), function () { - $(e).click(); - }); - } - }); - }); -}; - -// Create the events for the help panel. -coverage.wire_up_help_panel = function () { - $("#keyboard_icon").click(function () { - // Show the help panel, and position it so the keyboard icon in the - // panel is in the same place as the keyboard icon in the header. - $(".help_panel").show(); - var koff = $("#keyboard_icon").offset(); - var poff = $("#panel_icon").position(); - $(".help_panel").offset({ - top: koff.top-poff.top, - left: koff.left-poff.left - }); - }); - $("#panel_icon").click(function () { - $(".help_panel").hide(); - }); -}; - -// Loaded on index.html -coverage.index_ready = function ($) { - // Look for a cookie containing previous sort settings: - var sort_list = []; - var cookie_name = "COVERAGE_INDEX_SORT"; - var i; - - // This almost makes it worth installing the jQuery cookie plugin: - if (document.cookie.indexOf(cookie_name) > -1) { - var cookies = document.cookie.split(";"); - for (i = 0; i < cookies.length; i++) { - var parts = cookies[i].split("="); - - if ($.trim(parts[0]) === cookie_name && parts[1]) { - sort_list = eval("[[" + parts[1] + "]]"); - break; - } - } - } - - // Create a new widget which exists only to save and restore - // the sort order: - $.tablesorter.addWidget({ - id: "persistentSort", - - // Format is called by the widget before displaying: - format: function (table) { - if (table.config.sortList.length === 0 && sort_list.length > 0) { - // This table hasn't been sorted before - we'll use - // our stored settings: - $(table).trigger('sorton', [sort_list]); - } - else { - // This is not the first load - something has - // already defined sorting so we'll just update - // our stored value to match: - sort_list = table.config.sortList; - } - } - }); - - // Configure our tablesorter to handle the variable number of - // columns produced depending on report options: - var headers = []; - var col_count = $("table.index > thead > tr > th").length; - - headers[0] = { sorter: 'text' }; - for (i = 1; i < col_count-1; i++) { - headers[i] = { sorter: 'digit' }; - } - headers[col_count-1] = { sorter: 'percent' }; - - // Enable the table sorter: - $("table.index").tablesorter({ - widgets: ['persistentSort'], - headers: headers - }); - - coverage.assign_shortkeys(); - coverage.wire_up_help_panel(); - - // Watch for page unload events so we can save the final sort settings: - $(window).unload(function () { - document.cookie = cookie_name + "=" + sort_list.toString() + "; path=/"; - }); -}; - -// -- pyfile stuff -- - -coverage.pyfile_ready = function ($) { - // If we're directed to a particular line number, highlight the line. - var frag = location.hash; - if (frag.length > 2 && frag[1] === 'n') { - $(frag).addClass('highlight'); - coverage.set_sel(parseInt(frag.substr(2), 10)); - } - else { - coverage.set_sel(0); - } - - $(document) - .bind('keydown', 'j', coverage.to_next_chunk_nicely) - .bind('keydown', 'k', coverage.to_prev_chunk_nicely) - .bind('keydown', '0', coverage.to_top) - .bind('keydown', '1', coverage.to_first_chunk) - ; - - coverage.assign_shortkeys(); - coverage.wire_up_help_panel(); -}; - -coverage.toggle_lines = function (btn, cls) { - btn = $(btn); - var hide = "hide_"+cls; - if (btn.hasClass(hide)) { - $("#source ."+cls).removeClass(hide); - btn.removeClass(hide); - } - else { - $("#source ."+cls).addClass(hide); - btn.addClass(hide); - } -}; - -// Return the nth line div. -coverage.line_elt = function (n) { - return $("#t" + n); -}; - -// Return the nth line number div. -coverage.num_elt = function (n) { - return $("#n" + n); -}; - -// Return the container of all the code. -coverage.code_container = function () { - return $(".linenos"); -}; - -// Set the selection. b and e are line numbers. -coverage.set_sel = function (b, e) { - // The first line selected. - coverage.sel_begin = b; - // The next line not selected. - coverage.sel_end = (e === undefined) ? b+1 : e; -}; - -coverage.to_top = function () { - coverage.set_sel(0, 1); - coverage.scroll_window(0); -}; - -coverage.to_first_chunk = function () { - coverage.set_sel(0, 1); - coverage.to_next_chunk(); -}; - -coverage.is_transparent = function (color) { - // Different browsers return different colors for "none". - return color === "transparent" || color === "rgba(0, 0, 0, 0)"; -}; - -coverage.to_next_chunk = function () { - var c = coverage; - - // Find the start of the next colored chunk. - var probe = c.sel_end; - while (true) { - var probe_line = c.line_elt(probe); - if (probe_line.length === 0) { - return; - } - var color = probe_line.css("background-color"); - if (!c.is_transparent(color)) { - break; - } - probe++; - } - - // There's a next chunk, `probe` points to it. - var begin = probe; - - // Find the end of this chunk. - var next_color = color; - while (next_color === color) { - probe++; - probe_line = c.line_elt(probe); - next_color = probe_line.css("background-color"); - } - c.set_sel(begin, probe); - c.show_selection(); -}; - -coverage.to_prev_chunk = function () { - var c = coverage; - - // Find the end of the prev colored chunk. - var probe = c.sel_begin-1; - var probe_line = c.line_elt(probe); - if (probe_line.length === 0) { - return; - } - var color = probe_line.css("background-color"); - while (probe > 0 && c.is_transparent(color)) { - probe--; - probe_line = c.line_elt(probe); - if (probe_line.length === 0) { - return; - } - color = probe_line.css("background-color"); - } - - // There's a prev chunk, `probe` points to its last line. - var end = probe+1; - - // Find the beginning of this chunk. - var prev_color = color; - while (prev_color === color) { - probe--; - probe_line = c.line_elt(probe); - prev_color = probe_line.css("background-color"); - } - c.set_sel(probe+1, end); - c.show_selection(); -}; - -// Return the line number of the line nearest pixel position pos -coverage.line_at_pos = function (pos) { - var l1 = coverage.line_elt(1), - l2 = coverage.line_elt(2), - result; - if (l1.length && l2.length) { - var l1_top = l1.offset().top, - line_height = l2.offset().top - l1_top, - nlines = (pos - l1_top) / line_height; - if (nlines < 1) { - result = 1; - } - else { - result = Math.ceil(nlines); - } - } - else { - result = 1; - } - return result; -}; - -// Returns 0, 1, or 2: how many of the two ends of the selection are on -// the screen right now? -coverage.selection_ends_on_screen = function () { - if (coverage.sel_begin === 0) { - return 0; - } - - var top = coverage.line_elt(coverage.sel_begin); - var next = coverage.line_elt(coverage.sel_end-1); - - return ( - (top.isOnScreen() ? 1 : 0) + - (next.isOnScreen() ? 1 : 0) - ); -}; - -coverage.to_next_chunk_nicely = function () { - coverage.finish_scrolling(); - if (coverage.selection_ends_on_screen() === 0) { - // The selection is entirely off the screen: select the top line on - // the screen. - var win = $(window); - coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop())); - } - coverage.to_next_chunk(); -}; - -coverage.to_prev_chunk_nicely = function () { - coverage.finish_scrolling(); - if (coverage.selection_ends_on_screen() === 0) { - var win = $(window); - coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop() + win.height())); - } - coverage.to_prev_chunk(); -}; - -// Select line number lineno, or if it is in a colored chunk, select the -// entire chunk -coverage.select_line_or_chunk = function (lineno) { - var c = coverage; - var probe_line = c.line_elt(lineno); - if (probe_line.length === 0) { - return; - } - var the_color = probe_line.css("background-color"); - if (!c.is_transparent(the_color)) { - // The line is in a highlighted chunk. - // Search backward for the first line. - var probe = lineno; - var color = the_color; - while (probe > 0 && color === the_color) { - probe--; - probe_line = c.line_elt(probe); - if (probe_line.length === 0) { - break; - } - color = probe_line.css("background-color"); - } - var begin = probe + 1; - - // Search forward for the last line. - probe = lineno; - color = the_color; - while (color === the_color) { - probe++; - probe_line = c.line_elt(probe); - color = probe_line.css("background-color"); - } - - coverage.set_sel(begin, probe); - } - else { - coverage.set_sel(lineno); - } -}; - -coverage.show_selection = function () { - var c = coverage; - - // Highlight the lines in the chunk - c.code_container().find(".highlight").removeClass("highlight"); - for (var probe = c.sel_begin; probe > 0 && probe < c.sel_end; probe++) { - c.num_elt(probe).addClass("highlight"); - } - - c.scroll_to_selection(); -}; - -coverage.scroll_to_selection = function () { - // Scroll the page if the chunk isn't fully visible. - if (coverage.selection_ends_on_screen() < 2) { - // Need to move the page. The html,body trick makes it scroll in all - // browsers, got it from http://stackoverflow.com/questions/3042651 - var top = coverage.line_elt(coverage.sel_begin); - var top_pos = parseInt(top.offset().top, 10); - coverage.scroll_window(top_pos - 30); - } -}; - -coverage.scroll_window = function (to_pos) { - $("html,body").animate({scrollTop: to_pos}, 200); -}; - -coverage.finish_scrolling = function () { - $("html,body").stop(true, true); -}; - diff --git a/htmlcov/index.html b/htmlcov/index.html deleted file mode 100644 index 98345165..00000000 --- a/htmlcov/index.html +++ /dev/null @@ -1,404 +0,0 @@ - - - - - Coverage report - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- n - s - m - x - - c   change column sorting -

-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Modulestatementsmissingexcludedcoverage
Total3621406089%
rest_framework/__init__400100%
rest_framework/authentication16933080%
rest_framework/authtoken/__init__000100%
rest_framework/authtoken/models211095%
rest_framework/authtoken/serializers172088%
rest_framework/authtoken/views2100100%
rest_framework/decorators6000100%
rest_framework/exceptions512096%
rest_framework/fields59480087%
rest_framework/filters776092%
rest_framework/generics19634083%
rest_framework/mixins977093%
rest_framework/models000100%
rest_framework/negotiation414090%
rest_framework/pagination4300100%
rest_framework/parsers15313092%
rest_framework/permissions6312081%
rest_framework/relations36588076%
rest_framework/renderers28223092%
rest_framework/request1618095%
rest_framework/response421098%
rest_framework/reverse123075%
rest_framework/routers1087094%
rest_framework/serializers46427094%
rest_framework/settings442095%
rest_framework/status4600100%
rest_framework/throttling9017081%
rest_framework/urlpatterns314087%
rest_framework/urls400100%
rest_framework/utils/__init__000100%
rest_framework/utils/breadcrumbs2700100%
rest_framework/utils/encoders7019073%
rest_framework/utils/formatting391097%
rest_framework/utils/mediatypes4410077%
rest_framework/views14600100%
rest_framework/viewsets392095%
-
- - - - - diff --git a/htmlcov/jquery-1.4.3.min.js b/htmlcov/jquery-1.4.3.min.js deleted file mode 100644 index c941a5f7..00000000 --- a/htmlcov/jquery-1.4.3.min.js +++ /dev/null @@ -1,166 +0,0 @@ -/*! - * jQuery JavaScript Library v1.4.3 - * http://jquery.com/ - * - * Copyright 2010, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2010, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Thu Oct 14 23:10:06 2010 -0400 - */ -(function(E,A){function U(){return false}function ba(){return true}function ja(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function Ga(a){var b,d,e=[],f=[],h,k,l,n,s,v,B,D;k=c.data(this,this.nodeType?"events":"__events__");if(typeof k==="function")k=k.events;if(!(a.liveFired===this||!k||!k.live||a.button&&a.type==="click")){if(a.namespace)D=RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)");a.liveFired=this;var H=k.live.slice(0);for(n=0;nd)break;a.currentTarget=f.elem;a.data=f.handleObj.data; -a.handleObj=f.handleObj;D=f.handleObj.origHandler.apply(f.elem,arguments);if(D===false||a.isPropagationStopped()){d=f.level;if(D===false)b=false}}return b}}function Y(a,b){return(a&&a!=="*"?a+".":"")+b.replace(Ha,"`").replace(Ia,"&")}function ka(a,b,d){if(c.isFunction(b))return c.grep(a,function(f,h){return!!b.call(f,h,f)===d});else if(b.nodeType)return c.grep(a,function(f){return f===b===d});else if(typeof b==="string"){var e=c.grep(a,function(f){return f.nodeType===1});if(Ja.test(b))return c.filter(b, -e,!d);else b=c.filter(b,e)}return c.grep(a,function(f){return c.inArray(f,b)>=0===d})}function la(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var e=c.data(a[d++]),f=c.data(this,e);if(e=e&&e.events){delete f.handle;f.events={};for(var h in e)for(var k in e[h])c.event.add(this,h,e[h][k],e[h][k].data)}}})}function Ka(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)} -function ma(a,b,d){var e=b==="width"?a.offsetWidth:a.offsetHeight;if(d==="border")return e;c.each(b==="width"?La:Ma,function(){d||(e-=parseFloat(c.css(a,"padding"+this))||0);if(d==="margin")e+=parseFloat(c.css(a,"margin"+this))||0;else e-=parseFloat(c.css(a,"border"+this+"Width"))||0});return e}function ca(a,b,d,e){if(c.isArray(b)&&b.length)c.each(b,function(f,h){d||Na.test(a)?e(a,h):ca(a+"["+(typeof h==="object"||c.isArray(h)?f:"")+"]",h,d,e)});else if(!d&&b!=null&&typeof b==="object")c.isEmptyObject(b)? -e(a,""):c.each(b,function(f,h){ca(a+"["+f+"]",h,d,e)});else e(a,b)}function S(a,b){var d={};c.each(na.concat.apply([],na.slice(0,b)),function(){d[this]=a});return d}function oa(a){if(!da[a]){var b=c("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d==="")d="block";da[a]=d}return da[a]}function ea(a){return c.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var u=E.document,c=function(){function a(){if(!b.isReady){try{u.documentElement.doScroll("left")}catch(i){setTimeout(a, -1);return}b.ready()}}var b=function(i,r){return new b.fn.init(i,r)},d=E.jQuery,e=E.$,f,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,k=/\S/,l=/^\s+/,n=/\s+$/,s=/\W/,v=/\d/,B=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,D=/^[\],:{}\s]*$/,H=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,w=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,G=/(?:^|:|,)(?:\s*\[)+/g,M=/(webkit)[ \/]([\w.]+)/,g=/(opera)(?:.*version)?[ \/]([\w.]+)/,j=/(msie) ([\w.]+)/,o=/(mozilla)(?:.*? rv:([\w.]+))?/,m=navigator.userAgent,p=false, -q=[],t,x=Object.prototype.toString,C=Object.prototype.hasOwnProperty,P=Array.prototype.push,N=Array.prototype.slice,R=String.prototype.trim,Q=Array.prototype.indexOf,L={};b.fn=b.prototype={init:function(i,r){var y,z,F;if(!i)return this;if(i.nodeType){this.context=this[0]=i;this.length=1;return this}if(i==="body"&&!r&&u.body){this.context=u;this[0]=u.body;this.selector="body";this.length=1;return this}if(typeof i==="string")if((y=h.exec(i))&&(y[1]||!r))if(y[1]){F=r?r.ownerDocument||r:u;if(z=B.exec(i))if(b.isPlainObject(r)){i= -[u.createElement(z[1])];b.fn.attr.call(i,r,true)}else i=[F.createElement(z[1])];else{z=b.buildFragment([y[1]],[F]);i=(z.cacheable?z.fragment.cloneNode(true):z.fragment).childNodes}return b.merge(this,i)}else{if((z=u.getElementById(y[2]))&&z.parentNode){if(z.id!==y[2])return f.find(i);this.length=1;this[0]=z}this.context=u;this.selector=i;return this}else if(!r&&!s.test(i)){this.selector=i;this.context=u;i=u.getElementsByTagName(i);return b.merge(this,i)}else return!r||r.jquery?(r||f).find(i):b(r).find(i); -else if(b.isFunction(i))return f.ready(i);if(i.selector!==A){this.selector=i.selector;this.context=i.context}return b.makeArray(i,this)},selector:"",jquery:"1.4.3",length:0,size:function(){return this.length},toArray:function(){return N.call(this,0)},get:function(i){return i==null?this.toArray():i<0?this.slice(i)[0]:this[i]},pushStack:function(i,r,y){var z=b();b.isArray(i)?P.apply(z,i):b.merge(z,i);z.prevObject=this;z.context=this.context;if(r==="find")z.selector=this.selector+(this.selector?" ": -"")+y;else if(r)z.selector=this.selector+"."+r+"("+y+")";return z},each:function(i,r){return b.each(this,i,r)},ready:function(i){b.bindReady();if(b.isReady)i.call(u,b);else q&&q.push(i);return this},eq:function(i){return i===-1?this.slice(i):this.slice(i,+i+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(N.apply(this,arguments),"slice",N.call(arguments).join(","))},map:function(i){return this.pushStack(b.map(this,function(r,y){return i.call(r, -y,r)}))},end:function(){return this.prevObject||b(null)},push:P,sort:[].sort,splice:[].splice};b.fn.init.prototype=b.fn;b.extend=b.fn.extend=function(){var i=arguments[0]||{},r=1,y=arguments.length,z=false,F,I,K,J,fa;if(typeof i==="boolean"){z=i;i=arguments[1]||{};r=2}if(typeof i!=="object"&&!b.isFunction(i))i={};if(y===r){i=this;--r}for(;r0)){if(q){for(var r=0;i=q[r++];)i.call(u,b);q=null}b.fn.triggerHandler&&b(u).triggerHandler("ready")}}},bindReady:function(){if(!p){p=true;if(u.readyState==="complete")return setTimeout(b.ready, -1);if(u.addEventListener){u.addEventListener("DOMContentLoaded",t,false);E.addEventListener("load",b.ready,false)}else if(u.attachEvent){u.attachEvent("onreadystatechange",t);E.attachEvent("onload",b.ready);var i=false;try{i=E.frameElement==null}catch(r){}u.documentElement.doScroll&&i&&a()}}},isFunction:function(i){return b.type(i)==="function"},isArray:Array.isArray||function(i){return b.type(i)==="array"},isWindow:function(i){return i&&typeof i==="object"&&"setInterval"in i},isNaN:function(i){return i== -null||!v.test(i)||isNaN(i)},type:function(i){return i==null?String(i):L[x.call(i)]||"object"},isPlainObject:function(i){if(!i||b.type(i)!=="object"||i.nodeType||b.isWindow(i))return false;if(i.constructor&&!C.call(i,"constructor")&&!C.call(i.constructor.prototype,"isPrototypeOf"))return false;for(var r in i);return r===A||C.call(i,r)},isEmptyObject:function(i){for(var r in i)return false;return true},error:function(i){throw i;},parseJSON:function(i){if(typeof i!=="string"||!i)return null;i=b.trim(i); -if(D.test(i.replace(H,"@").replace(w,"]").replace(G,"")))return E.JSON&&E.JSON.parse?E.JSON.parse(i):(new Function("return "+i))();else b.error("Invalid JSON: "+i)},noop:function(){},globalEval:function(i){if(i&&k.test(i)){var r=u.getElementsByTagName("head")[0]||u.documentElement,y=u.createElement("script");y.type="text/javascript";if(b.support.scriptEval)y.appendChild(u.createTextNode(i));else y.text=i;r.insertBefore(y,r.firstChild);r.removeChild(y)}},nodeName:function(i,r){return i.nodeName&&i.nodeName.toUpperCase()=== -r.toUpperCase()},each:function(i,r,y){var z,F=0,I=i.length,K=I===A||b.isFunction(i);if(y)if(K)for(z in i){if(r.apply(i[z],y)===false)break}else for(;F";a=u.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var s=u.createElement("div"); -s.style.width=s.style.paddingLeft="1px";u.body.appendChild(s);c.boxModel=c.support.boxModel=s.offsetWidth===2;if("zoom"in s.style){s.style.display="inline";s.style.zoom=1;c.support.inlineBlockNeedsLayout=s.offsetWidth===2;s.style.display="";s.innerHTML="
";c.support.shrinkWrapBlocks=s.offsetWidth!==2}s.innerHTML="
t
";var v=s.getElementsByTagName("td");c.support.reliableHiddenOffsets=v[0].offsetHeight=== -0;v[0].style.display="";v[1].style.display="none";c.support.reliableHiddenOffsets=c.support.reliableHiddenOffsets&&v[0].offsetHeight===0;s.innerHTML="";u.body.removeChild(s).style.display="none"});a=function(s){var v=u.createElement("div");s="on"+s;var B=s in v;if(!B){v.setAttribute(s,"return;");B=typeof v[s]==="function"}return B};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=f=h=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength", -cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var pa={},Oa=/^(?:\{.*\}|\[.*\])$/;c.extend({cache:{},uuid:0,expando:"jQuery"+c.now(),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},data:function(a,b,d){if(c.acceptData(a)){a=a==E?pa:a;var e=a.nodeType,f=e?a[c.expando]:null,h=c.cache;if(!(e&&!f&&typeof b==="string"&&d===A)){if(e)f||(a[c.expando]=f=++c.uuid);else h=a;if(typeof b==="object")if(e)h[f]= -c.extend(h[f],b);else c.extend(h,b);else if(e&&!h[f])h[f]={};a=e?h[f]:h;if(d!==A)a[b]=d;return typeof b==="string"?a[b]:a}}},removeData:function(a,b){if(c.acceptData(a)){a=a==E?pa:a;var d=a.nodeType,e=d?a[c.expando]:a,f=c.cache,h=d?f[e]:e;if(b){if(h){delete h[b];d&&c.isEmptyObject(h)&&c.removeData(a)}}else if(d&&c.support.deleteExpando)delete a[c.expando];else if(a.removeAttribute)a.removeAttribute(c.expando);else if(d)delete f[e];else for(var k in a)delete a[k]}},acceptData:function(a){if(a.nodeName){var b= -c.noData[a.nodeName.toLowerCase()];if(b)return!(b===true||a.getAttribute("classid")!==b)}return true}});c.fn.extend({data:function(a,b){if(typeof a==="undefined")return this.length?c.data(this[0]):null;else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===A){var e=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(e===A&&this.length){e=c.data(this[0],a);if(e===A&&this[0].nodeType===1){e=this[0].getAttribute("data-"+a);if(typeof e=== -"string")try{e=e==="true"?true:e==="false"?false:e==="null"?null:!c.isNaN(e)?parseFloat(e):Oa.test(e)?c.parseJSON(e):e}catch(f){}else e=A}}return e===A&&d[1]?this.data(d[0]):e}else return this.each(function(){var h=c(this),k=[d[0],b];h.triggerHandler("setData"+d[1]+"!",k);c.data(this,a,b);h.triggerHandler("changeData"+d[1]+"!",k)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var e=c.data(a,b);if(!d)return e|| -[];if(!e||c.isArray(d))e=c.data(a,b,c.makeArray(d));else e.push(d);return e}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),e=d.shift();if(e==="inprogress")e=d.shift();if(e){b==="fx"&&d.unshift("inprogress");e.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===A)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this, -a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var qa=/[\n\t]/g,ga=/\s+/,Pa=/\r/g,Qa=/^(?:href|src|style)$/,Ra=/^(?:button|input)$/i,Sa=/^(?:button|input|object|select|textarea)$/i,Ta=/^a(?:rea)?$/i,ra=/^(?:radio|checkbox)$/i;c.fn.extend({attr:function(a,b){return c.access(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this, -a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(s){var v=c(this);v.addClass(a.call(this,s,v.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ga),d=0,e=this.length;d-1)return true;return false}, -val:function(a){if(!arguments.length){var b=this[0];if(b){if(c.nodeName(b,"option")){var d=b.attributes.value;return!d||d.specified?b.value:b.text}if(c.nodeName(b,"select")){var e=b.selectedIndex;d=[];var f=b.options;b=b.type==="select-one";if(e<0)return null;var h=b?e:0;for(e=b?e+1:f.length;h=0;else if(c.nodeName(this,"select")){var B=c.makeArray(v);c("option",this).each(function(){this.selected= -c.inArray(c(this).val(),B)>=0});if(!B.length)this.selectedIndex=-1}else this.value=v}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,e){if(!a||a.nodeType===3||a.nodeType===8)return A;if(e&&b in c.attrFn)return c(a)[b](d);e=a.nodeType!==1||!c.isXMLDoc(a);var f=d!==A;b=e&&c.props[b]||b;if(a.nodeType===1){var h=Qa.test(b);if((b in a||a[b]!==A)&&e&&!h){if(f){b==="type"&&Ra.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); -if(d===null)a.nodeType===1&&a.removeAttribute(b);else a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:Sa.test(a.nodeName)||Ta.test(a.nodeName)&&a.href?0:A;return a[b]}if(!c.support.style&&e&&b==="style"){if(f)a.style.cssText=""+d;return a.style.cssText}f&&a.setAttribute(b,""+d);if(!a.attributes[b]&&a.hasAttribute&&!a.hasAttribute(b))return A;a=!c.support.hrefNormalized&&e&& -h?a.getAttribute(b,2):a.getAttribute(b);return a===null?A:a}}});var X=/\.(.*)$/,ha=/^(?:textarea|input|select)$/i,Ha=/\./g,Ia=/ /g,Ua=/[^\w\s.|`]/g,Va=function(a){return a.replace(Ua,"\\$&")},sa={focusin:0,focusout:0};c.event={add:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(c.isWindow(a)&&a!==E&&!a.frameElement)a=E;if(d===false)d=U;var f,h;if(d.handler){f=d;d=f.handler}if(!d.guid)d.guid=c.guid++;if(h=c.data(a)){var k=a.nodeType?"events":"__events__",l=h[k],n=h.handle;if(typeof l=== -"function"){n=l.handle;l=l.events}else if(!l){a.nodeType||(h[k]=h=function(){});h.events=l={}}if(!n)h.handle=n=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(n.elem,arguments):A};n.elem=a;b=b.split(" ");for(var s=0,v;k=b[s++];){h=f?c.extend({},f):{handler:d,data:e};if(k.indexOf(".")>-1){v=k.split(".");k=v.shift();h.namespace=v.slice(0).sort().join(".")}else{v=[];h.namespace=""}h.type=k;if(!h.guid)h.guid=d.guid;var B=l[k],D=c.event.special[k]||{};if(!B){B=l[k]=[]; -if(!D.setup||D.setup.call(a,e,v,n)===false)if(a.addEventListener)a.addEventListener(k,n,false);else a.attachEvent&&a.attachEvent("on"+k,n)}if(D.add){D.add.call(a,h);if(!h.handler.guid)h.handler.guid=d.guid}B.push(h);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(d===false)d=U;var f,h,k=0,l,n,s,v,B,D,H=a.nodeType?"events":"__events__",w=c.data(a),G=w&&w[H];if(w&&G){if(typeof G==="function"){w=G;G=G.events}if(b&&b.type){d=b.handler;b=b.type}if(!b|| -typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(f in G)c.event.remove(a,f+b)}else{for(b=b.split(" ");f=b[k++];){v=f;l=f.indexOf(".")<0;n=[];if(!l){n=f.split(".");f=n.shift();s=RegExp("(^|\\.)"+c.map(n.slice(0).sort(),Va).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(B=G[f])if(d){v=c.event.special[f]||{};for(h=e||0;h=0){a.type= -f=f.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[f]&&c.each(c.cache,function(){this.events&&this.events[f]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return A;a.result=A;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(e=d.nodeType?c.data(d,"handle"):(c.data(d,"__events__")||{}).handle)&&e.apply(d,b);e=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+f]&&d["on"+f].apply(d,b)=== -false){a.result=false;a.preventDefault()}}catch(h){}if(!a.isPropagationStopped()&&e)c.event.trigger(a,b,e,true);else if(!a.isDefaultPrevented()){e=a.target;var k,l=f.replace(X,""),n=c.nodeName(e,"a")&&l==="click",s=c.event.special[l]||{};if((!s._default||s._default.call(d,a)===false)&&!n&&!(e&&e.nodeName&&c.noData[e.nodeName.toLowerCase()])){try{if(e[l]){if(k=e["on"+l])e["on"+l]=null;c.event.triggered=true;e[l]()}}catch(v){}if(k)e["on"+l]=k;c.event.triggered=false}}},handle:function(a){var b,d,e; -d=[];var f,h=c.makeArray(arguments);a=h[0]=c.event.fix(a||E.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;if(!b){e=a.type.split(".");a.type=e.shift();d=e.slice(0).sort();e=RegExp("(^|\\.)"+d.join("\\.(?:.*\\.)?")+"(\\.|$)")}a.namespace=a.namespace||d.join(".");f=c.data(this,this.nodeType?"events":"__events__");if(typeof f==="function")f=f.events;d=(f||{})[a.type];if(f&&d){d=d.slice(0);f=0;for(var k=d.length;f-1?c.map(a.options,function(e){return e.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},Z=function(a,b){var d=a.target,e,f;if(!(!ha.test(d.nodeName)||d.readOnly)){e=c.data(d,"_change_data");f=va(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",f);if(!(e===A||f===e))if(e!=null||f){a.type="change";a.liveFired= -A;return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:Z,beforedeactivate:Z,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return Z.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return Z.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,"_change_data",va(a))}},setup:function(){if(this.type=== -"file")return false;for(var a in V)c.event.add(this,a+".specialChange",V[a]);return ha.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return ha.test(this.nodeName)}};V=c.event.special.change.filters;V.focus=V.beforeactivate}u.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(e){e=c.event.fix(e);e.type=b;return c.event.trigger(e,null,e.target)}c.event.special[b]={setup:function(){sa[b]++===0&&u.addEventListener(a,d,true)},teardown:function(){--sa[b]=== -0&&u.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,e,f){if(typeof d==="object"){for(var h in d)this[b](h,e,d[h],f);return this}if(c.isFunction(e)||e===false){f=e;e=A}var k=b==="one"?c.proxy(f,function(n){c(this).unbind(n,k);return f.apply(this,arguments)}):f;if(d==="unload"&&b!=="one")this.one(d,e,f);else{h=0;for(var l=this.length;h0?this.bind(b,d,e):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});E.attachEvent&&!E.addEventListener&&c(E).bind("unload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}}); -(function(){function a(g,j,o,m,p,q){p=0;for(var t=m.length;p0){C=x;break}}x=x[g]}m[p]=C}}}var d=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,h=false,k=true;[0,0].sort(function(){k=false;return 0});var l=function(g,j,o,m){o=o||[];var p=j=j||u;if(j.nodeType!==1&&j.nodeType!==9)return[];if(!g||typeof g!=="string")return o;var q=[],t,x,C,P,N=true,R=l.isXML(j),Q=g,L;do{d.exec("");if(t=d.exec(Q)){Q=t[3];q.push(t[1]);if(t[2]){P=t[3]; -break}}}while(t);if(q.length>1&&s.exec(g))if(q.length===2&&n.relative[q[0]])x=M(q[0]+q[1],j);else for(x=n.relative[q[0]]?[j]:l(q.shift(),j);q.length;){g=q.shift();if(n.relative[g])g+=q.shift();x=M(g,x)}else{if(!m&&q.length>1&&j.nodeType===9&&!R&&n.match.ID.test(q[0])&&!n.match.ID.test(q[q.length-1])){t=l.find(q.shift(),j,R);j=t.expr?l.filter(t.expr,t.set)[0]:t.set[0]}if(j){t=m?{expr:q.pop(),set:D(m)}:l.find(q.pop(),q.length===1&&(q[0]==="~"||q[0]==="+")&&j.parentNode?j.parentNode:j,R);x=t.expr?l.filter(t.expr, -t.set):t.set;if(q.length>0)C=D(x);else N=false;for(;q.length;){t=L=q.pop();if(n.relative[L])t=q.pop();else L="";if(t==null)t=j;n.relative[L](C,t,R)}}else C=[]}C||(C=x);C||l.error(L||g);if(f.call(C)==="[object Array]")if(N)if(j&&j.nodeType===1)for(g=0;C[g]!=null;g++){if(C[g]&&(C[g]===true||C[g].nodeType===1&&l.contains(j,C[g])))o.push(x[g])}else for(g=0;C[g]!=null;g++)C[g]&&C[g].nodeType===1&&o.push(x[g]);else o.push.apply(o,C);else D(C,o);if(P){l(P,p,o,m);l.uniqueSort(o)}return o};l.uniqueSort=function(g){if(w){h= -k;g.sort(w);if(h)for(var j=1;j0};l.find=function(g,j,o){var m;if(!g)return[];for(var p=0,q=n.order.length;p":function(g,j){var o=typeof j==="string",m,p=0,q=g.length;if(o&&!/\W/.test(j))for(j=j.toLowerCase();p=0))o||m.push(t);else if(o)j[q]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},CHILD:function(g){if(g[1]==="nth"){var j=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=j[1]+(j[2]||1)-0;g[3]=j[3]-0}g[0]=e++;return g},ATTR:function(g,j,o, -m,p,q){j=g[1].replace(/\\/g,"");if(!q&&n.attrMap[j])g[1]=n.attrMap[j];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,j,o,m,p){if(g[1]==="not")if((d.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=l(g[3],null,null,j);else{g=l.filter(g[3],j,o,true^p);o||m.push.apply(m,g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled=== -true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,j,o){return!!l(o[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"=== -g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},setFilters:{first:function(g,j){return j===0},last:function(g,j,o,m){return j===m.length-1},even:function(g,j){return j%2===0},odd:function(g,j){return j%2===1},lt:function(g,j,o){return jo[3]-0},nth:function(g,j,o){return o[3]- -0===j},eq:function(g,j,o){return o[3]-0===j}},filter:{PSEUDO:function(g,j,o,m){var p=j[1],q=n.filters[p];if(q)return q(g,o,j,m);else if(p==="contains")return(g.textContent||g.innerText||l.getText([g])||"").indexOf(j[3])>=0;else if(p==="not"){j=j[3];o=0;for(m=j.length;o=0}},ID:function(g,j){return g.nodeType===1&&g.getAttribute("id")===j},TAG:function(g,j){return j==="*"&&g.nodeType===1||g.nodeName.toLowerCase()=== -j},CLASS:function(g,j){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(j)>-1},ATTR:function(g,j){var o=j[1];o=n.attrHandle[o]?n.attrHandle[o](g):g[o]!=null?g[o]:g.getAttribute(o);var m=o+"",p=j[2],q=j[4];return o==null?p==="!=":p==="="?m===q:p==="*="?m.indexOf(q)>=0:p==="~="?(" "+m+" ").indexOf(q)>=0:!q?m&&o!==false:p==="!="?m!==q:p==="^="?m.indexOf(q)===0:p==="$="?m.substr(m.length-q.length)===q:p==="|="?m===q||m.substr(0,q.length+1)===q+"-":false},POS:function(g,j,o,m){var p=n.setFilters[j[2]]; -if(p)return p(g,o,j,m)}}},s=n.match.POS,v=function(g,j){return"\\"+(j-0+1)},B;for(B in n.match){n.match[B]=RegExp(n.match[B].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[B]=RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[B].source.replace(/\\(\d+)/g,v))}var D=function(g,j){g=Array.prototype.slice.call(g,0);if(j){j.push.apply(j,g);return j}return g};try{Array.prototype.slice.call(u.documentElement.childNodes,0)}catch(H){D=function(g,j){var o=j||[],m=0;if(f.call(g)==="[object Array]")Array.prototype.push.apply(o, -g);else if(typeof g.length==="number")for(var p=g.length;m";var o=u.documentElement;o.insertBefore(g,o.firstChild);if(u.getElementById(j)){n.find.ID=function(m,p,q){if(typeof p.getElementById!=="undefined"&&!q)return(p=p.getElementById(m[1]))?p.id===m[1]||typeof p.getAttributeNode!=="undefined"&&p.getAttributeNode("id").nodeValue===m[1]?[p]:A:[]};n.filter.ID=function(m,p){var q=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&q&&q.nodeValue===p}}o.removeChild(g); -o=g=null})();(function(){var g=u.createElement("div");g.appendChild(u.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(j,o){var m=o.getElementsByTagName(j[1]);if(j[1]==="*"){for(var p=[],q=0;m[q];q++)m[q].nodeType===1&&p.push(m[q]);m=p}return m};g.innerHTML="";if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(j){return j.getAttribute("href",2)};g=null})();u.querySelectorAll&& -function(){var g=l,j=u.createElement("div");j.innerHTML="

";if(!(j.querySelectorAll&&j.querySelectorAll(".TEST").length===0)){l=function(m,p,q,t){p=p||u;if(!t&&!l.isXML(p))if(p.nodeType===9)try{return D(p.querySelectorAll(m),q)}catch(x){}else if(p.nodeType===1&&p.nodeName.toLowerCase()!=="object"){var C=p.id,P=p.id="__sizzle__";try{return D(p.querySelectorAll("#"+P+" "+m),q)}catch(N){}finally{if(C)p.id=C;else p.removeAttribute("id")}}return g(m,p,q,t)};for(var o in g)l[o]=g[o]; -j=null}}();(function(){var g=u.documentElement,j=g.matchesSelector||g.mozMatchesSelector||g.webkitMatchesSelector||g.msMatchesSelector,o=false;try{j.call(u.documentElement,":sizzle")}catch(m){o=true}if(j)l.matchesSelector=function(p,q){try{if(o||!n.match.PSEUDO.test(q))return j.call(p,q)}catch(t){}return l(q,null,null,[p]).length>0}})();(function(){var g=u.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length=== -0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(j,o,m){if(typeof o.getElementsByClassName!=="undefined"&&!m)return o.getElementsByClassName(j[1])};g=null}}})();l.contains=u.documentElement.contains?function(g,j){return g!==j&&(g.contains?g.contains(j):true)}:function(g,j){return!!(g.compareDocumentPosition(j)&16)};l.isXML=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false};var M=function(g, -j){for(var o=[],m="",p,q=j.nodeType?[j]:j;p=n.match.PSEUDO.exec(g);){m+=p[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;p=0;for(var t=q.length;p0)for(var h=d;h0},closest:function(a, -b){var d=[],e,f,h=this[0];if(c.isArray(a)){var k={},l,n=1;if(h&&a.length){e=0;for(f=a.length;e-1:c(h).is(e))d.push({selector:l,elem:h,level:n})}h=h.parentNode;n++}}return d}k=$a.test(a)?c(a,b||this.context):null;e=0;for(f=this.length;e-1:c.find.matchesSelector(h,a)){d.push(h);break}else{h=h.parentNode;if(!h|| -!h.ownerDocument||h===b)break}d=d.length>1?c.unique(d):d;return this.pushStack(d,"closest",a)},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var d=typeof a==="string"?c(a,b||this.context):c.makeArray(a),e=c.merge(this.get(),d);return this.pushStack(!d[0]||!d[0].parentNode||d[0].parentNode.nodeType===11||!e[0]||!e[0].parentNode||e[0].parentNode.nodeType===11?e:c.unique(e))},andSelf:function(){return this.add(this.prevObject)}}); -c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling", -d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,e){var f=c.map(this,b,d);Wa.test(a)||(e=d);if(e&&typeof e==="string")f=c.filter(e,f);f=this.length>1?c.unique(f):f;if((this.length>1||Ya.test(e))&&Xa.test(a))f=f.reverse();return this.pushStack(f,a,Za.call(arguments).join(","))}}); -c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return b.length===1?c.find.matchesSelector(b[0],a)?[b[0]]:[]:c.find.matches(a,b)},dir:function(a,b,d){var e=[];for(a=a[b];a&&a.nodeType!==9&&(d===A||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&e.push(a);a=a[b]}return e},nth:function(a,b,d){b=b||1;for(var e=0;a;a=a[d])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var xa=/ jQuery\d+="(?:\d+|null)"/g, -$=/^\s+/,ya=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,za=/<([\w:]+)/,ab=/\s]+\/)>/g,O={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"], -area:[1,"",""],_default:[0,"",""]};O.optgroup=O.option;O.tbody=O.tfoot=O.colgroup=O.caption=O.thead;O.th=O.td;if(!c.support.htmlSerialize)O._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==A)return this.empty().append((this[0]&&this[0].ownerDocument||u).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this, -d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})}, -unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a= -c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,e;(e=this[d])!=null;d++)if(!a||c.filter(a,[e]).length){if(!b&&e.nodeType===1){c.cleanData(e.getElementsByTagName("*")); -c.cleanData([e])}e.parentNode&&e.parentNode.removeChild(e)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,e=this.ownerDocument;if(!d){d=e.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(xa,"").replace(cb,'="$1">').replace($, -"")],e)[0]}else return this.cloneNode(true)});if(a===true){la(this,b);la(this.find("*"),b.find("*"))}return b},html:function(a){if(a===A)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(xa,""):null;else if(typeof a==="string"&&!Aa.test(a)&&(c.support.leadingWhitespace||!$.test(a))&&!O[(za.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ya,"<$1>");try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?l.cloneNode(true):l)}k.length&&c.each(k,Ka)}return this}});c.buildFragment=function(a,b,d){var e,f,h;b=b&&b[0]?b[0].ownerDocument||b[0]:u;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===u&&!Aa.test(a[0])&&(c.support.checkClone|| -!Ba.test(a[0]))){f=true;if(h=c.fragments[a[0]])if(h!==1)e=h}if(!e){e=b.createDocumentFragment();c.clean(a,b,e,d)}if(f)c.fragments[a[0]]=h?e:1;return{fragment:e,cacheable:f}};c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var e=[];d=c(d);var f=this.length===1&&this[0].parentNode;if(f&&f.nodeType===11&&f.childNodes.length===1&&d.length===1){d[b](this[0]);return this}else{f=0;for(var h= -d.length;f0?this.clone(true):this).get();c(d[f])[b](k);e=e.concat(k)}return this.pushStack(e,a,d.selector)}}});c.extend({clean:function(a,b,d,e){b=b||u;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||u;for(var f=[],h=0,k;(k=a[h])!=null;h++){if(typeof k==="number")k+="";if(k){if(typeof k==="string"&&!bb.test(k))k=b.createTextNode(k);else if(typeof k==="string"){k=k.replace(ya,"<$1>");var l=(za.exec(k)||["",""])[1].toLowerCase(),n=O[l]||O._default, -s=n[0],v=b.createElement("div");for(v.innerHTML=n[1]+k+n[2];s--;)v=v.lastChild;if(!c.support.tbody){s=ab.test(k);l=l==="table"&&!s?v.firstChild&&v.firstChild.childNodes:n[1]===""&&!s?v.childNodes:[];for(n=l.length-1;n>=0;--n)c.nodeName(l[n],"tbody")&&!l[n].childNodes.length&&l[n].parentNode.removeChild(l[n])}!c.support.leadingWhitespace&&$.test(k)&&v.insertBefore(b.createTextNode($.exec(k)[0]),v.firstChild);k=v.childNodes}if(k.nodeType)f.push(k);else f=c.merge(f,k)}}if(d)for(h=0;f[h];h++)if(e&& -c.nodeName(f[h],"script")&&(!f[h].type||f[h].type.toLowerCase()==="text/javascript"))e.push(f[h].parentNode?f[h].parentNode.removeChild(f[h]):f[h]);else{f[h].nodeType===1&&f.splice.apply(f,[h+1,0].concat(c.makeArray(f[h].getElementsByTagName("script"))));d.appendChild(f[h])}return f},cleanData:function(a){for(var b,d,e=c.cache,f=c.event.special,h=c.support.deleteExpando,k=0,l;(l=a[k])!=null;k++)if(!(l.nodeName&&c.noData[l.nodeName.toLowerCase()]))if(d=l[c.expando]){if((b=e[d])&&b.events)for(var n in b.events)f[n]? -c.event.remove(l,n):c.removeEvent(l,n,b.handle);if(h)delete l[c.expando];else l.removeAttribute&&l.removeAttribute(c.expando);delete e[d]}}});var Ca=/alpha\([^)]*\)/i,db=/opacity=([^)]*)/,eb=/-([a-z])/ig,fb=/([A-Z])/g,Da=/^-?\d+(?:px)?$/i,gb=/^-?\d/,hb={position:"absolute",visibility:"hidden",display:"block"},La=["Left","Right"],Ma=["Top","Bottom"],W,ib=u.defaultView&&u.defaultView.getComputedStyle,jb=function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){if(arguments.length===2&&b===A)return this; -return c.access(this,a,b,true,function(d,e,f){return f!==A?c.style(d,e,f):c.css(d,e)})};c.extend({cssHooks:{opacity:{get:function(a,b){if(b){var d=W(a,"opacity","opacity");return d===""?"1":d}else return a.style.opacity}}},cssNumber:{zIndex:true,fontWeight:true,opacity:true,zoom:true,lineHeight:true},cssProps:{"float":c.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,d,e){if(!(!a||a.nodeType===3||a.nodeType===8||!a.style)){var f,h=c.camelCase(b),k=a.style,l=c.cssHooks[h];b=c.cssProps[h]|| -h;if(d!==A){if(!(typeof d==="number"&&isNaN(d)||d==null)){if(typeof d==="number"&&!c.cssNumber[h])d+="px";if(!l||!("set"in l)||(d=l.set(a,d))!==A)try{k[b]=d}catch(n){}}}else{if(l&&"get"in l&&(f=l.get(a,false,e))!==A)return f;return k[b]}}},css:function(a,b,d){var e,f=c.camelCase(b),h=c.cssHooks[f];b=c.cssProps[f]||f;if(h&&"get"in h&&(e=h.get(a,true,d))!==A)return e;else if(W)return W(a,b,f)},swap:function(a,b,d){var e={},f;for(f in b){e[f]=a.style[f];a.style[f]=b[f]}d.call(a);for(f in b)a.style[f]= -e[f]},camelCase:function(a){return a.replace(eb,jb)}});c.curCSS=c.css;c.each(["height","width"],function(a,b){c.cssHooks[b]={get:function(d,e,f){var h;if(e){if(d.offsetWidth!==0)h=ma(d,b,f);else c.swap(d,hb,function(){h=ma(d,b,f)});return h+"px"}},set:function(d,e){if(Da.test(e)){e=parseFloat(e);if(e>=0)return e+"px"}else return e}}});if(!c.support.opacity)c.cssHooks.opacity={get:function(a,b){return db.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"": -b?"1":""},set:function(a,b){var d=a.style;d.zoom=1;var e=c.isNaN(b)?"":"alpha(opacity="+b*100+")",f=d.filter||"";d.filter=Ca.test(f)?f.replace(Ca,e):d.filter+" "+e}};if(ib)W=function(a,b,d){var e;d=d.replace(fb,"-$1").toLowerCase();if(!(b=a.ownerDocument.defaultView))return A;if(b=b.getComputedStyle(a,null)){e=b.getPropertyValue(d);if(e===""&&!c.contains(a.ownerDocument.documentElement,a))e=c.style(a,d)}return e};else if(u.documentElement.currentStyle)W=function(a,b){var d,e,f=a.currentStyle&&a.currentStyle[b], -h=a.style;if(!Da.test(f)&&gb.test(f)){d=h.left;e=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;h.left=b==="fontSize"?"1em":f||0;f=h.pixelLeft+"px";h.left=d;a.runtimeStyle.left=e}return f};if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=a.offsetHeight;return a.offsetWidth===0&&b===0||!c.support.reliableHiddenOffsets&&(a.style.display||c.css(a,"display"))==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var kb=c.now(),lb=/)<[^<]*)*<\/script>/gi, -mb=/^(?:select|textarea)/i,nb=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ob=/^(?:GET|HEAD|DELETE)$/,Na=/\[\]$/,T=/\=\?(&|$)/,ia=/\?/,pb=/([?&])_=[^&]*/,qb=/^(\w+:)?\/\/([^\/?#]+)/,rb=/%20/g,sb=/#.*$/,Ea=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!=="string"&&Ea)return Ea.apply(this,arguments);else if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var f=a.slice(e,a.length);a=a.slice(0,e)}e="GET";if(b)if(c.isFunction(b)){d= -b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);e="POST"}var h=this;c.ajax({url:a,type:e,dataType:"html",data:b,complete:function(k,l){if(l==="success"||l==="notmodified")h.html(f?c("
").append(k.responseText.replace(lb,"")).find(f):k.responseText);d&&h.each(d,[k.responseText,l,k])}});return this},serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&& -!this.disabled&&(this.checked||mb.test(this.nodeName)||nb.test(this.type))}).map(function(a,b){var d=c(this).val();return d==null?null:c.isArray(d)?c.map(d,function(e){return{name:b.name,value:e}}):{name:b.name,value:d}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:e})}, -getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:e})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return new E.XMLHttpRequest},accepts:{xml:"application/xml, text/xml",html:"text/html", -script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},ajax:function(a){var b=c.extend(true,{},c.ajaxSettings,a),d,e,f,h=b.type.toUpperCase(),k=ob.test(h);b.url=b.url.replace(sb,"");b.context=a&&a.context!=null?a.context:b;if(b.data&&b.processData&&typeof b.data!=="string")b.data=c.param(b.data,b.traditional);if(b.dataType==="jsonp"){if(h==="GET")T.test(b.url)||(b.url+=(ia.test(b.url)?"&":"?")+(b.jsonp||"callback")+"=?");else if(!b.data|| -!T.test(b.data))b.data=(b.data?b.data+"&":"")+(b.jsonp||"callback")+"=?";b.dataType="json"}if(b.dataType==="json"&&(b.data&&T.test(b.data)||T.test(b.url))){d=b.jsonpCallback||"jsonp"+kb++;if(b.data)b.data=(b.data+"").replace(T,"="+d+"$1");b.url=b.url.replace(T,"="+d+"$1");b.dataType="script";var l=E[d];E[d]=function(m){f=m;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);if(c.isFunction(l))l(m);else{E[d]=A;try{delete E[d]}catch(p){}}v&&v.removeChild(B)}}if(b.dataType==="script"&&b.cache===null)b.cache= -false;if(b.cache===false&&h==="GET"){var n=c.now(),s=b.url.replace(pb,"$1_="+n);b.url=s+(s===b.url?(ia.test(b.url)?"&":"?")+"_="+n:"")}if(b.data&&h==="GET")b.url+=(ia.test(b.url)?"&":"?")+b.data;b.global&&c.active++===0&&c.event.trigger("ajaxStart");n=(n=qb.exec(b.url))&&(n[1]&&n[1]!==location.protocol||n[2]!==location.host);if(b.dataType==="script"&&h==="GET"&&n){var v=u.getElementsByTagName("head")[0]||u.documentElement,B=u.createElement("script");if(b.scriptCharset)B.charset=b.scriptCharset;B.src= -b.url;if(!d){var D=false;B.onload=B.onreadystatechange=function(){if(!D&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){D=true;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);B.onload=B.onreadystatechange=null;v&&B.parentNode&&v.removeChild(B)}}}v.insertBefore(B,v.firstChild);return A}var H=false,w=b.xhr();if(w){b.username?w.open(h,b.url,b.async,b.username,b.password):w.open(h,b.url,b.async);try{if(b.data!=null&&!k||a&&a.contentType)w.setRequestHeader("Content-Type", -b.contentType);if(b.ifModified){c.lastModified[b.url]&&w.setRequestHeader("If-Modified-Since",c.lastModified[b.url]);c.etag[b.url]&&w.setRequestHeader("If-None-Match",c.etag[b.url])}n||w.setRequestHeader("X-Requested-With","XMLHttpRequest");w.setRequestHeader("Accept",b.dataType&&b.accepts[b.dataType]?b.accepts[b.dataType]+", */*; q=0.01":b.accepts._default)}catch(G){}if(b.beforeSend&&b.beforeSend.call(b.context,w,b)===false){b.global&&c.active--===1&&c.event.trigger("ajaxStop");w.abort();return false}b.global&& -c.triggerGlobal(b,"ajaxSend",[w,b]);var M=w.onreadystatechange=function(m){if(!w||w.readyState===0||m==="abort"){H||c.handleComplete(b,w,e,f);H=true;if(w)w.onreadystatechange=c.noop}else if(!H&&w&&(w.readyState===4||m==="timeout")){H=true;w.onreadystatechange=c.noop;e=m==="timeout"?"timeout":!c.httpSuccess(w)?"error":b.ifModified&&c.httpNotModified(w,b.url)?"notmodified":"success";var p;if(e==="success")try{f=c.httpData(w,b.dataType,b)}catch(q){e="parsererror";p=q}if(e==="success"||e==="notmodified")d|| -c.handleSuccess(b,w,e,f);else c.handleError(b,w,e,p);d||c.handleComplete(b,w,e,f);m==="timeout"&&w.abort();if(b.async)w=null}};try{var g=w.abort;w.abort=function(){w&&g.call&&g.call(w);M("abort")}}catch(j){}b.async&&b.timeout>0&&setTimeout(function(){w&&!H&&M("timeout")},b.timeout);try{w.send(k||b.data==null?null:b.data)}catch(o){c.handleError(b,w,null,o);c.handleComplete(b,w,e,f)}b.async||M();return w}},param:function(a,b){var d=[],e=function(h,k){k=c.isFunction(k)?k():k;d[d.length]=encodeURIComponent(h)+ -"="+encodeURIComponent(k)};if(b===A)b=c.ajaxSettings.traditional;if(c.isArray(a)||a.jquery)c.each(a,function(){e(this.name,this.value)});else for(var f in a)ca(f,a[f],b,e);return d.join("&").replace(rb,"+")}});c.extend({active:0,lastModified:{},etag:{},handleError:function(a,b,d,e){a.error&&a.error.call(a.context,b,d,e);a.global&&c.triggerGlobal(a,"ajaxError",[b,a,e])},handleSuccess:function(a,b,d,e){a.success&&a.success.call(a.context,e,d,b);a.global&&c.triggerGlobal(a,"ajaxSuccess",[b,a])},handleComplete:function(a, -b,d){a.complete&&a.complete.call(a.context,b,d);a.global&&c.triggerGlobal(a,"ajaxComplete",[b,a]);a.global&&c.active--===1&&c.event.trigger("ajaxStop")},triggerGlobal:function(a,b,d){(a.context&&a.context.url==null?c(a.context):c.event).trigger(b,d)},httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===1223}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),e=a.getResponseHeader("Etag"); -if(d)c.lastModified[b]=d;if(e)c.etag[b]=e;return a.status===304},httpData:function(a,b,d){var e=a.getResponseHeader("content-type")||"",f=b==="xml"||!b&&e.indexOf("xml")>=0;a=f?a.responseXML:a.responseText;f&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b==="json"||!b&&e.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&e.indexOf("javascript")>=0)c.globalEval(a);return a}});if(E.ActiveXObject)c.ajaxSettings.xhr= -function(){if(E.location.protocol!=="file:")try{return new E.XMLHttpRequest}catch(a){}try{return new E.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}};c.support.ajax=!!c.ajaxSettings.xhr();var da={},tb=/^(?:toggle|show|hide)$/,ub=/^([+\-]=)?([\d+.\-]+)(.*)$/,aa,na=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b,d){if(a||a===0)return this.animate(S("show",3),a,b,d);else{a= -0;for(b=this.length;a=0;e--)if(d[e].elem===this){b&&d[e](true);d.splice(e,1)}});b||this.dequeue();return this}});c.each({slideDown:S("show",1),slideUp:S("hide",1),slideToggle:S("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,e,f){return this.animate(b, -d,e,f)}});c.extend({speed:function(a,b,d){var e=a&&typeof a==="object"?c.extend({},a):{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};e.duration=c.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in c.fx.speeds?c.fx.speeds[e.duration]:c.fx.speeds._default;e.old=e.complete;e.complete=function(){e.queue!==false&&c(this).dequeue();c.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,d,e){return d+e*a},swing:function(a,b,d,e){return(-Math.cos(a* -Math.PI)/2+0.5)*e+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||c.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a=parseFloat(c.css(this.elem,this.prop));return a&&a>-1E4?a:0},custom:function(a,b,d){function e(h){return f.step(h)} -this.startTime=c.now();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;this.pos=this.state=0;var f=this;a=c.fx;e.elem=this.elem;if(e()&&c.timers.push(e)&&!aa)aa=setInterval(a.tick,a.interval)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true; -this.custom(this.cur(),0)},step:function(a){var b=c.now(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var e in this.options.curAnim)if(this.options.curAnim[e]!==true)d=false;if(d){if(this.options.overflow!=null&&!c.support.shrinkWrapBlocks){var f=this.elem,h=this.options;c.each(["","X","Y"],function(l,n){f.style["overflow"+n]=h.overflow[l]})}this.options.hide&&c(this.elem).hide();if(this.options.hide|| -this.options.show)for(var k in this.options.curAnim)c.style(this.elem,k,this.options.orig[k]);this.options.complete.call(this.elem)}return false}else{a=b-this.startTime;this.state=a/this.options.duration;b=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||b](this.state,a,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a= -c.timers,b=0;b-1;e={};var s={};if(n)s=f.position();k=n?s.top:parseInt(k,10)||0;l=n?s.left:parseInt(l,10)||0;if(c.isFunction(b))b=b.call(a,d,h);if(b.top!=null)e.top=b.top-h.top+k;if(b.left!=null)e.left=b.left-h.left+l;"using"in b?b.using.call(a, -e):f.css(e)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),e=Fa.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.css(a,"marginTop"))||0;d.left-=parseFloat(c.css(a,"marginLeft"))||0;e.top+=parseFloat(c.css(b[0],"borderTopWidth"))||0;e.left+=parseFloat(c.css(b[0],"borderLeftWidth"))||0;return{top:d.top-e.top,left:d.left-e.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||u.body;a&&!Fa.test(a.nodeName)&& -c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(e){var f=this[0],h;if(!f)return null;if(e!==A)return this.each(function(){if(h=ea(this))h.scrollTo(!a?e:c(h).scrollLeft(),a?e:c(h).scrollTop());else this[d]=e});else return(h=ea(f))?"pageXOffset"in h?h[a?"pageYOffset":"pageXOffset"]:c.support.boxModel&&h.document.documentElement[d]||h.document.body[d]:f[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase(); -c.fn["inner"+b]=function(){return this[0]?parseFloat(c.css(this[0],d,"padding")):null};c.fn["outer"+b]=function(e){return this[0]?parseFloat(c.css(this[0],d,e?"margin":"border")):null};c.fn[d]=function(e){var f=this[0];if(!f)return e==null?null:this;if(c.isFunction(e))return this.each(function(h){var k=c(this);k[d](e.call(this,h,k[d]()))});return c.isWindow(f)?f.document.compatMode==="CSS1Compat"&&f.document.documentElement["client"+b]||f.document.body["client"+b]:f.nodeType===9?Math.max(f.documentElement["client"+ -b],f.body["scroll"+b],f.documentElement["scroll"+b],f.body["offset"+b],f.documentElement["offset"+b]):e===A?parseFloat(c.css(f,d)):this.css(d,typeof e==="string"?e:e+"px")}})})(window); diff --git a/htmlcov/jquery.hotkeys.js b/htmlcov/jquery.hotkeys.js deleted file mode 100644 index 09b21e03..00000000 --- a/htmlcov/jquery.hotkeys.js +++ /dev/null @@ -1,99 +0,0 @@ -/* - * jQuery Hotkeys Plugin - * Copyright 2010, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * - * Based upon the plugin by Tzury Bar Yochay: - * http://github.com/tzuryby/hotkeys - * - * Original idea by: - * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ -*/ - -(function(jQuery){ - - jQuery.hotkeys = { - version: "0.8", - - specialKeys: { - 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", - 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", - 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", - 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", - 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", - 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", - 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta" - }, - - shiftNums: { - "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", - "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", - ".": ">", "/": "?", "\\": "|" - } - }; - - function keyHandler( handleObj ) { - // Only care when a possible input has been specified - if ( typeof handleObj.data !== "string" ) { - return; - } - - var origHandler = handleObj.handler, - keys = handleObj.data.toLowerCase().split(" "); - - handleObj.handler = function( event ) { - // Don't fire in text-accepting inputs that we didn't directly bind to - if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || - event.target.type === "text") ) { - return; - } - - // Keypress represents characters, not special keys - var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], - character = String.fromCharCode( event.which ).toLowerCase(), - key, modif = "", possible = {}; - - // check combinations (alt|ctrl|shift+anything) - if ( event.altKey && special !== "alt" ) { - modif += "alt+"; - } - - if ( event.ctrlKey && special !== "ctrl" ) { - modif += "ctrl+"; - } - - // TODO: Need to make sure this works consistently across platforms - if ( event.metaKey && !event.ctrlKey && special !== "meta" ) { - modif += "meta+"; - } - - if ( event.shiftKey && special !== "shift" ) { - modif += "shift+"; - } - - if ( special ) { - possible[ modif + special ] = true; - - } else { - possible[ modif + character ] = true; - possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; - - // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" - if ( modif === "shift+" ) { - possible[ jQuery.hotkeys.shiftNums[ character ] ] = true; - } - } - - for ( var i = 0, l = keys.length; i < l; i++ ) { - if ( possible[ keys[i] ] ) { - return origHandler.apply( this, arguments ); - } - } - }; - } - - jQuery.each([ "keydown", "keyup", "keypress" ], function() { - jQuery.event.special[ this ] = { add: keyHandler }; - }); - -})( jQuery ); diff --git a/htmlcov/jquery.isonscreen.js b/htmlcov/jquery.isonscreen.js deleted file mode 100644 index 0182ebd2..00000000 --- a/htmlcov/jquery.isonscreen.js +++ /dev/null @@ -1,53 +0,0 @@ -/* Copyright (c) 2010 - * @author Laurence Wheway - * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) - * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. - * - * @version 1.2.0 - */ -(function($) { - jQuery.extend({ - isOnScreen: function(box, container) { - //ensure numbers come in as intgers (not strings) and remove 'px' is it's there - for(var i in box){box[i] = parseFloat(box[i])}; - for(var i in container){container[i] = parseFloat(container[i])}; - - if(!container){ - container = { - left: $(window).scrollLeft(), - top: $(window).scrollTop(), - width: $(window).width(), - height: $(window).height() - } - } - - if( box.left+box.width-container.left > 0 && - box.left < container.width+container.left && - box.top+box.height-container.top > 0 && - box.top < container.height+container.top - ) return true; - return false; - } - }) - - - jQuery.fn.isOnScreen = function (container) { - for(var i in container){container[i] = parseFloat(container[i])}; - - if(!container){ - container = { - left: $(window).scrollLeft(), - top: $(window).scrollTop(), - width: $(window).width(), - height: $(window).height() - } - } - - if( $(this).offset().left+$(this).width()-container.left > 0 && - $(this).offset().left < container.width+container.left && - $(this).offset().top+$(this).height()-container.top > 0 && - $(this).offset().top < container.height+container.top - ) return true; - return false; - } -})(jQuery); diff --git a/htmlcov/jquery.tablesorter.min.js b/htmlcov/jquery.tablesorter.min.js deleted file mode 100644 index 64c70071..00000000 --- a/htmlcov/jquery.tablesorter.min.js +++ /dev/null @@ -1,2 +0,0 @@ - -(function($){$.extend({tablesorter:new function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'.',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}var rows=table.tBodies[0].rows;if(table.tBodies[0].rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('
').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;ib)?1:0));};function sortTextDesc(a,b){return((ba)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){$this.trigger("sortStart");var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){var $cell=$(this);var i=this.column;this.order=this.count++%2;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i - - - - - - - Coverage for rest_framework/__init__: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
-
- - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

- -
-

__version__ = '2.3.5' 

-

 

-

VERSION = __version__  # synonym 

-

 

-

# Header encoding (see RFC5987) 

-

HTTP_HEADER_ENCODING = 'iso-8859-1' 

-

 

-

# Default datetime input and output formats 

-

ISO_8601 = 'iso-8601' 

- -
- - - - - - diff --git a/htmlcov/rest_framework_authentication.html b/htmlcov/rest_framework_authentication.html deleted file mode 100644 index 899d0677..00000000 --- a/htmlcov/rest_framework_authentication.html +++ /dev/null @@ -1,767 +0,0 @@ - - - - - - - - Coverage for rest_framework/authentication: 80% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

-

296

-

297

-

298

-

299

-

300

-

301

-

302

-

303

-

304

-

305

-

306

-

307

-

308

-

309

-

310

-

311

-

312

-

313

-

314

-

315

-

316

-

317

-

318

-

319

-

320

-

321

-

322

-

323

-

324

-

325

-

326

-

327

-

328

-

329

-

330

-

331

-

332

-

333

-

334

-

335

-

336

-

337

-

338

-

339

-

340

-

341

-

342

-

343

- -
-

""" 

-

Provides various authentication policies. 

-

""" 

-

from __future__ import unicode_literals 

-

import base64 

-

from datetime import datetime 

-

 

-

from django.contrib.auth import authenticate 

-

from django.core.exceptions import ImproperlyConfigured 

-

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 

-

from rest_framework.authtoken.models import Token 

-

 

-

 

-

def get_authorization_header(request): 

-

    """ 

-

    Return request's 'Authorization:' header, as a bytestring. 

-

 

-

    Hide some test client ickyness where the header can be unicode. 

-

    """ 

-

    auth = request.META.get('HTTP_AUTHORIZATION', b'') 

-

    if type(auth) == type(''): 

-

        # Work around django test client oddness 

-

        auth = auth.encode(HTTP_HEADER_ENCODING) 

-

    return auth 

-

 

-

 

-

class BaseAuthentication(object): 

-

    """ 

-

    All authentication classes should extend BaseAuthentication. 

-

    """ 

-

 

-

    def authenticate(self, request): 

-

        """ 

-

        Authenticate the request and return a two-tuple of (user, token). 

-

        """ 

-

        raise NotImplementedError(".authenticate() must be overridden.") 

-

 

-

    def authenticate_header(self, request): 

-

        """ 

-

        Return a string to be used as the value of the `WWW-Authenticate` 

-

        header in a `401 Unauthenticated` response, or `None` if the 

-

        authentication scheme should return `403 Permission Denied` responses. 

-

        """ 

-

        pass 

-

 

-

 

-

class BasicAuthentication(BaseAuthentication): 

-

    """ 

-

    HTTP Basic authentication against username/password. 

-

    """ 

-

    www_authenticate_realm = 'api' 

-

 

-

    def authenticate(self, request): 

-

        """ 

-

        Returns a `User` if a correct username and password have been supplied 

-

        using HTTP Basic authentication.  Otherwise returns `None`. 

-

        """ 

-

        auth = get_authorization_header(request).split() 

-

 

-

        if not auth or auth[0].lower() != b'basic': 

-

            return None 

-

 

-

        if len(auth) == 1: 

-

            msg = 'Invalid basic header. No credentials provided.' 

-

            raise exceptions.AuthenticationFailed(msg) 

-

        elif len(auth) > 2: 

-

            msg = 'Invalid basic header. Credentials string should not contain spaces.' 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        try: 

-

            auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':') 

-

        except (TypeError, UnicodeDecodeError): 

-

            msg = 'Invalid basic header. Credentials not correctly base64 encoded' 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        userid, password = auth_parts[0], auth_parts[2] 

-

        return self.authenticate_credentials(userid, password) 

-

 

-

    def authenticate_credentials(self, userid, password): 

-

        """ 

-

        Authenticate the userid and password against username and password. 

-

        """ 

-

        user = authenticate(username=userid, password=password) 

-

        if user is None or not user.is_active: 

-

            raise exceptions.AuthenticationFailed('Invalid username/password') 

-

        return (user, None) 

-

 

-

    def authenticate_header(self, request): 

-

        return 'Basic realm="%s"' % self.www_authenticate_realm 

-

 

-

 

-

class SessionAuthentication(BaseAuthentication): 

-

    """ 

-

    Use Django's session framework for authentication. 

-

    """ 

-

 

-

    def authenticate(self, request): 

-

        """ 

-

        Returns a `User` if the request session currently has a logged in user. 

-

        Otherwise returns `None`. 

-

        """ 

-

 

-

        # Get the underlying HttpRequest object 

-

        http_request = request._request 

-

        user = getattr(http_request, 'user', None) 

-

 

-

        # Unauthenticated, CSRF validation not required 

-

        if not user or not user.is_active: 

-

            return None 

-

 

-

        # Enforce CSRF validation for session based authentication. 

-

        class CSRFCheck(CsrfViewMiddleware): 

-

            def _reject(self, request, reason): 

-

                # Return the failure reason instead of an HttpResponse 

-

                return reason 

-

 

-

        reason = CSRFCheck().process_view(http_request, None, (), {}) 

-

        if reason: 

-

            # CSRF failed, bail with explicit error message 

-

            raise exceptions.AuthenticationFailed('CSRF Failed: %s' % reason) 

-

 

-

        # CSRF passed with authenticated user 

-

        return (user, None) 

-

 

-

 

-

class TokenAuthentication(BaseAuthentication): 

-

    """ 

-

    Simple token based authentication. 

-

 

-

    Clients should authenticate by passing the token key in the "Authorization" 

-

    HTTP header, prepended with the string "Token ".  For example: 

-

 

-

        Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a 

-

    """ 

-

 

-

    model = Token 

-

    """ 

-

    A custom token model may be used, but must have the following properties. 

-

 

-

    * key -- The string identifying the token 

-

    * user -- The user to which the token belongs 

-

    """ 

-

 

-

    def authenticate(self, request): 

-

        auth = get_authorization_header(request).split() 

-

 

-

        if not auth or auth[0].lower() != b'token': 

-

            return None 

-

 

-

        if len(auth) == 1: 

-

            msg = 'Invalid token header. No credentials provided.' 

-

            raise exceptions.AuthenticationFailed(msg) 

-

        elif len(auth) > 2: 

-

            msg = 'Invalid token header. Token string should not contain spaces.' 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        return self.authenticate_credentials(auth[1]) 

-

 

-

    def authenticate_credentials(self, key): 

-

        try: 

-

            token = self.model.objects.get(key=key) 

-

        except self.model.DoesNotExist: 

-

            raise exceptions.AuthenticationFailed('Invalid token') 

-

 

-

        if not token.user.is_active: 

-

            raise exceptions.AuthenticationFailed('User inactive or deleted') 

-

 

-

        return (token.user, token) 

-

 

-

    def authenticate_header(self, request): 

-

        return 'Token' 

-

 

-

 

-

class OAuthAuthentication(BaseAuthentication): 

-

    """ 

-

    OAuth 1.0a authentication backend using `django-oauth-plus` and `oauth2`. 

-

 

-

    Note: The `oauth2` package actually provides oauth1.0a support.  Urg. 

-

          We import it from the `compat` module as `oauth`. 

-

    """ 

-

    www_authenticate_realm = 'api' 

-

 

-

    def __init__(self, *args, **kwargs): 

-

        super(OAuthAuthentication, self).__init__(*args, **kwargs) 

-

 

-

        if oauth is None: 

-

            raise ImproperlyConfigured( 

-

                "The 'oauth2' package could not be imported." 

-

                "It is required for use with the 'OAuthAuthentication' class.") 

-

 

-

        if oauth_provider is None: 

-

            raise ImproperlyConfigured( 

-

                "The 'django-oauth-plus' package could not be imported." 

-

                "It is required for use with the 'OAuthAuthentication' class.") 

-

 

-

    def authenticate(self, request): 

-

        """ 

-

        Returns two-tuple of (user, token) if authentication succeeds, 

-

        or None otherwise. 

-

        """ 

-

        try: 

-

            oauth_request = oauth_provider.utils.get_oauth_request(request) 

-

        except oauth.Error as err: 

-

            raise exceptions.AuthenticationFailed(err.message) 

-

 

-

        if not oauth_request: 

-

            return None 

-

 

-

        oauth_params = oauth_provider.consts.OAUTH_PARAMETERS_NAMES 

-

 

-

        found = any(param for param in oauth_params if param in oauth_request) 

-

        missing = list(param for param in oauth_params if param not in oauth_request) 

-

 

-

        if not found: 

-

            # OAuth authentication was not attempted. 

-

            return None 

-

 

-

        if missing: 

-

            # OAuth was attempted but missing parameters. 

-

            msg = 'Missing parameters: %s' % (', '.join(missing)) 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        if not self.check_nonce(request, oauth_request): 

-

            msg = 'Nonce check failed' 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        try: 

-

            consumer_key = oauth_request.get_parameter('oauth_consumer_key') 

-

            consumer = oauth_provider_store.get_consumer(request, oauth_request, consumer_key) 

-

        except oauth_provider.store.InvalidConsumerError: 

-

            msg = 'Invalid consumer token: %s' % oauth_request.get_parameter('oauth_consumer_key') 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        if consumer.status != oauth_provider.consts.ACCEPTED: 

-

            msg = 'Invalid consumer key status: %s' % consumer.get_status_display() 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        try: 

-

            token_param = oauth_request.get_parameter('oauth_token') 

-

            token = oauth_provider_store.get_access_token(request, oauth_request, consumer, token_param) 

-

        except oauth_provider.store.InvalidTokenError: 

-

            msg = 'Invalid access token: %s' % oauth_request.get_parameter('oauth_token') 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        try: 

-

            self.validate_token(request, consumer, token) 

-

        except oauth.Error as err: 

-

            raise exceptions.AuthenticationFailed(err.message) 

-

 

-

        user = token.user 

-

 

-

        if not user.is_active: 

-

            msg = 'User inactive or deleted: %s' % user.username 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        return (token.user, token) 

-

 

-

    def authenticate_header(self, request): 

-

        """ 

-

        If permission is denied, return a '401 Unauthorized' response, 

-

        with an appropraite 'WWW-Authenticate' header. 

-

        """ 

-

        return 'OAuth realm="%s"' % self.www_authenticate_realm 

-

 

-

    def validate_token(self, request, consumer, token): 

-

        """ 

-

        Check the token and raise an `oauth.Error` exception if invalid. 

-

        """ 

-

        oauth_server, oauth_request = oauth_provider.utils.initialize_server_request(request) 

-

        oauth_server.verify_request(oauth_request, consumer, token) 

-

 

-

    def check_nonce(self, request, oauth_request): 

-

        """ 

-

        Checks nonce of request, and return True if valid. 

-

        """ 

-

        return oauth_provider_store.check_nonce(request, oauth_request, oauth_request['oauth_nonce']) 

-

 

-

 

-

class OAuth2Authentication(BaseAuthentication): 

-

    """ 

-

    OAuth 2 authentication backend using `django-oauth2-provider` 

-

    """ 

-

    www_authenticate_realm = 'api' 

-

 

-

    def __init__(self, *args, **kwargs): 

-

        super(OAuth2Authentication, self).__init__(*args, **kwargs) 

-

 

-

        if oauth2_provider is None: 

-

            raise ImproperlyConfigured( 

-

                "The 'django-oauth2-provider' package could not be imported. " 

-

                "It is required for use with the 'OAuth2Authentication' class.") 

-

 

-

    def authenticate(self, request): 

-

        """ 

-

        Returns two-tuple of (user, token) if authentication succeeds, 

-

        or None otherwise. 

-

        """ 

-

 

-

        auth = get_authorization_header(request).split() 

-

 

-

        if not auth or auth[0].lower() != b'bearer': 

-

            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, auth[1]) 

-

 

-

    def authenticate_credentials(self, request, access_token): 

-

        """ 

-

        Authenticate the request, given the access token. 

-

        """ 

-

 

-

        try: 

-

            token = oauth2_provider.models.AccessToken.objects.select_related('user') 

-

            # TODO: Change to timezone aware datetime when oauth2_provider add 

-

            # support to it. 

-

            token = token.get(token=access_token, expires__gt=datetime.now()) 

-

        except oauth2_provider.models.AccessToken.DoesNotExist: 

-

            raise exceptions.AuthenticationFailed('Invalid token') 

-

 

-

        user = token.user 

-

 

-

        if not user.is_active: 

-

            msg = 'User inactive or deleted: %s' % user.username 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        return (user, token) 

-

 

-

    def authenticate_header(self, request): 

-

        """ 

-

        Bearer is the only finalized type currently 

-

 

-

        Check details on the `OAuth2Authentication.authenticate` method 

-

        """ 

-

        return 'Bearer realm="%s"' % self.www_authenticate_realm 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_authtoken___init__.html b/htmlcov/rest_framework_authtoken___init__.html deleted file mode 100644 index f7257493..00000000 --- a/htmlcov/rest_framework_authtoken___init__.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - Coverage for rest_framework/authtoken/__init__: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
- - - -
-
- - - - - diff --git a/htmlcov/rest_framework_authtoken_models.html b/htmlcov/rest_framework_authtoken_models.html deleted file mode 100644 index 27d2fff1..00000000 --- a/htmlcov/rest_framework_authtoken_models.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - Coverage for rest_framework/authtoken/models: 95% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

- -
-

import uuid 

-

import hmac 

-

from hashlib import sha1 

-

from rest_framework.compat import User 

-

from django.conf import settings 

-

from django.db import models 

-

 

-

 

-

class Token(models.Model): 

-

    """ 

-

    The default authorization token model. 

-

    """ 

-

    key = models.CharField(max_length=40, primary_key=True) 

-

    user = models.OneToOneField(User, related_name='auth_token') 

-

    created = models.DateTimeField(auto_now_add=True) 

-

 

-

    class Meta: 

-

        # Work around for a bug in Django: 

-

        # https://code.djangoproject.com/ticket/19422 

-

        # 

-

        # Also see corresponding ticket: 

-

        # https://github.com/tomchristie/django-rest-framework/issues/705 

-

        abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS 

-

 

-

    def save(self, *args, **kwargs): 

-

        if not self.key: 

-

            self.key = self.generate_key() 

-

        return super(Token, self).save(*args, **kwargs) 

-

 

-

    def generate_key(self): 

-

        unique = uuid.uuid4() 

-

        return hmac.new(unique.bytes, digestmod=sha1).hexdigest() 

-

 

-

    def __unicode__(self): 

-

        return self.key 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_authtoken_serializers.html b/htmlcov/rest_framework_authtoken_serializers.html deleted file mode 100644 index 8997d9a7..00000000 --- a/htmlcov/rest_framework_authtoken_serializers.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - Coverage for rest_framework/authtoken/serializers: 88% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

- -
-

from django.contrib.auth import authenticate 

-

from rest_framework import serializers 

-

 

-

 

-

class AuthTokenSerializer(serializers.Serializer): 

-

    username = serializers.CharField() 

-

    password = serializers.CharField() 

-

 

-

    def validate(self, attrs): 

-

        username = attrs.get('username') 

-

        password = attrs.get('password') 

-

 

-

        if username and password: 

-

            user = authenticate(username=username, password=password) 

-

 

-

            if user: 

-

                if not user.is_active: 

-

                    raise serializers.ValidationError('User account is disabled.') 

-

                attrs['user'] = user 

-

                return attrs 

-

            else: 

-

                raise serializers.ValidationError('Unable to login with provided credentials.') 

-

        else: 

-

            raise serializers.ValidationError('Must include "username" and "password"') 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_authtoken_views.html b/htmlcov/rest_framework_authtoken_views.html deleted file mode 100644 index d13746ea..00000000 --- a/htmlcov/rest_framework_authtoken_views.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - - - Coverage for rest_framework/authtoken/views: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

- -
-

from rest_framework.views import APIView 

-

from rest_framework import status 

-

from rest_framework import parsers 

-

from rest_framework import renderers 

-

from rest_framework.response import Response 

-

from rest_framework.authtoken.models import Token 

-

from rest_framework.authtoken.serializers import AuthTokenSerializer 

-

 

-

 

-

class ObtainAuthToken(APIView): 

-

    throttle_classes = () 

-

    permission_classes = () 

-

    parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,) 

-

    renderer_classes = (renderers.JSONRenderer,) 

-

    serializer_class = AuthTokenSerializer 

-

    model = Token 

-

 

-

    def post(self, request): 

-

        serializer = self.serializer_class(data=request.DATA) 

-

        if serializer.is_valid(): 

-

            token, created = Token.objects.get_or_create(user=serializer.object['user']) 

-

            return Response({'token': token.key}) 

-

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 

-

 

-

 

-

obtain_auth_token = ObtainAuthToken.as_view() 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_decorators.html b/htmlcov/rest_framework_decorators.html deleted file mode 100644 index 6ad6f6b5..00000000 --- a/htmlcov/rest_framework_decorators.html +++ /dev/null @@ -1,339 +0,0 @@ - - - - - - - - Coverage for rest_framework/decorators: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

- -
-

""" 

-

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 

-

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 

-

 

-

 

-

def api_view(http_method_names): 

-

 

-

    """ 

-

    Decorator that converts a function-based view into an APIView subclass. 

-

    Takes a list of allowed methods for the view as an argument. 

-

    """ 

-

 

-

    def decorator(func): 

-

 

-

        WrappedAPIView = type( 

-

            six.PY3 and 'WrappedAPIView' or b'WrappedAPIView', 

-

            (APIView,), 

-

            {'__doc__': func.__doc__} 

-

        ) 

-

 

-

        # Note, the above allows us to set the docstring. 

-

        # It is the equivalent of: 

-

        # 

-

        #     class WrappedAPIView(APIView): 

-

        #         pass 

-

        #     WrappedAPIView.__doc__ = func.doc    <--- Not possible to do this 

-

 

-

        # api_view applied without (method_names) 

-

        assert not(isinstance(http_method_names, types.FunctionType)), \ 

-

            '@api_view missing list of allowed HTTP methods' 

-

 

-

        # api_view applied with eg. string instead of list of strings 

-

        assert isinstance(http_method_names, (list, tuple)), \ 

-

            '@api_view expected a list of strings, received %s' % type(http_method_names).__name__ 

-

 

-

        allowed_methods = set(http_method_names) | set(('options',)) 

-

        WrappedAPIView.http_method_names = [method.lower() for method in allowed_methods] 

-

 

-

        def handler(self, *args, **kwargs): 

-

            return func(*args, **kwargs) 

-

 

-

        for method in http_method_names: 

-

            setattr(WrappedAPIView, method.lower(), handler) 

-

 

-

        WrappedAPIView.__name__ = func.__name__ 

-

 

-

        WrappedAPIView.renderer_classes = getattr(func, 'renderer_classes', 

-

                                                  APIView.renderer_classes) 

-

 

-

        WrappedAPIView.parser_classes = getattr(func, 'parser_classes', 

-

                                                APIView.parser_classes) 

-

 

-

        WrappedAPIView.authentication_classes = getattr(func, 'authentication_classes', 

-

                                                        APIView.authentication_classes) 

-

 

-

        WrappedAPIView.throttle_classes = getattr(func, 'throttle_classes', 

-

                                                  APIView.throttle_classes) 

-

 

-

        WrappedAPIView.permission_classes = getattr(func, 'permission_classes', 

-

                                                    APIView.permission_classes) 

-

 

-

        return WrappedAPIView.as_view() 

-

    return decorator 

-

 

-

 

-

def renderer_classes(renderer_classes): 

-

    def decorator(func): 

-

        func.renderer_classes = renderer_classes 

-

        return func 

-

    return decorator 

-

 

-

 

-

def parser_classes(parser_classes): 

-

    def decorator(func): 

-

        func.parser_classes = parser_classes 

-

        return func 

-

    return decorator 

-

 

-

 

-

def authentication_classes(authentication_classes): 

-

    def decorator(func): 

-

        func.authentication_classes = authentication_classes 

-

        return func 

-

    return decorator 

-

 

-

 

-

def throttle_classes(throttle_classes): 

-

    def decorator(func): 

-

        func.throttle_classes = throttle_classes 

-

        return func 

-

    return decorator 

-

 

-

 

-

def permission_classes(permission_classes): 

-

    def decorator(func): 

-

        func.permission_classes = permission_classes 

-

        return func 

-

    return decorator 

-

 

-

 

-

def 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.kwargs = kwargs 

-

        return func 

-

    return decorator 

-

 

-

 

-

def 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.kwargs = kwargs 

-

        return func 

-

    return decorator 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_exceptions.html b/htmlcov/rest_framework_exceptions.html deleted file mode 100644 index d975a848..00000000 --- a/htmlcov/rest_framework_exceptions.html +++ /dev/null @@ -1,257 +0,0 @@ - - - - - - - - Coverage for rest_framework/exceptions: 96% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

- -
-

""" 

-

Handled exceptions raised by REST framework. 

-

 

-

In addition Django's built in 403 and 404 exceptions are handled. 

-

(`django.http.Http404` and `django.core.exceptions.PermissionDenied`) 

-

""" 

-

from __future__ import unicode_literals 

-

from rest_framework import status 

-

 

-

 

-

class APIException(Exception): 

-

    """ 

-

    Base class for REST framework exceptions. 

-

    Subclasses should provide `.status_code` and `.detail` properties. 

-

    """ 

-

    pass 

-

 

-

 

-

class ParseError(APIException): 

-

    status_code = status.HTTP_400_BAD_REQUEST 

-

    default_detail = 'Malformed request.' 

-

 

-

    def __init__(self, detail=None): 

-

        self.detail = detail or self.default_detail 

-

 

-

 

-

class AuthenticationFailed(APIException): 

-

    status_code = status.HTTP_401_UNAUTHORIZED 

-

    default_detail = 'Incorrect authentication credentials.' 

-

 

-

    def __init__(self, detail=None): 

-

        self.detail = detail or self.default_detail 

-

 

-

 

-

class NotAuthenticated(APIException): 

-

    status_code = status.HTTP_401_UNAUTHORIZED 

-

    default_detail = 'Authentication credentials were not provided.' 

-

 

-

    def __init__(self, detail=None): 

-

        self.detail = detail or self.default_detail 

-

 

-

 

-

class PermissionDenied(APIException): 

-

    status_code = status.HTTP_403_FORBIDDEN 

-

    default_detail = 'You do not have permission to perform this action.' 

-

 

-

    def __init__(self, detail=None): 

-

        self.detail = detail or self.default_detail 

-

 

-

 

-

class MethodNotAllowed(APIException): 

-

    status_code = status.HTTP_405_METHOD_NOT_ALLOWED 

-

    default_detail = "Method '%s' not allowed." 

-

 

-

    def __init__(self, method, detail=None): 

-

        self.detail = (detail or self.default_detail) % method 

-

 

-

 

-

class NotAcceptable(APIException): 

-

    status_code = status.HTTP_406_NOT_ACCEPTABLE 

-

    default_detail = "Could not satisfy the request's Accept header" 

-

 

-

    def __init__(self, detail=None, available_renderers=None): 

-

        self.detail = detail or self.default_detail 

-

        self.available_renderers = available_renderers 

-

 

-

 

-

class UnsupportedMediaType(APIException): 

-

    status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE 

-

    default_detail = "Unsupported media type '%s' in request." 

-

 

-

    def __init__(self, media_type, detail=None): 

-

        self.detail = (detail or self.default_detail) % media_type 

-

 

-

 

-

class Throttled(APIException): 

-

    status_code = status.HTTP_429_TOO_MANY_REQUESTS 

-

    default_detail = "Request was throttled." 

-

    extra_detail = "Expected available in %d second%s." 

-

 

-

    def __init__(self, wait=None, detail=None): 

-

        import math 

-

        self.wait = wait and math.ceil(wait) or None 

-

        if wait is not None: 

-

            format = detail or self.default_detail + self.extra_detail 

-

            self.detail = format % (self.wait, self.wait != 1 and 's' or '') 

-

        else: 

-

            self.detail = detail or self.default_detail 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_fields.html b/htmlcov/rest_framework_fields.html deleted file mode 100644 index cf2731d2..00000000 --- a/htmlcov/rest_framework_fields.html +++ /dev/null @@ -1,1991 +0,0 @@ - - - - - - - - Coverage for rest_framework/fields: 87% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

-

296

-

297

-

298

-

299

-

300

-

301

-

302

-

303

-

304

-

305

-

306

-

307

-

308

-

309

-

310

-

311

-

312

-

313

-

314

-

315

-

316

-

317

-

318

-

319

-

320

-

321

-

322

-

323

-

324

-

325

-

326

-

327

-

328

-

329

-

330

-

331

-

332

-

333

-

334

-

335

-

336

-

337

-

338

-

339

-

340

-

341

-

342

-

343

-

344

-

345

-

346

-

347

-

348

-

349

-

350

-

351

-

352

-

353

-

354

-

355

-

356

-

357

-

358

-

359

-

360

-

361

-

362

-

363

-

364

-

365

-

366

-

367

-

368

-

369

-

370

-

371

-

372

-

373

-

374

-

375

-

376

-

377

-

378

-

379

-

380

-

381

-

382

-

383

-

384

-

385

-

386

-

387

-

388

-

389

-

390

-

391

-

392

-

393

-

394

-

395

-

396

-

397

-

398

-

399

-

400

-

401

-

402

-

403

-

404

-

405

-

406

-

407

-

408

-

409

-

410

-

411

-

412

-

413

-

414

-

415

-

416

-

417

-

418

-

419

-

420

-

421

-

422

-

423

-

424

-

425

-

426

-

427

-

428

-

429

-

430

-

431

-

432

-

433

-

434

-

435

-

436

-

437

-

438

-

439

-

440

-

441

-

442

-

443

-

444

-

445

-

446

-

447

-

448

-

449

-

450

-

451

-

452

-

453

-

454

-

455

-

456

-

457

-

458

-

459

-

460

-

461

-

462

-

463

-

464

-

465

-

466

-

467

-

468

-

469

-

470

-

471

-

472

-

473

-

474

-

475

-

476

-

477

-

478

-

479

-

480

-

481

-

482

-

483

-

484

-

485

-

486

-

487

-

488

-

489

-

490

-

491

-

492

-

493

-

494

-

495

-

496

-

497

-

498

-

499

-

500

-

501

-

502

-

503

-

504

-

505

-

506

-

507

-

508

-

509

-

510

-

511

-

512

-

513

-

514

-

515

-

516

-

517

-

518

-

519

-

520

-

521

-

522

-

523

-

524

-

525

-

526

-

527

-

528

-

529

-

530

-

531

-

532

-

533

-

534

-

535

-

536

-

537

-

538

-

539

-

540

-

541

-

542

-

543

-

544

-

545

-

546

-

547

-

548

-

549

-

550

-

551

-

552

-

553

-

554

-

555

-

556

-

557

-

558

-

559

-

560

-

561

-

562

-

563

-

564

-

565

-

566

-

567

-

568

-

569

-

570

-

571

-

572

-

573

-

574

-

575

-

576

-

577

-

578

-

579

-

580

-

581

-

582

-

583

-

584

-

585

-

586

-

587

-

588

-

589

-

590

-

591

-

592

-

593

-

594

-

595

-

596

-

597

-

598

-

599

-

600

-

601

-

602

-

603

-

604

-

605

-

606

-

607

-

608

-

609

-

610

-

611

-

612

-

613

-

614

-

615

-

616

-

617

-

618

-

619

-

620

-

621

-

622

-

623

-

624

-

625

-

626

-

627

-

628

-

629

-

630

-

631

-

632

-

633

-

634

-

635

-

636

-

637

-

638

-

639

-

640

-

641

-

642

-

643

-

644

-

645

-

646

-

647

-

648

-

649

-

650

-

651

-

652

-

653

-

654

-

655

-

656

-

657

-

658

-

659

-

660

-

661

-

662

-

663

-

664

-

665

-

666

-

667

-

668

-

669

-

670

-

671

-

672

-

673

-

674

-

675

-

676

-

677

-

678

-

679

-

680

-

681

-

682

-

683

-

684

-

685

-

686

-

687

-

688

-

689

-

690

-

691

-

692

-

693

-

694

-

695

-

696

-

697

-

698

-

699

-

700

-

701

-

702

-

703

-

704

-

705

-

706

-

707

-

708

-

709

-

710

-

711

-

712

-

713

-

714

-

715

-

716

-

717

-

718

-

719

-

720

-

721

-

722

-

723

-

724

-

725

-

726

-

727

-

728

-

729

-

730

-

731

-

732

-

733

-

734

-

735

-

736

-

737

-

738

-

739

-

740

-

741

-

742

-

743

-

744

-

745

-

746

-

747

-

748

-

749

-

750

-

751

-

752

-

753

-

754

-

755

-

756

-

757

-

758

-

759

-

760

-

761

-

762

-

763

-

764

-

765

-

766

-

767

-

768

-

769

-

770

-

771

-

772

-

773

-

774

-

775

-

776

-

777

-

778

-

779

-

780

-

781

-

782

-

783

-

784

-

785

-

786

-

787

-

788

-

789

-

790

-

791

-

792

-

793

-

794

-

795

-

796

-

797

-

798

-

799

-

800

-

801

-

802

-

803

-

804

-

805

-

806

-

807

-

808

-

809

-

810

-

811

-

812

-

813

-

814

-

815

-

816

-

817

-

818

-

819

-

820

-

821

-

822

-

823

-

824

-

825

-

826

-

827

-

828

-

829

-

830

-

831

-

832

-

833

-

834

-

835

-

836

-

837

-

838

-

839

-

840

-

841

-

842

-

843

-

844

-

845

-

846

-

847

-

848

-

849

-

850

-

851

-

852

-

853

-

854

-

855

-

856

-

857

-

858

-

859

-

860

-

861

-

862

-

863

-

864

-

865

-

866

-

867

-

868

-

869

-

870

-

871

-

872

-

873

-

874

-

875

-

876

-

877

-

878

-

879

-

880

-

881

-

882

-

883

-

884

-

885

-

886

-

887

-

888

-

889

-

890

-

891

-

892

-

893

-

894

-

895

-

896

-

897

-

898

-

899

-

900

-

901

-

902

-

903

-

904

-

905

-

906

-

907

-

908

-

909

-

910

-

911

-

912

-

913

-

914

-

915

-

916

-

917

-

918

-

919

-

920

-

921

-

922

-

923

-

924

-

925

-

926

-

927

-

928

-

929

-

930

-

931

-

932

-

933

-

934

-

935

-

936

-

937

-

938

-

939

-

940

-

941

-

942

-

943

-

944

-

945

-

946

-

947

-

948

-

949

-

950

-

951

-

952

-

953

-

954

-

955

- -
-

""" 

-

Serializer fields perform validation on incoming data. 

-

 

-

They are very similar to Django's form fields. 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

import copy 

-

import datetime 

-

import inspect 

-

import re 

-

import warnings 

-

from decimal import Decimal, DecimalException 

-

from django import forms 

-

from django.core import validators 

-

from django.core.exceptions import ValidationError 

-

from django.conf import settings 

-

from django.db.models.fields import BLANK_CHOICE_DASH 

-

from django.forms import widgets 

-

from django.utils.encoding import is_protected_type 

-

from django.utils.translation import ugettext_lazy as _ 

-

from django.utils.datastructures import SortedDict 

-

from rest_framework import ISO_8601 

-

from rest_framework.compat import ( 

-

    timezone, parse_date, parse_datetime, parse_time, BytesIO, six, smart_text, 

-

    force_text, is_non_str_iterable 

-

) 

-

from rest_framework.settings import api_settings 

-

 

-

 

-

def is_simple_callable(obj): 

-

    """ 

-

    True if the object is a callable that takes no arguments. 

-

    """ 

-

    function = inspect.isfunction(obj) 

-

    method = inspect.ismethod(obj) 

-

 

-

    if not (function or method): 

-

        return False 

-

 

-

    args, _, _, defaults = inspect.getargspec(obj) 

-

    len_args = len(args) if function else len(args) - 1 

-

    len_defaults = len(defaults) if defaults else 0 

-

    return len_args <= len_defaults 

-

 

-

 

-

def get_component(obj, attr_name): 

-

    """ 

-

    Given an object, and an attribute name, 

-

    return that attribute on the object. 

-

    """ 

-

    if isinstance(obj, dict): 

-

        val = obj.get(attr_name) 

-

    else: 

-

        val = getattr(obj, attr_name) 

-

 

-

    if is_simple_callable(val): 

-

        return val() 

-

    return val 

-

 

-

 

-

def readable_datetime_formats(formats): 

-

    format = ', '.join(formats).replace(ISO_8601, 

-

             'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HHMM|-HHMM|Z]') 

-

    return humanize_strptime(format) 

-

 

-

 

-

def readable_date_formats(formats): 

-

    format = ', '.join(formats).replace(ISO_8601, 'YYYY[-MM[-DD]]') 

-

    return humanize_strptime(format) 

-

 

-

 

-

def readable_time_formats(formats): 

-

    format = ', '.join(formats).replace(ISO_8601, 'hh:mm[:ss[.uuuuuu]]') 

-

    return humanize_strptime(format) 

-

 

-

 

-

def humanize_strptime(format_string): 

-

    # Note that we're missing some of the locale specific mappings that 

-

    # don't really make sense. 

-

    mapping = { 

-

        "%Y": "YYYY", 

-

        "%y": "YY", 

-

        "%m": "MM", 

-

        "%b": "[Jan-Dec]", 

-

        "%B": "[January-December]", 

-

        "%d": "DD", 

-

        "%H": "hh", 

-

        "%I": "hh",  # Requires '%p' to differentiate from '%H'. 

-

        "%M": "mm", 

-

        "%S": "ss", 

-

        "%f": "uuuuuu", 

-

        "%a": "[Mon-Sun]", 

-

        "%A": "[Monday-Sunday]", 

-

        "%p": "[AM|PM]", 

-

        "%z": "[+HHMM|-HHMM]" 

-

    } 

-

    for key, val in mapping.items(): 

-

        format_string = format_string.replace(key, val) 

-

    return format_string 

-

 

-

 

-

class Field(object): 

-

    read_only = True 

-

    creation_counter = 0 

-

    empty = '' 

-

    type_name = None 

-

    partial = False 

-

    use_files = False 

-

    form_field_class = forms.CharField 

-

    type_label = 'field' 

-

 

-

    def __init__(self, source=None, label=None, help_text=None): 

-

        self.parent = None 

-

 

-

        self.creation_counter = Field.creation_counter 

-

        Field.creation_counter += 1 

-

 

-

        self.source = source 

-

 

-

        if label is not None: 

-

            self.label = smart_text(label) 

-

 

-

        if help_text is not None: 

-

            self.help_text = smart_text(help_text) 

-

 

-

    def initialize(self, parent, field_name): 

-

        """ 

-

        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. 

-

        """ 

-

        self.parent = parent 

-

        self.root = parent.root or parent 

-

        self.context = self.root.context 

-

        self.partial = self.root.partial 

-

        if self.partial: 

-

            self.required = False 

-

 

-

    def field_from_native(self, data, files, field_name, into): 

-

        """ 

-

        Given a dictionary and a field name, updates the dictionary `into`, 

-

        with the field and it's deserialized value. 

-

        """ 

-

        return 

-

 

-

    def field_to_native(self, obj, field_name): 

-

        """ 

-

        Given and object and a field name, returns the value that should be 

-

        serialized for that field. 

-

        """ 

-

        if obj is None: 

-

            return self.empty 

-

 

-

        if self.source == '*': 

-

            return self.to_native(obj) 

-

 

-

        source = self.source or field_name 

-

        value = obj 

-

 

-

        for component in source.split('.'): 

-

            value = get_component(value, component) 

-

            if value is None: 

-

                break 

-

 

-

        return self.to_native(value) 

-

 

-

    def to_native(self, value): 

-

        """ 

-

        Converts the field's value into it's simple representation. 

-

        """ 

-

        if is_simple_callable(value): 

-

            value = value() 

-

 

-

        if is_protected_type(value): 

-

            return value 

-

        elif (is_non_str_iterable(value) and 

-

              not isinstance(value, (dict, six.string_types))): 

-

            return [self.to_native(item) for item in value] 

-

        elif isinstance(value, dict): 

-

            # Make sure we preserve field ordering, if it exists 

-

            ret = SortedDict() 

-

            for key, val in value.items(): 

-

                ret[key] = self.to_native(val) 

-

            return ret 

-

        return force_text(value) 

-

 

-

    def attributes(self): 

-

        """ 

-

        Returns a dictionary of attributes to be used when serializing to xml. 

-

        """ 

-

        if self.type_name: 

-

            return {'type': self.type_name} 

-

        return {} 

-

 

-

    def metadata(self): 

-

        metadata = SortedDict() 

-

        metadata['type'] = self.type_label 

-

        metadata['required'] = getattr(self, 'required', False) 

-

        optional_attrs = ['read_only', 'label', 'help_text', 

-

                          'min_length', 'max_length'] 

-

        for attr in optional_attrs: 

-

            value = getattr(self, attr, None) 

-

            if value is not None and value != '': 

-

                metadata[attr] = force_text(value, strings_only=True) 

-

        return metadata 

-

 

-

 

-

class WritableField(Field): 

-

    """ 

-

    Base for read/write fields. 

-

    """ 

-

    default_validators = [] 

-

    default_error_messages = { 

-

        'required': _('This field is required.'), 

-

        'invalid': _('Invalid value.'), 

-

    } 

-

    widget = widgets.TextInput 

-

    default = None 

-

 

-

    def __init__(self, source=None, label=None, help_text=None, 

-

                 read_only=False, required=None, 

-

                 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 

-

        if required is None: 

-

            self.required = not(read_only) 

-

        else: 

-

            assert not (read_only and required), "Cannot set required=True and read_only=True" 

-

            self.required = required 

-

 

-

        messages = {} 

-

        for c in reversed(self.__class__.__mro__): 

-

            messages.update(getattr(c, 'default_error_messages', {})) 

-

        messages.update(error_messages or {}) 

-

        self.error_messages = messages 

-

 

-

        self.validators = self.default_validators + validators 

-

        self.default = default if default is not None else self.default 

-

 

-

        # Widgets are ony used for HTML forms. 

-

        widget = widget or self.widget 

-

        if isinstance(widget, type): 

-

            widget = widget() 

-

        self.widget = widget 

-

 

-

    def __deepcopy__(self, memo): 

-

        result = copy.copy(self) 

-

        memo[id(self)] = result 

-

        result.validators = self.validators[:] 

-

        return result 

-

 

-

    def validate(self, value): 

-

        if value in validators.EMPTY_VALUES and self.required: 

-

            raise ValidationError(self.error_messages['required']) 

-

 

-

    def run_validators(self, value): 

-

        if value in validators.EMPTY_VALUES: 

-

            return 

-

        errors = [] 

-

        for v in self.validators: 

-

            try: 

-

                v(value) 

-

            except ValidationError as e: 

-

                if hasattr(e, 'code') and e.code in self.error_messages: 

-

                    message = self.error_messages[e.code] 

-

                    if e.params: 

-

                        message = message % e.params 

-

                    errors.append(message) 

-

                else: 

-

                    errors.extend(e.messages) 

-

        if errors: 

-

            raise ValidationError(errors) 

-

 

-

    def field_from_native(self, data, files, field_name, into): 

-

        """ 

-

        Given a dictionary and a field name, updates the dictionary `into`, 

-

        with the field and it's deserialized value. 

-

        """ 

-

        if self.read_only: 

-

            return 

-

 

-

        try: 

-

            if self.use_files: 

-

                files = files or {} 

-

                native = files[field_name] 

-

            else: 

-

                native = data[field_name] 

-

        except KeyError: 

-

            if self.default is not None and not self.partial: 

-

                # Note: partial updates shouldn't set defaults 

-

                if is_simple_callable(self.default): 

-

                    native = self.default() 

-

                else: 

-

                    native = self.default 

-

            else: 

-

                if self.required: 

-

                    raise ValidationError(self.error_messages['required']) 

-

                return 

-

 

-

        value = self.from_native(native) 

-

        if self.source == '*': 

-

            if value: 

-

                into.update(value) 

-

        else: 

-

            self.validate(value) 

-

            self.run_validators(value) 

-

            into[self.source or field_name] = value 

-

 

-

    def from_native(self, value): 

-

        """ 

-

        Reverts a simple representation back to the field's value. 

-

        """ 

-

        return value 

-

 

-

 

-

class ModelField(WritableField): 

-

    """ 

-

    A generic field that can be used against an arbitrary model field. 

-

    """ 

-

    def __init__(self, *args, **kwargs): 

-

        try: 

-

            self.model_field = kwargs.pop('model_field') 

-

        except KeyError: 

-

            raise ValueError("ModelField requires 'model_field' kwarg") 

-

 

-

        self.min_length = kwargs.pop('min_length', 

-

                                     getattr(self.model_field, 'min_length', None)) 

-

        self.max_length = kwargs.pop('max_length', 

-

                                     getattr(self.model_field, 'max_length', None)) 

-

        self.min_value = kwargs.pop('min_value', 

-

                                    getattr(self.model_field, 'min_value', None)) 

-

        self.max_value = kwargs.pop('max_value', 

-

                                    getattr(self.model_field, 'max_value', None)) 

-

 

-

        super(ModelField, self).__init__(*args, **kwargs) 

-

 

-

        if self.min_length is not None: 

-

            self.validators.append(validators.MinLengthValidator(self.min_length)) 

-

        if self.max_length is not None: 

-

            self.validators.append(validators.MaxLengthValidator(self.max_length)) 

-

        if self.min_value is not None: 

-

            self.validators.append(validators.MinValueValidator(self.min_value)) 

-

        if self.max_value is not None: 

-

            self.validators.append(validators.MaxValueValidator(self.max_value)) 

-

 

-

    def from_native(self, value): 

-

        rel = getattr(self.model_field, "rel", None) 

-

        if rel is not None: 

-

            return rel.to._meta.get_field(rel.field_name).to_python(value) 

-

        else: 

-

            return self.model_field.to_python(value) 

-

 

-

    def field_to_native(self, obj, field_name): 

-

        value = self.model_field._get_val_from_obj(obj) 

-

        if is_protected_type(value): 

-

            return value 

-

        return self.model_field.value_to_string(obj) 

-

 

-

    def attributes(self): 

-

        return { 

-

            "type": self.model_field.get_internal_type() 

-

        } 

-

 

-

 

-

##### Typed Fields ##### 

-

 

-

class BooleanField(WritableField): 

-

    type_name = 'BooleanField' 

-

    type_label = 'boolean' 

-

    form_field_class = forms.BooleanField 

-

    widget = widgets.CheckboxInput 

-

    default_error_messages = { 

-

        'invalid': _("'%s' value must be either True or False."), 

-

    } 

-

    empty = False 

-

 

-

    # Note: we set default to `False` in order to fill in missing value not 

-

    # supplied by html form.  TODO: Fix so that only html form input gets 

-

    # this behavior. 

-

    default = False 

-

 

-

    def from_native(self, value): 

-

        if value in ('true', 't', 'True', '1'): 

-

            return True 

-

        if value in ('false', 'f', 'False', '0'): 

-

            return False 

-

        return bool(value) 

-

 

-

 

-

class CharField(WritableField): 

-

    type_name = 'CharField' 

-

    type_label = 'string' 

-

    form_field_class = forms.CharField 

-

 

-

    def __init__(self, max_length=None, min_length=None, *args, **kwargs): 

-

        self.max_length, self.min_length = max_length, min_length 

-

        super(CharField, self).__init__(*args, **kwargs) 

-

        if min_length is not None: 

-

            self.validators.append(validators.MinLengthValidator(min_length)) 

-

        if max_length is not None: 

-

            self.validators.append(validators.MaxLengthValidator(max_length)) 

-

 

-

    def from_native(self, value): 

-

        if isinstance(value, six.string_types) or value is None: 

-

            return value 

-

        return smart_text(value) 

-

 

-

 

-

class URLField(CharField): 

-

    type_name = 'URLField' 

-

    type_label = 'url' 

-

 

-

    def __init__(self, **kwargs): 

-

        kwargs['validators'] = [validators.URLValidator()] 

-

        super(URLField, self).__init__(**kwargs) 

-

 

-

 

-

class SlugField(CharField): 

-

    type_name = 'SlugField' 

-

    type_label = 'slug' 

-

    form_field_class = forms.SlugField 

-

 

-

    default_error_messages = { 

-

        'invalid': _("Enter a valid 'slug' consisting of letters, numbers," 

-

                     " underscores or hyphens."), 

-

    } 

-

    default_validators = [validators.validate_slug] 

-

 

-

    def __init__(self, *args, **kwargs): 

-

        super(SlugField, self).__init__(*args, **kwargs) 

-

 

-

 

-

class ChoiceField(WritableField): 

-

    type_name = 'ChoiceField' 

-

    type_label = 'multiple choice' 

-

    form_field_class = forms.ChoiceField 

-

    widget = widgets.Select 

-

    default_error_messages = { 

-

        'invalid_choice': _('Select a valid choice. %(value)s is not one of ' 

-

                            'the available choices.'), 

-

    } 

-

 

-

    def __init__(self, choices=(), *args, **kwargs): 

-

        super(ChoiceField, self).__init__(*args, **kwargs) 

-

        self.choices = choices 

-

        if not self.required: 

-

            self.choices = BLANK_CHOICE_DASH + self.choices 

-

 

-

    def _get_choices(self): 

-

        return self._choices 

-

 

-

    def _set_choices(self, value): 

-

        # Setting choices also sets the choices on the widget. 

-

        # choices can be any iterable, but we call list() on it because 

-

        # it will be consumed more than once. 

-

        self._choices = self.widget.choices = list(value) 

-

 

-

    choices = property(_get_choices, _set_choices) 

-

 

-

    def validate(self, value): 

-

        """ 

-

        Validates that the input is in self.choices. 

-

        """ 

-

        super(ChoiceField, self).validate(value) 

-

        if value and not self.valid_value(value): 

-

            raise ValidationError(self.error_messages['invalid_choice'] % {'value': value}) 

-

 

-

    def valid_value(self, value): 

-

        """ 

-

        Check to see if the provided value is a valid choice. 

-

        """ 

-

        for k, v in self.choices: 

-

            if isinstance(v, (list, tuple)): 

-

                # This is an optgroup, so look inside the group for options 

-

                for k2, v2 in v: 

-

                    if value == smart_text(k2): 

-

                        return True 

-

            else: 

-

                if value == smart_text(k) or value == k: 

-

                    return True 

-

        return False 

-

 

-

 

-

class EmailField(CharField): 

-

    type_name = 'EmailField' 

-

    type_label = 'email' 

-

    form_field_class = forms.EmailField 

-

 

-

    default_error_messages = { 

-

        'invalid': _('Enter a valid e-mail address.'), 

-

    } 

-

    default_validators = [validators.validate_email] 

-

 

-

    def from_native(self, value): 

-

        ret = super(EmailField, self).from_native(value) 

-

        if ret is None: 

-

            return None 

-

        return ret.strip() 

-

 

-

 

-

class RegexField(CharField): 

-

    type_name = 'RegexField' 

-

    type_label = 'regex' 

-

    form_field_class = forms.RegexField 

-

 

-

    def __init__(self, regex, max_length=None, min_length=None, *args, **kwargs): 

-

        super(RegexField, self).__init__(max_length, min_length, *args, **kwargs) 

-

        self.regex = regex 

-

 

-

    def _get_regex(self): 

-

        return self._regex 

-

 

-

    def _set_regex(self, regex): 

-

        if isinstance(regex, six.string_types): 

-

            regex = re.compile(regex) 

-

        self._regex = regex 

-

        if hasattr(self, '_regex_validator') and self._regex_validator in self.validators: 

-

            self.validators.remove(self._regex_validator) 

-

        self._regex_validator = validators.RegexValidator(regex=regex) 

-

        self.validators.append(self._regex_validator) 

-

 

-

    regex = property(_get_regex, _set_regex) 

-

 

-

 

-

class DateField(WritableField): 

-

    type_name = 'DateField' 

-

    type_label = 'date' 

-

    widget = widgets.DateInput 

-

    form_field_class = forms.DateField 

-

 

-

    default_error_messages = { 

-

        'invalid': _("Date has wrong format. Use one of these formats instead: %s"), 

-

    } 

-

    empty = None 

-

    input_formats = api_settings.DATE_INPUT_FORMATS 

-

    format = api_settings.DATE_FORMAT 

-

 

-

    def __init__(self, input_formats=None, format=None, *args, **kwargs): 

-

        self.input_formats = input_formats if input_formats is not None else self.input_formats 

-

        self.format = format if format is not None else self.format 

-

        super(DateField, self).__init__(*args, **kwargs) 

-

 

-

    def from_native(self, value): 

-

        if value in validators.EMPTY_VALUES: 

-

            return None 

-

 

-

        if isinstance(value, datetime.datetime): 

-

            if timezone and settings.USE_TZ and timezone.is_aware(value): 

-

                # Convert aware datetimes to the default time zone 

-

                # before casting them to dates (#17742). 

-

                default_timezone = timezone.get_default_timezone() 

-

                value = timezone.make_naive(value, default_timezone) 

-

            return value.date() 

-

        if isinstance(value, datetime.date): 

-

            return value 

-

 

-

        for format in self.input_formats: 

-

            if format.lower() == ISO_8601: 

-

                try: 

-

                    parsed = parse_date(value) 

-

                except (ValueError, TypeError): 

-

                    pass 

-

                else: 

-

                    if parsed is not None: 

-

                        return parsed 

-

            else: 

-

                try: 

-

                    parsed = datetime.datetime.strptime(value, format) 

-

                except (ValueError, TypeError): 

-

                    pass 

-

                else: 

-

                    return parsed.date() 

-

 

-

        msg = self.error_messages['invalid'] % readable_date_formats(self.input_formats) 

-

        raise ValidationError(msg) 

-

 

-

    def to_native(self, value): 

-

        if value is None or self.format is None: 

-

            return value 

-

 

-

        if isinstance(value, datetime.datetime): 

-

            value = value.date() 

-

 

-

        if self.format.lower() == ISO_8601: 

-

            return value.isoformat() 

-

        return value.strftime(self.format) 

-

 

-

 

-

class DateTimeField(WritableField): 

-

    type_name = 'DateTimeField' 

-

    type_label = 'datetime' 

-

    widget = widgets.DateTimeInput 

-

    form_field_class = forms.DateTimeField 

-

 

-

    default_error_messages = { 

-

        'invalid': _("Datetime has wrong format. Use one of these formats instead: %s"), 

-

    } 

-

    empty = None 

-

    input_formats = api_settings.DATETIME_INPUT_FORMATS 

-

    format = api_settings.DATETIME_FORMAT 

-

 

-

    def __init__(self, input_formats=None, format=None, *args, **kwargs): 

-

        self.input_formats = input_formats if input_formats is not None else self.input_formats 

-

        self.format = format if format is not None else self.format 

-

        super(DateTimeField, self).__init__(*args, **kwargs) 

-

 

-

    def from_native(self, value): 

-

        if value in validators.EMPTY_VALUES: 

-

            return None 

-

 

-

        if isinstance(value, datetime.datetime): 

-

            return value 

-

        if isinstance(value, datetime.date): 

-

            value = datetime.datetime(value.year, value.month, value.day) 

-

            if settings.USE_TZ: 

-

                # For backwards compatibility, interpret naive datetimes in 

-

                # local time. This won't work during DST change, but we can't 

-

                # do much about it, so we let the exceptions percolate up the 

-

                # call stack. 

-

                warnings.warn("DateTimeField received a naive datetime (%s)" 

-

                              " while time zone support is active." % value, 

-

                              RuntimeWarning) 

-

                default_timezone = timezone.get_default_timezone() 

-

                value = timezone.make_aware(value, default_timezone) 

-

            return value 

-

 

-

        for format in self.input_formats: 

-

            if format.lower() == ISO_8601: 

-

                try: 

-

                    parsed = parse_datetime(value) 

-

                except (ValueError, TypeError): 

-

                    pass 

-

                else: 

-

                    if parsed is not None: 

-

                        return parsed 

-

            else: 

-

                try: 

-

                    parsed = datetime.datetime.strptime(value, format) 

-

                except (ValueError, TypeError): 

-

                    pass 

-

                else: 

-

                    return parsed 

-

 

-

        msg = self.error_messages['invalid'] % readable_datetime_formats(self.input_formats) 

-

        raise ValidationError(msg) 

-

 

-

    def to_native(self, value): 

-

        if value is None or self.format is None: 

-

            return value 

-

 

-

        if self.format.lower() == ISO_8601: 

-

            ret = value.isoformat() 

-

            if ret.endswith('+00:00'): 

-

                ret = ret[:-6] + 'Z' 

-

            return ret 

-

        return value.strftime(self.format) 

-

 

-

 

-

class TimeField(WritableField): 

-

    type_name = 'TimeField' 

-

    type_label = 'time' 

-

    widget = widgets.TimeInput 

-

    form_field_class = forms.TimeField 

-

 

-

    default_error_messages = { 

-

        'invalid': _("Time has wrong format. Use one of these formats instead: %s"), 

-

    } 

-

    empty = None 

-

    input_formats = api_settings.TIME_INPUT_FORMATS 

-

    format = api_settings.TIME_FORMAT 

-

 

-

    def __init__(self, input_formats=None, format=None, *args, **kwargs): 

-

        self.input_formats = input_formats if input_formats is not None else self.input_formats 

-

        self.format = format if format is not None else self.format 

-

        super(TimeField, self).__init__(*args, **kwargs) 

-

 

-

    def from_native(self, value): 

-

        if value in validators.EMPTY_VALUES: 

-

            return None 

-

 

-

        if isinstance(value, datetime.time): 

-

            return value 

-

 

-

        for format in self.input_formats: 

-

            if format.lower() == ISO_8601: 

-

                try: 

-

                    parsed = parse_time(value) 

-

                except (ValueError, TypeError): 

-

                    pass 

-

                else: 

-

                    if parsed is not None: 

-

                        return parsed 

-

            else: 

-

                try: 

-

                    parsed = datetime.datetime.strptime(value, format) 

-

                except (ValueError, TypeError): 

-

                    pass 

-

                else: 

-

                    return parsed.time() 

-

 

-

        msg = self.error_messages['invalid'] % readable_time_formats(self.input_formats) 

-

        raise ValidationError(msg) 

-

 

-

    def to_native(self, value): 

-

        if value is None or self.format is None: 

-

            return value 

-

 

-

        if isinstance(value, datetime.datetime): 

-

            value = value.time() 

-

 

-

        if self.format.lower() == ISO_8601: 

-

            return value.isoformat() 

-

        return value.strftime(self.format) 

-

 

-

 

-

class IntegerField(WritableField): 

-

    type_name = 'IntegerField' 

-

    type_label = 'integer' 

-

    form_field_class = forms.IntegerField 

-

 

-

    default_error_messages = { 

-

        'invalid': _('Enter a whole number.'), 

-

        'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), 

-

        'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), 

-

    } 

-

 

-

    def __init__(self, max_value=None, min_value=None, *args, **kwargs): 

-

        self.max_value, self.min_value = max_value, min_value 

-

        super(IntegerField, self).__init__(*args, **kwargs) 

-

 

-

        if max_value is not None: 

-

            self.validators.append(validators.MaxValueValidator(max_value)) 

-

        if min_value is not None: 

-

            self.validators.append(validators.MinValueValidator(min_value)) 

-

 

-

    def from_native(self, value): 

-

        if value in validators.EMPTY_VALUES: 

-

            return None 

-

 

-

        try: 

-

            value = int(str(value)) 

-

        except (ValueError, TypeError): 

-

            raise ValidationError(self.error_messages['invalid']) 

-

        return value 

-

 

-

 

-

class FloatField(WritableField): 

-

    type_name = 'FloatField' 

-

    type_label = 'float' 

-

    form_field_class = forms.FloatField 

-

 

-

    default_error_messages = { 

-

        'invalid': _("'%s' value must be a float."), 

-

    } 

-

 

-

    def from_native(self, value): 

-

        if value in validators.EMPTY_VALUES: 

-

            return None 

-

 

-

        try: 

-

            return float(value) 

-

        except (TypeError, ValueError): 

-

            msg = self.error_messages['invalid'] % value 

-

            raise ValidationError(msg) 

-

 

-

 

-

class DecimalField(WritableField): 

-

    type_name = 'DecimalField' 

-

    type_label = 'decimal' 

-

    form_field_class = forms.DecimalField 

-

 

-

    default_error_messages = { 

-

        'invalid': _('Enter a number.'), 

-

        'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), 

-

        'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), 

-

        'max_digits': _('Ensure that there are no more than %s digits in total.'), 

-

        'max_decimal_places': _('Ensure that there are no more than %s decimal places.'), 

-

        'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.') 

-

    } 

-

 

-

    def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs): 

-

        self.max_value, self.min_value = max_value, min_value 

-

        self.max_digits, self.decimal_places = max_digits, decimal_places 

-

        super(DecimalField, self).__init__(*args, **kwargs) 

-

 

-

        if max_value is not None: 

-

            self.validators.append(validators.MaxValueValidator(max_value)) 

-

        if min_value is not None: 

-

            self.validators.append(validators.MinValueValidator(min_value)) 

-

 

-

    def from_native(self, value): 

-

        """ 

-

        Validates that the input is a decimal number. Returns a Decimal 

-

        instance. Returns None for empty values. Ensures that there are no more 

-

        than max_digits in the number, and no more than decimal_places digits 

-

        after the decimal point. 

-

        """ 

-

        if value in validators.EMPTY_VALUES: 

-

            return None 

-

        value = smart_text(value).strip() 

-

        try: 

-

            value = Decimal(value) 

-

        except DecimalException: 

-

            raise ValidationError(self.error_messages['invalid']) 

-

        return value 

-

 

-

    def validate(self, value): 

-

        super(DecimalField, self).validate(value) 

-

        if value in validators.EMPTY_VALUES: 

-

            return 

-

        # Check for NaN, Inf and -Inf values. We can't compare directly for NaN, 

-

        # since it is never equal to itself. However, NaN is the only value that 

-

        # isn't equal to itself, so we can use this to identify NaN 

-

        if value != value or value == Decimal("Inf") or value == Decimal("-Inf"): 

-

            raise ValidationError(self.error_messages['invalid']) 

-

        sign, digittuple, exponent = value.as_tuple() 

-

        decimals = abs(exponent) 

-

        # digittuple doesn't include any leading zeros. 

-

        digits = len(digittuple) 

-

        if decimals > digits: 

-

            # We have leading zeros up to or past the decimal point.  Count 

-

            # everything past the decimal point as a digit.  We do not count 

-

            # 0 before the decimal point as a digit since that would mean 

-

            # we would not allow max_digits = decimal_places. 

-

            digits = decimals 

-

        whole_digits = digits - decimals 

-

 

-

        if self.max_digits is not None and digits > self.max_digits: 

-

            raise ValidationError(self.error_messages['max_digits'] % self.max_digits) 

-

        if self.decimal_places is not None and decimals > self.decimal_places: 

-

            raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places) 

-

        if self.max_digits is not None and self.decimal_places is not None and whole_digits > (self.max_digits - self.decimal_places): 

-

            raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places)) 

-

        return value 

-

 

-

 

-

class FileField(WritableField): 

-

    use_files = True 

-

    type_name = 'FileField' 

-

    type_label = 'file upload' 

-

    form_field_class = forms.FileField 

-

    widget = widgets.FileInput 

-

 

-

    default_error_messages = { 

-

        'invalid': _("No file was submitted. Check the encoding type on the form."), 

-

        'missing': _("No file was submitted."), 

-

        'empty': _("The submitted file is empty."), 

-

        'max_length': _('Ensure this filename has at most %(max)d characters (it has %(length)d).'), 

-

        'contradiction': _('Please either submit a file or check the clear checkbox, not both.') 

-

    } 

-

 

-

    def __init__(self, *args, **kwargs): 

-

        self.max_length = kwargs.pop('max_length', None) 

-

        self.allow_empty_file = kwargs.pop('allow_empty_file', False) 

-

        super(FileField, self).__init__(*args, **kwargs) 

-

 

-

    def from_native(self, data): 

-

        if data in validators.EMPTY_VALUES: 

-

            return None 

-

 

-

        # UploadedFile objects should have name and size attributes. 

-

        try: 

-

            file_name = data.name 

-

            file_size = data.size 

-

        except AttributeError: 

-

            raise ValidationError(self.error_messages['invalid']) 

-

 

-

        if self.max_length is not None and len(file_name) > self.max_length: 

-

            error_values = {'max': self.max_length, 'length': len(file_name)} 

-

            raise ValidationError(self.error_messages['max_length'] % error_values) 

-

        if not file_name: 

-

            raise ValidationError(self.error_messages['invalid']) 

-

        if not self.allow_empty_file and not file_size: 

-

            raise ValidationError(self.error_messages['empty']) 

-

 

-

        return data 

-

 

-

    def to_native(self, value): 

-

        return value.name 

-

 

-

 

-

class ImageField(FileField): 

-

    use_files = True 

-

    type_name = 'ImageField' 

-

    type_label = 'image upload' 

-

    form_field_class = forms.ImageField 

-

 

-

    default_error_messages = { 

-

        'invalid_image': _("Upload a valid image. The file you uploaded was " 

-

                           "either not an image or a corrupted image."), 

-

    } 

-

 

-

    def from_native(self, data): 

-

        """ 

-

        Checks that the file-upload field data contains a valid image (GIF, JPG, 

-

        PNG, possibly others -- whatever the Python Imaging Library supports). 

-

        """ 

-

        f = super(ImageField, self).from_native(data) 

-

        if f is None: 

-

            return None 

-

 

-

        from compat import Image 

-

        assert Image is not None, 'PIL must be installed for ImageField support' 

-

 

-

        # We need to get a file object for PIL. We might have a path or we might 

-

        # have to read the data into memory. 

-

        if hasattr(data, 'temporary_file_path'): 

-

            file = data.temporary_file_path() 

-

        else: 

-

            if hasattr(data, 'read'): 

-

                file = BytesIO(data.read()) 

-

            else: 

-

                file = BytesIO(data['content']) 

-

 

-

        try: 

-

            # load() could spot a truncated JPEG, but it loads the entire 

-

            # image in memory, which is a DoS vector. See #3848 and #18520. 

-

            # verify() must be called immediately after the constructor. 

-

            Image.open(file).verify() 

-

        except ImportError: 

-

            # Under PyPy, it is possible to import PIL. However, the underlying 

-

            # _imaging C module isn't available, so an ImportError will be 

-

            # raised. Catch and re-raise. 

-

            raise 

-

        except Exception:  # Python Imaging Library doesn't recognize it as an image 

-

            raise ValidationError(self.error_messages['invalid_image']) 

-

        if hasattr(f, 'seek') and callable(f.seek): 

-

            f.seek(0) 

-

        return f 

-

 

-

 

-

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): 

-

        self.method_name = method_name 

-

        super(SerializerMethodField, self).__init__() 

-

 

-

    def field_to_native(self, obj, field_name): 

-

        value = getattr(self.parent, self.method_name)(obj) 

-

        return self.to_native(value) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_filters.html b/htmlcov/rest_framework_filters.html deleted file mode 100644 index 28b6eaae..00000000 --- a/htmlcov/rest_framework_filters.html +++ /dev/null @@ -1,367 +0,0 @@ - - - - - - - - Coverage for rest_framework/filters: 92% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

- -
-

""" 

-

Provides generic filtering backends that can be used to filter the results 

-

returned by list views. 

-

""" 

-

from __future__ import unicode_literals 

-

from django.db import models 

-

from rest_framework.compat import django_filters, six 

-

from functools import reduce 

-

import operator 

-

 

-

FilterSet = django_filters and django_filters.FilterSet or None 

-

 

-

 

-

class BaseFilterBackend(object): 

-

    """ 

-

    A base class from which all filter backend classes should inherit. 

-

    """ 

-

 

-

    def filter_queryset(self, request, queryset, view): 

-

        """ 

-

        Return a filtered queryset. 

-

        """ 

-

        raise NotImplementedError(".filter_queryset() must be overridden.") 

-

 

-

 

-

class DjangoFilterBackend(BaseFilterBackend): 

-

    """ 

-

    A filter backend that uses django-filter. 

-

    """ 

-

    default_filter_set = FilterSet 

-

 

-

    def __init__(self): 

-

        assert django_filters, 'Using DjangoFilterBackend, but django-filter is not installed' 

-

 

-

    def get_filter_class(self, view, queryset=None): 

-

        """ 

-

        Return the django-filters `FilterSet` used to filter the queryset. 

-

        """ 

-

        filter_class = getattr(view, 'filter_class', None) 

-

        filter_fields = getattr(view, 'filter_fields', None) 

-

 

-

        if filter_class: 

-

            filter_model = filter_class.Meta.model 

-

 

-

            assert issubclass(filter_model, queryset.model), \ 

-

                'FilterSet model %s does not match queryset model %s' % \ 

-

                (filter_model, queryset.model) 

-

 

-

            return filter_class 

-

 

-

        if filter_fields: 

-

            class AutoFilterSet(self.default_filter_set): 

-

                class Meta: 

-

                    model = queryset.model 

-

                    fields = filter_fields 

-

            return AutoFilterSet 

-

 

-

        return None 

-

 

-

    def filter_queryset(self, request, queryset, view): 

-

        filter_class = self.get_filter_class(view, queryset) 

-

 

-

        if filter_class: 

-

            return filter_class(request.QUERY_PARAMS, queryset=queryset).qs 

-

 

-

        return queryset 

-

 

-

 

-

class SearchFilter(BaseFilterBackend): 

-

    search_param = 'search'  # The URL query parameter used for the search. 

-

 

-

    def get_search_terms(self, request): 

-

        """ 

-

        Search terms are set by a ?search=... query parameter, 

-

        and may be comma and/or whitespace delimited. 

-

        """ 

-

        params = request.QUERY_PARAMS.get(self.search_param, '') 

-

        return params.replace(',', ' ').split() 

-

 

-

    def construct_search(self, field_name): 

-

        if field_name.startswith('^'): 

-

            return "%s__istartswith" % field_name[1:] 

-

        elif field_name.startswith('='): 

-

            return "%s__iexact" % field_name[1:] 

-

        elif field_name.startswith('@'): 

-

            return "%s__search" % field_name[1:] 

-

        else: 

-

            return "%s__icontains" % field_name 

-

 

-

    def filter_queryset(self, request, queryset, view): 

-

        search_fields = getattr(view, 'search_fields', None) 

-

 

-

        if not search_fields: 

-

            return queryset 

-

 

-

        orm_lookups = [self.construct_search(str(search_field)) 

-

                       for search_field in search_fields] 

-

 

-

        for search_term in self.get_search_terms(request): 

-

            or_queries = [models.Q(**{orm_lookup: search_term}) 

-

                          for orm_lookup in orm_lookups] 

-

            queryset = queryset.filter(reduce(operator.or_, or_queries)) 

-

 

-

        return queryset 

-

 

-

 

-

class OrderingFilter(BaseFilterBackend): 

-

    ordering_param = 'ordering'  # The URL query parameter used for the ordering. 

-

 

-

    def get_ordering(self, request): 

-

        """ 

-

        Search terms are set by a ?search=... query parameter, 

-

        and may be comma and/or whitespace delimited. 

-

        """ 

-

        params = request.QUERY_PARAMS.get(self.ordering_param) 

-

        if params: 

-

            return [param.strip() for param in params.split(',')] 

-

 

-

    def get_default_ordering(self, view): 

-

        ordering = getattr(view, 'ordering', None) 

-

        if isinstance(ordering, six.string_types): 

-

            return (ordering,) 

-

        return ordering 

-

 

-

    def remove_invalid_fields(self, queryset, ordering): 

-

        field_names = [field.name for field in queryset.model._meta.fields] 

-

        return [term for term in ordering if term.lstrip('-') in field_names] 

-

 

-

    def filter_queryset(self, request, queryset, view): 

-

        ordering = self.get_ordering(request) 

-

 

-

        if ordering: 

-

            # Skip any incorrect parameters 

-

            ordering = self.remove_invalid_fields(queryset, ordering) 

-

 

-

        if not ordering: 

-

            # Use 'ordering' attribtue by default 

-

            ordering = self.get_default_ordering(view) 

-

 

-

        if ordering: 

-

            return queryset.order_by(*ordering) 

-

 

-

        return queryset 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_generics.html b/htmlcov/rest_framework_generics.html deleted file mode 100644 index 5f5851cb..00000000 --- a/htmlcov/rest_framework_generics.html +++ /dev/null @@ -1,1079 +0,0 @@ - - - - - - - - Coverage for rest_framework/generics: 83% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

-

296

-

297

-

298

-

299

-

300

-

301

-

302

-

303

-

304

-

305

-

306

-

307

-

308

-

309

-

310

-

311

-

312

-

313

-

314

-

315

-

316

-

317

-

318

-

319

-

320

-

321

-

322

-

323

-

324

-

325

-

326

-

327

-

328

-

329

-

330

-

331

-

332

-

333

-

334

-

335

-

336

-

337

-

338

-

339

-

340

-

341

-

342

-

343

-

344

-

345

-

346

-

347

-

348

-

349

-

350

-

351

-

352

-

353

-

354

-

355

-

356

-

357

-

358

-

359

-

360

-

361

-

362

-

363

-

364

-

365

-

366

-

367

-

368

-

369

-

370

-

371

-

372

-

373

-

374

-

375

-

376

-

377

-

378

-

379

-

380

-

381

-

382

-

383

-

384

-

385

-

386

-

387

-

388

-

389

-

390

-

391

-

392

-

393

-

394

-

395

-

396

-

397

-

398

-

399

-

400

-

401

-

402

-

403

-

404

-

405

-

406

-

407

-

408

-

409

-

410

-

411

-

412

-

413

-

414

-

415

-

416

-

417

-

418

-

419

-

420

-

421

-

422

-

423

-

424

-

425

-

426

-

427

-

428

-

429

-

430

-

431

-

432

-

433

-

434

-

435

-

436

-

437

-

438

-

439

-

440

-

441

-

442

-

443

-

444

-

445

-

446

-

447

-

448

-

449

-

450

-

451

-

452

-

453

-

454

-

455

-

456

-

457

-

458

-

459

-

460

-

461

-

462

-

463

-

464

-

465

-

466

-

467

-

468

-

469

-

470

-

471

-

472

-

473

-

474

-

475

-

476

-

477

-

478

-

479

-

480

-

481

-

482

-

483

-

484

-

485

-

486

-

487

-

488

-

489

-

490

-

491

-

492

-

493

-

494

-

495

-

496

-

497

-

498

-

499

- -
-

""" 

-

Generic views that provide commonly needed behaviour. 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

from django.core.exceptions import ImproperlyConfigured, PermissionDenied 

-

from django.core.paginator import Paginator, InvalidPage 

-

from django.http import Http404 

-

from django.shortcuts import get_object_or_404 as _get_object_or_404 

-

from django.utils.translation import ugettext as _ 

-

from rest_framework import views, mixins, exceptions 

-

from rest_framework.request import clone_request 

-

from rest_framework.settings import api_settings 

-

import warnings 

-

 

-

 

-

def get_object_or_404(queryset, **filter_kwargs): 

-

    """ 

-

    Same as Django's standard shortcut, but make sure to raise 404 

-

    if the filter_kwargs don't match the required types. 

-

    """ 

-

    try: 

-

        return _get_object_or_404(queryset, **filter_kwargs) 

-

    except (TypeError, ValueError): 

-

        raise Http404 

-

 

-

 

-

class GenericAPIView(views.APIView): 

-

    """ 

-

    Base class for all other generic views. 

-

    """ 

-

 

-

    # You'll need to either set these attributes, 

-

    # or override `get_queryset()`/`get_serializer_class()`. 

-

    queryset = None 

-

    serializer_class = None 

-

 

-

    # 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. 

-

    model = None 

-

 

-

    # If you want to use object lookups other than pk, set this attribute. 

-

    # For more complex lookup requirements override `get_object()`. 

-

    lookup_field = 'pk' 

-

 

-

    # Pagination settings 

-

    paginate_by = api_settings.PAGINATE_BY 

-

    paginate_by_param = api_settings.PAGINATE_BY_PARAM 

-

    pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS 

-

    page_kwarg = 'page' 

-

 

-

    # The filter backend classes to use for queryset filtering 

-

    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS 

-

 

-

    # The following attributes may be subject to change, 

-

    # and should be considered private API. 

-

    model_serializer_class = api_settings.DEFAULT_MODEL_SERIALIZER_CLASS 

-

    paginator_class = Paginator 

-

 

-

    ###################################### 

-

    # These are pending deprecation... 

-

 

-

    pk_url_kwarg = 'pk' 

-

    slug_url_kwarg = 'slug' 

-

    slug_field = 'slug' 

-

    allow_empty = True 

-

    filter_backend = api_settings.FILTER_BACKEND 

-

 

-

    def get_serializer_context(self): 

-

        """ 

-

        Extra context provided to the serializer class. 

-

        """ 

-

        return { 

-

            'request': self.request, 

-

            'format': self.format_kwarg, 

-

            'view': self 

-

        } 

-

 

-

    def get_serializer(self, instance=None, data=None, 

-

                       files=None, many=False, partial=False): 

-

        """ 

-

        Return the serializer instance that should be used for validating and 

-

        deserializing input, and for serializing output. 

-

        """ 

-

        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) 

-

 

-

    def get_pagination_serializer(self, page): 

-

        """ 

-

        Return a serializer instance to use with paginated data. 

-

        """ 

-

        class SerializerClass(self.pagination_serializer_class): 

-

            class Meta: 

-

                object_serializer_class = self.get_serializer_class() 

-

 

-

        pagination_serializer_class = SerializerClass 

-

        context = self.get_serializer_context() 

-

        return pagination_serializer_class(instance=page, context=context) 

-

 

-

    def paginate_queryset(self, queryset, page_size=None): 

-

        """ 

-

        Paginate a queryset if required, either returning a page object, 

-

        or `None` if pagination is not configured for this view. 

-

        """ 

-

        deprecated_style = False 

-

        if page_size is not None: 

-

            warnings.warn('The `page_size` parameter to `paginate_queryset()` ' 

-

                          'is due to be 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) 

-

            deprecated_style = True 

-

        else: 

-

            # Determine the required page size. 

-

            # If pagination is not configured, simply return None. 

-

            page_size = self.get_paginate_by() 

-

            if not page_size: 

-

                return None 

-

 

-

        if not self.allow_empty: 

-

            warnings.warn( 

-

                'The `allow_empty` parameter is due to be deprecated. ' 

-

                'To use `allow_empty=False` style behavior, You should override ' 

-

                '`get_queryset()` and explicitly raise a 404 on empty querysets.', 

-

                PendingDeprecationWarning, stacklevel=2 

-

            ) 

-

 

-

        paginator = self.paginator_class(queryset, page_size, 

-

                                         allow_empty_first_page=self.allow_empty) 

-

        page_kwarg = self.kwargs.get(self.page_kwarg) 

-

        page_query_param = self.request.QUERY_PARAMS.get(self.page_kwarg) 

-

        page = page_kwarg or page_query_param or 1 

-

        try: 

-

            page_number = int(page) 

-

        except ValueError: 

-

            if page == 'last': 

-

                page_number = paginator.num_pages 

-

            else: 

-

                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) 

-

            }) 

-

 

-

        if deprecated_style: 

-

            return (paginator, page, page.object_list, page.has_other_pages()) 

-

        return page 

-

 

-

    def filter_queryset(self, queryset): 

-

        """ 

-

        Given a queryset, filter it with whichever filter backend is in use. 

-

 

-

        You are unlikely to want to override this method, although you may need 

-

        to call it either from a list view, or from a custom `get_object` 

-

        method if you want to apply the configured filtering backend to the 

-

        default queryset. 

-

        """ 

-

        filter_backends = self.filter_backends or [] 

-

        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` ' 

-

                'attribute and `DEFAULT_FILTER_BACKENDS` setting, that take ' 

-

                'a *list* of filter backend classes.', 

-

                PendingDeprecationWarning, stacklevel=2 

-

            ) 

-

            filter_backends = [self.filter_backend] 

-

 

-

        for backend in filter_backends: 

-

            queryset = backend().filter_queryset(self.request, queryset, self) 

-

        return queryset 

-

 

-

    ######################## 

-

    ### The following methods provide default implementations 

-

    ### that you may want to override for more complex cases. 

-

 

-

    def get_paginate_by(self, queryset=None): 

-

        """ 

-

        Return the size of pages to use with pagination. 

-

 

-

        If `PAGINATE_BY_PARAM` is set it will attempt to get the page size 

-

        from a named query parameter in the url, eg. ?page_size=100 

-

 

-

        Otherwise defaults to using `self.paginate_by`. 

-

        """ 

-

        if queryset is not None: 

-

            warnings.warn('The `queryset` parameter to `get_paginate_by()` ' 

-

                          'is due to be deprecated.', 

-

                          PendingDeprecationWarning, stacklevel=2) 

-

 

-

        if self.paginate_by_param: 

-

            query_params = self.request.QUERY_PARAMS 

-

            try: 

-

                return int(query_params[self.paginate_by_param]) 

-

            except (KeyError, ValueError): 

-

                pass 

-

 

-

        return self.paginate_by 

-

 

-

    def get_serializer_class(self): 

-

        """ 

-

        Return the class to use for the serializer. 

-

        Defaults to using `self.serializer_class`. 

-

 

-

        You may want to override this if you need to provide different 

-

        serializations depending on the incoming request. 

-

 

-

        (Eg. admins get full serialization, others get basic serialization) 

-

        """ 

-

        serializer_class = self.serializer_class 

-

        if serializer_class is not None: 

-

            return serializer_class 

-

 

-

        assert self.model is not None, \ 

-

            "'%s' should either include a 'serializer_class' attribute, " \ 

-

            "or use the 'model' attribute as a shortcut for " \ 

-

            "automatically generating a serializer class." \ 

-

            % self.__class__.__name__ 

-

 

-

        class DefaultSerializer(self.model_serializer_class): 

-

            class Meta: 

-

                model = self.model 

-

        return DefaultSerializer 

-

 

-

    def get_queryset(self): 

-

        """ 

-

        Get the list of items for this view. 

-

        This must be an iterable, and may be a queryset. 

-

        Defaults to using `self.queryset`. 

-

 

-

        You may want to override this if you need to provide different 

-

        querysets depending on the incoming request. 

-

 

-

        (Eg. return a list of items that is specific to the user) 

-

        """ 

-

        if self.queryset is not None: 

-

            return self.queryset._clone() 

-

 

-

        if self.model is not None: 

-

            return self.model._default_manager.all() 

-

 

-

        raise ImproperlyConfigured("'%s' must define 'queryset' or 'model'" 

-

                                    % self.__class__.__name__) 

-

 

-

    def get_object(self, queryset=None): 

-

        """ 

-

        Returns the object the view is displaying. 

-

 

-

        You may want to override this if you need to provide non-standard 

-

        queryset lookups.  Eg if objects are referenced using multiple 

-

        keyword arguments in the url conf. 

-

        """ 

-

        # Determine the base queryset to use. 

-

        if queryset is None: 

-

            queryset = self.filter_queryset(self.get_queryset()) 

-

        else: 

-

            pass  # Deprecation warning 

-

 

-

        # Perform the lookup filtering. 

-

        pk = self.kwargs.get(self.pk_url_kwarg, None) 

-

        slug = self.kwargs.get(self.slug_url_kwarg, None) 

-

        lookup = self.kwargs.get(self.lookup_field, None) 

-

 

-

        if lookup is not None: 

-

            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. ' 

-

                'Use the `lookup_field` attribute instead', 

-

                PendingDeprecationWarning 

-

            ) 

-

            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. ' 

-

                'Use the `lookup_field` attribute instead', 

-

                PendingDeprecationWarning 

-

            ) 

-

            filter_kwargs = {self.slug_field: slug} 

-

        else: 

-

            raise ImproperlyConfigured( 

-

                'Expected view %s to be called with a URL keyword argument ' 

-

                'named "%s". Fix your URL conf, or set the `.lookup_field` ' 

-

                'attribute on the view correctly.' % 

-

                (self.__class__.__name__, self.lookup_field) 

-

            ) 

-

 

-

        obj = get_object_or_404(queryset, **filter_kwargs) 

-

 

-

        # May raise a permission denied 

-

        self.check_object_permissions(self.request, obj) 

-

 

-

        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. 

-

 

-

    def pre_save(self, obj): 

-

        """ 

-

        Placeholder method for calling before saving an object. 

-

 

-

        May be used to set attributes on the object that are implicit 

-

        in either the request, or the url. 

-

        """ 

-

        pass 

-

 

-

    def post_save(self, obj, created=False): 

-

        """ 

-

        Placeholder method for calling after saving an object. 

-

        """ 

-

        pass 

-

 

-

    def metadata(self, request): 

-

        """ 

-

        Return a dictionary of metadata about the view. 

-

        Used to return responses for OPTIONS requests. 

-

 

-

        We override the default behavior, and add some extra information 

-

        about the required request body for POST and PUT operations. 

-

        """ 

-

        ret = super(GenericAPIView, self).metadata(request) 

-

 

-

        actions = {} 

-

        for method in ('PUT', 'POST'): 

-

            if method not in self.allowed_methods: 

-

                continue 

-

 

-

            cloned_request = clone_request(request, method) 

-

            try: 

-

                # Test global permissions 

-

                self.check_permissions(cloned_request) 

-

                # Test object permissions 

-

                if method == 'PUT': 

-

                    self.get_object() 

-

            except (exceptions.APIException, PermissionDenied, Http404): 

-

                pass 

-

            else: 

-

                # If user has appropriate permissions for the view, include 

-

                # appropriate metadata about the fields that should be supplied. 

-

                serializer = self.get_serializer() 

-

                actions[method] = serializer.metadata() 

-

 

-

        if actions: 

-

            ret['actions'] = actions 

-

 

-

        return ret 

-

 

-

 

-

########################################################## 

-

### Concrete view classes that provide method handlers ### 

-

### by composing the mixin classes with the base view. ### 

-

########################################################## 

-

 

-

class CreateAPIView(mixins.CreateModelMixin, 

-

                    GenericAPIView): 

-

 

-

    """ 

-

    Concrete view for creating a model instance. 

-

    """ 

-

    def post(self, request, *args, **kwargs): 

-

        return self.create(request, *args, **kwargs) 

-

 

-

 

-

class ListAPIView(mixins.ListModelMixin, 

-

                  GenericAPIView): 

-

    """ 

-

    Concrete view for listing a queryset. 

-

    """ 

-

    def get(self, request, *args, **kwargs): 

-

        return self.list(request, *args, **kwargs) 

-

 

-

 

-

class RetrieveAPIView(mixins.RetrieveModelMixin, 

-

                      GenericAPIView): 

-

    """ 

-

    Concrete view for retrieving a model instance. 

-

    """ 

-

    def get(self, request, *args, **kwargs): 

-

        return self.retrieve(request, *args, **kwargs) 

-

 

-

 

-

class DestroyAPIView(mixins.DestroyModelMixin, 

-

                     GenericAPIView): 

-

 

-

    """ 

-

    Concrete view for deleting a model instance. 

-

    """ 

-

    def delete(self, request, *args, **kwargs): 

-

        return self.destroy(request, *args, **kwargs) 

-

 

-

 

-

class UpdateAPIView(mixins.UpdateModelMixin, 

-

                    GenericAPIView): 

-

 

-

    """ 

-

    Concrete view for updating a model instance. 

-

    """ 

-

    def put(self, request, *args, **kwargs): 

-

        return self.update(request, *args, **kwargs) 

-

 

-

    def patch(self, request, *args, **kwargs): 

-

        return self.partial_update(request, *args, **kwargs) 

-

 

-

 

-

class ListCreateAPIView(mixins.ListModelMixin, 

-

                        mixins.CreateModelMixin, 

-

                        GenericAPIView): 

-

    """ 

-

    Concrete view for listing a queryset or creating a model instance. 

-

    """ 

-

    def get(self, request, *args, **kwargs): 

-

        return self.list(request, *args, **kwargs) 

-

 

-

    def post(self, request, *args, **kwargs): 

-

        return self.create(request, *args, **kwargs) 

-

 

-

 

-

class RetrieveUpdateAPIView(mixins.RetrieveModelMixin, 

-

                            mixins.UpdateModelMixin, 

-

                            GenericAPIView): 

-

    """ 

-

    Concrete view for retrieving, updating a model instance. 

-

    """ 

-

    def get(self, request, *args, **kwargs): 

-

        return self.retrieve(request, *args, **kwargs) 

-

 

-

    def put(self, request, *args, **kwargs): 

-

        return self.update(request, *args, **kwargs) 

-

 

-

    def patch(self, request, *args, **kwargs): 

-

        return self.partial_update(request, *args, **kwargs) 

-

 

-

 

-

class RetrieveDestroyAPIView(mixins.RetrieveModelMixin, 

-

                             mixins.DestroyModelMixin, 

-

                             GenericAPIView): 

-

    """ 

-

    Concrete view for retrieving or deleting a model instance. 

-

    """ 

-

    def get(self, request, *args, **kwargs): 

-

        return self.retrieve(request, *args, **kwargs) 

-

 

-

    def delete(self, request, *args, **kwargs): 

-

        return self.destroy(request, *args, **kwargs) 

-

 

-

 

-

class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, 

-

                                   mixins.UpdateModelMixin, 

-

                                   mixins.DestroyModelMixin, 

-

                                   GenericAPIView): 

-

    """ 

-

    Concrete view for retrieving, updating or deleting a model instance. 

-

    """ 

-

    def get(self, request, *args, **kwargs): 

-

        return self.retrieve(request, *args, **kwargs) 

-

 

-

    def put(self, request, *args, **kwargs): 

-

        return self.update(request, *args, **kwargs) 

-

 

-

    def patch(self, request, *args, **kwargs): 

-

        return self.partial_update(request, *args, **kwargs) 

-

 

-

    def delete(self, request, *args, **kwargs): 

-

        return self.destroy(request, *args, **kwargs) 

-

 

-

 

-

########################## 

-

### Deprecated classes ### 

-

########################## 

-

 

-

class MultipleObjectAPIView(GenericAPIView): 

-

    def __init__(self, *args, **kwargs): 

-

        warnings.warn( 

-

            'Subclassing `MultipleObjectAPIView` is due to be deprecated. ' 

-

            'You should simply subclass `GenericAPIView` instead.', 

-

            PendingDeprecationWarning, stacklevel=2 

-

        ) 

-

        super(MultipleObjectAPIView, self).__init__(*args, **kwargs) 

-

 

-

 

-

class SingleObjectAPIView(GenericAPIView): 

-

    def __init__(self, *args, **kwargs): 

-

        warnings.warn( 

-

            'Subclassing `SingleObjectAPIView` is due to be deprecated. ' 

-

            'You should simply subclass `GenericAPIView` instead.', 

-

            PendingDeprecationWarning, stacklevel=2 

-

        ) 

-

        super(SingleObjectAPIView, self).__init__(*args, **kwargs) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_mixins.html b/htmlcov/rest_framework_mixins.html deleted file mode 100644 index fa62f2ae..00000000 --- a/htmlcov/rest_framework_mixins.html +++ /dev/null @@ -1,449 +0,0 @@ - - - - - - - - Coverage for rest_framework/mixins: 93% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

- -
-

""" 

-

Basic building blocks for generic class based views. 

-

 

-

We don't bind behaviour to http method handlers yet, 

-

which allows mixin classes to be composed in interesting ways. 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

from django.http import Http404 

-

from rest_framework import status 

-

from rest_framework.response import Response 

-

from rest_framework.request import clone_request 

-

import warnings 

-

 

-

 

-

def _get_validation_exclusions(obj, pk=None, slug_field=None, lookup_field=None): 

-

    """ 

-

    Given a model instance, and an optional pk and slug field, 

-

    return the full list of all other field names on that model. 

-

 

-

    For use when performing full_clean on a model instance, 

-

    so we only clean the required fields. 

-

    """ 

-

    include = [] 

-

 

-

    if pk: 

-

        # Pending deprecation 

-

        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 

-

        include.append(slug_field) 

-

 

-

    if lookup_field and lookup_field != 'pk': 

-

        include.append(lookup_field) 

-

 

-

    return [field.name for field in obj._meta.fields if field.name not in include] 

-

 

-

 

-

class CreateModelMixin(object): 

-

    """ 

-

    Create a model instance. 

-

    """ 

-

    def create(self, request, *args, **kwargs): 

-

        serializer = self.get_serializer(data=request.DATA, files=request.FILES) 

-

 

-

        if serializer.is_valid(): 

-

            self.pre_save(serializer.object) 

-

            self.object = serializer.save(force_insert=True) 

-

            self.post_save(self.object, created=True) 

-

            headers = self.get_success_headers(serializer.data) 

-

            return Response(serializer.data, status=status.HTTP_201_CREATED, 

-

                            headers=headers) 

-

 

-

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 

-

 

-

    def get_success_headers(self, data): 

-

        try: 

-

            return {'Location': data['url']} 

-

        except (TypeError, KeyError): 

-

            return {} 

-

 

-

 

-

class ListModelMixin(object): 

-

    """ 

-

    List a queryset. 

-

    """ 

-

    empty_error = "Empty list and '%(class_name)s.allow_empty' is False." 

-

 

-

    def list(self, request, *args, **kwargs): 

-

        self.object_list = self.filter_queryset(self.get_queryset()) 

-

 

-

        # Default is to allow empty querysets.  This can be altered by setting 

-

        # `.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. ' 

-

                'To use `allow_empty=False` style behavior, You should override ' 

-

                '`get_queryset()` and explicitly raise a 404 on empty querysets.', 

-

                PendingDeprecationWarning 

-

            ) 

-

            class_name = self.__class__.__name__ 

-

            error_msg = self.empty_error % {'class_name': class_name} 

-

            raise Http404(error_msg) 

-

 

-

        # Switch between paginated or standard style responses 

-

        page = self.paginate_queryset(self.object_list) 

-

        if page is not None: 

-

            serializer = self.get_pagination_serializer(page) 

-

        else: 

-

            serializer = self.get_serializer(self.object_list, many=True) 

-

 

-

        return Response(serializer.data) 

-

 

-

 

-

class RetrieveModelMixin(object): 

-

    """ 

-

    Retrieve a model instance. 

-

    """ 

-

    def retrieve(self, request, *args, **kwargs): 

-

        self.object = self.get_object() 

-

        serializer = self.get_serializer(self.object) 

-

        return Response(serializer.data) 

-

 

-

 

-

class UpdateModelMixin(object): 

-

    """ 

-

    Update a model instance. 

-

    """ 

-

    def update(self, request, *args, **kwargs): 

-

        partial = kwargs.pop('partial', False) 

-

        self.object = self.get_object_or_none() 

-

 

-

        if self.object is None: 

-

            created = True 

-

            save_kwargs = {'force_insert': True} 

-

            success_status_code = status.HTTP_201_CREATED 

-

        else: 

-

            created = False 

-

            save_kwargs = {'force_update': True} 

-

            success_status_code = status.HTTP_200_OK 

-

 

-

        serializer = self.get_serializer(self.object, data=request.DATA, 

-

                                         files=request.FILES, partial=partial) 

-

 

-

        if serializer.is_valid(): 

-

            self.pre_save(serializer.object) 

-

            self.object = serializer.save(**save_kwargs) 

-

            self.post_save(self.object, created=created) 

-

            return Response(serializer.data, status=success_status_code) 

-

 

-

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 

-

 

-

    def partial_update(self, request, *args, **kwargs): 

-

        kwargs['partial'] = True 

-

        return self.update(request, *args, **kwargs) 

-

 

-

    def get_object_or_none(self): 

-

        try: 

-

            return self.get_object() 

-

        except Http404: 

-

            # If this is a PUT-as-create operation, we need to ensure that 

-

            # we have relevant permissions, as if this was a POST request. 

-

            # This will either raise a PermissionDenied exception, 

-

            # or simply return None 

-

            self.check_permissions(clone_request(self.request, 'POST')) 

-

 

-

    def pre_save(self, obj): 

-

        """ 

-

        Set any attributes on the object that are implicit in the request. 

-

        """ 

-

        # pk and/or slug attributes are implicit in the URL. 

-

        lookup = self.kwargs.get(self.lookup_field, None) 

-

        pk = self.kwargs.get(self.pk_url_kwarg, None) 

-

        slug = self.kwargs.get(self.slug_url_kwarg, None) 

-

        slug_field = slug and self.slug_field or None 

-

 

-

        if lookup: 

-

            setattr(obj, self.lookup_field, lookup) 

-

 

-

        if pk: 

-

            setattr(obj, 'pk', pk) 

-

 

-

        if slug: 

-

            setattr(obj, slug_field, slug) 

-

 

-

        # Ensure we clean the attributes so that we don't eg return integer 

-

        # pk using a string representation, as provided by the url conf kwarg. 

-

        if hasattr(obj, 'full_clean'): 

-

            exclude = _get_validation_exclusions(obj, pk, slug_field, self.lookup_field) 

-

            obj.full_clean(exclude) 

-

 

-

 

-

class DestroyModelMixin(object): 

-

    """ 

-

    Destroy a model instance. 

-

    """ 

-

    def destroy(self, request, *args, **kwargs): 

-

        obj = self.get_object() 

-

        obj.delete() 

-

        return Response(status=status.HTTP_204_NO_CONTENT) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_models.html b/htmlcov/rest_framework_models.html deleted file mode 100644 index 6786c620..00000000 --- a/htmlcov/rest_framework_models.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - Coverage for rest_framework/models: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

- -
-

# Just to keep things like ./manage.py test happy 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_negotiation.html b/htmlcov/rest_framework_negotiation.html deleted file mode 100644 index 7ed526c9..00000000 --- a/htmlcov/rest_framework_negotiation.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - - - - Coverage for rest_framework/negotiation: 90% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

- -
-

""" 

-

Content negotiation deals with selecting an appropriate renderer given the 

-

incoming request.  Typically this will be based on the request's Accept header. 

-

""" 

-

from __future__ import unicode_literals 

-

from django.http import Http404 

-

from rest_framework import exceptions 

-

from rest_framework.settings import api_settings 

-

from rest_framework.utils.mediatypes import order_by_precedence, media_type_matches 

-

from rest_framework.utils.mediatypes import _MediaType 

-

 

-

 

-

class BaseContentNegotiation(object): 

-

    def select_parser(self, request, parsers): 

-

        raise NotImplementedError('.select_parser() must be implemented') 

-

 

-

    def select_renderer(self, request, renderers, format_suffix=None): 

-

        raise NotImplementedError('.select_renderer() must be implemented') 

-

 

-

 

-

class DefaultContentNegotiation(BaseContentNegotiation): 

-

    settings = api_settings 

-

 

-

    def select_parser(self, request, parsers): 

-

        """ 

-

        Given a list of parsers and a media type, return the appropriate 

-

        parser to handle the incoming request. 

-

        """ 

-

        for parser in parsers: 

-

            if media_type_matches(parser.media_type, request.content_type): 

-

                return parser 

-

        return None 

-

 

-

    def select_renderer(self, request, renderers, format_suffix=None): 

-

        """ 

-

        Given a request and a list of renderers, return a two-tuple of: 

-

        (renderer, media type). 

-

        """ 

-

        # Allow URL style format override.  eg. "?format=json 

-

        format_query_param = self.settings.URL_FORMAT_OVERRIDE 

-

        format = format_suffix or request.QUERY_PARAMS.get(format_query_param) 

-

 

-

        if format: 

-

            renderers = self.filter_renderers(renderers, format) 

-

 

-

        accepts = self.get_accept_list(request) 

-

 

-

        # Check the acceptable media types against each renderer, 

-

        # attempting more specific media types first 

-

        # NB. The inner loop here isn't as bad as it first looks :) 

-

        #     Worst case is we're looping over len(accept_list) * len(self.renderers) 

-

        for media_type_set in order_by_precedence(accepts): 

-

            for renderer in renderers: 

-

                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): 

-

                            # Eg client requests '*/*' 

-

                            # Accepted media type is 'application/json' 

-

                            return renderer, renderer.media_type 

-

                        else: 

-

                            # Eg client requests 'application/json; indent=8' 

-

                            # Accepted media type is 'application/json; indent=8' 

-

                            return renderer, media_type 

-

 

-

        raise exceptions.NotAcceptable(available_renderers=renderers) 

-

 

-

    def filter_renderers(self, renderers, format): 

-

        """ 

-

        If there is a '.json' style format suffix, filter the renderers 

-

        so that we only negotiation against those that accept that format. 

-

        """ 

-

        renderers = [renderer for renderer in renderers 

-

                     if renderer.format == format] 

-

        if not renderers: 

-

            raise Http404 

-

        return renderers 

-

 

-

    def get_accept_list(self, request): 

-

        """ 

-

        Given the incoming request, return a tokenised list of media 

-

        type strings. 

-

 

-

        Allows URL style accept override.  eg. "?accept=application/json" 

-

        """ 

-

        header = request.META.get('HTTP_ACCEPT', '*/*') 

-

        header = request.QUERY_PARAMS.get(self.settings.URL_ACCEPT_OVERRIDE, header) 

-

        return [token.strip() for token in header.split(',')] 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_pagination.html b/htmlcov/rest_framework_pagination.html deleted file mode 100644 index 5a3f76d8..00000000 --- a/htmlcov/rest_framework_pagination.html +++ /dev/null @@ -1,269 +0,0 @@ - - - - - - - - Coverage for rest_framework/pagination: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

- -
-

""" 

-

Pagination serializers determine the structure of the output that should 

-

be used for paginated responses. 

-

""" 

-

from __future__ import unicode_literals 

-

from rest_framework import serializers 

-

from rest_framework.templatetags.rest_framework import replace_query_param 

-

 

-

 

-

class NextPageField(serializers.Field): 

-

    """ 

-

    Field that returns a link to the next page in paginated results. 

-

    """ 

-

    page_field = 'page' 

-

 

-

    def to_native(self, value): 

-

        if not value.has_next(): 

-

            return None 

-

        page = value.next_page_number() 

-

        request = self.context.get('request') 

-

        url = request and request.build_absolute_uri() or '' 

-

        return replace_query_param(url, self.page_field, page) 

-

 

-

 

-

class PreviousPageField(serializers.Field): 

-

    """ 

-

    Field that returns a link to the previous page in paginated results. 

-

    """ 

-

    page_field = 'page' 

-

 

-

    def to_native(self, value): 

-

        if not value.has_previous(): 

-

            return None 

-

        page = value.previous_page_number() 

-

        request = self.context.get('request') 

-

        url = request and request.build_absolute_uri() or '' 

-

        return replace_query_param(url, self.page_field, page) 

-

 

-

 

-

class DefaultObjectSerializer(serializers.Field): 

-

    """ 

-

    If no object serializer is specified, then this serializer will be applied 

-

    as the default. 

-

    """ 

-

 

-

    def __init__(self, source=None, context=None): 

-

        # Note: Swallow context kwarg - only required for eg. ModelSerializer. 

-

        super(DefaultObjectSerializer, self).__init__(source=source) 

-

 

-

 

-

class PaginationSerializerOptions(serializers.SerializerOptions): 

-

    """ 

-

    An object that stores the options that may be provided to a 

-

    pagination serializer by using the inner `Meta` class. 

-

 

-

    Accessible on the instance as `serializer.opts`. 

-

    """ 

-

    def __init__(self, meta): 

-

        super(PaginationSerializerOptions, self).__init__(meta) 

-

        self.object_serializer_class = getattr(meta, 'object_serializer_class', 

-

                                               DefaultObjectSerializer) 

-

 

-

 

-

class BasePaginationSerializer(serializers.Serializer): 

-

    """ 

-

    A base class for pagination serializers to inherit from, 

-

    to make implementing custom serializers more easy. 

-

    """ 

-

    _options_class = PaginationSerializerOptions 

-

    results_field = 'results' 

-

 

-

    def __init__(self, *args, **kwargs): 

-

        """ 

-

        Override init to add in the object serializer field on-the-fly. 

-

        """ 

-

        super(BasePaginationSerializer, self).__init__(*args, **kwargs) 

-

        results_field = self.results_field 

-

        object_serializer = self.opts.object_serializer_class 

-

 

-

        if 'context' in kwargs: 

-

            context_kwarg = {'context': kwargs['context']} 

-

        else: 

-

            context_kwarg = {} 

-

 

-

        self.fields[results_field] = object_serializer(source='object_list', **context_kwarg) 

-

 

-

 

-

class PaginationSerializer(BasePaginationSerializer): 

-

    """ 

-

    A default implementation of a pagination serializer. 

-

    """ 

-

    count = serializers.Field(source='paginator.count') 

-

    next = NextPageField(source='*') 

-

    previous = PreviousPageField(source='*') 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_parsers.html b/htmlcov/rest_framework_parsers.html deleted file mode 100644 index 92f1db62..00000000 --- a/htmlcov/rest_framework_parsers.html +++ /dev/null @@ -1,671 +0,0 @@ - - - - - - - - Coverage for rest_framework/parsers: 92% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

- -
-

""" 

-

Parsers are used to parse the content of incoming HTTP requests. 

-

 

-

They give us a generic way of being able to handle various media types 

-

on the request, such as form content or json encoded data. 

-

""" 

-

from __future__ import unicode_literals 

-

from django.conf import settings 

-

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 yaml, etree 

-

from rest_framework.exceptions import ParseError 

-

from rest_framework.compat import six 

-

import json 

-

import datetime 

-

import decimal 

-

 

-

 

-

class DataAndFiles(object): 

-

    def __init__(self, data, files): 

-

        self.data = data 

-

        self.files = files 

-

 

-

 

-

class BaseParser(object): 

-

    """ 

-

    All parsers should extend `BaseParser`, specifying a `media_type` 

-

    attribute, and overriding the `.parse()` method. 

-

    """ 

-

 

-

    media_type = None 

-

 

-

    def parse(self, stream, media_type=None, parser_context=None): 

-

        """ 

-

        Given a stream to read from, return the parsed representation. 

-

        Should return parsed data, or a `DataAndFiles` object consisting of the 

-

        parsed data and files. 

-

        """ 

-

        raise NotImplementedError(".parse() must be overridden.") 

-

 

-

 

-

class JSONParser(BaseParser): 

-

    """ 

-

    Parses JSON-serialized data. 

-

    """ 

-

 

-

    media_type = 'application/json' 

-

 

-

    def parse(self, stream, media_type=None, parser_context=None): 

-

        """ 

-

        Returns a 2-tuple of `(data, files)`. 

-

 

-

        `data` will be an object which is the parsed content of the response. 

-

        `files` will always be `None`. 

-

        """ 

-

        parser_context = parser_context or {} 

-

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

-

 

-

        try: 

-

            data = stream.read().decode(encoding) 

-

            return json.loads(data) 

-

        except ValueError as exc: 

-

            raise ParseError('JSON parse error - %s' % six.text_type(exc)) 

-

 

-

 

-

class YAMLParser(BaseParser): 

-

    """ 

-

    Parses YAML-serialized data. 

-

    """ 

-

 

-

    media_type = 'application/yaml' 

-

 

-

    def parse(self, stream, media_type=None, parser_context=None): 

-

        """ 

-

        Returns a 2-tuple of `(data, files)`. 

-

 

-

        `data` will be an object which is the parsed content of the response. 

-

        `files` will always be `None`. 

-

        """ 

-

        assert yaml, 'YAMLParser requires pyyaml to be installed' 

-

 

-

        parser_context = parser_context or {} 

-

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

-

 

-

        try: 

-

            data = stream.read().decode(encoding) 

-

            return yaml.safe_load(data) 

-

        except (ValueError, yaml.parser.ParserError) as exc: 

-

            raise ParseError('YAML parse error - %s' % six.u(exc)) 

-

 

-

 

-

class FormParser(BaseParser): 

-

    """ 

-

    Parser for form data. 

-

    """ 

-

 

-

    media_type = 'application/x-www-form-urlencoded' 

-

 

-

    def parse(self, stream, media_type=None, parser_context=None): 

-

        """ 

-

        Returns a 2-tuple of `(data, files)`. 

-

 

-

        `data` will be a :class:`QueryDict` containing all the form parameters. 

-

        `files` will always be :const:`None`. 

-

        """ 

-

        parser_context = parser_context or {} 

-

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

-

        data = QueryDict(stream.read(), encoding=encoding) 

-

        return data 

-

 

-

 

-

class MultiPartParser(BaseParser): 

-

    """ 

-

    Parser for multipart form data, which may include file data. 

-

    """ 

-

 

-

    media_type = 'multipart/form-data' 

-

 

-

    def parse(self, stream, media_type=None, parser_context=None): 

-

        """ 

-

        Returns a DataAndFiles object. 

-

 

-

        `.data` will be a `QueryDict` containing all the form parameters. 

-

        `.files` will be a `QueryDict` containing all the form files. 

-

        """ 

-

        parser_context = parser_context or {} 

-

        request = parser_context['request'] 

-

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

-

        meta = request.META 

-

        upload_handlers = request.upload_handlers 

-

 

-

        try: 

-

            parser = DjangoMultiPartParser(meta, stream, upload_handlers, encoding) 

-

            data, files = parser.parse() 

-

            return DataAndFiles(data, files) 

-

        except MultiPartParserError as exc: 

-

            raise ParseError('Multipart form parse error - %s' % six.u(exc)) 

-

 

-

 

-

class XMLParser(BaseParser): 

-

    """ 

-

    XML parser. 

-

    """ 

-

 

-

    media_type = 'application/xml' 

-

 

-

    def parse(self, stream, media_type=None, parser_context=None): 

-

        assert etree, 'XMLParser requires defusedxml to be installed' 

-

 

-

        parser_context = parser_context or {} 

-

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

-

        parser = etree.DefusedXMLParser(encoding=encoding) 

-

        try: 

-

            tree = etree.parse(stream, parser=parser, forbid_dtd=True) 

-

        except (etree.ParseError, ValueError) as exc: 

-

            raise ParseError('XML parse error - %s' % six.u(exc)) 

-

        data = self._xml_convert(tree.getroot()) 

-

 

-

        return data 

-

 

-

    def _xml_convert(self, element): 

-

        """ 

-

        convert the xml `element` into the corresponding python object 

-

        """ 

-

 

-

        children = list(element) 

-

 

-

        if len(children) == 0: 

-

            return self._type_convert(element.text) 

-

        else: 

-

            # if the fist child tag is list-item means all children are list-item 

-

            if children[0].tag == "list-item": 

-

                data = [] 

-

                for child in children: 

-

                    data.append(self._xml_convert(child)) 

-

            else: 

-

                data = {} 

-

                for child in children: 

-

                    data[child.tag] = self._xml_convert(child) 

-

 

-

            return data 

-

 

-

    def _type_convert(self, value): 

-

        """ 

-

        Converts the value returned by the XMl parse into the equivalent 

-

        Python type 

-

        """ 

-

        if value is None: 

-

            return value 

-

 

-

        try: 

-

            return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S') 

-

        except ValueError: 

-

            pass 

-

 

-

        try: 

-

            return int(value) 

-

        except ValueError: 

-

            pass 

-

 

-

        try: 

-

            return decimal.Decimal(value) 

-

        except decimal.InvalidOperation: 

-

            pass 

-

 

-

        return value 

-

 

-

 

-

class FileUploadParser(BaseParser): 

-

    """ 

-

    Parser for file upload data. 

-

    """ 

-

    media_type = '*/*' 

-

 

-

    def parse(self, stream, media_type=None, parser_context=None): 

-

        """ 

-

        Returns a DataAndFiles object. 

-

 

-

        `.data` will be None (we expect request body to be a file content). 

-

        `.files` will be a `QueryDict` containing one 'file' element. 

-

        """ 

-

 

-

        parser_context = parser_context or {} 

-

        request = parser_context['request'] 

-

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

-

        meta = request.META 

-

        upload_handlers = request.upload_handlers 

-

        filename = self.get_filename(stream, media_type, parser_context) 

-

 

-

        # Note that this code is extracted from Django's handling of 

-

        # file uploads in MultiPartParser. 

-

        content_type = meta.get('HTTP_CONTENT_TYPE', 

-

                                meta.get('CONTENT_TYPE', '')) 

-

        try: 

-

            content_length = int(meta.get('HTTP_CONTENT_LENGTH', 

-

                                          meta.get('CONTENT_LENGTH', 0))) 

-

        except (ValueError, TypeError): 

-

            content_length = None 

-

 

-

        # See if the handler will want to take care of the parsing. 

-

        for handler in upload_handlers: 

-

            result = handler.handle_raw_input(None, 

-

                                              meta, 

-

                                              content_length, 

-

                                              None, 

-

                                              encoding) 

-

            if result is not None: 

-

                return DataAndFiles(None, {'file': result[1]}) 

-

 

-

        # This is the standard case. 

-

        possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size] 

-

        chunk_size = min([2 ** 31 - 4] + possible_sizes) 

-

        chunks = ChunkIter(stream, chunk_size) 

-

        counters = [0] * len(upload_handlers) 

-

 

-

        for handler in upload_handlers: 

-

            try: 

-

                handler.new_file(None, filename, content_type, 

-

                                 content_length, encoding) 

-

            except StopFutureHandlers: 

-

                break 

-

 

-

        for chunk in chunks: 

-

            for i, handler in enumerate(upload_handlers): 

-

                chunk_length = len(chunk) 

-

                chunk = handler.receive_data_chunk(chunk, counters[i]) 

-

                counters[i] += chunk_length 

-

                if chunk is None: 

-

                    break 

-

 

-

        for i, handler in enumerate(upload_handlers): 

-

            file_obj = handler.file_complete(counters[i]) 

-

            if file_obj: 

-

                return DataAndFiles(None, {'file': file_obj}) 

-

        raise ParseError("FileUpload parse error - " 

-

                         "none of upload handlers can handle the stream") 

-

 

-

    def get_filename(self, stream, media_type, parser_context): 

-

        """ 

-

        Detects the uploaded file name. First searches a 'filename' url kwarg. 

-

        Then tries to parse Content-Disposition header. 

-

        """ 

-

        try: 

-

            return parser_context['kwargs']['filename'] 

-

        except KeyError: 

-

            pass 

-

 

-

        try: 

-

            meta = parser_context['request'].META 

-

            disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION']) 

-

            return disposition[1]['filename'] 

-

        except (AttributeError, KeyError): 

-

            pass 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_permissions.html b/htmlcov/rest_framework_permissions.html deleted file mode 100644 index 20a29522..00000000 --- a/htmlcov/rest_framework_permissions.html +++ /dev/null @@ -1,429 +0,0 @@ - - - - - - - - Coverage for rest_framework/permissions: 81% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

- -
-

""" 

-

Provides a set of pluggable permission policies. 

-

""" 

-

from __future__ import unicode_literals 

-

import inspect 

-

import warnings 

-

 

-

SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS'] 

-

 

-

from rest_framework.compat import oauth2_provider_scope, oauth2_constants 

-

 

-

 

-

class BasePermission(object): 

-

    """ 

-

    A base class from which all permission classes should inherit. 

-

    """ 

-

 

-

    def has_permission(self, request, view): 

-

        """ 

-

        Return `True` if permission is granted, `False` otherwise. 

-

        """ 

-

        return True 

-

 

-

    def has_object_permission(self, request, view, obj): 

-

        """ 

-

        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 

-

 

-

 

-

class AllowAny(BasePermission): 

-

    """ 

-

    Allow any access. 

-

    This isn't strictly required, since you could use an empty 

-

    permission_classes list, but it's useful because it makes the intention 

-

    more explicit. 

-

    """ 

-

    def has_permission(self, request, view): 

-

        return True 

-

 

-

 

-

class IsAuthenticated(BasePermission): 

-

    """ 

-

    Allows access only to authenticated users. 

-

    """ 

-

 

-

    def has_permission(self, request, view): 

-

        if request.user and request.user.is_authenticated(): 

-

            return True 

-

        return False 

-

 

-

 

-

class IsAdminUser(BasePermission): 

-

    """ 

-

    Allows access only to admin users. 

-

    """ 

-

 

-

    def has_permission(self, request, view): 

-

        if request.user and request.user.is_staff: 

-

            return True 

-

        return False 

-

 

-

 

-

class IsAuthenticatedOrReadOnly(BasePermission): 

-

    """ 

-

    The request is authenticated as a user, or is a read-only request. 

-

    """ 

-

 

-

    def has_permission(self, request, view): 

-

        if (request.method in SAFE_METHODS or 

-

            request.user and 

-

            request.user.is_authenticated()): 

-

            return True 

-

        return False 

-

 

-

 

-

class DjangoModelPermissions(BasePermission): 

-

    """ 

-

    The request is authenticated using `django.contrib.auth` permissions. 

-

    See: https://docs.djangoproject.com/en/dev/topics/auth/#permissions 

-

 

-

    It ensures that the user is authenticated, and has the appropriate 

-

    `add`/`change`/`delete` permissions on the model. 

-

 

-

    This permission can only be applied against view classes that 

-

    provide a `.model` or `.queryset` attribute. 

-

    """ 

-

 

-

    # Map methods into required permission codes. 

-

    # Override this if you need to also provide 'view' permissions, 

-

    # or if you want to provide custom permission codes. 

-

    perms_map = { 

-

        'GET': [], 

-

        'OPTIONS': [], 

-

        'HEAD': [], 

-

        'POST': ['%(app_label)s.add_%(model_name)s'], 

-

        'PUT': ['%(app_label)s.change_%(model_name)s'], 

-

        'PATCH': ['%(app_label)s.change_%(model_name)s'], 

-

        'DELETE': ['%(app_label)s.delete_%(model_name)s'], 

-

    } 

-

 

-

    authenticated_users_only = True 

-

 

-

    def get_required_permissions(self, method, model_cls): 

-

        """ 

-

        Given a model and an HTTP method, return the list of permission 

-

        codes that the user is required to have. 

-

        """ 

-

        kwargs = { 

-

            'app_label': model_cls._meta.app_label, 

-

            'model_name': model_cls._meta.module_name 

-

        } 

-

        return [perm % kwargs for perm in self.perms_map[method]] 

-

 

-

    def has_permission(self, request, view): 

-

        model_cls = getattr(view, 'model', None) 

-

        queryset = getattr(view, 'queryset', None) 

-

 

-

        if model_cls is None and queryset is not None: 

-

            model_cls = queryset.model 

-

 

-

        # Workaround to ensure DjangoModelPermissions are not applied 

-

        # to the root view when using DefaultRouter. 

-

        if model_cls is None and getattr(view, '_ignore_model_permissions', False): 

-

            return True 

-

 

-

        assert model_cls, ('Cannot apply DjangoModelPermissions on a view that' 

-

                           ' does not have `.model` or `.queryset` property.') 

-

 

-

        perms = self.get_required_permissions(request.method, model_cls) 

-

 

-

        if (request.user and 

-

            (request.user.is_authenticated() or not self.authenticated_users_only) and 

-

            request.user.has_perms(perms)): 

-

            return True 

-

        return False 

-

 

-

 

-

class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions): 

-

    """ 

-

    Similar to DjangoModelPermissions, except that anonymous users are 

-

    allowed read-only access. 

-

    """ 

-

    authenticated_users_only = False 

-

 

-

 

-

class TokenHasReadWriteScope(BasePermission): 

-

    """ 

-

    The request is authenticated as a user and the token used has the right scope 

-

    """ 

-

 

-

    def has_permission(self, request, view): 

-

        token = request.auth 

-

        read_only = request.method in SAFE_METHODS 

-

 

-

        if not token: 

-

            return False 

-

 

-

        if hasattr(token, 'resource'):  # OAuth 1 

-

            return read_only or not request.auth.resource.is_readonly 

-

        elif hasattr(token, 'scope'):  # OAuth 2 

-

            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.') 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_relations.html b/htmlcov/rest_framework_relations.html deleted file mode 100644 index 29ad3cf6..00000000 --- a/htmlcov/rest_framework_relations.html +++ /dev/null @@ -1,1347 +0,0 @@ - - - - - - - - Coverage for rest_framework/relations: 76% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

-

296

-

297

-

298

-

299

-

300

-

301

-

302

-

303

-

304

-

305

-

306

-

307

-

308

-

309

-

310

-

311

-

312

-

313

-

314

-

315

-

316

-

317

-

318

-

319

-

320

-

321

-

322

-

323

-

324

-

325

-

326

-

327

-

328

-

329

-

330

-

331

-

332

-

333

-

334

-

335

-

336

-

337

-

338

-

339

-

340

-

341

-

342

-

343

-

344

-

345

-

346

-

347

-

348

-

349

-

350

-

351

-

352

-

353

-

354

-

355

-

356

-

357

-

358

-

359

-

360

-

361

-

362

-

363

-

364

-

365

-

366

-

367

-

368

-

369

-

370

-

371

-

372

-

373

-

374

-

375

-

376

-

377

-

378

-

379

-

380

-

381

-

382

-

383

-

384

-

385

-

386

-

387

-

388

-

389

-

390

-

391

-

392

-

393

-

394

-

395

-

396

-

397

-

398

-

399

-

400

-

401

-

402

-

403

-

404

-

405

-

406

-

407

-

408

-

409

-

410

-

411

-

412

-

413

-

414

-

415

-

416

-

417

-

418

-

419

-

420

-

421

-

422

-

423

-

424

-

425

-

426

-

427

-

428

-

429

-

430

-

431

-

432

-

433

-

434

-

435

-

436

-

437

-

438

-

439

-

440

-

441

-

442

-

443

-

444

-

445

-

446

-

447

-

448

-

449

-

450

-

451

-

452

-

453

-

454

-

455

-

456

-

457

-

458

-

459

-

460

-

461

-

462

-

463

-

464

-

465

-

466

-

467

-

468

-

469

-

470

-

471

-

472

-

473

-

474

-

475

-

476

-

477

-

478

-

479

-

480

-

481

-

482

-

483

-

484

-

485

-

486

-

487

-

488

-

489

-

490

-

491

-

492

-

493

-

494

-

495

-

496

-

497

-

498

-

499

-

500

-

501

-

502

-

503

-

504

-

505

-

506

-

507

-

508

-

509

-

510

-

511

-

512

-

513

-

514

-

515

-

516

-

517

-

518

-

519

-

520

-

521

-

522

-

523

-

524

-

525

-

526

-

527

-

528

-

529

-

530

-

531

-

532

-

533

-

534

-

535

-

536

-

537

-

538

-

539

-

540

-

541

-

542

-

543

-

544

-

545

-

546

-

547

-

548

-

549

-

550

-

551

-

552

-

553

-

554

-

555

-

556

-

557

-

558

-

559

-

560

-

561

-

562

-

563

-

564

-

565

-

566

-

567

-

568

-

569

-

570

-

571

-

572

-

573

-

574

-

575

-

576

-

577

-

578

-

579

-

580

-

581

-

582

-

583

-

584

-

585

-

586

-

587

-

588

-

589

-

590

-

591

-

592

-

593

-

594

-

595

-

596

-

597

-

598

-

599

-

600

-

601

-

602

-

603

-

604

-

605

-

606

-

607

-

608

-

609

-

610

-

611

-

612

-

613

-

614

-

615

-

616

-

617

-

618

-

619

-

620

-

621

-

622

-

623

-

624

-

625

-

626

-

627

-

628

-

629

-

630

-

631

-

632

-

633

- -
-

""" 

-

Serializer fields that deal with relationships. 

-

 

-

These fields allow you to specify the style that should be used to represent 

-

model relationships, including hyperlinks, primary keys, or slugs. 

-

""" 

-

from __future__ import unicode_literals 

-

from django.core.exceptions import ObjectDoesNotExist, ValidationError 

-

from django.core.urlresolvers import resolve, get_script_prefix, NoReverseMatch 

-

from django import forms 

-

from django.db.models.fields import BLANK_CHOICE_DASH 

-

from django.forms import widgets 

-

from django.forms.models import ModelChoiceIterator 

-

from django.utils.translation import ugettext_lazy as _ 

-

from rest_framework.fields import Field, WritableField, get_component, is_simple_callable 

-

from rest_framework.reverse import reverse 

-

from rest_framework.compat import urlparse 

-

from rest_framework.compat import smart_text 

-

import warnings 

-

 

-

 

-

##### Relational fields ##### 

-

 

-

 

-

# Not actually Writable, but subclasses may need to be. 

-

class RelatedField(WritableField): 

-

    """ 

-

    Base class for related model fields. 

-

 

-

    This represents a relationship using the unicode representation of the target. 

-

    """ 

-

    widget = widgets.Select 

-

    many_widget = widgets.SelectMultiple 

-

    form_field_class = forms.ChoiceField 

-

    many_form_field_class = forms.MultipleChoiceField 

-

 

-

    cache_choices = False 

-

    empty_label = None 

-

    read_only = True 

-

    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: 

-

            self.widget = self.many_widget 

-

            self.form_field_class = self.many_form_field_class 

-

 

-

        kwargs['read_only'] = kwargs.pop('read_only', self.read_only) 

-

        super(RelatedField, self).__init__(*args, **kwargs) 

-

 

-

        if not self.required: 

-

            self.empty_label = BLANK_CHOICE_DASH[0][1] 

-

 

-

        self.queryset = queryset 

-

 

-

    def initialize(self, parent, field_name): 

-

        super(RelatedField, self).initialize(parent, field_name) 

-

        if self.queryset is None and not self.read_only: 

-

            try: 

-

                manager = getattr(self.parent.opts.model, self.source or field_name) 

-

                if hasattr(manager, 'related'):  # Forward 

-

                    self.queryset = manager.related.model._default_manager.all() 

-

                else:  # Reverse 

-

                    self.queryset = manager.field.rel.to._default_manager.all() 

-

            except Exception: 

-

                msg = ('Serializer related fields must include a `queryset`' + 

-

                       ' argument or set `read_only=True') 

-

                raise Exception(msg) 

-

 

-

    ### We need this stuff to make form choices work... 

-

 

-

    def prepare_value(self, obj): 

-

        return self.to_native(obj) 

-

 

-

    def label_from_instance(self, obj): 

-

        """ 

-

        Return a readable representation for use with eg. select widgets. 

-

        """ 

-

        desc = smart_text(obj) 

-

        ident = smart_text(self.to_native(obj)) 

-

        if desc == ident: 

-

            return desc 

-

        return "%s - %s" % (desc, ident) 

-

 

-

    def _get_queryset(self): 

-

        return self._queryset 

-

 

-

    def _set_queryset(self, queryset): 

-

        self._queryset = queryset 

-

        self.widget.choices = self.choices 

-

 

-

    queryset = property(_get_queryset, _set_queryset) 

-

 

-

    def _get_choices(self): 

-

        # If self._choices is set, then somebody must have manually set 

-

        # the property self.choices. In this case, just return self._choices. 

-

        if hasattr(self, '_choices'): 

-

            return self._choices 

-

 

-

        # Otherwise, execute the QuerySet in self.queryset to determine the 

-

        # choices dynamically. Return a fresh ModelChoiceIterator that has not been 

-

        # consumed. Note that we're instantiating a new ModelChoiceIterator *each* 

-

        # time _get_choices() is called (and, thus, each time self.choices is 

-

        # accessed) so that we can ensure the QuerySet has not been consumed. This 

-

        # construct might look complicated but it allows for lazy evaluation of 

-

        # the queryset. 

-

        return ModelChoiceIterator(self) 

-

 

-

    def _set_choices(self, value): 

-

        # Setting choices also sets the choices on the widget. 

-

        # choices can be any iterable, but we call list() on it because 

-

        # it will be consumed more than once. 

-

        self._choices = self.widget.choices = list(value) 

-

 

-

    choices = property(_get_choices, _set_choices) 

-

 

-

    ### Regular serializer stuff... 

-

 

-

    def field_to_native(self, obj, field_name): 

-

        try: 

-

            if self.source == '*': 

-

                return self.to_native(obj) 

-

 

-

            source = self.source or field_name 

-

            value = obj 

-

 

-

            for component in source.split('.'): 

-

                value = get_component(value, component) 

-

                if value is None: 

-

                    break 

-

        except ObjectDoesNotExist: 

-

            return None 

-

 

-

        if value is None: 

-

            return None 

-

 

-

        if self.many: 

-

            if is_simple_callable(getattr(value, 'all', None)): 

-

                return [self.to_native(item) for item in value.all()] 

-

            else: 

-

                # Also support non-queryset iterables. 

-

                # This allows us to also support plain lists of related items. 

-

                return [self.to_native(item) for item in value] 

-

        return self.to_native(value) 

-

 

-

    def field_from_native(self, data, files, field_name, into): 

-

        if self.read_only: 

-

            return 

-

 

-

        try: 

-

            if self.many: 

-

                try: 

-

                    # Form data 

-

                    value = data.getlist(field_name) 

-

                    if value == [''] or value == []: 

-

                        raise KeyError 

-

                except AttributeError: 

-

                    # Non-form data 

-

                    value = data[field_name] 

-

            else: 

-

                value = data[field_name] 

-

        except KeyError: 

-

            if self.partial: 

-

                return 

-

            value = [] if self.many else None 

-

 

-

        if value in (None, '') and self.required: 

-

            raise ValidationError(self.error_messages['required']) 

-

        elif value in (None, ''): 

-

            into[(self.source or field_name)] = None 

-

        elif self.many: 

-

            into[(self.source or field_name)] = [self.from_native(item) for item in value] 

-

        else: 

-

            into[(self.source or field_name)] = self.from_native(value) 

-

 

-

 

-

### PrimaryKey relationships 

-

 

-

class PrimaryKeyRelatedField(RelatedField): 

-

    """ 

-

    Represents a relationship as a pk value. 

-

    """ 

-

    read_only = False 

-

 

-

    default_error_messages = { 

-

        'does_not_exist': _("Invalid pk '%s' - object does not exist."), 

-

        'incorrect_type': _('Incorrect type.  Expected pk value, received %s.'), 

-

    } 

-

 

-

    # TODO: Remove these field hacks... 

-

    def prepare_value(self, obj): 

-

        return self.to_native(obj.pk) 

-

 

-

    def label_from_instance(self, obj): 

-

        """ 

-

        Return a readable representation for use with eg. select widgets. 

-

        """ 

-

        desc = smart_text(obj) 

-

        ident = smart_text(self.to_native(obj.pk)) 

-

        if desc == ident: 

-

            return desc 

-

        return "%s - %s" % (desc, ident) 

-

 

-

    # TODO: Possibly change this to just take `obj`, through prob less performant 

-

    def to_native(self, pk): 

-

        return pk 

-

 

-

    def from_native(self, data): 

-

        if self.queryset is None: 

-

            raise Exception('Writable related fields must include a `queryset` argument') 

-

 

-

        try: 

-

            return self.queryset.get(pk=data) 

-

        except ObjectDoesNotExist: 

-

            msg = self.error_messages['does_not_exist'] % smart_text(data) 

-

            raise ValidationError(msg) 

-

        except (TypeError, ValueError): 

-

            received = type(data).__name__ 

-

            msg = self.error_messages['incorrect_type'] % received 

-

            raise ValidationError(msg) 

-

 

-

    def field_to_native(self, obj, field_name): 

-

        if self.many: 

-

            # To-many relationship 

-

 

-

            queryset = None 

-

            if not self.source: 

-

                # Prefer obj.serializable_value for performance reasons 

-

                try: 

-

                    queryset = obj.serializable_value(field_name) 

-

                except AttributeError: 

-

                    pass 

-

            if queryset is None: 

-

                # RelatedManager (reverse relationship) 

-

                source = self.source or field_name 

-

                queryset = obj 

-

                for component in source.split('.'): 

-

                    queryset = get_component(queryset, component) 

-

 

-

            # Forward relationship 

-

            if is_simple_callable(getattr(queryset, 'all', None)): 

-

                return [self.to_native(item.pk) for item in queryset.all()] 

-

            else: 

-

                # Also support non-queryset iterables. 

-

                # This allows us to also support plain lists of related items. 

-

                return [self.to_native(item.pk) for item in queryset] 

-

 

-

        # To-one relationship 

-

        try: 

-

            # Prefer obj.serializable_value for performance reasons 

-

            pk = obj.serializable_value(self.source or field_name) 

-

        except AttributeError: 

-

            # RelatedObject (reverse relationship) 

-

            try: 

-

                pk = getattr(obj, self.source or field_name).pk 

-

            except ObjectDoesNotExist: 

-

                return None 

-

 

-

        # Forward relationship 

-

        return self.to_native(pk) 

-

 

-

 

-

### Slug relationships 

-

 

-

 

-

class SlugRelatedField(RelatedField): 

-

    """ 

-

    Represents a relationship using a unique field on the target. 

-

    """ 

-

    read_only = False 

-

 

-

    default_error_messages = { 

-

        'does_not_exist': _("Object with %s=%s does not exist."), 

-

        'invalid': _('Invalid value.'), 

-

    } 

-

 

-

    def __init__(self, *args, **kwargs): 

-

        self.slug_field = kwargs.pop('slug_field', None) 

-

        assert self.slug_field, 'slug_field is required' 

-

        super(SlugRelatedField, self).__init__(*args, **kwargs) 

-

 

-

    def to_native(self, obj): 

-

        return getattr(obj, self.slug_field) 

-

 

-

    def from_native(self, data): 

-

        if self.queryset is None: 

-

            raise Exception('Writable related fields must include a `queryset` argument') 

-

 

-

        try: 

-

            return self.queryset.get(**{self.slug_field: data}) 

-

        except ObjectDoesNotExist: 

-

            raise ValidationError(self.error_messages['does_not_exist'] % 

-

                                  (self.slug_field, smart_text(data))) 

-

        except (TypeError, ValueError): 

-

            msg = self.error_messages['invalid'] 

-

            raise ValidationError(msg) 

-

 

-

 

-

### Hyperlinked relationships 

-

 

-

class HyperlinkedRelatedField(RelatedField): 

-

    """ 

-

    Represents a relationship using hyperlinking. 

-

    """ 

-

    read_only = False 

-

    lookup_field = 'pk' 

-

 

-

    default_error_messages = { 

-

        'no_match': _('Invalid hyperlink - No URL match'), 

-

        'incorrect_match': _('Invalid hyperlink - Incorrect URL match'), 

-

        'configuration_error': _('Invalid hyperlink due to configuration error'), 

-

        'does_not_exist': _("Invalid hyperlink - object does not exist."), 

-

        'incorrect_type': _('Incorrect type.  Expected url string, received %s.'), 

-

    } 

-

 

-

    # These are all pending deprecation 

-

    pk_url_kwarg = 'pk' 

-

    slug_field = 'slug' 

-

    slug_url_kwarg = None  # Defaults to same as `slug_field` unless overridden 

-

 

-

    def __init__(self, *args, **kwargs): 

-

        try: 

-

            self.view_name = kwargs.pop('view_name') 

-

        except KeyError: 

-

            raise ValueError("Hyperlinked field requires 'view_name' kwarg") 

-

 

-

        self.lookup_field = kwargs.pop('lookup_field', self.lookup_field) 

-

        self.format = kwargs.pop('format', None) 

-

 

-

        # These are pending deprecation 

-

        if 'pk_url_kwarg' in kwargs: 

-

            msg = 'pk_url_kwarg is pending deprecation. Use lookup_field instead.' 

-

            warnings.warn(msg, PendingDeprecationWarning, 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) 

-

        if 'slug_field' in kwargs: 

-

            msg = 'slug_field is pending deprecation. Use lookup_field instead.' 

-

            warnings.warn(msg, PendingDeprecationWarning, 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) 

-

        default_slug_kwarg = self.slug_url_kwarg or self.slug_field 

-

        self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', default_slug_kwarg) 

-

 

-

        super(HyperlinkedRelatedField, self).__init__(*args, **kwargs) 

-

 

-

    def get_url(self, obj, view_name, request, format): 

-

        """ 

-

        Given an object, return the URL that hyperlinks to the object. 

-

 

-

        May raise a `NoReverseMatch` if the `view_name` and `lookup_field` 

-

        attributes are not configured to correctly match the URL conf. 

-

        """ 

-

        lookup_field = getattr(obj, self.lookup_field) 

-

        kwargs = {self.lookup_field: lookup_field} 

-

        try: 

-

            return reverse(view_name, kwargs=kwargs, request=request, format=format) 

-

        except NoReverseMatch: 

-

            pass 

-

 

-

        if self.pk_url_kwarg != 'pk': 

-

            # Only try pk if it has been explicitly set. 

-

            # Otherwise, the default `lookup_field = 'pk'` has us covered. 

-

            pk = obj.pk 

-

            kwargs = {self.pk_url_kwarg: pk} 

-

            try: 

-

                return reverse(view_name, kwargs=kwargs, request=request, format=format) 

-

            except NoReverseMatch: 

-

                pass 

-

 

-

        slug = getattr(obj, self.slug_field, None) 

-

        if slug is not None: 

-

            # Only try slug if it corresponds to an attribute on the object. 

-

            kwargs = {self.slug_url_kwarg: slug} 

-

            try: 

-

                ret = reverse(view_name, kwargs=kwargs, request=request, format=format) 

-

                if self.slug_field == 'slug' and self.slug_url_kwarg == 'slug': 

-

                    # 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.' \ 

-

                          'You should set `lookup_field=slug` on the HyperlinkedRelatedField.' 

-

                    warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

-

                return ret 

-

            except NoReverseMatch: 

-

                pass 

-

 

-

        raise NoReverseMatch() 

-

 

-

    def get_object(self, queryset, view_name, view_args, view_kwargs): 

-

        """ 

-

        Return the object corresponding to a matched URL. 

-

 

-

        Takes the matched URL conf arguments, and the queryset, and should 

-

        return an object instance, or raise an `ObjectDoesNotExist` exception. 

-

        """ 

-

        lookup = view_kwargs.get(self.lookup_field, None) 

-

        pk = view_kwargs.get(self.pk_url_kwarg, None) 

-

        slug = view_kwargs.get(self.slug_url_kwarg, None) 

-

 

-

        if lookup is not None: 

-

            filter_kwargs = {self.lookup_field: lookup} 

-

        elif pk is not None: 

-

            filter_kwargs = {'pk': pk} 

-

        elif slug is not None: 

-

            filter_kwargs = {self.slug_field: slug} 

-

        else: 

-

            raise ObjectDoesNotExist() 

-

 

-

        return queryset.get(**filter_kwargs) 

-

 

-

    def to_native(self, obj): 

-

        view_name = self.view_name 

-

        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) 

-

 

-

        # If the object has not yet been saved then we cannot hyperlink to it. 

-

        if getattr(obj, 'pk', None) is None: 

-

            return 

-

 

-

        # Return the hyperlink, or error if incorrectly configured. 

-

        try: 

-

            return self.get_url(obj, view_name, request, format) 

-

        except NoReverseMatch: 

-

            msg = ( 

-

                'Could not resolve URL for hyperlinked relationship using ' 

-

                'view name "%s". You may have failed to include the related ' 

-

                'model in your API, or incorrectly configured the ' 

-

                '`lookup_field` attribute on this field.' 

-

            ) 

-

            raise Exception(msg % view_name) 

-

 

-

    def from_native(self, value): 

-

        # Convert URL -> model instance pk 

-

        # TODO: Use values_list 

-

        queryset = self.queryset 

-

        if queryset is None: 

-

            raise Exception('Writable related fields must include a `queryset` argument') 

-

 

-

        try: 

-

            http_prefix = value.startswith(('http:', 'https:')) 

-

        except AttributeError: 

-

            msg = self.error_messages['incorrect_type'] 

-

            raise ValidationError(msg % type(value).__name__) 

-

 

-

        if http_prefix: 

-

            # If needed convert absolute URLs to relative path 

-

            value = urlparse.urlparse(value).path 

-

            prefix = get_script_prefix() 

-

            if value.startswith(prefix): 

-

                value = '/' + value[len(prefix):] 

-

 

-

        try: 

-

            match = resolve(value) 

-

        except Exception: 

-

            raise ValidationError(self.error_messages['no_match']) 

-

 

-

        if match.view_name != self.view_name: 

-

            raise ValidationError(self.error_messages['incorrect_match']) 

-

 

-

        try: 

-

            return self.get_object(queryset, match.view_name, 

-

                                   match.args, match.kwargs) 

-

        except (ObjectDoesNotExist, TypeError, ValueError): 

-

            raise ValidationError(self.error_messages['does_not_exist']) 

-

 

-

 

-

class HyperlinkedIdentityField(Field): 

-

    """ 

-

    Represents the instance, or a property on the instance, using hyperlinking. 

-

    """ 

-

    lookup_field = 'pk' 

-

    read_only = True 

-

 

-

    # These are all pending deprecation 

-

    pk_url_kwarg = 'pk' 

-

    slug_field = 'slug' 

-

    slug_url_kwarg = None  # Defaults to same as `slug_field` unless overridden 

-

 

-

    def __init__(self, *args, **kwargs): 

-

        try: 

-

            self.view_name = kwargs.pop('view_name') 

-

        except KeyError: 

-

            msg = "HyperlinkedIdentityField requires 'view_name' argument" 

-

            raise ValueError(msg) 

-

 

-

        self.format = kwargs.pop('format', None) 

-

        lookup_field = kwargs.pop('lookup_field', None) 

-

        self.lookup_field = lookup_field or self.lookup_field 

-

 

-

        # These are pending deprecation 

-

        if 'pk_url_kwarg' in kwargs: 

-

            msg = 'pk_url_kwarg is pending deprecation. Use lookup_field instead.' 

-

            warnings.warn(msg, PendingDeprecationWarning, 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) 

-

        if 'slug_field' in kwargs: 

-

            msg = 'slug_field is pending deprecation. Use lookup_field instead.' 

-

            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

-

 

-

        self.slug_field = kwargs.pop('slug_field', self.slug_field) 

-

        default_slug_kwarg = self.slug_url_kwarg or self.slug_field 

-

        self.pk_url_kwarg = kwargs.pop('pk_url_kwarg', self.pk_url_kwarg) 

-

        self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', default_slug_kwarg) 

-

 

-

        super(HyperlinkedIdentityField, self).__init__(*args, **kwargs) 

-

 

-

    def field_to_native(self, obj, field_name): 

-

        request = self.context.get('request', None) 

-

        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) 

-

 

-

        # By default use whatever format is given for the current context 

-

        # unless the target is a different type to the source. 

-

        # 

-

        # Eg. Consider a HyperlinkedIdentityField pointing from a json 

-

        # representation to an html property of that representation... 

-

        # 

-

        # '/snippets/1/' should link to '/snippets/1/highlight/' 

-

        # ...but... 

-

        # '/snippets/1/.json' should link to '/snippets/1/highlight/.html' 

-

        if format and self.format and self.format != format: 

-

            format = self.format 

-

 

-

        # Return the hyperlink, or error if incorrectly configured. 

-

        try: 

-

            return self.get_url(obj, view_name, request, format) 

-

        except NoReverseMatch: 

-

            msg = ( 

-

                'Could not resolve URL for hyperlinked relationship using ' 

-

                'view name "%s". You may have failed to include the related ' 

-

                'model in your API, or incorrectly configured the ' 

-

                '`lookup_field` attribute on this field.' 

-

            ) 

-

            raise Exception(msg % view_name) 

-

 

-

    def get_url(self, obj, view_name, request, format): 

-

        """ 

-

        Given an object, return the URL that hyperlinks to the object. 

-

 

-

        May raise a `NoReverseMatch` if the `view_name` and `lookup_field` 

-

        attributes are not configured to correctly match the URL conf. 

-

        """ 

-

        lookup_field = getattr(obj, self.lookup_field) 

-

        kwargs = {self.lookup_field: lookup_field} 

-

        try: 

-

            return reverse(view_name, kwargs=kwargs, request=request, format=format) 

-

        except NoReverseMatch: 

-

            pass 

-

 

-

        if self.pk_url_kwarg != 'pk': 

-

            # Only try pk lookup if it has been explicitly set. 

-

            # Otherwise, the default `lookup_field = 'pk'` has us covered. 

-

            kwargs = {self.pk_url_kwarg: obj.pk} 

-

            try: 

-

                return reverse(view_name, kwargs=kwargs, request=request, format=format) 

-

            except NoReverseMatch: 

-

                pass 

-

 

-

        slug = getattr(obj, self.slug_field, None) 

-

        if slug: 

-

            # Only use slug lookup if a slug field exists on the model 

-

            kwargs = {self.slug_url_kwarg: slug} 

-

            try: 

-

                return reverse(view_name, kwargs=kwargs, request=request, format=format) 

-

            except NoReverseMatch: 

-

                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/htmlcov/rest_framework_renderers.html b/htmlcov/rest_framework_renderers.html deleted file mode 100644 index 58c71b85..00000000 --- a/htmlcov/rest_framework_renderers.html +++ /dev/null @@ -1,1227 +0,0 @@ - - - - - - - - Coverage for rest_framework/renderers: 92% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

-

296

-

297

-

298

-

299

-

300

-

301

-

302

-

303

-

304

-

305

-

306

-

307

-

308

-

309

-

310

-

311

-

312

-

313

-

314

-

315

-

316

-

317

-

318

-

319

-

320

-

321

-

322

-

323

-

324

-

325

-

326

-

327

-

328

-

329

-

330

-

331

-

332

-

333

-

334

-

335

-

336

-

337

-

338

-

339

-

340

-

341

-

342

-

343

-

344

-

345

-

346

-

347

-

348

-

349

-

350

-

351

-

352

-

353

-

354

-

355

-

356

-

357

-

358

-

359

-

360

-

361

-

362

-

363

-

364

-

365

-

366

-

367

-

368

-

369

-

370

-

371

-

372

-

373

-

374

-

375

-

376

-

377

-

378

-

379

-

380

-

381

-

382

-

383

-

384

-

385

-

386

-

387

-

388

-

389

-

390

-

391

-

392

-

393

-

394

-

395

-

396

-

397

-

398

-

399

-

400

-

401

-

402

-

403

-

404

-

405

-

406

-

407

-

408

-

409

-

410

-

411

-

412

-

413

-

414

-

415

-

416

-

417

-

418

-

419

-

420

-

421

-

422

-

423

-

424

-

425

-

426

-

427

-

428

-

429

-

430

-

431

-

432

-

433

-

434

-

435

-

436

-

437

-

438

-

439

-

440

-

441

-

442

-

443

-

444

-

445

-

446

-

447

-

448

-

449

-

450

-

451

-

452

-

453

-

454

-

455

-

456

-

457

-

458

-

459

-

460

-

461

-

462

-

463

-

464

-

465

-

466

-

467

-

468

-

469

-

470

-

471

-

472

-

473

-

474

-

475

-

476

-

477

-

478

-

479

-

480

-

481

-

482

-

483

-

484

-

485

-

486

-

487

-

488

-

489

-

490

-

491

-

492

-

493

-

494

-

495

-

496

-

497

-

498

-

499

-

500

-

501

-

502

-

503

-

504

-

505

-

506

-

507

-

508

-

509

-

510

-

511

-

512

-

513

-

514

-

515

-

516

-

517

-

518

-

519

-

520

-

521

-

522

-

523

-

524

-

525

-

526

-

527

-

528

-

529

-

530

-

531

-

532

-

533

-

534

-

535

-

536

-

537

-

538

-

539

-

540

-

541

-

542

-

543

-

544

-

545

-

546

-

547

-

548

-

549

-

550

-

551

-

552

-

553

-

554

-

555

-

556

-

557

-

558

-

559

-

560

-

561

-

562

-

563

-

564

-

565

-

566

-

567

-

568

-

569

-

570

-

571

-

572

-

573

- -
-

""" 

-

Renderers are used to serialize a response into specific media types. 

-

 

-

They give us a generic way of being able to handle various media types 

-

on the response, such as JSON encoded data or HTML output. 

-

 

-

REST framework also provides an HTML renderer the renders the browsable API. 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

import copy 

-

import json 

-

from django import forms 

-

from django.core.exceptions import ImproperlyConfigured 

-

from django.http.multipartparser import parse_header 

-

from django.template import RequestContext, loader, Template 

-

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.settings import api_settings 

-

from rest_framework.request import clone_request 

-

from rest_framework.utils import encoders 

-

from rest_framework.utils.breadcrumbs import get_breadcrumbs 

-

from rest_framework.utils.formatting import get_view_name, get_view_description 

-

from rest_framework import exceptions, parsers, status, VERSION 

-

 

-

 

-

class BaseRenderer(object): 

-

    """ 

-

    All renderers should extend this class, setting the `media_type` 

-

    and `format` attributes, and override the `.render()` method. 

-

    """ 

-

 

-

    media_type = None 

-

    format = None 

-

    charset = 'utf-8' 

-

 

-

    def render(self, data, accepted_media_type=None, renderer_context=None): 

-

        raise NotImplemented('Renderer class requires .render() to be implemented') 

-

 

-

 

-

class JSONRenderer(BaseRenderer): 

-

    """ 

-

    Renderer which serializes to JSON. 

-

    Applies JSON's backslash-u character escaping for non-ascii characters. 

-

    """ 

-

 

-

    media_type = 'application/json' 

-

    format = 'json' 

-

    encoder_class = encoders.JSONEncoder 

-

    ensure_ascii = True 

-

    charset = 'utf-8' 

-

    # Note that JSON encodings must be utf-8, utf-16 or utf-32. 

-

    # See: http://www.ietf.org/rfc/rfc4627.txt 

-

 

-

    def render(self, data, accepted_media_type=None, renderer_context=None): 

-

        """ 

-

        Render `data` into JSON. 

-

        """ 

-

        if data is None: 

-

            return '' 

-

 

-

        # 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) 

-

 

-

        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 

-

 

-

        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, 

-

        # and may (or may not) be unicode. 

-

        # On python 3.x json.dumps() returns unicode strings. 

-

        if isinstance(ret, six.text_type): 

-

            return bytes(ret.encode(self.charset)) 

-

        return ret 

-

 

-

 

-

class UnicodeJSONRenderer(JSONRenderer): 

-

    ensure_ascii = False 

-

    charset = 'utf-8' 

-

    """ 

-

    Renderer which serializes to JSON. 

-

    Does *not* apply JSON's character escaping for non-ascii characters. 

-

    """ 

-

 

-

 

-

class JSONPRenderer(JSONRenderer): 

-

    """ 

-

    Renderer which serializes to json, 

-

    wrapping the json output in a callback function. 

-

    """ 

-

 

-

    media_type = 'application/javascript' 

-

    format = 'jsonp' 

-

    callback_parameter = 'callback' 

-

    default_callback = 'callback' 

-

 

-

    def get_callback(self, renderer_context): 

-

        """ 

-

        Determine the name of the callback to wrap around the json output. 

-

        """ 

-

        request = renderer_context.get('request', None) 

-

        params = request and request.QUERY_PARAMS or {} 

-

        return params.get(self.callback_parameter, self.default_callback) 

-

 

-

    def render(self, data, accepted_media_type=None, renderer_context=None): 

-

        """ 

-

        Renders into jsonp, wrapping the json output in a callback function. 

-

 

-

        Clients may set the callback function name using a query parameter 

-

        on the URL, for example: ?callback=exampleCallbackName 

-

        """ 

-

        renderer_context = renderer_context or {} 

-

        callback = self.get_callback(renderer_context) 

-

        json = super(JSONPRenderer, self).render(data, accepted_media_type, 

-

                                                 renderer_context) 

-

        return callback.encode(self.charset) + b'(' + json + b');' 

-

 

-

 

-

class XMLRenderer(BaseRenderer): 

-

    """ 

-

    Renderer which serializes to XML. 

-

    """ 

-

 

-

    media_type = 'application/xml' 

-

    format = 'xml' 

-

    charset = 'utf-8' 

-

 

-

    def render(self, data, accepted_media_type=None, renderer_context=None): 

-

        """ 

-

        Renders *obj* into serialized XML. 

-

        """ 

-

        if data is None: 

-

            return '' 

-

 

-

        stream = StringIO() 

-

 

-

        xml = SimplerXMLGenerator(stream, self.charset) 

-

        xml.startDocument() 

-

        xml.startElement("root", {}) 

-

 

-

        self._to_xml(xml, data) 

-

 

-

        xml.endElement("root") 

-

        xml.endDocument() 

-

        return stream.getvalue() 

-

 

-

    def _to_xml(self, xml, data): 

-

        if isinstance(data, (list, tuple)): 

-

            for item in data: 

-

                xml.startElement("list-item", {}) 

-

                self._to_xml(xml, item) 

-

                xml.endElement("list-item") 

-

 

-

        elif isinstance(data, dict): 

-

            for key, value in six.iteritems(data): 

-

                xml.startElement(key, {}) 

-

                self._to_xml(xml, value) 

-

                xml.endElement(key) 

-

 

-

        elif data is None: 

-

            # Don't output any value 

-

            pass 

-

 

-

        else: 

-

            xml.characters(smart_text(data)) 

-

 

-

 

-

class YAMLRenderer(BaseRenderer): 

-

    """ 

-

    Renderer which serializes to YAML. 

-

    """ 

-

 

-

    media_type = 'application/yaml' 

-

    format = 'yaml' 

-

    encoder = encoders.SafeDumper 

-

    charset = 'utf-8' 

-

 

-

    def render(self, data, accepted_media_type=None, renderer_context=None): 

-

        """ 

-

        Renders *obj* into serialized YAML. 

-

        """ 

-

        assert yaml, 'YAMLRenderer requires pyyaml to be installed' 

-

 

-

        if data is None: 

-

            return '' 

-

 

-

        return yaml.dump(data, stream=None, encoding=self.charset, Dumper=self.encoder) 

-

 

-

 

-

class TemplateHTMLRenderer(BaseRenderer): 

-

    """ 

-

    An HTML renderer for use with templates. 

-

 

-

    The data supplied to the Response object should be a dictionary that will 

-

    be used as context for the template. 

-

 

-

    The template name is determined by (in order of preference): 

-

 

-

    1. An explicit `.template_name` attribute set on the response. 

-

    2. An explicit `.template_name` attribute set on this class. 

-

    3. The return result of calling `view.get_template_names()`. 

-

 

-

    For example: 

-

        data = {'users': User.objects.all()} 

-

        return Response(data, template_name='users.html') 

-

 

-

    For pre-rendered HTML, see StaticHTMLRenderer. 

-

    """ 

-

 

-

    media_type = 'text/html' 

-

    format = 'html' 

-

    template_name = None 

-

    exception_template_names = [ 

-

        '%(status_code)s.html', 

-

        'api_exception.html' 

-

    ] 

-

    charset = 'utf-8' 

-

 

-

    def render(self, data, accepted_media_type=None, renderer_context=None): 

-

        """ 

-

        Renders data to HTML, using Django's standard template rendering. 

-

 

-

        The template name is determined by (in order of preference): 

-

 

-

        1. An explicit .template_name set on the response. 

-

        2. An explicit .template_name set on this class. 

-

        3. The return result of calling view.get_template_names(). 

-

        """ 

-

        renderer_context = renderer_context or {} 

-

        view = renderer_context['view'] 

-

        request = renderer_context['request'] 

-

        response = renderer_context['response'] 

-

 

-

        if response.exception: 

-

            template = self.get_exception_template(response) 

-

        else: 

-

            template_names = self.get_template_names(response, view) 

-

            template = self.resolve_template(template_names) 

-

 

-

        context = self.resolve_context(data, request, response) 

-

        return template.render(context) 

-

 

-

    def resolve_template(self, template_names): 

-

        return loader.select_template(template_names) 

-

 

-

    def resolve_context(self, data, request, response): 

-

        if response.exception: 

-

            data['status_code'] = response.status_code 

-

        return RequestContext(request, data) 

-

 

-

    def get_template_names(self, response, view): 

-

        if response.template_name: 

-

            return [response.template_name] 

-

        elif self.template_name: 

-

            return [self.template_name] 

-

        elif hasattr(view, 'get_template_names'): 

-

            return view.get_template_names() 

-

        raise ImproperlyConfigured('Returned a template response with no template_name') 

-

 

-

    def get_exception_template(self, response): 

-

        template_names = [name % {'status_code': response.status_code} 

-

                          for name in self.exception_template_names] 

-

 

-

        try: 

-

            # Try to find an appropriate error template 

-

            return self.resolve_template(template_names) 

-

        except Exception: 

-

            # Fall back to using eg '404 Not Found' 

-

            return Template('%d %s' % (response.status_code, 

-

                                       response.status_text.title())) 

-

 

-

 

-

# Note, subclass TemplateHTMLRenderer simply for the exception behavior 

-

class StaticHTMLRenderer(TemplateHTMLRenderer): 

-

    """ 

-

    An HTML renderer class that simply returns pre-rendered HTML. 

-

 

-

    The data supplied to the Response object should be a string representing 

-

    the pre-rendered HTML content. 

-

 

-

    For example: 

-

        data = '<html><body>example</body></html>' 

-

        return Response(data) 

-

 

-

    For template rendered HTML, see TemplateHTMLRenderer. 

-

    """ 

-

    media_type = 'text/html' 

-

    format = 'html' 

-

    charset = 'utf-8' 

-

 

-

    def render(self, data, accepted_media_type=None, renderer_context=None): 

-

        renderer_context = renderer_context or {} 

-

        response = renderer_context['response'] 

-

 

-

        if response and response.exception: 

-

            request = renderer_context['request'] 

-

            template = self.get_exception_template(response) 

-

            context = self.resolve_context(data, request, response) 

-

            return template.render(context) 

-

 

-

        return data 

-

 

-

 

-

class BrowsableAPIRenderer(BaseRenderer): 

-

    """ 

-

    HTML renderer used to self-document the API. 

-

    """ 

-

    media_type = 'text/html' 

-

    format = 'api' 

-

    template = 'rest_framework/api.html' 

-

    charset = 'utf-8' 

-

 

-

    def get_default_renderer(self, view): 

-

        """ 

-

        Return an instance of the first valid renderer. 

-

        (Don't use another documenting renderer.) 

-

        """ 

-

        renderers = [renderer for renderer in view.renderer_classes 

-

                     if not issubclass(renderer, BrowsableAPIRenderer)] 

-

        if not renderers: 

-

            return None 

-

        return renderers[0]() 

-

 

-

    def get_content(self, renderer, data, 

-

                    accepted_media_type, renderer_context): 

-

        """ 

-

        Get the content as if it had been rendered by the default 

-

        non-documenting renderer. 

-

        """ 

-

        if not renderer: 

-

            return '[No renderers were found]' 

-

 

-

        renderer_context['indent'] = 4 

-

        content = renderer.render(data, accepted_media_type, renderer_context) 

-

 

-

        if renderer.charset is None: 

-

            return '[%d bytes of binary content]' % len(content) 

-

 

-

        return content 

-

 

-

    def show_form_for_method(self, view, method, request, obj): 

-

        """ 

-

        Returns True if a form should be shown for this method. 

-

        """ 

-

        if not method in view.allowed_methods: 

-

            return  # Not a valid method 

-

 

-

        if not api_settings.FORM_METHOD_OVERRIDE: 

-

            return  # Cannot use form overloading 

-

 

-

        try: 

-

            view.check_permissions(request) 

-

            if obj is not None: 

-

                view.check_object_permissions(request, obj) 

-

        except exceptions.APIException: 

-

            return False  # Doesn't have permissions 

-

        return True 

-

 

-

    def serializer_to_form_fields(self, serializer): 

-

        fields = {} 

-

        for k, v in serializer.get_fields().items(): 

-

            if getattr(v, 'read_only', True): 

-

                continue 

-

 

-

            kwargs = {} 

-

            kwargs['required'] = v.required 

-

 

-

            #if getattr(v, 'queryset', None): 

-

            #    kwargs['queryset'] = v.queryset 

-

 

-

            if getattr(v, 'choices', None) is not None: 

-

                kwargs['choices'] = v.choices 

-

 

-

            if getattr(v, 'regex', None) is not None: 

-

                kwargs['regex'] = v.regex 

-

 

-

            if getattr(v, 'widget', None): 

-

                widget = copy.deepcopy(v.widget) 

-

                kwargs['widget'] = widget 

-

 

-

            if getattr(v, 'default', None) is not None: 

-

                kwargs['initial'] = v.default 

-

 

-

            if getattr(v, 'label', None) is not None: 

-

                kwargs['label'] = v.label 

-

 

-

            if getattr(v, 'help_text', None) is not None: 

-

                kwargs['help_text'] = v.help_text 

-

 

-

            fields[k] = v.form_field_class(**kwargs) 

-

 

-

        return fields 

-

 

-

    def _get_form(self, view, method, request): 

-

        # We need to impersonate a request with the correct method, 

-

        # so that eg. any dynamic get_serializer_class methods return the 

-

        # correct form for each method. 

-

        restore = view.request 

-

        request = clone_request(request, method) 

-

        view.request = request 

-

        try: 

-

            return self.get_form(view, method, request) 

-

        finally: 

-

            view.request = restore 

-

 

-

    def _get_raw_data_form(self, view, method, request, media_types): 

-

        # We need to impersonate a request with the correct method, 

-

        # so that eg. any dynamic get_serializer_class methods return the 

-

        # correct form for each method. 

-

        restore = view.request 

-

        request = clone_request(request, method) 

-

        view.request = request 

-

        try: 

-

            return self.get_raw_data_form(view, method, request, media_types) 

-

        finally: 

-

            view.request = restore 

-

 

-

    def get_form(self, view, method, request): 

-

        """ 

-

        Get a form, possibly bound to either the input or output data. 

-

        In the absence on of the Resource having an associated form then 

-

        provide a form that can be used to submit arbitrary content. 

-

        """ 

-

        obj = getattr(view, 'object', None) 

-

        if not self.show_form_for_method(view, method, request, obj): 

-

            return 

-

 

-

        if method in ('DELETE', 'OPTIONS'): 

-

            return True  # Don't actually need to return a form 

-

 

-

        if not getattr(view, 'get_serializer', None) or not parsers.FormParser in view.parser_classes: 

-

            return 

-

 

-

        serializer = view.get_serializer(instance=obj) 

-

        fields = self.serializer_to_form_fields(serializer) 

-

 

-

        # Creating an on the fly form see: 

-

        # http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python 

-

        OnTheFlyForm = type(str("OnTheFlyForm"), (forms.Form,), fields) 

-

        data = (obj is not None) and serializer.data or None 

-

        form_instance = OnTheFlyForm(data) 

-

        return form_instance 

-

 

-

    def get_raw_data_form(self, view, method, request, media_types): 

-

        """ 

-

        Returns a form that allows for arbitrary content types to be tunneled 

-

        via standard HTML forms. 

-

        (Which are typically application/x-www-form-urlencoded) 

-

        """ 

-

 

-

        # If we're not using content overloading there's no point in supplying a generic form, 

-

        # as the view won't treat the form's value as the content of the request. 

-

        if not (api_settings.FORM_CONTENT_OVERRIDE 

-

                and api_settings.FORM_CONTENTTYPE_OVERRIDE): 

-

            return None 

-

 

-

        # Check permissions 

-

        obj = getattr(view, 'object', None) 

-

        if not self.show_form_for_method(view, method, request, obj): 

-

            return 

-

 

-

        content_type_field = api_settings.FORM_CONTENTTYPE_OVERRIDE 

-

        content_field = api_settings.FORM_CONTENT_OVERRIDE 

-

        choices = [(media_type, media_type) for media_type in media_types] 

-

        initial = media_types[0] 

-

 

-

        # NB. http://jacobian.org/writing/dynamic-form-generation/ 

-

        class GenericContentForm(forms.Form): 

-

            def __init__(self): 

-

                super(GenericContentForm, self).__init__() 

-

 

-

                self.fields[content_type_field] = forms.ChoiceField( 

-

                    label='Media type', 

-

                    choices=choices, 

-

                    initial=initial 

-

                ) 

-

                self.fields[content_field] = forms.CharField( 

-

                    label='Content', 

-

                    widget=forms.Textarea 

-

                ) 

-

 

-

        return GenericContentForm() 

-

 

-

    def get_name(self, view): 

-

        return get_view_name(view.__class__, getattr(view, 'suffix', None)) 

-

 

-

    def get_description(self, view): 

-

        return get_view_description(view.__class__, html=True) 

-

 

-

    def get_breadcrumbs(self, request): 

-

        return get_breadcrumbs(request.path) 

-

 

-

    def render(self, data, accepted_media_type=None, renderer_context=None): 

-

        """ 

-

        Render the HTML for the browsable API representation. 

-

        """ 

-

        accepted_media_type = accepted_media_type or '' 

-

        renderer_context = renderer_context or {} 

-

 

-

        view = renderer_context['view'] 

-

        request = renderer_context['request'] 

-

        response = renderer_context['response'] 

-

        media_types = [parser.media_type for parser in view.parser_classes] 

-

 

-

        renderer = self.get_default_renderer(view) 

-

        content = self.get_content(renderer, data, accepted_media_type, renderer_context) 

-

 

-

        put_form = self._get_form(view, 'PUT', request) 

-

        post_form = self._get_form(view, 'POST', request) 

-

        patch_form = self._get_form(view, 'PATCH', request) 

-

        delete_form = self._get_form(view, 'DELETE', request) 

-

        options_form = self._get_form(view, 'OPTIONS', request) 

-

 

-

        raw_data_put_form = self._get_raw_data_form(view, 'PUT', request, media_types) 

-

        raw_data_post_form = self._get_raw_data_form(view, 'POST', request, media_types) 

-

        raw_data_patch_form = self._get_raw_data_form(view, 'PATCH', request, media_types) 

-

        raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form 

-

 

-

        name = self.get_name(view) 

-

        description = self.get_description(view) 

-

        breadcrumb_list = self.get_breadcrumbs(request) 

-

 

-

        template = loader.get_template(self.template) 

-

        context = RequestContext(request, { 

-

            'content': content, 

-

            'view': view, 

-

            'request': request, 

-

            'response': response, 

-

            'description': description, 

-

            'name': name, 

-

            'version': VERSION, 

-

            'breadcrumblist': breadcrumb_list, 

-

            'allowed_methods': view.allowed_methods, 

-

            'available_formats': [renderer.format for renderer in view.renderer_classes], 

-

 

-

            'put_form': put_form, 

-

            'post_form': post_form, 

-

            'patch_form': patch_form, 

-

            'delete_form': delete_form, 

-

            'options_form': options_form, 

-

 

-

            'raw_data_put_form': raw_data_put_form, 

-

            'raw_data_post_form': raw_data_post_form, 

-

            'raw_data_patch_form': raw_data_patch_form, 

-

            'raw_data_put_or_patch_form': raw_data_put_or_patch_form, 

-

 

-

            'api_settings': api_settings 

-

        }) 

-

 

-

        ret = template.render(context) 

-

 

-

        # Munge DELETE Response code to allow us to return content 

-

        # (Do this *after* we've rendered the template so that we include 

-

        # the normal deletion response code in the output) 

-

        if response.status_code == status.HTTP_204_NO_CONTENT: 

-

            response.status_code = status.HTTP_200_OK 

-

 

-

        return ret 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_request.html b/htmlcov/rest_framework_request.html deleted file mode 100644 index 03f2c3e3..00000000 --- a/htmlcov/rest_framework_request.html +++ /dev/null @@ -1,819 +0,0 @@ - - - - - - - - Coverage for rest_framework/request: 95% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

-

296

-

297

-

298

-

299

-

300

-

301

-

302

-

303

-

304

-

305

-

306

-

307

-

308

-

309

-

310

-

311

-

312

-

313

-

314

-

315

-

316

-

317

-

318

-

319

-

320

-

321

-

322

-

323

-

324

-

325

-

326

-

327

-

328

-

329

-

330

-

331

-

332

-

333

-

334

-

335

-

336

-

337

-

338

-

339

-

340

-

341

-

342

-

343

-

344

-

345

-

346

-

347

-

348

-

349

-

350

-

351

-

352

-

353

-

354

-

355

-

356

-

357

-

358

-

359

-

360

-

361

-

362

-

363

-

364

-

365

-

366

-

367

-

368

-

369

- -
-

""" 

-

The Request class is used as a wrapper around the standard request object. 

-

 

-

The wrapped request then offers a richer API, in particular : 

-

 

-

    - content automatically parsed according to `Content-Type` header, 

-

      and available as `request.DATA` 

-

    - full support of PUT method, including support for file uploads 

-

    - form overloading of HTTP method, content type and content 

-

""" 

-

from __future__ import unicode_literals 

-

from django.conf import settings 

-

from django.http import QueryDict 

-

from django.http.multipartparser import parse_header 

-

from django.utils.datastructures import MultiValueDict 

-

from rest_framework import HTTP_HEADER_ENCODING 

-

from rest_framework import exceptions 

-

from rest_framework.compat import BytesIO 

-

from rest_framework.settings import api_settings 

-

 

-

 

-

def is_form_media_type(media_type): 

-

    """ 

-

    Return True if the media type is a valid form media type. 

-

    """ 

-

    base_media_type, params = parse_header(media_type.encode(HTTP_HEADER_ENCODING)) 

-

    return (base_media_type == 'application/x-www-form-urlencoded' or 

-

            base_media_type == 'multipart/form-data') 

-

 

-

 

-

class Empty(object): 

-

    """ 

-

    Placeholder for unset attributes. 

-

    Cannot use `None`, as that may be a valid value. 

-

    """ 

-

    pass 

-

 

-

 

-

def _hasattr(obj, name): 

-

    return not getattr(obj, name) is Empty 

-

 

-

 

-

def clone_request(request, method): 

-

    """ 

-

    Internal helper method to clone a request, replacing with a different 

-

    HTTP method.  Used for checking permissions against other methods. 

-

    """ 

-

    ret = Request(request=request._request, 

-

                  parsers=request.parsers, 

-

                  authenticators=request.authenticators, 

-

                  negotiator=request.negotiator, 

-

                  parser_context=request.parser_context) 

-

    ret._data = request._data 

-

    ret._files = request._files 

-

    ret._content_type = request._content_type 

-

    ret._stream = request._stream 

-

    ret._method = method 

-

    if hasattr(request, '_user'): 

-

        ret._user = request._user 

-

    if hasattr(request, '_auth'): 

-

        ret._auth = request._auth 

-

    if hasattr(request, '_authenticator'): 

-

        ret._authenticator = request._authenticator 

-

    return ret 

-

 

-

 

-

class Request(object): 

-

    """ 

-

    Wrapper allowing to enhance a standard `HttpRequest` instance. 

-

 

-

    Kwargs: 

-

        - request(HttpRequest). The original request instance. 

-

        - parsers_classes(list/tuple). The parsers to use for parsing the 

-

          request content. 

-

        - authentication_classes(list/tuple). The authentications used to try 

-

          authenticating the request's user. 

-

    """ 

-

 

-

    _METHOD_PARAM = api_settings.FORM_METHOD_OVERRIDE 

-

    _CONTENT_PARAM = api_settings.FORM_CONTENT_OVERRIDE 

-

    _CONTENTTYPE_PARAM = api_settings.FORM_CONTENTTYPE_OVERRIDE 

-

 

-

    def __init__(self, request, parsers=None, authenticators=None, 

-

                 negotiator=None, parser_context=None): 

-

        self._request = request 

-

        self.parsers = parsers or () 

-

        self.authenticators = authenticators or () 

-

        self.negotiator = negotiator or self._default_negotiator() 

-

        self.parser_context = parser_context 

-

        self._data = Empty 

-

        self._files = Empty 

-

        self._method = Empty 

-

        self._content_type = Empty 

-

        self._stream = Empty 

-

 

-

        if self.parser_context is None: 

-

            self.parser_context = {} 

-

        self.parser_context['request'] = self 

-

        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET 

-

 

-

    def _default_negotiator(self): 

-

        return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS() 

-

 

-

    @property 

-

    def method(self): 

-

        """ 

-

        Returns the HTTP method. 

-

 

-

        This allows the `method` to be overridden by using a hidden `form` 

-

        field on a form POST request. 

-

        """ 

-

        if not _hasattr(self, '_method'): 

-

            self._load_method_and_content_type() 

-

        return self._method 

-

 

-

    @property 

-

    def content_type(self): 

-

        """ 

-

        Returns the content type header. 

-

 

-

        This should be used instead of `request.META.get('HTTP_CONTENT_TYPE')`, 

-

        as it allows the content type to be overridden by using a hidden form 

-

        field on a form POST request. 

-

        """ 

-

        if not _hasattr(self, '_content_type'): 

-

            self._load_method_and_content_type() 

-

        return self._content_type 

-

 

-

    @property 

-

    def stream(self): 

-

        """ 

-

        Returns an object that may be used to stream the request content. 

-

        """ 

-

        if not _hasattr(self, '_stream'): 

-

            self._load_stream() 

-

        return self._stream 

-

 

-

    @property 

-

    def QUERY_PARAMS(self): 

-

        """ 

-

        More semantically correct name for request.GET. 

-

        """ 

-

        return self._request.GET 

-

 

-

    @property 

-

    def DATA(self): 

-

        """ 

-

        Parses the request body and returns the data. 

-

 

-

        Similar to usual behaviour of `request.POST`, except that it handles 

-

        arbitrary parsers, and also works on methods other than POST (eg PUT). 

-

        """ 

-

        if not _hasattr(self, '_data'): 

-

            self._load_data_and_files() 

-

        return self._data 

-

 

-

    @property 

-

    def FILES(self): 

-

        """ 

-

        Parses the request body and returns any files uploaded in the request. 

-

 

-

        Similar to usual behaviour of `request.FILES`, except that it handles 

-

        arbitrary parsers, and also works on methods other than POST (eg PUT). 

-

        """ 

-

        if not _hasattr(self, '_files'): 

-

            self._load_data_and_files() 

-

        return self._files 

-

 

-

    @property 

-

    def user(self): 

-

        """ 

-

        Returns the user associated with the current request, as authenticated 

-

        by the authentication classes provided to the request. 

-

        """ 

-

        if not hasattr(self, '_user'): 

-

            self._authenticate() 

-

        return self._user 

-

 

-

    @user.setter 

-

    def user(self, value): 

-

        """ 

-

        Sets the user on the current request. This is necessary to maintain 

-

        compatilbility with django.contrib.auth where the user proprety is 

-

        set in the login and logout functions. 

-

        """ 

-

        self._user = value 

-

 

-

    @property 

-

    def auth(self): 

-

        """ 

-

        Returns any non-user authentication information associated with the 

-

        request, such as an authentication token. 

-

        """ 

-

        if not hasattr(self, '_auth'): 

-

            self._authenticate() 

-

        return self._auth 

-

 

-

    @auth.setter 

-

    def auth(self, value): 

-

        """ 

-

        Sets any non-user authentication information associated with the 

-

        request, such as an authentication token. 

-

        """ 

-

        self._auth = value 

-

 

-

    @property 

-

    def successful_authenticator(self): 

-

        """ 

-

        Return the instance of the authentication instance class that was used 

-

        to authenticate the request, or `None`. 

-

        """ 

-

        if not hasattr(self, '_authenticator'): 

-

            self._authenticate() 

-

        return self._authenticator 

-

 

-

    def _load_data_and_files(self): 

-

        """ 

-

        Parses the request content into self.DATA and self.FILES. 

-

        """ 

-

        if not _hasattr(self, '_content_type'): 

-

            self._load_method_and_content_type() 

-

 

-

        if not _hasattr(self, '_data'): 

-

            self._data, self._files = self._parse() 

-

 

-

    def _load_method_and_content_type(self): 

-

        """ 

-

        Sets the method and content_type, and then check if they've 

-

        been overridden. 

-

        """ 

-

        self._content_type = self.META.get('HTTP_CONTENT_TYPE', 

-

                                           self.META.get('CONTENT_TYPE', '')) 

-

 

-

        self._perform_form_overloading() 

-

 

-

        if not _hasattr(self, '_method'): 

-

            self._method = self._request.method 

-

 

-

            if self._method == 'POST': 

-

                # Allow X-HTTP-METHOD-OVERRIDE header 

-

                self._method = self.META.get('HTTP_X_HTTP_METHOD_OVERRIDE', 

-

                                             self._method) 

-

 

-

    def _load_stream(self): 

-

        """ 

-

        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'))) 

-

        except (ValueError, TypeError): 

-

            content_length = 0 

-

 

-

        if content_length == 0: 

-

            self._stream = None 

-

        elif hasattr(self._request, 'read'): 

-

            self._stream = self._request 

-

        else: 

-

            self._stream = BytesIO(self.raw_post_data) 

-

 

-

    def _perform_form_overloading(self): 

-

        """ 

-

        If this is a form POST request, then we need to check if the method and 

-

        content/content_type have been overridden by setting them in hidden 

-

        form fields or not. 

-

        """ 

-

 

-

        USE_FORM_OVERLOADING = ( 

-

            self._METHOD_PARAM or 

-

            (self._CONTENT_PARAM and self._CONTENTTYPE_PARAM) 

-

        ) 

-

 

-

        # We only need to use form overloading on form POST requests. 

-

        if (not USE_FORM_OVERLOADING 

-

            or self._request.method != 'POST' 

-

            or not is_form_media_type(self._content_type)): 

-

            return 

-

 

-

        # At this point we're committed to parsing the request as form data. 

-

        self._data = self._request.POST 

-

        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): 

-

            self._method = self._data[self._METHOD_PARAM].upper() 

-

 

-

        # Content overloading - modify the content type, and force re-parse. 

-

        if (self._CONTENT_PARAM and 

-

            self._CONTENTTYPE_PARAM and 

-

            self._CONTENT_PARAM in self._data and 

-

            self._CONTENTTYPE_PARAM in self._data): 

-

            self._content_type = self._data[self._CONTENTTYPE_PARAM] 

-

            self._stream = BytesIO(self._data[self._CONTENT_PARAM].encode(HTTP_HEADER_ENCODING)) 

-

            self._data, self._files = (Empty, Empty) 

-

 

-

    def _parse(self): 

-

        """ 

-

        Parse the request content, returning a two-tuple of (data, files) 

-

 

-

        May raise an `UnsupportedMediaType`, or `ParseError` exception. 

-

        """ 

-

        stream = self.stream 

-

        media_type = self.content_type 

-

 

-

        if stream is None or media_type is None: 

-

            empty_data = QueryDict('', self._request._encoding) 

-

            empty_files = MultiValueDict() 

-

            return (empty_data, empty_files) 

-

 

-

        parser = self.negotiator.select_parser(self, self.parsers) 

-

 

-

        if not parser: 

-

            raise exceptions.UnsupportedMediaType(media_type) 

-

 

-

        parsed = parser.parse(stream, media_type, self.parser_context) 

-

 

-

        # Parser classes may return the raw data, or a 

-

        # DataAndFiles object.  Unpack the result as required. 

-

        try: 

-

            return (parsed.data, parsed.files) 

-

        except AttributeError: 

-

            empty_files = MultiValueDict() 

-

            return (parsed, empty_files) 

-

 

-

    def _authenticate(self): 

-

        """ 

-

        Attempt to authenticate the request using each authentication instance 

-

        in turn. 

-

        Returns a three-tuple of (authenticator, user, authtoken). 

-

        """ 

-

        for authenticator in self.authenticators: 

-

            try: 

-

                user_auth_tuple = authenticator.authenticate(self) 

-

            except exceptions.APIException: 

-

                self._not_authenticated() 

-

                raise 

-

 

-

            if not user_auth_tuple is None: 

-

                self._authenticator = authenticator 

-

                self._user, self._auth = user_auth_tuple 

-

                return 

-

 

-

        self._not_authenticated() 

-

 

-

    def _not_authenticated(self): 

-

        """ 

-

        Return a three-tuple of (authenticator, user, authtoken), representing 

-

        an unauthenticated request. 

-

 

-

        By default this will be (None, AnonymousUser, None). 

-

        """ 

-

        self._authenticator = None 

-

 

-

        if api_settings.UNAUTHENTICATED_USER: 

-

            self._user = api_settings.UNAUTHENTICATED_USER() 

-

        else: 

-

            self._user = None 

-

 

-

        if api_settings.UNAUTHENTICATED_TOKEN: 

-

            self._auth = api_settings.UNAUTHENTICATED_TOKEN() 

-

        else: 

-

            self._auth = None 

-

 

-

    def __getattr__(self, attr): 

-

        """ 

-

        Proxy other attributes to the underlying HttpRequest object. 

-

        """ 

-

        return getattr(self._request, attr) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_response.html b/htmlcov/rest_framework_response.html deleted file mode 100644 index d297ecd0..00000000 --- a/htmlcov/rest_framework_response.html +++ /dev/null @@ -1,249 +0,0 @@ - - - - - - - - Coverage for rest_framework/response: 98% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

- -
-

""" 

-

The Response class in REST framework is similar to HTTPResponse, except that 

-

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 

-

from django.core.handlers.wsgi import STATUS_CODE_TEXT 

-

from django.template.response import SimpleTemplateResponse 

-

from rest_framework.compat import six 

-

 

-

 

-

class Response(SimpleTemplateResponse): 

-

    """ 

-

    An HttpResponse that allows its data to be rendered into 

-

    arbitrary media types. 

-

    """ 

-

 

-

    def __init__(self, data=None, status=200, 

-

                 template_name=None, headers=None, 

-

                 exception=False, content_type=None): 

-

        """ 

-

        Alters the init arguments slightly. 

-

        For example, drop 'template_name', and instead use 'data'. 

-

 

-

        Setting 'renderer' and 'media_type' will typically be deferred, 

-

        For example being set automatically by the `APIView`. 

-

        """ 

-

        super(Response, self).__init__(None, status=status) 

-

        self.data = data 

-

        self.template_name = template_name 

-

        self.exception = exception 

-

        self.content_type = content_type 

-

 

-

        if headers: 

-

            for name, value in six.iteritems(headers): 

-

                self[name] = value 

-

 

-

    @property 

-

    def rendered_content(self): 

-

        renderer = getattr(self, 'accepted_renderer', None) 

-

        media_type = getattr(self, 'accepted_media_type', None) 

-

        context = getattr(self, 'renderer_context', None) 

-

 

-

        assert renderer, ".accepted_renderer not set on Response" 

-

        assert media_type, ".accepted_media_type not set on Response" 

-

        assert context, ".renderer_context not set on Response" 

-

        context['response'] = self 

-

 

-

        charset = renderer.charset 

-

        content_type = self.content_type 

-

 

-

        if content_type is None and charset is not None: 

-

            content_type = "{0}; charset={1}".format(media_type, charset) 

-

        elif content_type is None: 

-

            content_type = media_type 

-

        self['Content-Type'] = content_type 

-

 

-

        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.' 

-

            return bytes(ret.encode(charset)) 

-

        return ret 

-

 

-

    @property 

-

    def status_text(self): 

-

        """ 

-

        Returns reason text corresponding to our HTTP response status code. 

-

        Provided for convenience. 

-

        """ 

-

        # TODO: Deprecate and use a template tag instead 

-

        # TODO: Status code text for RFC 6585 status codes 

-

        return STATUS_CODE_TEXT.get(self.status_code, '') 

-

 

-

    def __getstate__(self): 

-

        """ 

-

        Remove attributes from the response that shouldn't be cached 

-

        """ 

-

        state = super(Response, self).__getstate__() 

-

        for key in ('accepted_renderer', 'renderer_context', 'data'): 

-

            if key in state: 

-

                del state[key] 

-

        return state 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_reverse.html b/htmlcov/rest_framework_reverse.html deleted file mode 100644 index 4e7a8de2..00000000 --- a/htmlcov/rest_framework_reverse.html +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - Coverage for rest_framework/reverse: 75% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

- -
-

""" 

-

Provide reverse functions that return fully qualified URLs 

-

""" 

-

from __future__ import unicode_literals 

-

from django.core.urlresolvers import reverse as django_reverse 

-

from django.utils.functional import lazy 

-

 

-

 

-

def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra): 

-

    """ 

-

    Same as `django.core.urlresolvers.reverse`, but optionally takes a request 

-

    and returns a fully qualified URL, using the request to get the base URL. 

-

    """ 

-

    if format is not None: 

-

        kwargs = kwargs or {} 

-

        kwargs['format'] = format 

-

    url = django_reverse(viewname, args=args, kwargs=kwargs, **extra) 

-

    if request: 

-

        return request.build_absolute_uri(url) 

-

    return url 

-

 

-

 

-

reverse_lazy = lazy(reverse, str) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_routers.html b/htmlcov/rest_framework_routers.html deleted file mode 100644 index f08d5007..00000000 --- a/htmlcov/rest_framework_routers.html +++ /dev/null @@ -1,595 +0,0 @@ - - - - - - - - Coverage for rest_framework/routers: 94% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

- -
-

""" 

-

Routers provide a convenient and consistent way of automatically 

-

determining the URL conf for your API. 

-

 

-

They are used by simply instantiating a Router class, and then registering 

-

all the required ViewSets with that router. 

-

 

-

For example, you might have a `urls.py` that looks something like this: 

-

 

-

    router = routers.DefaultRouter() 

-

    router.register('users', UserViewSet, 'user') 

-

    router.register('accounts', AccountViewSet, 'account') 

-

 

-

    urlpatterns = router.urls 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

from collections import namedtuple 

-

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 

-

 

-

 

-

Route = namedtuple('Route', ['url', 'mapping', 'name', 'initkwargs']) 

-

 

-

 

-

def replace_methodname(format_string, methodname): 

-

    """ 

-

    Partially format a format_string, swapping out any 

-

    '{methodname}' or '{methodnamehyphen}' components. 

-

    """ 

-

    methodnamehyphen = methodname.replace('_', '-') 

-

    ret = format_string 

-

    ret = ret.replace('{methodname}', methodname) 

-

    ret = ret.replace('{methodnamehyphen}', methodnamehyphen) 

-

    return ret 

-

 

-

 

-

class BaseRouter(object): 

-

    def __init__(self): 

-

        self.registry = [] 

-

 

-

    def register(self, prefix, viewset, base_name=None): 

-

        if base_name is None: 

-

            base_name = self.get_default_base_name(viewset) 

-

        self.registry.append((prefix, viewset, base_name)) 

-

 

-

    def get_default_base_name(self, viewset): 

-

        """ 

-

        If `base_name` is not specified, attempt to automatically determine 

-

        it from the viewset. 

-

        """ 

-

        raise NotImplemented('get_default_base_name must be overridden') 

-

 

-

    def get_urls(self): 

-

        """ 

-

        Return a list of URL patterns, given the registered viewsets. 

-

        """ 

-

        raise NotImplemented('get_urls must be overridden') 

-

 

-

    @property 

-

    def urls(self): 

-

        if not hasattr(self, '_urls'): 

-

            self._urls = patterns('', *self.get_urls()) 

-

        return self._urls 

-

 

-

 

-

class SimpleRouter(BaseRouter): 

-

    routes = [ 

-

        # List route. 

-

        Route( 

-

            url=r'^{prefix}{trailing_slash}$', 

-

            mapping={ 

-

                'get': 'list', 

-

                'post': 'create' 

-

            }, 

-

            name='{basename}-list', 

-

            initkwargs={'suffix': 'List'} 

-

        ), 

-

        # Detail route. 

-

        Route( 

-

            url=r'^{prefix}/{lookup}{trailing_slash}$', 

-

            mapping={ 

-

                'get': 'retrieve', 

-

                'put': 'update', 

-

                'patch': 'partial_update', 

-

                'delete': 'destroy' 

-

            }, 

-

            name='{basename}-detail', 

-

            initkwargs={'suffix': 'Instance'} 

-

        ), 

-

        # Dynamically generated routes. 

-

        # Generated using @action or @link decorators on methods of the viewset. 

-

        Route( 

-

            url=r'^{prefix}/{lookup}/{methodname}{trailing_slash}$', 

-

            mapping={ 

-

                '{httpmethod}': '{methodname}', 

-

            }, 

-

            name='{basename}-{methodnamehyphen}', 

-

            initkwargs={} 

-

        ), 

-

    ] 

-

 

-

    def __init__(self, trailing_slash=True): 

-

        self.trailing_slash = trailing_slash and '/' or '' 

-

        super(SimpleRouter, self).__init__() 

-

 

-

    def get_default_base_name(self, viewset): 

-

        """ 

-

        If `base_name` is not specified, attempt to automatically determine 

-

        it from the viewset. 

-

        """ 

-

        model_cls = getattr(viewset, 'model', None) 

-

        queryset = getattr(viewset, 'queryset', None) 

-

        if model_cls is None and queryset is not None: 

-

            model_cls = queryset.model 

-

 

-

        assert model_cls, '`name` not argument not specified, and could ' \ 

-

            'not automatically determine the name from the viewset, as ' \ 

-

            'it does not have a `.model` or `.queryset` attribute.' 

-

 

-

        return model_cls._meta.object_name.lower() 

-

 

-

    def get_routes(self, viewset): 

-

        """ 

-

        Augment `self.routes` with any dynamically generated routes. 

-

 

-

        Returns a list of the Route namedtuple. 

-

        """ 

-

 

-

        # Determine any `@action` or `@link` decorated methods on the viewset 

-

        dynamic_routes = [] 

-

        for methodname in dir(viewset): 

-

            attr = getattr(viewset, methodname) 

-

            httpmethods = getattr(attr, 'bind_to_methods', None) 

-

            if httpmethods: 

-

                dynamic_routes.append((httpmethods, methodname)) 

-

 

-

        ret = [] 

-

        for route in self.routes: 

-

            if route.mapping == {'{httpmethod}': '{methodname}'}: 

-

                # 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( 

-

                        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) 

-

 

-

        return ret 

-

 

-

    def get_method_map(self, viewset, method_map): 

-

        """ 

-

        Given a viewset, and a mapping of http methods to actions, 

-

        return a new mapping which only includes any mappings that 

-

        are actually implemented by the viewset. 

-

        """ 

-

        bound_methods = {} 

-

        for method, action in method_map.items(): 

-

            if hasattr(viewset, action): 

-

                bound_methods[method] = action 

-

        return bound_methods 

-

 

-

    def get_lookup_regex(self, viewset): 

-

        """ 

-

        Given a viewset, return the portion of URL regex that is used 

-

        to match against a single instance. 

-

        """ 

-

        base_regex = '(?P<{lookup_field}>[^/]+)' 

-

        lookup_field = getattr(viewset, 'lookup_field', 'pk') 

-

        return base_regex.format(lookup_field=lookup_field) 

-

 

-

    def get_urls(self): 

-

        """ 

-

        Use the registered viewsets to generate a list of URL patterns. 

-

        """ 

-

        ret = [] 

-

 

-

        for prefix, viewset, basename in self.registry: 

-

            lookup = self.get_lookup_regex(viewset) 

-

            routes = self.get_routes(viewset) 

-

 

-

            for route in routes: 

-

 

-

                # Only actions which actually exist on the viewset will be bound 

-

                mapping = self.get_method_map(viewset, route.mapping) 

-

                if not mapping: 

-

                    continue 

-

 

-

                # Build the url pattern 

-

                regex = route.url.format( 

-

                    prefix=prefix, 

-

                    lookup=lookup, 

-

                    trailing_slash=self.trailing_slash 

-

                ) 

-

                view = viewset.as_view(mapping, **route.initkwargs) 

-

                name = route.name.format(basename=basename) 

-

                ret.append(url(regex, view, name=name)) 

-

 

-

        return ret 

-

 

-

 

-

class DefaultRouter(SimpleRouter): 

-

    """ 

-

    The default router extends the SimpleRouter, but also adds in a default 

-

    API root view, and adds format suffix patterns to the URLs. 

-

    """ 

-

    include_root_view = True 

-

    include_format_suffixes = True 

-

    root_view_name = 'api-root' 

-

 

-

    def get_api_root_view(self): 

-

        """ 

-

        Return a view to use as the API root. 

-

        """ 

-

        api_root_dict = {} 

-

        list_name = self.routes[0].name 

-

        for prefix, viewset, basename in self.registry: 

-

            api_root_dict[prefix] = list_name.format(basename=basename) 

-

 

-

        class APIRoot(views.APIView): 

-

            _ignore_model_permissions = True 

-

 

-

            def get(self, request, format=None): 

-

                ret = {} 

-

                for key, url_name in api_root_dict.items(): 

-

                    ret[key] = reverse(url_name, request=request, format=format) 

-

                return Response(ret) 

-

 

-

        return APIRoot.as_view() 

-

 

-

    def get_urls(self): 

-

        """ 

-

        Generate the list of URL patterns, including a default root view 

-

        for the API, and appending `.json` style format suffixes. 

-

        """ 

-

        urls = [] 

-

 

-

        if self.include_root_view: 

-

            root_url = url(r'^$', self.get_api_root_view(), name=self.root_view_name) 

-

            urls.append(root_url) 

-

 

-

        default_urls = super(DefaultRouter, self).get_urls() 

-

        urls.extend(default_urls) 

-

 

-

        if self.include_format_suffixes: 

-

            urls = format_suffix_patterns(urls) 

-

 

-

        return urls 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_serializers.html b/htmlcov/rest_framework_serializers.html deleted file mode 100644 index 79dc5647..00000000 --- a/htmlcov/rest_framework_serializers.html +++ /dev/null @@ -1,2011 +0,0 @@ - - - - - - - - Coverage for rest_framework/serializers: 94% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

-

296

-

297

-

298

-

299

-

300

-

301

-

302

-

303

-

304

-

305

-

306

-

307

-

308

-

309

-

310

-

311

-

312

-

313

-

314

-

315

-

316

-

317

-

318

-

319

-

320

-

321

-

322

-

323

-

324

-

325

-

326

-

327

-

328

-

329

-

330

-

331

-

332

-

333

-

334

-

335

-

336

-

337

-

338

-

339

-

340

-

341

-

342

-

343

-

344

-

345

-

346

-

347

-

348

-

349

-

350

-

351

-

352

-

353

-

354

-

355

-

356

-

357

-

358

-

359

-

360

-

361

-

362

-

363

-

364

-

365

-

366

-

367

-

368

-

369

-

370

-

371

-

372

-

373

-

374

-

375

-

376

-

377

-

378

-

379

-

380

-

381

-

382

-

383

-

384

-

385

-

386

-

387

-

388

-

389

-

390

-

391

-

392

-

393

-

394

-

395

-

396

-

397

-

398

-

399

-

400

-

401

-

402

-

403

-

404

-

405

-

406

-

407

-

408

-

409

-

410

-

411

-

412

-

413

-

414

-

415

-

416

-

417

-

418

-

419

-

420

-

421

-

422

-

423

-

424

-

425

-

426

-

427

-

428

-

429

-

430

-

431

-

432

-

433

-

434

-

435

-

436

-

437

-

438

-

439

-

440

-

441

-

442

-

443

-

444

-

445

-

446

-

447

-

448

-

449

-

450

-

451

-

452

-

453

-

454

-

455

-

456

-

457

-

458

-

459

-

460

-

461

-

462

-

463

-

464

-

465

-

466

-

467

-

468

-

469

-

470

-

471

-

472

-

473

-

474

-

475

-

476

-

477

-

478

-

479

-

480

-

481

-

482

-

483

-

484

-

485

-

486

-

487

-

488

-

489

-

490

-

491

-

492

-

493

-

494

-

495

-

496

-

497

-

498

-

499

-

500

-

501

-

502

-

503

-

504

-

505

-

506

-

507

-

508

-

509

-

510

-

511

-

512

-

513

-

514

-

515

-

516

-

517

-

518

-

519

-

520

-

521

-

522

-

523

-

524

-

525

-

526

-

527

-

528

-

529

-

530

-

531

-

532

-

533

-

534

-

535

-

536

-

537

-

538

-

539

-

540

-

541

-

542

-

543

-

544

-

545

-

546

-

547

-

548

-

549

-

550

-

551

-

552

-

553

-

554

-

555

-

556

-

557

-

558

-

559

-

560

-

561

-

562

-

563

-

564

-

565

-

566

-

567

-

568

-

569

-

570

-

571

-

572

-

573

-

574

-

575

-

576

-

577

-

578

-

579

-

580

-

581

-

582

-

583

-

584

-

585

-

586

-

587

-

588

-

589

-

590

-

591

-

592

-

593

-

594

-

595

-

596

-

597

-

598

-

599

-

600

-

601

-

602

-

603

-

604

-

605

-

606

-

607

-

608

-

609

-

610

-

611

-

612

-

613

-

614

-

615

-

616

-

617

-

618

-

619

-

620

-

621

-

622

-

623

-

624

-

625

-

626

-

627

-

628

-

629

-

630

-

631

-

632

-

633

-

634

-

635

-

636

-

637

-

638

-

639

-

640

-

641

-

642

-

643

-

644

-

645

-

646

-

647

-

648

-

649

-

650

-

651

-

652

-

653

-

654

-

655

-

656

-

657

-

658

-

659

-

660

-

661

-

662

-

663

-

664

-

665

-

666

-

667

-

668

-

669

-

670

-

671

-

672

-

673

-

674

-

675

-

676

-

677

-

678

-

679

-

680

-

681

-

682

-

683

-

684

-

685

-

686

-

687

-

688

-

689

-

690

-

691

-

692

-

693

-

694

-

695

-

696

-

697

-

698

-

699

-

700

-

701

-

702

-

703

-

704

-

705

-

706

-

707

-

708

-

709

-

710

-

711

-

712

-

713

-

714

-

715

-

716

-

717

-

718

-

719

-

720

-

721

-

722

-

723

-

724

-

725

-

726

-

727

-

728

-

729

-

730

-

731

-

732

-

733

-

734

-

735

-

736

-

737

-

738

-

739

-

740

-

741

-

742

-

743

-

744

-

745

-

746

-

747

-

748

-

749

-

750

-

751

-

752

-

753

-

754

-

755

-

756

-

757

-

758

-

759

-

760

-

761

-

762

-

763

-

764

-

765

-

766

-

767

-

768

-

769

-

770

-

771

-

772

-

773

-

774

-

775

-

776

-

777

-

778

-

779

-

780

-

781

-

782

-

783

-

784

-

785

-

786

-

787

-

788

-

789

-

790

-

791

-

792

-

793

-

794

-

795

-

796

-

797

-

798

-

799

-

800

-

801

-

802

-

803

-

804

-

805

-

806

-

807

-

808

-

809

-

810

-

811

-

812

-

813

-

814

-

815

-

816

-

817

-

818

-

819

-

820

-

821

-

822

-

823

-

824

-

825

-

826

-

827

-

828

-

829

-

830

-

831

-

832

-

833

-

834

-

835

-

836

-

837

-

838

-

839

-

840

-

841

-

842

-

843

-

844

-

845

-

846

-

847

-

848

-

849

-

850

-

851

-

852

-

853

-

854

-

855

-

856

-

857

-

858

-

859

-

860

-

861

-

862

-

863

-

864

-

865

-

866

-

867

-

868

-

869

-

870

-

871

-

872

-

873

-

874

-

875

-

876

-

877

-

878

-

879

-

880

-

881

-

882

-

883

-

884

-

885

-

886

-

887

-

888

-

889

-

890

-

891

-

892

-

893

-

894

-

895

-

896

-

897

-

898

-

899

-

900

-

901

-

902

-

903

-

904

-

905

-

906

-

907

-

908

-

909

-

910

-

911

-

912

-

913

-

914

-

915

-

916

-

917

-

918

-

919

-

920

-

921

-

922

-

923

-

924

-

925

-

926

-

927

-

928

-

929

-

930

-

931

-

932

-

933

-

934

-

935

-

936

-

937

-

938

-

939

-

940

-

941

-

942

-

943

-

944

-

945

-

946

-

947

-

948

-

949

-

950

-

951

-

952

-

953

-

954

-

955

-

956

-

957

-

958

-

959

-

960

-

961

-

962

-

963

-

964

-

965

- -
-

""" 

-

Serializers and ModelSerializers are similar to Forms and ModelForms. 

-

Unlike forms, they are not constrained to dealing with HTML output, and 

-

form encoded input. 

-

 

-

Serialization in REST framework is a two-phase process: 

-

 

-

1. Serializers marshal between complex types like model instances, and 

-

python primatives. 

-

2. The process of marshalling between python primatives and request and 

-

response content is handled by parsers and renderers. 

-

""" 

-

from __future__ import unicode_literals 

-

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 

-

from rest_framework.compat import get_concrete_model, six 

-

 

-

# Note: We do the following so that users of the framework can use this style: 

-

# 

-

#     example_field = serializers.CharField(...) 

-

# 

-

# 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 * 

-

 

-

 

-

class NestedValidationError(ValidationError): 

-

    """ 

-

    The default ValidationError behavior is to stringify each item in the list 

-

    if the messages are a list of error messages. 

-

 

-

    In the case of nested serializers, where the parent has many children, 

-

    then the child's `serializer.errors` will be a list of dicts.  In the case 

-

    of a single child, the `serializer.errors` will be a dict. 

-

 

-

    We need to override the default behavior to get properly nested error dicts. 

-

    """ 

-

 

-

    def __init__(self, message): 

-

        if isinstance(message, dict): 

-

            self.messages = [message] 

-

        else: 

-

            self.messages = message 

-

 

-

 

-

class DictWithMetadata(dict): 

-

    """ 

-

    A dict-like object, that can have additional properties attached. 

-

    """ 

-

    def __getstate__(self): 

-

        """ 

-

        Used by pickle (e.g., caching). 

-

        Overridden to remove the metadata from the dict, since it shouldn't be 

-

        pickled and may in some instances be unpickleable. 

-

        """ 

-

        return dict(self) 

-

 

-

 

-

class SortedDictWithMetadata(SortedDict): 

-

    """ 

-

    A sorted dict-like object, that can have additional properties attached. 

-

    """ 

-

    def __getstate__(self): 

-

        """ 

-

        Used by pickle (e.g., caching). 

-

        Overriden to remove the metadata from the dict, since it shouldn't be 

-

        pickle and may in some instances be unpickleable. 

-

        """ 

-

        return SortedDict(self).__dict__ 

-

 

-

 

-

def _is_protected_type(obj): 

-

    """ 

-

    True if the object is a native datatype that does not need to 

-

    be serialized further. 

-

    """ 

-

    return isinstance(obj, ( 

-

        types.NoneType, 

-

        int, long, 

-

        datetime.datetime, datetime.date, datetime.time, 

-

        float, Decimal, 

-

        basestring) 

-

    ) 

-

 

-

 

-

def _get_declared_fields(bases, attrs): 

-

    """ 

-

    Create a list of serializer field instances from the passed in 'attrs', 

-

    plus any fields on the base classes (in 'bases'). 

-

 

-

    Note that all fields from the base classes are used. 

-

    """ 

-

    fields = [(field_name, attrs.pop(field_name)) 

-

              for field_name, obj in list(six.iteritems(attrs)) 

-

              if isinstance(obj, Field)] 

-

    fields.sort(key=lambda x: x[1].creation_counter) 

-

 

-

    # If this class is subclassing another Serializer, add that Serializer's 

-

    # fields.  Note that we loop over the bases in *reverse*. This is necessary 

-

    # in order to maintain the correct order of fields. 

-

    for base in bases[::-1]: 

-

        if hasattr(base, 'base_fields'): 

-

            fields = list(base.base_fields.items()) + fields 

-

 

-

    return SortedDict(fields) 

-

 

-

 

-

class SerializerMetaclass(type): 

-

    def __new__(cls, name, bases, attrs): 

-

        attrs['base_fields'] = _get_declared_fields(bases, attrs) 

-

        return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs) 

-

 

-

 

-

class SerializerOptions(object): 

-

    """ 

-

    Meta class options for Serializer 

-

    """ 

-

    def __init__(self, meta): 

-

        self.depth = getattr(meta, 'depth', 0) 

-

        self.fields = getattr(meta, 'fields', ()) 

-

        self.exclude = getattr(meta, 'exclude', ()) 

-

 

-

 

-

class BaseSerializer(WritableField): 

-

    """ 

-

    This is the Serializer implementation. 

-

    We need to implement it as `BaseSerializer` due to metaclass magicks. 

-

    """ 

-

    class Meta(object): 

-

        pass 

-

 

-

    _options_class = SerializerOptions 

-

    _dict_class = SortedDictWithMetadata 

-

 

-

    def __init__(self, instance=None, data=None, files=None, 

-

                 context=None, partial=False, many=None, 

-

                 allow_add_remove=False, **kwargs): 

-

        super(BaseSerializer, self).__init__(**kwargs) 

-

        self.opts = self._options_class(self.Meta) 

-

        self.parent = None 

-

        self.root = None 

-

        self.partial = partial 

-

        self.many = many 

-

        self.allow_add_remove = allow_add_remove 

-

 

-

        self.context = context or {} 

-

 

-

        self.init_data = data 

-

        self.init_files = files 

-

        self.object = instance 

-

        self.fields = self.get_fields() 

-

 

-

        self._data = None 

-

        self._files = None 

-

        self._errors = None 

-

        self._deleted = None 

-

 

-

        if many and instance is not None and not hasattr(instance, '__iter__'): 

-

            raise ValueError('instance should be a queryset or other iterable with many=True') 

-

 

-

        if allow_add_remove and not many: 

-

            raise ValueError('allow_add_remove should only be used for bulk updates, but you have not set many=True') 

-

 

-

    ##### 

-

    # Methods to determine which fields to use when (de)serializing objects. 

-

 

-

    def get_default_fields(self): 

-

        """ 

-

        Return the complete set of default fields for the object, as a dict. 

-

        """ 

-

        return {} 

-

 

-

    def get_fields(self): 

-

        """ 

-

        Returns the complete set of fields for the object as a dict. 

-

 

-

        This will be the set of any explicitly declared fields, 

-

        plus the set of fields returned by get_default_fields(). 

-

        """ 

-

        ret = SortedDict() 

-

 

-

        # Get the explicitly declared fields 

-

        base_fields = copy.deepcopy(self.base_fields) 

-

        for key, field in base_fields.items(): 

-

            ret[key] = field 

-

 

-

        # Add in the default fields 

-

        default_fields = self.get_default_fields() 

-

        for key, val in default_fields.items(): 

-

            if key not in ret: 

-

                ret[key] = val 

-

 

-

        # If 'fields' is specified, use those fields, in that order. 

-

        if self.opts.fields: 

-

            assert isinstance(self.opts.fields, (list, tuple)), '`fields` must be a list or tuple' 

-

            new = SortedDict() 

-

            for key in self.opts.fields: 

-

                new[key] = ret[key] 

-

            ret = new 

-

 

-

        # Remove anything in 'exclude' 

-

        if self.opts.exclude: 

-

            assert isinstance(self.opts.exclude, (list, tuple)), '`exclude` must be a list or tuple' 

-

            for key in self.opts.exclude: 

-

                ret.pop(key, None) 

-

 

-

        for key, field in ret.items(): 

-

            field.initialize(parent=self, field_name=key) 

-

 

-

        return ret 

-

 

-

    ##### 

-

    # Methods to convert or revert from objects <--> primitive representations. 

-

 

-

    def get_field_key(self, field_name): 

-

        """ 

-

        Return the key that should be used for a given field. 

-

        """ 

-

        return field_name 

-

 

-

    def restore_fields(self, data, files): 

-

        """ 

-

        Core of deserialization, together with `restore_object`. 

-

        Converts a dictionary of data into a dictionary of deserialized fields. 

-

        """ 

-

        reverted_data = {} 

-

 

-

        if data is not None and not isinstance(data, dict): 

-

            self._errors['non_field_errors'] = ['Invalid data'] 

-

            return None 

-

 

-

        for field_name, field in self.fields.items(): 

-

            field.initialize(parent=self, field_name=field_name) 

-

            try: 

-

                field.field_from_native(data, files, field_name, reverted_data) 

-

            except ValidationError as err: 

-

                self._errors[field_name] = list(err.messages) 

-

 

-

        return reverted_data 

-

 

-

    def perform_validation(self, attrs): 

-

        """ 

-

        Run `validate_<fieldname>()` and `validate()` methods on the serializer 

-

        """ 

-

        for field_name, field in self.fields.items(): 

-

            if field_name in self._errors: 

-

                continue 

-

            try: 

-

                validate_method = getattr(self, 'validate_%s' % field_name, None) 

-

                if validate_method: 

-

                    source = field.source or field_name 

-

                    attrs = validate_method(attrs, source) 

-

            except ValidationError as err: 

-

                self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages) 

-

 

-

        # If there are already errors, we don't run .validate() because 

-

        # field-validation failed and thus `attrs` may not be complete. 

-

        # which in turn can cause inconsistent validation errors. 

-

        if not self._errors: 

-

            try: 

-

                attrs = self.validate(attrs) 

-

            except ValidationError as err: 

-

                if hasattr(err, 'message_dict'): 

-

                    for field_name, error_messages in err.message_dict.items(): 

-

                        self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) 

-

                elif hasattr(err, 'messages'): 

-

                    self._errors['non_field_errors'] = err.messages 

-

 

-

        return attrs 

-

 

-

    def validate(self, attrs): 

-

        """ 

-

        Stub method, to be overridden in Serializer subclasses 

-

        """ 

-

        return attrs 

-

 

-

    def restore_object(self, attrs, instance=None): 

-

        """ 

-

        Deserialize a dictionary of attributes into an object instance. 

-

        You should override this method to control how deserialized objects 

-

        are instantiated. 

-

        """ 

-

        if instance is not None: 

-

            instance.update(attrs) 

-

            return instance 

-

        return attrs 

-

 

-

    def to_native(self, obj): 

-

        """ 

-

        Serialize objects -> primitives. 

-

        """ 

-

        ret = self._dict_class() 

-

        ret.fields = {} 

-

 

-

        for field_name, field in self.fields.items(): 

-

            field.initialize(parent=self, field_name=field_name) 

-

            key = self.get_field_key(field_name) 

-

            value = field.field_to_native(obj, field_name) 

-

            ret[key] = value 

-

            ret.fields[key] = field 

-

        return ret 

-

 

-

    def from_native(self, data, files): 

-

        """ 

-

        Deserialize primitives -> objects. 

-

        """ 

-

        self._errors = {} 

-

        if data is not None or files is not None: 

-

            attrs = self.restore_fields(data, files) 

-

            if attrs is not None: 

-

                attrs = self.perform_validation(attrs) 

-

        else: 

-

            self._errors['non_field_errors'] = ['No input provided'] 

-

 

-

        if not self._errors: 

-

            return self.restore_object(attrs, instance=getattr(self, 'object', None)) 

-

 

-

    def field_to_native(self, obj, field_name): 

-

        """ 

-

        Override default so that the serializer can be used as a nested field 

-

        across relationships. 

-

        """ 

-

        if self.source == '*': 

-

            return self.to_native(obj) 

-

 

-

        try: 

-

            source = self.source or field_name 

-

            value = obj 

-

 

-

            for component in source.split('.'): 

-

                value = get_component(value, component) 

-

                if value is None: 

-

                    break 

-

        except ObjectDoesNotExist: 

-

            return None 

-

 

-

        if is_simple_callable(getattr(value, 'all', None)): 

-

            return [self.to_native(item) for item in value.all()] 

-

 

-

        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: 

-

            return [self.to_native(item) for item in value] 

-

        return self.to_native(value) 

-

 

-

    def field_from_native(self, data, files, field_name, into): 

-

        """ 

-

        Override default so that the serializer can be used as a writable 

-

        nested field across relationships. 

-

        """ 

-

        if self.read_only: 

-

            return 

-

 

-

        try: 

-

            value = data[field_name] 

-

        except KeyError: 

-

            if self.default is not None and not self.partial: 

-

                # Note: partial updates shouldn't set defaults 

-

                value = copy.deepcopy(self.default) 

-

            else: 

-

                if self.required: 

-

                    raise ValidationError(self.error_messages['required']) 

-

                return 

-

 

-

        # Set the serializer object if it exists 

-

        obj = getattr(self.parent.object, field_name) if self.parent.object else None 

-

 

-

        if self.source == '*': 

-

            if value: 

-

                into.update(value) 

-

        else: 

-

            if value in (None, ''): 

-

                into[(self.source or field_name)] = None 

-

            else: 

-

                kwargs = { 

-

                    'instance': obj, 

-

                    'data': value, 

-

                    'context': self.context, 

-

                    'partial': self.partial, 

-

                    'many': self.many 

-

                } 

-

                serializer = self.__class__(**kwargs) 

-

 

-

                if serializer.is_valid(): 

-

                    into[self.source or field_name] = serializer.object 

-

                else: 

-

                    # Propagate errors up to our parent 

-

                    raise NestedValidationError(serializer.errors) 

-

 

-

    def get_identity(self, data): 

-

        """ 

-

        This hook is required for bulk update. 

-

        It is used to determine the canonical identity of a given object. 

-

 

-

        Note that the data has not been validated at this point, so we need 

-

        to make sure that we catch any cases of incorrect datatypes being 

-

        passed to this method. 

-

        """ 

-

        try: 

-

            return data.get('id', None) 

-

        except AttributeError: 

-

            return None 

-

 

-

    @property 

-

    def errors(self): 

-

        """ 

-

        Run deserialization and return error data, 

-

        setting self.object if no errors occurred. 

-

        """ 

-

        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: 

-

                ret = [] 

-

                errors = [] 

-

                update = self.object is not None 

-

 

-

                if update: 

-

                    # If this is a bulk update we need to map all the objects 

-

                    # to a canonical identity so we can determine which 

-

                    # individual object is being updated for each item in the 

-

                    # incoming data 

-

                    objects = self.object 

-

                    identities = [self.get_identity(self.to_native(obj)) for obj in objects] 

-

                    identity_to_objects = dict(zip(identities, objects)) 

-

 

-

                if hasattr(data, '__iter__') and not isinstance(data, (dict, six.text_type)): 

-

                    for item in data: 

-

                        if update: 

-

                            # Determine which object we're updating 

-

                            identity = self.get_identity(item) 

-

                            self.object = identity_to_objects.pop(identity, None) 

-

                            if self.object is None and not self.allow_add_remove: 

-

                                ret.append(None) 

-

                                errors.append({'non_field_errors': ['Cannot create a new item, only existing items may be updated.']}) 

-

                                continue 

-

 

-

                        ret.append(self.from_native(item, None)) 

-

                        errors.append(self._errors) 

-

 

-

                    if update: 

-

                        self._deleted = identity_to_objects.values() 

-

 

-

                    self._errors = any(errors) and errors or [] 

-

                else: 

-

                    self._errors = {'non_field_errors': ['Expected a list of items.']} 

-

            else: 

-

                ret = self.from_native(data, files) 

-

 

-

            if not self._errors: 

-

                self.object = ret 

-

 

-

        return self._errors 

-

 

-

    def is_valid(self): 

-

        return not self.errors 

-

 

-

    @property 

-

    def data(self): 

-

        """ 

-

        Returns the serialized data on the serializer. 

-

        """ 

-

        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: 

-

                self._data = [self.to_native(item) for item in obj] 

-

            else: 

-

                self._data = self.to_native(obj) 

-

 

-

        return self._data 

-

 

-

    def save_object(self, obj, **kwargs): 

-

        obj.save(**kwargs) 

-

 

-

    def delete_object(self, obj): 

-

        obj.delete() 

-

 

-

    def save(self, **kwargs): 

-

        """ 

-

        Save the deserialized object and return it. 

-

        """ 

-

        if isinstance(self.object, list): 

-

            [self.save_object(item, **kwargs) for item in self.object] 

-

        else: 

-

            self.save_object(self.object, **kwargs) 

-

 

-

        if self.allow_add_remove and self._deleted: 

-

            [self.delete_object(item) for item in self._deleted] 

-

 

-

        return self.object 

-

 

-

    def metadata(self): 

-

        """ 

-

        Return a dictionary of metadata about the fields on the serializer. 

-

        Useful for things like responding to OPTIONS requests, or generating 

-

        API schemas for auto-documentation. 

-

        """ 

-

        return SortedDict( 

-

            [(field_name, field.metadata()) 

-

            for field_name, field in six.iteritems(self.fields)] 

-

        ) 

-

 

-

 

-

class Serializer(six.with_metaclass(SerializerMetaclass, BaseSerializer)): 

-

    pass 

-

 

-

 

-

class ModelSerializerOptions(SerializerOptions): 

-

    """ 

-

    Meta class options for ModelSerializer 

-

    """ 

-

    def __init__(self, meta): 

-

        super(ModelSerializerOptions, self).__init__(meta) 

-

        self.model = getattr(meta, 'model', None) 

-

        self.read_only_fields = getattr(meta, 'read_only_fields', ()) 

-

 

-

 

-

class ModelSerializer(Serializer): 

-

    """ 

-

    A serializer that deals with model instances and querysets. 

-

    """ 

-

    _options_class = ModelSerializerOptions 

-

 

-

    field_mapping = { 

-

        models.AutoField: IntegerField, 

-

        models.FloatField: FloatField, 

-

        models.IntegerField: IntegerField, 

-

        models.PositiveIntegerField: IntegerField, 

-

        models.SmallIntegerField: IntegerField, 

-

        models.PositiveSmallIntegerField: IntegerField, 

-

        models.DateTimeField: DateTimeField, 

-

        models.DateField: DateField, 

-

        models.TimeField: TimeField, 

-

        models.DecimalField: DecimalField, 

-

        models.EmailField: EmailField, 

-

        models.CharField: CharField, 

-

        models.URLField: URLField, 

-

        models.SlugField: SlugField, 

-

        models.TextField: CharField, 

-

        models.CommaSeparatedIntegerField: CharField, 

-

        models.BooleanField: BooleanField, 

-

        models.FileField: FileField, 

-

        models.ImageField: ImageField, 

-

    } 

-

 

-

    def get_default_fields(self): 

-

        """ 

-

        Return all the fields that should be serialized for the model. 

-

        """ 

-

 

-

        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 

-

        ret = SortedDict() 

-

        nested = bool(self.opts.depth) 

-

 

-

        # Deal with adding the primary key field 

-

        pk_field = opts.pk 

-

        while pk_field.rel and pk_field.rel.parent_link: 

-

            # 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 

-

 

-

        # Deal with forward relationships 

-

        forward_rels = [field for field in opts.fields if field.serialize] 

-

        forward_rels += [field for field in opts.many_to_many if field.serialize] 

-

 

-

        for model_field in forward_rels: 

-

            has_through_model = False 

-

 

-

            if model_field.rel: 

-

                to_many = isinstance(model_field, 

-

                                     models.fields.related.ManyToManyField) 

-

                related_model = model_field.rel.to 

-

 

-

                if to_many and not model_field.rel.through._meta.auto_created: 

-

                    has_through_model = True 

-

 

-

            if model_field.rel and nested: 

-

                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. ' 

-

                        'Use `get_nested_field(model_field, related_model, ' 

-

                        'to_many) instead', 

-

                        PendingDeprecationWarning 

-

                    ) 

-

                    field = self.get_nested_field(model_field) 

-

                else: 

-

                    field = self.get_nested_field(model_field, related_model, to_many) 

-

            elif model_field.rel: 

-

                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. ' 

-

                        'Use `get_related_field(model_field, related_model, ' 

-

                        'to_many) instead', 

-

                        PendingDeprecationWarning 

-

                    ) 

-

                    field = self.get_related_field(model_field, to_many=to_many) 

-

                else: 

-

                    field = self.get_related_field(model_field, related_model, to_many) 

-

            else: 

-

                field = self.get_field(model_field) 

-

 

-

            if field: 

-

                if has_through_model: 

-

                    field.read_only = True 

-

 

-

                ret[model_field.name] = field 

-

 

-

        # Deal with reverse relationships 

-

        if not self.opts.fields: 

-

            reverse_rels = [] 

-

        else: 

-

            # Reverse relationships are only included if they are explicitly 

-

            # present in the `fields` option on the serializer 

-

            reverse_rels = opts.get_all_related_objects() 

-

            reverse_rels += opts.get_all_related_many_to_many_objects() 

-

 

-

        for relation in reverse_rels: 

-

            accessor_name = relation.get_accessor_name() 

-

            if not self.opts.fields or accessor_name not in self.opts.fields: 

-

                continue 

-

            related_model = relation.model 

-

            to_many = relation.field.rel.multiple 

-

            has_through_model = False 

-

            is_m2m = isinstance(relation.field, 

-

                                models.fields.related.ManyToManyField) 

-

 

-

            if is_m2m and not relation.field.rel.through._meta.auto_created: 

-

                has_through_model = True 

-

 

-

            if nested: 

-

                field = self.get_nested_field(None, related_model, to_many) 

-

            else: 

-

                field = self.get_related_field(None, related_model, to_many) 

-

 

-

            if field: 

-

                if has_through_model: 

-

                    field.read_only = True 

-

 

-

                ret[accessor_name] = field 

-

 

-

        # Add the `read_only` flag to any fields that have bee specified 

-

        # in the `read_only_fields` option 

-

        for field_name in self.opts.read_only_fields: 

-

            assert field_name not in self.base_fields.keys(), \ 

-

                "field '%s' on serializer '%s' specfied in " \ 

-

                "`read_only_fields`, but also added " \ 

-

                "as an explict field.  Remove it from `read_only_fields`." % \ 

-

                (field_name, self.__class__.__name__) 

-

            assert field_name in ret, \ 

-

                "Noexistant field '%s' specified in `read_only_fields` " \ 

-

                "on serializer '%s'." % \ 

-

                (self.__class__.__name__, field_name) 

-

            ret[field_name].read_only = True 

-

 

-

        return ret 

-

 

-

    def get_pk_field(self, model_field): 

-

        """ 

-

        Returns a default instance of the pk field. 

-

        """ 

-

        return self.get_field(model_field) 

-

 

-

    def get_nested_field(self, model_field, related_model, to_many): 

-

        """ 

-

        Creates a default instance of a nested relational field. 

-

 

-

        Note that model_field will be `None` for reverse relationships. 

-

        """ 

-

        class NestedModelSerializer(ModelSerializer): 

-

            class Meta: 

-

                model = related_model 

-

                depth = self.opts.depth - 1 

-

 

-

        return NestedModelSerializer(many=to_many) 

-

 

-

    def get_related_field(self, model_field, related_model, to_many): 

-

        """ 

-

        Creates a default instance of a flat relational field. 

-

 

-

        Note that model_field will be `None` for reverse relationships. 

-

        """ 

-

        # TODO: filter queryset using: 

-

        # .using(db).complex_filter(self.rel.limit_choices_to) 

-

 

-

        kwargs = { 

-

            'queryset': related_model._default_manager, 

-

            'many': to_many 

-

        } 

-

 

-

        if model_field: 

-

            kwargs['required'] = not(model_field.null or model_field.blank) 

-

 

-

        return PrimaryKeyRelatedField(**kwargs) 

-

 

-

    def get_field(self, model_field): 

-

        """ 

-

        Creates a default instance of a basic non-relational field. 

-

        """ 

-

        kwargs = {} 

-

 

-

        if model_field.null or model_field.blank: 

-

            kwargs['required'] = False 

-

 

-

        if isinstance(model_field, models.AutoField) or not model_field.editable: 

-

            kwargs['read_only'] = True 

-

 

-

        if model_field.has_default(): 

-

            kwargs['default'] = model_field.get_default() 

-

 

-

        if issubclass(model_field.__class__, models.TextField): 

-

            kwargs['widget'] = widgets.Textarea 

-

 

-

        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 

-

 

-

        # TODO: TypedChoiceField? 

-

        if model_field.flatchoices:  # This ModelField contains choices 

-

            kwargs['choices'] = model_field.flatchoices 

-

            return ChoiceField(**kwargs) 

-

 

-

        # put this below the ChoiceField because min_value isn't a valid initializer 

-

        if issubclass(model_field.__class__, models.PositiveIntegerField) or\ 

-

                issubclass(model_field.__class__, models.PositiveSmallIntegerField): 

-

            kwargs['min_value'] = 0 

-

 

-

        attribute_dict = { 

-

            models.CharField: ['max_length'], 

-

            models.CommaSeparatedIntegerField: ['max_length'], 

-

            models.DecimalField: ['max_digits', 'decimal_places'], 

-

            models.EmailField: ['max_length'], 

-

            models.FileField: ['max_length'], 

-

            models.ImageField: ['max_length'], 

-

            models.SlugField: ['max_length'], 

-

            models.URLField: ['max_length'], 

-

        } 

-

 

-

        if model_field.__class__ in attribute_dict: 

-

            attributes = attribute_dict[model_field.__class__] 

-

            for attribute in attributes: 

-

                kwargs.update({attribute: getattr(model_field, attribute)}) 

-

 

-

        try: 

-

            return self.field_mapping[model_field.__class__](**kwargs) 

-

        except KeyError: 

-

            return ModelField(model_field=model_field, **kwargs) 

-

 

-

    def get_validation_exclusions(self): 

-

        """ 

-

        Return a list of field names to exclude from model validation. 

-

        """ 

-

        cls = self.opts.model 

-

        opts = get_concrete_model(cls)._meta 

-

        exclusions = [field.name for field in opts.fields + opts.many_to_many] 

-

        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: 

-

                exclusions.remove(field_name) 

-

        return exclusions 

-

 

-

    def full_clean(self, instance): 

-

        """ 

-

        Perform Django's full_clean, and populate the `errors` dictionary 

-

        if any validation errors occur. 

-

 

-

        Note that we don't perform this inside the `.restore_object()` method, 

-

        so that subclasses can override `.restore_object()`, and still get 

-

        the full_clean validation checking. 

-

        """ 

-

        try: 

-

            instance.full_clean(exclude=self.get_validation_exclusions()) 

-

        except ValidationError as err: 

-

            self._errors = err.message_dict 

-

            return None 

-

        return instance 

-

 

-

    def restore_object(self, attrs, instance=None): 

-

        """ 

-

        Restore the model instance. 

-

        """ 

-

        m2m_data = {} 

-

        related_data = {} 

-

        meta = self.opts.model._meta 

-

 

-

        # Reverse fk or one-to-one relations 

-

        for (obj, model) in meta.get_all_related_objects_with_model(): 

-

            field_name = obj.field.related_query_name() 

-

            if field_name in attrs: 

-

                related_data[field_name] = attrs.pop(field_name) 

-

 

-

        # Reverse m2m relations 

-

        for (obj, model) in meta.get_all_related_m2m_objects_with_model(): 

-

            field_name = obj.field.related_query_name() 

-

            if field_name in attrs: 

-

                m2m_data[field_name] = attrs.pop(field_name) 

-

 

-

        # Forward m2m relations 

-

        for field in meta.many_to_many: 

-

            if field.name in attrs: 

-

                m2m_data[field.name] = attrs.pop(field.name) 

-

 

-

        # Update an existing instance... 

-

        if instance is not None: 

-

            for key, val in attrs.items(): 

-

                setattr(instance, key, val) 

-

 

-

        # ...or create a new instance 

-

        else: 

-

            instance = self.opts.model(**attrs) 

-

 

-

        # Any relations that cannot be set until we've 

-

        # saved the model get hidden away on these 

-

        # private attributes, so we can deal with them 

-

        # at the point of save. 

-

        instance._related_data = related_data 

-

        instance._m2m_data = m2m_data 

-

 

-

        return instance 

-

 

-

    def from_native(self, data, files): 

-

        """ 

-

        Override the default method to also include model field validation. 

-

        """ 

-

        instance = super(ModelSerializer, self).from_native(data, files) 

-

        if not self._errors: 

-

            return self.full_clean(instance) 

-

 

-

    def save_object(self, obj, **kwargs): 

-

        """ 

-

        Save the deserialized object and return it. 

-

        """ 

-

        obj.save(**kwargs) 

-

 

-

        if getattr(obj, '_m2m_data', None): 

-

            for accessor_name, object_list in obj._m2m_data.items(): 

-

                setattr(obj, accessor_name, object_list) 

-

            del(obj._m2m_data) 

-

 

-

        if getattr(obj, '_related_data', None): 

-

            for accessor_name, related in obj._related_data.items(): 

-

                setattr(obj, accessor_name, related) 

-

            del(obj._related_data) 

-

 

-

 

-

class HyperlinkedModelSerializerOptions(ModelSerializerOptions): 

-

    """ 

-

    Options for HyperlinkedModelSerializer 

-

    """ 

-

    def __init__(self, meta): 

-

        super(HyperlinkedModelSerializerOptions, self).__init__(meta) 

-

        self.view_name = getattr(meta, 'view_name', None) 

-

        self.lookup_field = getattr(meta, 'lookup_field', None) 

-

 

-

 

-

class HyperlinkedModelSerializer(ModelSerializer): 

-

    """ 

-

    A subclass of ModelSerializer that uses hyperlinked relationships, 

-

    instead of primary key relationships. 

-

    """ 

-

    _options_class = HyperlinkedModelSerializerOptions 

-

    _default_view_name = '%(model_name)s-detail' 

-

    _hyperlink_field_class = HyperlinkedRelatedField 

-

 

-

    def get_default_fields(self): 

-

        fields = super(HyperlinkedModelSerializer, self).get_default_fields() 

-

 

-

        if self.opts.view_name is None: 

-

            self.opts.view_name = self._get_default_view_name(self.opts.model) 

-

 

-

        if 'url' not in fields: 

-

            url_field = HyperlinkedIdentityField( 

-

                view_name=self.opts.view_name, 

-

                lookup_field=self.opts.lookup_field 

-

            ) 

-

            fields.insert(0, 'url', url_field) 

-

 

-

        return fields 

-

 

-

    def get_pk_field(self, model_field): 

-

        if self.opts.fields and model_field.name in self.opts.fields: 

-

            return self.get_field(model_field) 

-

 

-

    def get_related_field(self, model_field, related_model, to_many): 

-

        """ 

-

        Creates a default instance of a flat relational field. 

-

        """ 

-

        # TODO: filter queryset using: 

-

        # .using(db).complex_filter(self.rel.limit_choices_to) 

-

        kwargs = { 

-

            'queryset': related_model._default_manager, 

-

            'view_name': self._get_default_view_name(related_model), 

-

            'many': to_many 

-

        } 

-

 

-

        if model_field: 

-

            kwargs['required'] = not(model_field.null or model_field.blank) 

-

 

-

        if self.opts.lookup_field: 

-

            kwargs['lookup_field'] = self.opts.lookup_field 

-

 

-

        return self._hyperlink_field_class(**kwargs) 

-

 

-

    def get_identity(self, data): 

-

        """ 

-

        This hook is required for bulk update. 

-

        We need to override the default, to use the url as the identity. 

-

        """ 

-

        try: 

-

            return data.get('url', None) 

-

        except AttributeError: 

-

            return None 

-

 

-

    def _get_default_view_name(self, model): 

-

        """ 

-

        Return the view name to use if 'view_name' is not specified in 'Meta' 

-

        """ 

-

        model_meta = model._meta 

-

        format_kwargs = { 

-

            'app_label': model_meta.app_label, 

-

            'model_name': model_meta.object_name.lower() 

-

        } 

-

        return self._default_view_name % format_kwargs 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_settings.html b/htmlcov/rest_framework_settings.html deleted file mode 100644 index ae47b5bc..00000000 --- a/htmlcov/rest_framework_settings.html +++ /dev/null @@ -1,465 +0,0 @@ - - - - - - - - Coverage for rest_framework/settings: 95% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

- -
-

""" 

-

Settings for REST framework are all namespaced in the REST_FRAMEWORK setting. 

-

For example your project's `settings.py` file might look like this: 

-

 

-

REST_FRAMEWORK = { 

-

    'DEFAULT_RENDERER_CLASSES': ( 

-

        'rest_framework.renderers.JSONRenderer', 

-

        'rest_framework.renderers.YAMLRenderer', 

-

    ) 

-

    'DEFAULT_PARSER_CLASSES': ( 

-

        'rest_framework.parsers.JSONParser', 

-

        'rest_framework.parsers.YAMLParser', 

-

    ) 

-

} 

-

 

-

This module provides the `api_setting` object, that is used to access 

-

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 rest_framework import ISO_8601 

-

from rest_framework.compat import six 

-

 

-

 

-

USER_SETTINGS = getattr(settings, 'REST_FRAMEWORK', None) 

-

 

-

DEFAULTS = { 

-

    # Base API policies 

-

    'DEFAULT_RENDERER_CLASSES': ( 

-

        'rest_framework.renderers.JSONRenderer', 

-

        'rest_framework.renderers.BrowsableAPIRenderer', 

-

    ), 

-

    'DEFAULT_PARSER_CLASSES': ( 

-

        'rest_framework.parsers.JSONParser', 

-

        'rest_framework.parsers.FormParser', 

-

        'rest_framework.parsers.MultiPartParser' 

-

    ), 

-

    'DEFAULT_AUTHENTICATION_CLASSES': ( 

-

        'rest_framework.authentication.SessionAuthentication', 

-

        'rest_framework.authentication.BasicAuthentication' 

-

    ), 

-

    'DEFAULT_PERMISSION_CLASSES': ( 

-

        'rest_framework.permissions.AllowAny', 

-

    ), 

-

    '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_FILTER_BACKENDS': (), 

-

 

-

    # Throttling 

-

    'DEFAULT_THROTTLE_RATES': { 

-

        'user': None, 

-

        'anon': None, 

-

    }, 

-

 

-

    # Pagination 

-

    'PAGINATE_BY': None, 

-

    'PAGINATE_BY_PARAM': None, 

-

 

-

    # Authentication 

-

    'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser', 

-

    'UNAUTHENTICATED_TOKEN': None, 

-

 

-

    # Browser enhancements 

-

    'FORM_METHOD_OVERRIDE': '_method', 

-

    'FORM_CONTENT_OVERRIDE': '_content', 

-

    'FORM_CONTENTTYPE_OVERRIDE': '_content_type', 

-

    'URL_ACCEPT_OVERRIDE': 'accept', 

-

    'URL_FORMAT_OVERRIDE': 'format', 

-

 

-

    'FORMAT_SUFFIX_KWARG': 'format', 

-

 

-

    # Input and output formats 

-

    'DATE_INPUT_FORMATS': ( 

-

        ISO_8601, 

-

    ), 

-

    'DATE_FORMAT': None, 

-

 

-

    'DATETIME_INPUT_FORMATS': ( 

-

        ISO_8601, 

-

    ), 

-

    'DATETIME_FORMAT': None, 

-

 

-

    'TIME_INPUT_FORMATS': ( 

-

        ISO_8601, 

-

    ), 

-

    'TIME_FORMAT': None, 

-

 

-

    # Pending deprecation 

-

    'FILTER_BACKEND': None, 

-

} 

-

 

-

 

-

# List of settings that may be in string import notation. 

-

IMPORT_STRINGS = ( 

-

    'DEFAULT_RENDERER_CLASSES', 

-

    'DEFAULT_PARSER_CLASSES', 

-

    'DEFAULT_AUTHENTICATION_CLASSES', 

-

    'DEFAULT_PERMISSION_CLASSES', 

-

    'DEFAULT_THROTTLE_CLASSES', 

-

    'DEFAULT_CONTENT_NEGOTIATION_CLASS', 

-

    'DEFAULT_MODEL_SERIALIZER_CLASS', 

-

    'DEFAULT_PAGINATION_SERIALIZER_CLASS', 

-

    'DEFAULT_FILTER_BACKENDS', 

-

    'FILTER_BACKEND', 

-

    'UNAUTHENTICATED_USER', 

-

    'UNAUTHENTICATED_TOKEN', 

-

) 

-

 

-

 

-

def perform_import(val, setting_name): 

-

    """ 

-

    If the given setting is a string import notation, 

-

    then perform the necessary import or imports. 

-

    """ 

-

    if isinstance(val, six.string_types): 

-

        return import_from_string(val, setting_name) 

-

    elif isinstance(val, (list, tuple)): 

-

        return [import_from_string(item, setting_name) for item in val] 

-

    return val 

-

 

-

 

-

def import_from_string(val, setting_name): 

-

    """ 

-

    Attempt to import a class from a string representation. 

-

    """ 

-

    try: 

-

        # Nod to tastypie's use of importlib. 

-

        parts = val.split('.') 

-

        module_path, class_name = '.'.join(parts[:-1]), parts[-1] 

-

        module = importlib.import_module(module_path) 

-

        return getattr(module, class_name) 

-

    except ImportError as e: 

-

        msg = "Could not import '%s' for API setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e) 

-

        raise ImportError(msg) 

-

 

-

 

-

class APISettings(object): 

-

    """ 

-

    A settings object, that allows API settings to be accessed as properties. 

-

    For example: 

-

 

-

        from rest_framework.settings import api_settings 

-

        print api_settings.DEFAULT_RENDERER_CLASSES 

-

 

-

    Any setting with string import paths will be automatically resolved 

-

    and return the class, rather than the string literal. 

-

    """ 

-

    def __init__(self, user_settings=None, defaults=None, import_strings=None): 

-

        self.user_settings = user_settings or {} 

-

        self.defaults = defaults or {} 

-

        self.import_strings = import_strings or () 

-

 

-

    def __getattr__(self, attr): 

-

        if attr not in self.defaults.keys(): 

-

            raise AttributeError("Invalid API setting: '%s'" % attr) 

-

 

-

        try: 

-

            # Check if present in user settings 

-

            val = self.user_settings[attr] 

-

        except KeyError: 

-

            # Fall back to defaults 

-

            val = self.defaults[attr] 

-

 

-

        # Coerce import strings into classes 

-

        if val and attr in self.import_strings: 

-

            val = perform_import(val, attr) 

-

 

-

        self.validate_setting(attr, val) 

-

 

-

        # Cache the result 

-

        setattr(self, attr, val) 

-

        return val 

-

 

-

    def validate_setting(self, attr, val): 

-

        if attr == 'FILTER_BACKEND' and val is not None: 

-

            # Make sure we can initialize the class 

-

            val() 

-

 

-

api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_status.html b/htmlcov/rest_framework_status.html deleted file mode 100644 index 85f919f6..00000000 --- a/htmlcov/rest_framework_status.html +++ /dev/null @@ -1,187 +0,0 @@ - - - - - - - - Coverage for rest_framework/status: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

- -
-

""" 

-

Descriptive HTTP status codes, for code readability. 

-

 

-

See RFC 2616 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 

-

And RFC 6585 - http://tools.ietf.org/html/rfc6585 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

HTTP_100_CONTINUE = 100 

-

HTTP_101_SWITCHING_PROTOCOLS = 101 

-

HTTP_200_OK = 200 

-

HTTP_201_CREATED = 201 

-

HTTP_202_ACCEPTED = 202 

-

HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203 

-

HTTP_204_NO_CONTENT = 204 

-

HTTP_205_RESET_CONTENT = 205 

-

HTTP_206_PARTIAL_CONTENT = 206 

-

HTTP_300_MULTIPLE_CHOICES = 300 

-

HTTP_301_MOVED_PERMANENTLY = 301 

-

HTTP_302_FOUND = 302 

-

HTTP_303_SEE_OTHER = 303 

-

HTTP_304_NOT_MODIFIED = 304 

-

HTTP_305_USE_PROXY = 305 

-

HTTP_306_RESERVED = 306 

-

HTTP_307_TEMPORARY_REDIRECT = 307 

-

HTTP_400_BAD_REQUEST = 400 

-

HTTP_401_UNAUTHORIZED = 401 

-

HTTP_402_PAYMENT_REQUIRED = 402 

-

HTTP_403_FORBIDDEN = 403 

-

HTTP_404_NOT_FOUND = 404 

-

HTTP_405_METHOD_NOT_ALLOWED = 405 

-

HTTP_406_NOT_ACCEPTABLE = 406 

-

HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407 

-

HTTP_408_REQUEST_TIMEOUT = 408 

-

HTTP_409_CONFLICT = 409 

-

HTTP_410_GONE = 410 

-

HTTP_411_LENGTH_REQUIRED = 411 

-

HTTP_412_PRECONDITION_FAILED = 412 

-

HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413 

-

HTTP_414_REQUEST_URI_TOO_LONG = 414 

-

HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415 

-

HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416 

-

HTTP_417_EXPECTATION_FAILED = 417 

-

HTTP_428_PRECONDITION_REQUIRED = 428 

-

HTTP_429_TOO_MANY_REQUESTS = 429 

-

HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431 

-

HTTP_500_INTERNAL_SERVER_ERROR = 500 

-

HTTP_501_NOT_IMPLEMENTED = 501 

-

HTTP_502_BAD_GATEWAY = 502 

-

HTTP_503_SERVICE_UNAVAILABLE = 503 

-

HTTP_504_GATEWAY_TIMEOUT = 504 

-

HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505 

-

HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_throttling.html b/htmlcov/rest_framework_throttling.html deleted file mode 100644 index 778b0293..00000000 --- a/htmlcov/rest_framework_throttling.html +++ /dev/null @@ -1,533 +0,0 @@ - - - - - - - - Coverage for rest_framework/throttling: 81% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

- -
-

""" 

-

Provides various throttling policies. 

-

""" 

-

from __future__ import unicode_literals 

-

from django.core.cache import cache 

-

from django.core.exceptions import ImproperlyConfigured 

-

from rest_framework.settings import api_settings 

-

import time 

-

 

-

 

-

class BaseThrottle(object): 

-

    """ 

-

    Rate throttling of requests. 

-

    """ 

-

    def allow_request(self, request, view): 

-

        """ 

-

        Return `True` if the request should be allowed, `False` otherwise. 

-

        """ 

-

        raise NotImplementedError('.allow_request() must be overridden') 

-

 

-

    def wait(self): 

-

        """ 

-

        Optionally, return a recommended number of seconds to wait before 

-

        the next request. 

-

        """ 

-

        return None 

-

 

-

 

-

class SimpleRateThrottle(BaseThrottle): 

-

    """ 

-

    A simple cache implementation, that only requires `.get_cache_key()` 

-

    to be overridden. 

-

 

-

    The rate (requests / seconds) is set by a `throttle` attribute on the View 

-

    class.  The attribute is a string of the form 'number_of_requests/period'. 

-

 

-

    Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day') 

-

 

-

    Previous request information used for throttling is stored in the cache. 

-

    """ 

-

 

-

    timer = time.time 

-

    cache_format = 'throtte_%(scope)s_%(ident)s' 

-

    scope = None 

-

    THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES 

-

 

-

    def __init__(self): 

-

        if not getattr(self, 'rate', None): 

-

            self.rate = self.get_rate() 

-

        self.num_requests, self.duration = self.parse_rate(self.rate) 

-

 

-

    def get_cache_key(self, request, view): 

-

        """ 

-

        Should return a unique cache-key which can be used for throttling. 

-

        Must be overridden. 

-

 

-

        May return `None` if the request should not be throttled. 

-

        """ 

-

        raise NotImplementedError('.get_cache_key() must be overridden') 

-

 

-

    def get_rate(self): 

-

        """ 

-

        Determine the string representation of the allowed request rate. 

-

        """ 

-

        if not getattr(self, 'scope', None): 

-

            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" % 

-

                   self.__class__.__name__) 

-

            raise ImproperlyConfigured(msg) 

-

 

-

        try: 

-

            return self.THROTTLE_RATES[self.scope] 

-

        except KeyError: 

-

            msg = "No default throttle rate set for '%s' scope" % self.scope 

-

            raise ImproperlyConfigured(msg) 

-

 

-

    def parse_rate(self, rate): 

-

        """ 

-

        Given the request rate string, return a two tuple of: 

-

        <allowed number of requests>, <period of time in seconds> 

-

        """ 

-

        if rate is None: 

-

            return (None, None) 

-

        num, period = rate.split('/') 

-

        num_requests = int(num) 

-

        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]] 

-

        return (num_requests, duration) 

-

 

-

    def allow_request(self, request, view): 

-

        """ 

-

        Implement the check to see if the request should be throttled. 

-

 

-

        On success calls `throttle_success`. 

-

        On failure calls `throttle_failure`. 

-

        """ 

-

        if self.rate is None: 

-

            return True 

-

 

-

        self.key = self.get_cache_key(request, view) 

-

        self.history = cache.get(self.key, []) 

-

        self.now = self.timer() 

-

 

-

        # Drop any requests from the history which have now passed the 

-

        # throttle duration 

-

        while self.history and self.history[-1] <= self.now - self.duration: 

-

            self.history.pop() 

-

        if len(self.history) >= self.num_requests: 

-

            return self.throttle_failure() 

-

        return self.throttle_success() 

-

 

-

    def throttle_success(self): 

-

        """ 

-

        Inserts the current request's timestamp along with the key 

-

        into the cache. 

-

        """ 

-

        self.history.insert(0, self.now) 

-

        cache.set(self.key, self.history, self.duration) 

-

        return True 

-

 

-

    def throttle_failure(self): 

-

        """ 

-

        Called when a request to the API has failed due to throttling. 

-

        """ 

-

        return False 

-

 

-

    def wait(self): 

-

        """ 

-

        Returns the recommended next request time in seconds. 

-

        """ 

-

        if self.history: 

-

            remaining_duration = self.duration - (self.now - self.history[-1]) 

-

        else: 

-

            remaining_duration = self.duration 

-

 

-

        available_requests = self.num_requests - len(self.history) + 1 

-

 

-

        return remaining_duration / float(available_requests) 

-

 

-

 

-

class AnonRateThrottle(SimpleRateThrottle): 

-

    """ 

-

    Limits the rate of API calls that may be made by a anonymous users. 

-

 

-

    The IP address of the request will be used as the unique cache key. 

-

    """ 

-

    scope = 'anon' 

-

 

-

    def get_cache_key(self, request, view): 

-

        if request.user.is_authenticated(): 

-

            return None  # Only throttle unauthenticated requests. 

-

 

-

        ident = request.META.get('REMOTE_ADDR', None) 

-

 

-

        return self.cache_format % { 

-

            'scope': self.scope, 

-

            'ident': ident 

-

        } 

-

 

-

 

-

class UserRateThrottle(SimpleRateThrottle): 

-

    """ 

-

    Limits the rate of API calls that may be made by a given user. 

-

 

-

    The user id will be used as a unique cache key if the user is 

-

    authenticated.  For anonymous requests, the IP address of the request will 

-

    be used. 

-

    """ 

-

    scope = 'user' 

-

 

-

    def get_cache_key(self, request, view): 

-

        if request.user.is_authenticated(): 

-

            ident = request.user.id 

-

        else: 

-

            ident = request.META.get('REMOTE_ADDR', None) 

-

 

-

        return self.cache_format % { 

-

            'scope': self.scope, 

-

            'ident': ident 

-

        } 

-

 

-

 

-

class ScopedRateThrottle(SimpleRateThrottle): 

-

    """ 

-

    Limits the rate of API calls by different amounts for various parts of 

-

    the API.  Any view that has the `throttle_scope` property set will be 

-

    throttled.  The unique cache key will be generated by concatenating the 

-

    user id of the request, and the scope of the view being accessed. 

-

    """ 

-

    scope_attr = 'throttle_scope' 

-

 

-

    def __init__(self): 

-

        # Override the usual SimpleRateThrottle, because we can't determine 

-

        # the rate until called by the view. 

-

        pass 

-

 

-

    def allow_request(self, request, view): 

-

        # We can only determine the scope once we're called by the view. 

-

        self.scope = getattr(view, self.scope_attr, None) 

-

 

-

        # If a view does not have a `throttle_scope` always allow the request 

-

        if not self.scope: 

-

            return True 

-

 

-

        # Determine the allowed request rate as we normally would during 

-

        # the `__init__` call. 

-

        self.rate = self.get_rate() 

-

        self.num_requests, self.duration = self.parse_rate(self.rate) 

-

 

-

        # We can now proceed as normal. 

-

        return super(ScopedRateThrottle, self).allow_request(request, view) 

-

 

-

    def get_cache_key(self, request, view): 

-

        """ 

-

        If `view.throttle_scope` is not set, don't apply this throttle. 

-

 

-

        Otherwise generate the unique cache key by concatenating the user id 

-

        with the '.throttle_scope` property of the view. 

-

        """ 

-

        if request.user.is_authenticated(): 

-

            ident = request.user.id 

-

        else: 

-

            ident = request.META.get('REMOTE_ADDR', None) 

-

 

-

        return self.cache_format % { 

-

            'scope': self.scope, 

-

            'ident': ident 

-

        } 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_urlpatterns.html b/htmlcov/rest_framework_urlpatterns.html deleted file mode 100644 index 4c824a77..00000000 --- a/htmlcov/rest_framework_urlpatterns.html +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - - - Coverage for rest_framework/urlpatterns: 87% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

- -
-

from __future__ import unicode_literals 

-

from django.core.urlresolvers import RegexURLResolver 

-

from rest_framework.compat import url, include 

-

from rest_framework.settings import api_settings 

-

 

-

 

-

def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required): 

-

    ret = [] 

-

    for urlpattern in urlpatterns: 

-

        if isinstance(urlpattern, RegexURLResolver): 

-

            # Set of included URL patterns 

-

            regex = urlpattern.regex.pattern 

-

            namespace = urlpattern.namespace 

-

            app_name = urlpattern.app_name 

-

            kwargs = urlpattern.default_kwargs 

-

            # Add in the included patterns, after applying the suffixes 

-

            patterns = apply_suffix_patterns(urlpattern.url_patterns, 

-

                                             suffix_pattern, 

-

                                             suffix_required) 

-

            ret.append(url(regex, include(patterns, namespace, app_name), kwargs)) 

-

 

-

        else: 

-

            # Regular URL pattern 

-

            regex = urlpattern.regex.pattern.rstrip('$') + suffix_pattern 

-

            view = urlpattern._callback or urlpattern._callback_str 

-

            kwargs = urlpattern.default_args 

-

            name = urlpattern.name 

-

            # Add in both the existing and the new urlpattern 

-

            if not suffix_required: 

-

                ret.append(urlpattern) 

-

            ret.append(url(regex, view, kwargs, name)) 

-

 

-

    return ret 

-

 

-

 

-

def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None): 

-

    """ 

-

    Supplement existing urlpatterns with corresponding patterns that also 

-

    include a '.format' suffix.  Retains urlpattern ordering. 

-

 

-

    urlpatterns: 

-

        A list of URL patterns. 

-

 

-

    suffix_required: 

-

        If `True`, only suffixed URLs will be generated, and non-suffixed 

-

        URLs will not be used.  Defaults to `False`. 

-

 

-

    allowed: 

-

        An optional tuple/list of allowed suffixes.  eg ['json', 'api'] 

-

        Defaults to `None`, which allows any suffix. 

-

    """ 

-

    suffix_kwarg = api_settings.FORMAT_SUFFIX_KWARG 

-

    if allowed: 

-

        if len(allowed) == 1: 

-

            allowed_pattern = allowed[0] 

-

        else: 

-

            allowed_pattern = '(%s)' % '|'.join(allowed) 

-

        suffix_pattern = r'\.(?P<%s>%s)$' % (suffix_kwarg, allowed_pattern) 

-

    else: 

-

        suffix_pattern = r'\.(?P<%s>[a-z]+)$' % suffix_kwarg 

-

 

-

    return apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_urls.html b/htmlcov/rest_framework_urls.html deleted file mode 100644 index 7720a6d4..00000000 --- a/htmlcov/rest_framework_urls.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - Coverage for rest_framework/urls: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

- -
-

""" 

-

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`. 

-

 

-

    urlpatterns = patterns('', 

-

        ... 

-

        url(r'^auth', include('rest_framework.urls', namespace='rest_framework')) 

-

    ) 

-

""" 

-

from __future__ import unicode_literals 

-

from rest_framework.compat import patterns, url 

-

 

-

 

-

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'), 

-

) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_utils___init__.html b/htmlcov/rest_framework_utils___init__.html deleted file mode 100644 index 99eb18c4..00000000 --- a/htmlcov/rest_framework_utils___init__.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - Coverage for rest_framework/utils/__init__: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
- - - -
-
- - - - - diff --git a/htmlcov/rest_framework_utils_breadcrumbs.html b/htmlcov/rest_framework_utils_breadcrumbs.html deleted file mode 100644 index 14fb8955..00000000 --- a/htmlcov/rest_framework_utils_breadcrumbs.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - Coverage for rest_framework/utils/breadcrumbs: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

- -
-

from __future__ import unicode_literals 

-

from django.core.urlresolvers import resolve, get_script_prefix 

-

from rest_framework.utils.formatting import get_view_name 

-

 

-

 

-

def get_breadcrumbs(url): 

-

    """ 

-

    Given a url returns a list of breadcrumbs, which are each a 

-

    tuple of (name, url). 

-

    """ 

-

 

-

    from rest_framework.views import APIView 

-

 

-

    def breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen): 

-

        """ 

-

        Add tuples of (name, url) to the breadcrumbs list, 

-

        progressively chomping off parts of the url. 

-

        """ 

-

 

-

        try: 

-

            (view, unused_args, unused_kwargs) = resolve(url) 

-

        except Exception: 

-

            pass 

-

        else: 

-

            # Check if this is a REST framework view, 

-

            # and if so add it to the breadcrumbs 

-

            cls = getattr(view, 'cls', None) 

-

            if cls is not None and issubclass(cls, APIView): 

-

                # Don't list the same view twice in a row. 

-

                # Probably an optional trailing slash. 

-

                if not seen or seen[-1] != view: 

-

                    suffix = getattr(view, 'suffix', None) 

-

                    name = get_view_name(view.cls, suffix) 

-

                    breadcrumbs_list.insert(0, (name, prefix + url)) 

-

                    seen.append(view) 

-

 

-

        if url == '': 

-

            # All done 

-

            return breadcrumbs_list 

-

 

-

        elif url.endswith('/'): 

-

            # Drop trailing slash off the end and continue to try to 

-

            # resolve more breadcrumbs 

-

            url = url.rstrip('/') 

-

            return breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen) 

-

 

-

        # Drop trailing non-slash off the end and continue to try to 

-

        # resolve more breadcrumbs 

-

        url = url[:url.rfind('/') + 1] 

-

        return breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen) 

-

 

-

    prefix = get_script_prefix().rstrip('/') 

-

    url = url[len(prefix):] 

-

    return breadcrumbs_recursive(url, [], prefix, []) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_utils_encoders.html b/htmlcov/rest_framework_utils_encoders.html deleted file mode 100644 index 9f0ca343..00000000 --- a/htmlcov/rest_framework_utils_encoders.html +++ /dev/null @@ -1,275 +0,0 @@ - - - - - - - - Coverage for rest_framework/utils/encoders: 73% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

- -
-

""" 

-

Helper classes for parsers. 

-

""" 

-

from __future__ import unicode_literals 

-

from django.utils.datastructures import SortedDict 

-

from django.utils.functional import Promise 

-

from rest_framework.compat import timezone, force_text 

-

from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata 

-

import datetime 

-

import decimal 

-

import types 

-

import json 

-

 

-

 

-

class JSONEncoder(json.JSONEncoder): 

-

    """ 

-

    JSONEncoder subclass that knows how to encode date/time/timedelta, 

-

    decimal types, and generators. 

-

    """ 

-

    def default(self, o): 

-

        # For Date Time string spec, see ECMA 262 

-

        # http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 

-

        if isinstance(o, Promise): 

-

            return force_text(o) 

-

        elif isinstance(o, datetime.datetime): 

-

            r = o.isoformat() 

-

            if o.microsecond: 

-

                r = r[:23] + r[26:] 

-

            if r.endswith('+00:00'): 

-

                r = r[:-6] + 'Z' 

-

            return r 

-

        elif isinstance(o, datetime.date): 

-

            return o.isoformat() 

-

        elif isinstance(o, datetime.time): 

-

            if timezone and timezone.is_aware(o): 

-

                raise ValueError("JSON can't represent timezone-aware times.") 

-

            r = o.isoformat() 

-

            if o.microsecond: 

-

                r = r[:12] 

-

            return r 

-

        elif isinstance(o, datetime.timedelta): 

-

            return str(o.total_seconds()) 

-

        elif isinstance(o, decimal.Decimal): 

-

            return str(o) 

-

        elif hasattr(o, '__iter__'): 

-

            return [i for i in o] 

-

        return super(JSONEncoder, self).default(o) 

-

 

-

 

-

try: 

-

    import yaml 

-

except ImportError: 

-

    SafeDumper = None 

-

else: 

-

    # Adapted from http://pyyaml.org/attachment/ticket/161/use_ordered_dict.py 

-

    class SafeDumper(yaml.SafeDumper): 

-

        """ 

-

        Handles decimals as strings. 

-

        Handles SortedDicts as usual dicts, but preserves field order, rather 

-

        than the usual behaviour of sorting the keys. 

-

        """ 

-

        def represent_decimal(self, data): 

-

            return self.represent_scalar('tag:yaml.org,2002:str', str(data)) 

-

 

-

        def represent_mapping(self, tag, mapping, flow_style=None): 

-

            value = [] 

-

            node = yaml.MappingNode(tag, value, flow_style=flow_style) 

-

            if self.alias_key is not None: 

-

                self.represented_objects[self.alias_key] = node 

-

            best_style = True 

-

            if hasattr(mapping, 'items'): 

-

                mapping = list(mapping.items()) 

-

                if not isinstance(mapping, SortedDict): 

-

                    mapping.sort() 

-

            for item_key, item_value in mapping: 

-

                node_key = self.represent_data(item_key) 

-

                node_value = self.represent_data(item_value) 

-

                if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style): 

-

                    best_style = False 

-

                if not (isinstance(node_value, yaml.ScalarNode) and not node_value.style): 

-

                    best_style = False 

-

                value.append((node_key, node_value)) 

-

            if flow_style is None: 

-

                if self.default_flow_style is not None: 

-

                    node.flow_style = self.default_flow_style 

-

                else: 

-

                    node.flow_style = best_style 

-

            return node 

-

 

-

    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/htmlcov/rest_framework_utils_formatting.html b/htmlcov/rest_framework_utils_formatting.html deleted file mode 100644 index 54e1570f..00000000 --- a/htmlcov/rest_framework_utils_formatting.html +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - - - Coverage for rest_framework/utils/formatting: 97% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

- -
-

""" 

-

Utility functions to return a formatted name and description for a given view. 

-

""" 

-

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 

-

import re 

-

 

-

 

-

def _remove_trailing_string(content, trailing): 

-

    """ 

-

    Strip trailing component `trailing` from `content` if it exists. 

-

    Used when generating names from view classes. 

-

    """ 

-

    if content.endswith(trailing) and content != trailing: 

-

        return content[:-len(trailing)] 

-

    return content 

-

 

-

 

-

def _remove_leading_indent(content): 

-

    """ 

-

    Remove leading indent from a block of text. 

-

    Used when generating descriptions from docstrings. 

-

    """ 

-

    whitespace_counts = [len(line) - len(line.lstrip(' ')) 

-

                         for line in content.splitlines()[1:] if line.lstrip()] 

-

 

-

    # unindent the content if needed 

-

    if whitespace_counts: 

-

        whitespace_pattern = '^' + (' ' * min(whitespace_counts)) 

-

        content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content) 

-

    content = content.strip('\n') 

-

    return content 

-

 

-

 

-

def _camelcase_to_spaces(content): 

-

    """ 

-

    Translate 'CamelCaseNames' to 'Camel Case Names'. 

-

    Used when generating names from view classes. 

-

    """ 

-

    camelcase_boundry = '(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))' 

-

    content = re.sub(camelcase_boundry, ' \\1', content).strip() 

-

    return ' '.join(content.split('_')).title() 

-

 

-

 

-

def get_view_name(cls, suffix=None): 

-

    """ 

-

    Return a formatted name for an `APIView` class or `@api_view` function. 

-

    """ 

-

    name = cls.__name__ 

-

    name = _remove_trailing_string(name, 'View') 

-

    name = _remove_trailing_string(name, 'ViewSet') 

-

    name = _camelcase_to_spaces(name) 

-

    if suffix: 

-

        name += ' ' + suffix 

-

    return name 

-

 

-

 

-

def get_view_description(cls, html=False): 

-

    """ 

-

    Return a description for an `APIView` class or `@api_view` function. 

-

    """ 

-

    description = cls.__doc__ or '' 

-

    description = _remove_leading_indent(description) 

-

    if html: 

-

        return markup_description(description) 

-

    return description 

-

 

-

 

-

def markup_description(description): 

-

    """ 

-

    Apply HTML markup to the given description. 

-

    """ 

-

    if apply_markdown: 

-

        description = apply_markdown(description) 

-

    else: 

-

        description = escape(description).replace('\n', '<br />') 

-

    return mark_safe(description) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_utils_mediatypes.html b/htmlcov/rest_framework_utils_mediatypes.html deleted file mode 100644 index 2ce44ab5..00000000 --- a/htmlcov/rest_framework_utils_mediatypes.html +++ /dev/null @@ -1,257 +0,0 @@ - - - - - - - - Coverage for rest_framework/utils/mediatypes: 77% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

- -
-

""" 

-

Handling of media types, as found in HTTP Content-Type and Accept headers. 

-

 

-

See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 

-

""" 

-

from __future__ import unicode_literals 

-

from django.http.multipartparser import parse_header 

-

from rest_framework import HTTP_HEADER_ENCODING 

-

 

-

 

-

def media_type_matches(lhs, rhs): 

-

    """ 

-

    Returns ``True`` if the media type in the first argument <= the 

-

    media type in the second argument.  The media types are strings 

-

    as described by the HTTP spec. 

-

 

-

    Valid media type strings include: 

-

 

-

    'application/json; indent=4' 

-

    'application/json' 

-

    'text/*' 

-

    '*/*' 

-

    """ 

-

    lhs = _MediaType(lhs) 

-

    rhs = _MediaType(rhs) 

-

    return lhs.match(rhs) 

-

 

-

 

-

def order_by_precedence(media_type_lst): 

-

    """ 

-

    Returns a list of sets of media type strings, ordered by precedence. 

-

    Precedence is determined by how specific a media type is: 

-

 

-

    3. 'type/subtype; param=val' 

-

    2. 'type/subtype' 

-

    1. 'type/*' 

-

    0. '*/*' 

-

    """ 

-

    ret = [set(), set(), set(), set()] 

-

    for media_type in media_type_lst: 

-

        precedence = _MediaType(media_type).precedence 

-

        ret[3 - precedence].add(media_type) 

-

    return [media_types for media_types in ret if media_types] 

-

 

-

 

-

class _MediaType(object): 

-

    def __init__(self, media_type_str): 

-

        if media_type_str is None: 

-

            media_type_str = '' 

-

        self.orig = media_type_str 

-

        self.full_type, self.params = parse_header(media_type_str.encode(HTTP_HEADER_ENCODING)) 

-

        self.main_type, sep, self.sub_type = self.full_type.partition('/') 

-

 

-

    def match(self, other): 

-

        """Return true if this MediaType satisfies the given MediaType.""" 

-

        for key in self.params.keys(): 

-

            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: 

-

            return False 

-

 

-

        if self.main_type != '*' and other.main_type != '*' and other.main_type != self.main_type: 

-

            return False 

-

 

-

        return True 

-

 

-

    @property 

-

    def precedence(self): 

-

        """ 

-

        Return a precedence level from 0-3 for the media type given how specific it is. 

-

        """ 

-

        if self.main_type == '*': 

-

            return 0 

-

        elif self.sub_type == '*': 

-

            return 1 

-

        elif not self.params or self.params.keys() == ['q']: 

-

            return 2 

-

        return 3 

-

 

-

    def __str__(self): 

-

        return unicode(self).encode('utf-8') 

-

 

-

    def __unicode__(self): 

-

        ret = "%s/%s" % (self.main_type, self.sub_type) 

-

        for key, val in self.params.items(): 

-

            ret += "; %s=%s" % (key, val) 

-

        return ret 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_views.html b/htmlcov/rest_framework_views.html deleted file mode 100644 index f836e71f..00000000 --- a/htmlcov/rest_framework_views.html +++ /dev/null @@ -1,793 +0,0 @@ - - - - - - - - Coverage for rest_framework/views: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

-

296

-

297

-

298

-

299

-

300

-

301

-

302

-

303

-

304

-

305

-

306

-

307

-

308

-

309

-

310

-

311

-

312

-

313

-

314

-

315

-

316

-

317

-

318

-

319

-

320

-

321

-

322

-

323

-

324

-

325

-

326

-

327

-

328

-

329

-

330

-

331

-

332

-

333

-

334

-

335

-

336

-

337

-

338

-

339

-

340

-

341

-

342

-

343

-

344

-

345

-

346

-

347

-

348

-

349

-

350

-

351

-

352

-

353

-

354

-

355

-

356

- -
-

""" 

-

Provides an APIView class that is the base of all views in REST framework. 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

from django.core.exceptions import PermissionDenied 

-

from django.http import Http404, HttpResponse 

-

from django.utils.datastructures import SortedDict 

-

from django.views.decorators.csrf import csrf_exempt 

-

from rest_framework import status, exceptions 

-

from rest_framework.compat import View 

-

from rest_framework.request import Request 

-

from rest_framework.response import Response 

-

from rest_framework.settings import api_settings 

-

from rest_framework.utils.formatting import get_view_name, get_view_description 

-

 

-

 

-

class APIView(View): 

-

    settings = api_settings 

-

 

-

    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES 

-

    parser_classes = api_settings.DEFAULT_PARSER_CLASSES 

-

    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES 

-

    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES 

-

    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES 

-

    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS 

-

 

-

    @classmethod 

-

    def as_view(cls, **initkwargs): 

-

        """ 

-

        Store the original class on the view function. 

-

 

-

        This allows us to discover information about the view when we do URL 

-

        reverse lookups.  Used for breadcrumb generation. 

-

        """ 

-

        view = super(APIView, cls).as_view(**initkwargs) 

-

        view.cls = cls 

-

        return view 

-

 

-

    @property 

-

    def allowed_methods(self): 

-

        """ 

-

        Wrap Django's private `_allowed_methods` interface in a public property. 

-

        """ 

-

        return self._allowed_methods() 

-

 

-

    @property 

-

    def default_response_headers(self): 

-

        # TODO: deprecate? 

-

        # TODO: Only vary by accept if multiple renderers 

-

        return { 

-

            'Allow': ', '.join(self.allowed_methods), 

-

            'Vary': 'Accept' 

-

        } 

-

 

-

    def http_method_not_allowed(self, request, *args, **kwargs): 

-

        """ 

-

        If `request.method` does not correspond to a handler method, 

-

        determine what kind of exception to raise. 

-

        """ 

-

        raise exceptions.MethodNotAllowed(request.method) 

-

 

-

    def permission_denied(self, request): 

-

        """ 

-

        If request is not permitted, determine what kind of exception to raise. 

-

        """ 

-

        if not self.request.successful_authenticator: 

-

            raise exceptions.NotAuthenticated() 

-

        raise exceptions.PermissionDenied() 

-

 

-

    def throttled(self, request, wait): 

-

        """ 

-

        If request is throttled, determine what kind of exception to raise. 

-

        """ 

-

        raise exceptions.Throttled(wait) 

-

 

-

    def get_authenticate_header(self, request): 

-

        """ 

-

        If a request is unauthenticated, determine the WWW-Authenticate 

-

        header to use for 401 responses, if any. 

-

        """ 

-

        authenticators = self.get_authenticators() 

-

        if authenticators: 

-

            return authenticators[0].authenticate_header(request) 

-

 

-

    def get_parser_context(self, http_request): 

-

        """ 

-

        Returns a dict that is passed through to Parser.parse(), 

-

        as the `parser_context` keyword argument. 

-

        """ 

-

        # Note: Additionally `request` will also be added to the context 

-

        #       by the Request object. 

-

        return { 

-

            'view': self, 

-

            'args': getattr(self, 'args', ()), 

-

            'kwargs': getattr(self, 'kwargs', {}) 

-

        } 

-

 

-

    def get_renderer_context(self): 

-

        """ 

-

        Returns a dict that is passed through to Renderer.render(), 

-

        as the `renderer_context` keyword argument. 

-

        """ 

-

        # Note: Additionally 'response' will also be added to the context, 

-

        #       by the Response object. 

-

        return { 

-

            'view': self, 

-

            'args': getattr(self, 'args', ()), 

-

            'kwargs': getattr(self, 'kwargs', {}), 

-

            'request': getattr(self, 'request', None) 

-

        } 

-

 

-

    # API policy instantiation methods 

-

 

-

    def get_format_suffix(self, **kwargs): 

-

        """ 

-

        Determine if the request includes a '.json' style format suffix 

-

        """ 

-

        if self.settings.FORMAT_SUFFIX_KWARG: 

-

            return kwargs.get(self.settings.FORMAT_SUFFIX_KWARG) 

-

 

-

    def get_renderers(self): 

-

        """ 

-

        Instantiates and returns the list of renderers that this view can use. 

-

        """ 

-

        return [renderer() for renderer in self.renderer_classes] 

-

 

-

    def get_parsers(self): 

-

        """ 

-

        Instantiates and returns the list of parsers that this view can use. 

-

        """ 

-

        return [parser() for parser in self.parser_classes] 

-

 

-

    def get_authenticators(self): 

-

        """ 

-

        Instantiates and returns the list of authenticators that this view can use. 

-

        """ 

-

        return [auth() for auth in self.authentication_classes] 

-

 

-

    def get_permissions(self): 

-

        """ 

-

        Instantiates and returns the list of permissions that this view requires. 

-

        """ 

-

        return [permission() for permission in self.permission_classes] 

-

 

-

    def get_throttles(self): 

-

        """ 

-

        Instantiates and returns the list of throttles that this view uses. 

-

        """ 

-

        return [throttle() for throttle in self.throttle_classes] 

-

 

-

    def get_content_negotiator(self): 

-

        """ 

-

        Instantiate and return the content negotiation class to use. 

-

        """ 

-

        if not getattr(self, '_negotiator', None): 

-

            self._negotiator = self.content_negotiation_class() 

-

        return self._negotiator 

-

 

-

    # API policy implementation methods 

-

 

-

    def perform_content_negotiation(self, request, force=False): 

-

        """ 

-

        Determine which renderer and media type to use render the response. 

-

        """ 

-

        renderers = self.get_renderers() 

-

        conneg = self.get_content_negotiator() 

-

 

-

        try: 

-

            return conneg.select_renderer(request, renderers, self.format_kwarg) 

-

        except Exception: 

-

            if force: 

-

                return (renderers[0], renderers[0].media_type) 

-

            raise 

-

 

-

    def perform_authentication(self, request): 

-

        """ 

-

        Perform authentication on the incoming request. 

-

 

-

        Note that if you override this and simply 'pass', then authentication 

-

        will instead be performed lazily, the first time either 

-

        `request.user` or `request.auth` is accessed. 

-

        """ 

-

        request.user 

-

 

-

    def check_permissions(self, request): 

-

        """ 

-

        Check if the request should be permitted. 

-

        Raises an appropriate exception if the request is not permitted. 

-

        """ 

-

        for permission in self.get_permissions(): 

-

            if not permission.has_permission(request, self): 

-

                self.permission_denied(request) 

-

 

-

    def check_object_permissions(self, request, obj): 

-

        """ 

-

        Check if the request should be permitted for a given object. 

-

        Raises an appropriate exception if the request is not permitted. 

-

        """ 

-

        for permission in self.get_permissions(): 

-

            if not permission.has_object_permission(request, self, obj): 

-

                self.permission_denied(request) 

-

 

-

    def check_throttles(self, request): 

-

        """ 

-

        Check if request should be throttled. 

-

        Raises an appropriate exception if the request is throttled. 

-

        """ 

-

        for throttle in self.get_throttles(): 

-

            if not throttle.allow_request(request, self): 

-

                self.throttled(request, throttle.wait()) 

-

 

-

    # Dispatch methods 

-

 

-

    def initialize_request(self, request, *args, **kargs): 

-

        """ 

-

        Returns the initial request object. 

-

        """ 

-

        parser_context = self.get_parser_context(request) 

-

 

-

        return Request(request, 

-

                       parsers=self.get_parsers(), 

-

                       authenticators=self.get_authenticators(), 

-

                       negotiator=self.get_content_negotiator(), 

-

                       parser_context=parser_context) 

-

 

-

    def initial(self, request, *args, **kwargs): 

-

        """ 

-

        Runs anything that needs to occur prior to calling the method handler. 

-

        """ 

-

        self.format_kwarg = self.get_format_suffix(**kwargs) 

-

 

-

        # Ensure that the incoming request is permitted 

-

        self.perform_authentication(request) 

-

        self.check_permissions(request) 

-

        self.check_throttles(request) 

-

 

-

        # Perform content negotiation and store the accepted info on the request 

-

        neg = self.perform_content_negotiation(request) 

-

        request.accepted_renderer, request.accepted_media_type = neg 

-

 

-

    def finalize_response(self, request, response, *args, **kwargs): 

-

        """ 

-

        Returns the final response object. 

-

        """ 

-

        # Make the error obvious if a proper response is not returned 

-

        assert isinstance(response, HttpResponse), ( 

-

            'Expected a `Response` to be returned from the view, ' 

-

            'but received a `%s`' % type(response) 

-

        ) 

-

 

-

        if isinstance(response, Response): 

-

            if not getattr(request, 'accepted_renderer', None): 

-

                neg = self.perform_content_negotiation(request, force=True) 

-

                request.accepted_renderer, request.accepted_media_type = neg 

-

 

-

            response.accepted_renderer = request.accepted_renderer 

-

            response.accepted_media_type = request.accepted_media_type 

-

            response.renderer_context = self.get_renderer_context() 

-

 

-

        for key, value in self.headers.items(): 

-

            response[key] = value 

-

 

-

        return response 

-

 

-

    def handle_exception(self, exc): 

-

        """ 

-

        Handle any exception that occurs, by returning an appropriate response, 

-

        or re-raising the error. 

-

        """ 

-

        if isinstance(exc, exceptions.Throttled): 

-

            # Throttle wait header 

-

            self.headers['X-Throttle-Wait-Seconds'] = '%d' % exc.wait 

-

 

-

        if isinstance(exc, (exceptions.NotAuthenticated, 

-

                            exceptions.AuthenticationFailed)): 

-

            # WWW-Authenticate header for 401 responses, else coerce to 403 

-

            auth_header = self.get_authenticate_header(self.request) 

-

 

-

            if auth_header: 

-

                self.headers['WWW-Authenticate'] = auth_header 

-

            else: 

-

                exc.status_code = status.HTTP_403_FORBIDDEN 

-

 

-

        if isinstance(exc, exceptions.APIException): 

-

            return Response({'detail': exc.detail}, 

-

                            status=exc.status_code, 

-

                            exception=True) 

-

        elif isinstance(exc, Http404): 

-

            return Response({'detail': 'Not found'}, 

-

                            status=status.HTTP_404_NOT_FOUND, 

-

                            exception=True) 

-

        elif isinstance(exc, PermissionDenied): 

-

            return Response({'detail': 'Permission denied'}, 

-

                            status=status.HTTP_403_FORBIDDEN, 

-

                            exception=True) 

-

        raise 

-

 

-

    # Note: session based authentication is explicitly CSRF validated, 

-

    # all other authentication is CSRF exempt. 

-

    @csrf_exempt 

-

    def dispatch(self, request, *args, **kwargs): 

-

        """ 

-

        `.dispatch()` is pretty much the same as Django's regular dispatch, 

-

        but with extra hooks for startup, finalize, and exception handling. 

-

        """ 

-

        self.args = args 

-

        self.kwargs = kwargs 

-

        request = self.initialize_request(request, *args, **kwargs) 

-

        self.request = request 

-

        self.headers = self.default_response_headers  # deprecate? 

-

 

-

        try: 

-

            self.initial(request, *args, **kwargs) 

-

 

-

            # Get the appropriate handler method 

-

            if request.method.lower() in self.http_method_names: 

-

                handler = getattr(self, request.method.lower(), 

-

                                  self.http_method_not_allowed) 

-

            else: 

-

                handler = self.http_method_not_allowed 

-

 

-

            response = handler(request, *args, **kwargs) 

-

 

-

        except Exception as exc: 

-

            response = self.handle_exception(exc) 

-

 

-

        self.response = self.finalize_response(request, response, *args, **kwargs) 

-

        return self.response 

-

 

-

    def options(self, request, *args, **kwargs): 

-

        """ 

-

        Handler method for HTTP 'OPTIONS' request. 

-

        We may as well implement this as Django will otherwise provide 

-

        a less useful default implementation. 

-

        """ 

-

        return Response(self.metadata(request), status=status.HTTP_200_OK) 

-

 

-

    def metadata(self, request): 

-

        """ 

-

        Return a dictionary of metadata about the view. 

-

        Used to return responses for OPTIONS requests. 

-

        """ 

-

 

-

        # This is used by ViewSets to disambiguate instance vs list views 

-

        view_name_suffix = getattr(self, 'suffix', None) 

-

 

-

        # By default we can't provide any form-like information, however the 

-

        # generic views override this implementation and add additional 

-

        # information for POST and PUT methods, based on the serializer. 

-

        ret = SortedDict() 

-

        ret['name'] = get_view_name(self.__class__, view_name_suffix) 

-

        ret['description'] = get_view_description(self.__class__) 

-

        ret['renders'] = [renderer.media_type for renderer in self.renderer_classes] 

-

        ret['parses'] = [parser.media_type for parser in self.parser_classes] 

-

        return ret 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_viewsets.html b/htmlcov/rest_framework_viewsets.html deleted file mode 100644 index 8264ddc0..00000000 --- a/htmlcov/rest_framework_viewsets.html +++ /dev/null @@ -1,359 +0,0 @@ - - - - - - - - Coverage for rest_framework/viewsets: 95% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

- -
-

""" 

-

ViewSets are essentially just a type of class based view, that doesn't provide 

-

any method handlers, such as `get()`, `post()`, etc... but instead has actions, 

-

such as `list()`, `retrieve()`, `create()`, etc... 

-

 

-

Actions are only bound to methods at the point of instantiating the views. 

-

 

-

    user_list = UserViewSet.as_view({'get': 'list'}) 

-

    user_detail = UserViewSet.as_view({'get': 'retrieve'}) 

-

 

-

Typically, rather than instantiate views from viewsets directly, you'll 

-

regsiter the viewset with a router and let the URL conf be determined 

-

automatically. 

-

 

-

    router = DefaultRouter() 

-

    router.register(r'users', UserViewSet, 'user') 

-

    urlpatterns = router.urls 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

from functools import update_wrapper 

-

from django.utils.decorators import classonlymethod 

-

from rest_framework import views, generics, mixins 

-

 

-

 

-

class ViewSetMixin(object): 

-

    """ 

-

    This is the magic. 

-

 

-

    Overrides `.as_view()` so that it takes an `actions` keyword that performs 

-

    the binding of HTTP methods to actions on the Resource. 

-

 

-

    For example, to create a concrete view binding the 'GET' and 'POST' methods 

-

    to the 'list' and 'create' actions... 

-

 

-

    view = MyViewSet.as_view({'get': 'list', 'post': 'create'}) 

-

    """ 

-

 

-

    @classonlymethod 

-

    def as_view(cls, actions=None, **initkwargs): 

-

        """ 

-

        Because of the way class based views create a closure around the 

-

        instantiated view, we need to totally reimplement `.as_view`, 

-

        and slightly modify the view function that is created and returned. 

-

        """ 

-

        # The suffix initkwarg is reserved for identifing the viewset type 

-

        # eg. 'List' or 'Instance'. 

-

        cls.suffix = None 

-

 

-

        # 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) 

-

            # We also store the mapping of request methods to actions, 

-

            # so that we can later set the action attribute. 

-

            # eg. `self.action = 'list'` on an incoming GET request. 

-

            self.action_map = actions 

-

 

-

            # Bind methods to actions 

-

            # This is the bit that's different to a standard view 

-

            for method, action in actions.items(): 

-

                handler = getattr(self, action) 

-

                setattr(self, method, handler) 

-

 

-

            # Patch this in as it's otherwise only present from 1.5 onwards 

-

            if hasattr(self, 'get') and not hasattr(self, 'head'): 

-

                self.head = self.get 

-

 

-

            # And continue as usual 

-

            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=()) 

-

 

-

        # We need to set these on the view function, so that breadcrumb 

-

        # generation can pick out these bits of information from a 

-

        # resolved URL. 

-

        view.cls = cls 

-

        view.suffix = initkwargs.get('suffix', None) 

-

        return view 

-

 

-

    def initialize_request(self, request, *args, **kargs): 

-

        """ 

-

        Set the `.action` attribute on the view, 

-

        depending on the request method. 

-

        """ 

-

        request = super(ViewSetMixin, self).initialize_request(request, *args, **kargs) 

-

        self.action = self.action_map.get(request.method.lower()) 

-

        return request 

-

 

-

 

-

class ViewSet(ViewSetMixin, views.APIView): 

-

    """ 

-

    The base ViewSet class does not provide any actions by default. 

-

    """ 

-

    pass 

-

 

-

 

-

class GenericViewSet(ViewSetMixin, generics.GenericAPIView): 

-

    """ 

-

    The GenericViewSet class does not provide any actions by default, 

-

    but does include the base set of generic view behavior, such as 

-

    the `get_object` and `get_queryset` methods. 

-

    """ 

-

    pass 

-

 

-

 

-

class ReadOnlyModelViewSet(mixins.RetrieveModelMixin, 

-

                           mixins.ListModelMixin, 

-

                           GenericViewSet): 

-

    """ 

-

    A viewset that provides default `list()` and `retrieve()` actions. 

-

    """ 

-

    pass 

-

 

-

 

-

class ModelViewSet(mixins.CreateModelMixin, 

-

                    mixins.RetrieveModelMixin, 

-

                    mixins.UpdateModelMixin, 

-

                    mixins.DestroyModelMixin, 

-

                    mixins.ListModelMixin, 

-

                    GenericViewSet): 

-

    """ 

-

    A viewset that provides default `create()`, `retrieve()`, `update()`, 

-

    `partial_update()`, `destroy()` and `list()` actions. 

-

    """ 

-

    pass 

- -
-
- - - - - diff --git a/htmlcov/status.dat b/htmlcov/status.dat deleted file mode 100644 index 9e448e37..00000000 --- a/htmlcov/status.dat +++ /dev/null @@ -1,1258 +0,0 @@ -(dp1 -S'files' -p2 -(dp3 -S'rest_framework_utils_encoders' -p4 -(dp5 -S'index' -p6 -(dp7 -S'par' -p8 -I0 -sS'html_filename' -p9 -S'rest_framework_utils_encoders.html' -p10 -sS'name' -p11 -S'rest_framework/utils/encoders' -p12 -sS'nums' -p13 -ccopy_reg -_reconstructor -p14 -(ccoverage.results -Numbers -p15 -c__builtin__ -object -p16 -NtRp17 -(dp18 -S'n_files' -p19 -I1 -sS'n_branches' -p20 -I0 -sS'n_statements' -p21 -I70 -sS'n_excluded' -p22 -I0 -sS'n_missing' -p23 -I19 -sS'n_missing_branches' -p24 -I0 -sbssS'hash' -p25 -S'\x9d|\xea|-p\x0c#\xef\x82\x8c\x91\xf1\xcd}f' -p26 -ssS'rest_framework___init__' -p27 -(dp28 -g6 -(dp29 -g8 -I0 -sg9 -S'rest_framework___init__.html' -p30 -sg11 -S'rest_framework/__init__' -p31 -sg13 -g14 -(g15 -g16 -NtRp32 -(dp33 -g19 -I1 -sg20 -I0 -sg21 -I4 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'\xbc\xf1f\xd9[\xd4\xcf\x9cQ\x94H\xfd3)\xea[' -p34 -ssS'rest_framework_urlpatterns' -p35 -(dp36 -g6 -(dp37 -g8 -I0 -sg9 -S'rest_framework_urlpatterns.html' -p38 -sg11 -S'rest_framework/urlpatterns' -p39 -sg13 -g14 -(g15 -g16 -NtRp40 -(dp41 -g19 -I1 -sg20 -I0 -sg21 -I31 -sg22 -I0 -sg23 -I4 -sg24 -I0 -sbssg25 -S"\x84\xb0-\xc6Y\x7f\xebA'\x8c5+\xcf\xf6\xcf\xda" -p42 -ssS'rest_framework_permissions' -p43 -(dp44 -g6 -(dp45 -g8 -I0 -sg9 -S'rest_framework_permissions.html' -p46 -sg11 -S'rest_framework/permissions' -p47 -sg13 -g14 -(g15 -g16 -NtRp48 -(dp49 -g19 -I1 -sg20 -I0 -sg21 -I63 -sg22 -I0 -sg23 -I12 -sg24 -I0 -sbssg25 -S",\xc4,\xda\x05\x86\x17\xe8u2~ls*'\xc1" -p50 -ssS'rest_framework_fields' -p51 -(dp52 -g6 -(dp53 -g8 -I0 -sg9 -S'rest_framework_fields.html' -p54 -sg11 -S'rest_framework/fields' -p55 -sg13 -g14 -(g15 -g16 -NtRp56 -(dp57 -g19 -I1 -sg20 -I0 -sg21 -I594 -sg22 -I0 -sg23 -I80 -sg24 -I0 -sbssg25 -S'\x08\x1b\xd2m\x91l\x14e\x97CDA\x1c&k\xf9' -p58 -ssS'rest_framework_models' -p59 -(dp60 -g6 -(dp61 -g8 -I0 -sg9 -S'rest_framework_models.html' -p62 -sg11 -S'rest_framework/models' -p63 -sg13 -g14 -(g15 -g16 -NtRp64 -(dp65 -g19 -I1 -sg20 -I0 -sg21 -I0 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S' E\xaf\xdd\xe7\xbb\xc4\x11z\xf8\x80\x18v.\xec\xf6' -p66 -ssS'rest_framework_utils_breadcrumbs' -p67 -(dp68 -g6 -(dp69 -g8 -I0 -sg9 -S'rest_framework_utils_breadcrumbs.html' -p70 -sg11 -S'rest_framework/utils/breadcrumbs' -p71 -sg13 -g14 -(g15 -g16 -NtRp72 -(dp73 -g19 -I1 -sg20 -I0 -sg21 -I27 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'V"\xf6\xbc\\m)\x12R4>c\xff\xea\xde\x8b' -p74 -ssS'rest_framework_urls' -p75 -(dp76 -g6 -(dp77 -g8 -I0 -sg9 -S'rest_framework_urls.html' -p78 -sg11 -S'rest_framework/urls' -p79 -sg13 -g14 -(g15 -g16 -NtRp80 -(dp81 -g19 -I1 -sg20 -I0 -sg21 -I4 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'\xba\x9b\xdaeu\x17\x8b\xe0e\xc6-\xc5R\xba\xa2\xd5' -p82 -ssS'rest_framework_serializers' -p83 -(dp84 -g6 -(dp85 -g8 -I0 -sg9 -S'rest_framework_serializers.html' -p86 -sg11 -S'rest_framework/serializers' -p87 -sg13 -g14 -(g15 -g16 -NtRp88 -(dp89 -g19 -I1 -sg20 -I0 -sg21 -I464 -sg22 -I0 -sg23 -I27 -sg24 -I0 -sbssg25 -S'O\\\xf6\x81y\x95\xae\x9a)\xe9~\xb8\xab\t\x88#' -p90 -ssS'rest_framework_exceptions' -p91 -(dp92 -g6 -(dp93 -g8 -I0 -sg9 -S'rest_framework_exceptions.html' -p94 -sg11 -S'rest_framework/exceptions' -p95 -sg13 -g14 -(g15 -g16 -NtRp96 -(dp97 -g19 -I1 -sg20 -I0 -sg21 -I51 -sg22 -I0 -sg23 -I2 -sg24 -I0 -sbssg25 -S'\xdd\xcaE\x12\x1f4V\xe6\x91\x11\xef:T\xe1r\xca' -p98 -ssS'rest_framework_status' -p99 -(dp100 -g6 -(dp101 -g8 -I0 -sg9 -S'rest_framework_status.html' -p102 -sg11 -S'rest_framework/status' -p103 -sg13 -g14 -(g15 -g16 -NtRp104 -(dp105 -g19 -I1 -sg20 -I0 -sg21 -I46 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'\x97z\xcd\xfd\xdc\x0c\xe3\xa9j\x04\xab\x13]\x98\xbf\x80' -p106 -ssS'rest_framework_relations' -p107 -(dp108 -g6 -(dp109 -g8 -I0 -sg9 -S'rest_framework_relations.html' -p110 -sg11 -S'rest_framework/relations' -p111 -sg13 -g14 -(g15 -g16 -NtRp112 -(dp113 -g19 -I1 -sg20 -I0 -sg21 -I365 -sg22 -I0 -sg23 -I88 -sg24 -I0 -sbssg25 -S'\xdb"\xfe\xc2\xb3\x8a\xe2(\xbeoNk\x1b\xd3H9' -p114 -ssS'rest_framework_authtoken_views' -p115 -(dp116 -g6 -(dp117 -g8 -I0 -sg9 -S'rest_framework_authtoken_views.html' -p118 -sg11 -S'rest_framework/authtoken/views' -p119 -sg13 -g14 -(g15 -g16 -NtRp120 -(dp121 -g19 -I1 -sg20 -I0 -sg21 -I21 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'\xb8A\x13\xee\xfc9\x8b\x1eY-\xad\x00\xa7\x9dH]' -p122 -ssS'rest_framework_mixins' -p123 -(dp124 -g6 -(dp125 -g8 -I0 -sg9 -S'rest_framework_mixins.html' -p126 -sg11 -S'rest_framework/mixins' -p127 -sg13 -g14 -(g15 -g16 -NtRp128 -(dp129 -g19 -I1 -sg20 -I0 -sg21 -I97 -sg22 -I0 -sg23 -I7 -sg24 -I0 -sbssg25 -S'\xcd\xe5\x9f\xc2\xbb\xd9\xcb\x14*\x88\x99\xe8\xdf\xd2\xa8\xd6' -p130 -ssS'rest_framework_views' -p131 -(dp132 -g6 -(dp133 -g8 -I0 -sg9 -S'rest_framework_views.html' -p134 -sg11 -S'rest_framework/views' -p135 -sg13 -g14 -(g15 -g16 -NtRp136 -(dp137 -g19 -I1 -sg20 -I0 -sg21 -I146 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'ZBo\x84oh^\x1f\x8c\x94Mp$\xf3\xd2\xa1' -p138 -ssS'rest_framework_generics' -p139 -(dp140 -g6 -(dp141 -g8 -I0 -sg9 -S'rest_framework_generics.html' -p142 -sg11 -S'rest_framework/generics' -p143 -sg13 -g14 -(g15 -g16 -NtRp144 -(dp145 -g19 -I1 -sg20 -I0 -sg21 -I196 -sg22 -I0 -sg23 -I34 -sg24 -I0 -sbssg25 -S'@\x1c\x97\x176\x18\x9c\xfc"| |\xb8^\xbb\x83' -p146 -ssS'rest_framework_utils___init__' -p147 -(dp148 -g6 -(dp149 -g8 -I0 -sg9 -S'rest_framework_utils___init__.html' -p150 -sg11 -S'rest_framework/utils/__init__' -p151 -sg13 -g14 -(g15 -g16 -NtRp152 -(dp153 -g19 -I1 -sg20 -I0 -sg21 -I0 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'\xb0\xc8pN\xaf>\xa0\xbaz\x144\xe0A9\xb8?' -p154 -ssS'rest_framework_renderers' -p155 -(dp156 -g6 -(dp157 -g8 -I0 -sg9 -S'rest_framework_renderers.html' -p158 -sg11 -S'rest_framework/renderers' -p159 -sg13 -g14 -(g15 -g16 -NtRp160 -(dp161 -g19 -I1 -sg20 -I0 -sg21 -I282 -sg22 -I0 -sg23 -I23 -sg24 -I0 -sbssg25 -S'\t\x11\xd4\xafO\xae\\*\x8d\xaf\xa4f\xde\x86\xe8N' -p162 -ssS'rest_framework_negotiation' -p163 -(dp164 -g6 -(dp165 -g8 -I0 -sg9 -S'rest_framework_negotiation.html' -p166 -sg11 -S'rest_framework/negotiation' -p167 -sg13 -g14 -(g15 -g16 -NtRp168 -(dp169 -g19 -I1 -sg20 -I0 -sg21 -I41 -sg22 -I0 -sg23 -I4 -sg24 -I0 -sbssg25 -S'\xd2\xa2\x94\xc8}y\xba\x9eZE\xe5M\xa5>\x9f\x8d' -p170 -ssS'rest_framework_throttling' -p171 -(dp172 -g6 -(dp173 -g8 -I0 -sg9 -S'rest_framework_throttling.html' -p174 -sg11 -S'rest_framework/throttling' -p175 -sg13 -g14 -(g15 -g16 -NtRp176 -(dp177 -g19 -I1 -sg20 -I0 -sg21 -I90 -sg22 -I0 -sg23 -I17 -sg24 -I0 -sbssg25 -S'a\xbcT\xe7\xff\x1an\xb5\x886\xa3\xa2e\x90PZ' -p178 -ssS'rest_framework_reverse' -p179 -(dp180 -g6 -(dp181 -g8 -I0 -sg9 -S'rest_framework_reverse.html' -p182 -sg11 -S'rest_framework/reverse' -p183 -sg13 -g14 -(g15 -g16 -NtRp184 -(dp185 -g19 -I1 -sg20 -I0 -sg21 -I12 -sg22 -I0 -sg23 -I3 -sg24 -I0 -sbssg25 -S"#\xe7D\x01\x10\xe8'1\x9c\xc9yX4\xb4\xef\x19" -p186 -ssS'rest_framework_request' -p187 -(dp188 -g6 -(dp189 -g8 -I0 -sg9 -S'rest_framework_request.html' -p190 -sg11 -S'rest_framework/request' -p191 -sg13 -g14 -(g15 -g16 -NtRp192 -(dp193 -g19 -I1 -sg20 -I0 -sg21 -I161 -sg22 -I0 -sg23 -I8 -sg24 -I0 -sbssg25 -S'C\xd4v\x9b\xf2Z\xe47\xe8\xc8\x03\xf4\xf8\xac\xefs' -p194 -ssS'rest_framework_parsers' -p195 -(dp196 -g6 -(dp197 -g8 -I0 -sg9 -S'rest_framework_parsers.html' -p198 -sg11 -S'rest_framework/parsers' -p199 -sg13 -g14 -(g15 -g16 -NtRp200 -(dp201 -g19 -I1 -sg20 -I0 -sg21 -I153 -sg22 -I0 -sg23 -I13 -sg24 -I0 -sbssg25 -S'\x11o\x05[\x99{\x9c\x8bj\xa8\xd0t\xe8\x16\\\xae' -p202 -ssS'rest_framework_settings' -p203 -(dp204 -g6 -(dp205 -g8 -I0 -sg9 -S'rest_framework_settings.html' -p206 -sg11 -S'rest_framework/settings' -p207 -sg13 -g14 -(g15 -g16 -NtRp208 -(dp209 -g19 -I1 -sg20 -I0 -sg21 -I44 -sg22 -I0 -sg23 -I2 -sg24 -I0 -sbssg25 -S'\n\xb8|\x03\xa7d|\xfc9\xda\xb5\xb9\x1a\x00@\xc3' -p210 -ssS'rest_framework_authtoken_models' -p211 -(dp212 -g6 -(dp213 -g8 -I0 -sg9 -S'rest_framework_authtoken_models.html' -p214 -sg11 -S'rest_framework/authtoken/models' -p215 -sg13 -g14 -(g15 -g16 -NtRp216 -(dp217 -g19 -I1 -sg20 -I0 -sg21 -I21 -sg22 -I0 -sg23 -I1 -sg24 -I0 -sbssg25 -S'U;\xc7\xf5{\xf6r\xc7]\x95\xffF\xde\x8caE' -p218 -ssS'rest_framework_decorators' -p219 -(dp220 -g6 -(dp221 -g8 -I0 -sg9 -S'rest_framework_decorators.html' -p222 -sg11 -S'rest_framework/decorators' -p223 -sg13 -g14 -(g15 -g16 -NtRp224 -(dp225 -g19 -I1 -sg20 -I0 -sg21 -I60 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S"\xd4\x88\xa2\x16\xf4#X\xb4X\xe97Lj\xeb\x16'" -p226 -ssS'rest_framework_authentication' -p227 -(dp228 -g6 -(dp229 -g8 -I0 -sg9 -S'rest_framework_authentication.html' -p230 -sg11 -S'rest_framework/authentication' -p231 -sg13 -g14 -(g15 -g16 -NtRp232 -(dp233 -g19 -I1 -sg20 -I0 -sg21 -I169 -sg22 -I0 -sg23 -I33 -sg24 -I0 -sbssg25 -S'^\x80:,\x1cL\xde\t\xc1\x93\xe0\x8b\x11\xf4\xb8\x06' -p234 -ssS'rest_framework_utils_formatting' -p235 -(dp236 -g6 -(dp237 -g8 -I0 -sg9 -S'rest_framework_utils_formatting.html' -p238 -sg11 -S'rest_framework/utils/formatting' -p239 -sg13 -g14 -(g15 -g16 -NtRp240 -(dp241 -g19 -I1 -sg20 -I0 -sg21 -I39 -sg22 -I0 -sg23 -I1 -sg24 -I0 -sbssg25 -S'\xdd\x05M\xeb\xfe\tl\xe6\xdd\xc5k\xae\xa8\xf9um' -p242 -ssS'rest_framework_pagination' -p243 -(dp244 -g6 -(dp245 -g8 -I0 -sg9 -S'rest_framework_pagination.html' -p246 -sg11 -S'rest_framework/pagination' -p247 -sg13 -g14 -(g15 -g16 -NtRp248 -(dp249 -g19 -I1 -sg20 -I0 -sg21 -I43 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'y\xa8f\rv\x8c\x9b\x9a:9\xdc\x89\t>\x0c' -p282 -ssS'rest_framework_viewsets' -p283 -(dp284 -g6 -(dp285 -g8 -I0 -sg9 -S'rest_framework_viewsets.html' -p286 -sg11 -S'rest_framework/viewsets' -p287 -sg13 -g14 -(g15 -g16 -NtRp288 -(dp289 -g19 -I1 -sg20 -I0 -sg21 -I39 -sg22 -I0 -sg23 -I2 -sg24 -I0 -sbssg25 -S'ic\x82\xc6e\x93$\x1b\x0c\x8bK\x10\x0f9\xe8\n' -p290 -ssS'rest_framework_authtoken___init__' -p291 -(dp292 -g6 -(dp293 -g8 -I0 -sg9 -S'rest_framework_authtoken___init__.html' -p294 -sg11 -S'rest_framework/authtoken/__init__' -p295 -sg13 -g14 -(g15 -g16 -NtRp296 -(dp297 -g19 -I1 -sg20 -I0 -sg21 -I0 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'\xb0\xc8pN\xaf>\xa0\xbaz\x144\xe0A9\xb8?' -p298 -ssS'rest_framework_routers' -p299 -(dp300 -g6 -(dp301 -g8 -I0 -sg9 -S'rest_framework_routers.html' -p302 -sg11 -S'rest_framework/routers' -p303 -sg13 -g14 -(g15 -g16 -NtRp304 -(dp305 -g19 -I1 -sg20 -I0 -sg21 -I108 -sg22 -I0 -sg23 -I7 -sg24 -I0 -sbssg25 -S'i\xa8[\x1f\x0f|\xd6\xa0R\x98\xa9\xecs\xe53\xb3' -p306 -sssS'version' -p307 -S'3.5.1' -p308 -sS'settings' -p309 -S'\xfe\xa4\x01e\x06\x8a\x97H\x97\xaf\xbf\xcd\xfez\xe4\xbf' -p310 -sS'format' -p311 -I1 -s. \ No newline at end of file diff --git a/htmlcov/style.css b/htmlcov/style.css deleted file mode 100644 index c40357b8..00000000 --- a/htmlcov/style.css +++ /dev/null @@ -1,275 +0,0 @@ -/* CSS styles for Coverage. */ -/* Page-wide styles */ -html, body, h1, h2, h3, p, td, th { - margin: 0; - padding: 0; - border: 0; - outline: 0; - font-weight: inherit; - font-style: inherit; - font-size: 100%; - font-family: inherit; - vertical-align: baseline; - } - -/* Set baseline grid to 16 pt. */ -body { - font-family: georgia, serif; - font-size: 1em; - } - -html>body { - font-size: 16px; - } - -/* Set base font size to 12/16 */ -p { - font-size: .75em; /* 12/16 */ - line-height: 1.3333em; /* 16/12 */ - } - -table { - border-collapse: collapse; - } - -a.nav { - text-decoration: none; - color: inherit; - } -a.nav:hover { - text-decoration: underline; - color: inherit; - } - -/* Page structure */ -#header { - background: #f8f8f8; - width: 100%; - border-bottom: 1px solid #eee; - } - -#source { - padding: 1em; - font-family: "courier new", monospace; - } - -#indexfile #footer { - margin: 1em 3em; - } - -#pyfile #footer { - margin: 1em 1em; - } - -#footer .content { - padding: 0; - font-size: 85%; - font-family: verdana, sans-serif; - color: #666666; - font-style: italic; - } - -#index { - margin: 1em 0 0 3em; - } - -/* Header styles */ -#header .content { - padding: 1em 3em; - } - -h1 { - font-size: 1.25em; -} - -h2.stats { - margin-top: .5em; - font-size: 1em; -} -.stats span { - border: 1px solid; - padding: .1em .25em; - margin: 0 .1em; - cursor: pointer; - border-color: #999 #ccc #ccc #999; -} -.stats span.hide_run, .stats span.hide_exc, -.stats span.hide_mis, .stats span.hide_par, -.stats span.par.hide_run.hide_par { - border-color: #ccc #999 #999 #ccc; -} -.stats span.par.hide_run { - border-color: #999 #ccc #ccc #999; -} - -/* Help panel */ -#keyboard_icon { - float: right; - cursor: pointer; -} - -.help_panel { - position: absolute; - background: #ffc; - padding: .5em; - border: 1px solid #883; - display: none; -} - -#indexfile .help_panel { - width: 20em; height: 4em; -} - -#pyfile .help_panel { - width: 16em; height: 8em; -} - -.help_panel .legend { - font-style: italic; - margin-bottom: 1em; -} - -#panel_icon { - float: right; - cursor: pointer; -} - -.keyhelp { - margin: .75em; -} - -.keyhelp .key { - border: 1px solid black; - border-color: #888 #333 #333 #888; - padding: .1em .35em; - font-family: monospace; - font-weight: bold; - background: #eee; -} - -/* Source file styles */ -.linenos p { - text-align: right; - margin: 0; - padding: 0 .5em; - color: #999999; - font-family: verdana, sans-serif; - font-size: .625em; /* 10/16 */ - line-height: 1.6em; /* 16/10 */ - } -.linenos p.highlight { - background: #ffdd00; - } -.linenos p a { - text-decoration: none; - color: #999999; - } -.linenos p a:hover { - text-decoration: underline; - color: #999999; - } - -td.text { - width: 100%; - } -.text p { - margin: 0; - padding: 0 0 0 .5em; - border-left: 2px solid #ffffff; - white-space: nowrap; - } - -.text p.mis { - background: #ffdddd; - border-left: 2px solid #ff0000; - } -.text p.run, .text p.run.hide_par { - background: #ddffdd; - border-left: 2px solid #00ff00; - } -.text p.exc { - background: #eeeeee; - border-left: 2px solid #808080; - } -.text p.par, .text p.par.hide_run { - background: #ffffaa; - border-left: 2px solid #eeee99; - } -.text p.hide_run, .text p.hide_exc, .text p.hide_mis, .text p.hide_par, -.text p.hide_run.hide_par { - background: inherit; - } - -.text span.annotate { - font-family: georgia; - font-style: italic; - color: #666; - float: right; - padding-right: .5em; - } -.text p.hide_par span.annotate { - display: none; - } - -/* Syntax coloring */ -.text .com { - color: green; - font-style: italic; - line-height: 1px; - } -.text .key { - font-weight: bold; - line-height: 1px; - } -.text .str { - color: #000080; - } - -/* index styles */ -#index td, #index th { - text-align: right; - width: 5em; - padding: .25em .5em; - border-bottom: 1px solid #eee; - } -#index th { - font-style: italic; - color: #333; - border-bottom: 1px solid #ccc; - cursor: pointer; - } -#index th:hover { - background: #eee; - border-bottom: 1px solid #999; - } -#index td.left, #index th.left { - padding-left: 0; - } -#index td.right, #index th.right { - padding-right: 0; - } -#index th.headerSortDown, #index th.headerSortUp { - border-bottom: 1px solid #000; - } -#index td.name, #index th.name { - text-align: left; - width: auto; - } -#index td.name a { - text-decoration: none; - color: #000; - } -#index td.name a:hover { - text-decoration: underline; - color: #000; - } -#index tr.total { - } -#index tr.total td { - font-weight: bold; - border-top: 1px solid #ccc; - border-bottom: none; - } -#index tr.file:hover { - background: #eeeeee; - } -- cgit v1.2.3 From 209b65f426e1935c970c95fad389ba5c03388592 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 22:12:37 +0100 Subject: Update assertion error to reference 'base_name' argument, not incorrect 'name' argument. Closes #933 --- rest_framework/routers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/routers.py b/rest_framework/routers.py index f70c2cdb..2a26f6a7 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -117,7 +117,7 @@ class SimpleRouter(BaseRouter): if model_cls is None and queryset is not None: model_cls = queryset.model - assert model_cls, '`name` not argument not specified, and could ' \ + assert model_cls, '`base_name` argument not specified, and could ' \ 'not automatically determine the name from the viewset, as ' \ 'it does not have a `.model` or `.queryset` attribute.' -- cgit v1.2.3 From 2d5f7f201ffcc8c371e9f36821c2ae0e13dcecca Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 22:19:14 +0100 Subject: Update router docs on base_name. Refs #933. --- 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 f16fa946..b74b6e13 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -26,7 +26,7 @@ There are two mandatory arguments to the `register()` method: Optionally, you may also specify an additional argument: -* `base_name` - The base to use for the URL names that are created. If unset the basename will be automatically generated based on the `model` or `queryset` attribute on the viewset, if it has one. +* `base_name` - The base to use for the URL names that are created. If unset the basename will be automatically generated based on the `model` or `queryset` attribute on the viewset, if it has one. Note that if the viewset does not include a `model` or `queryset` attribute then you must set `base_name` when registering the viewset. The example above would generate the following URL patterns: -- cgit v1.2.3 From a68f473dd8438c7d7b0f8ec4b4dc74aa0544143d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 23:25:14 +0200 Subject: Brackets not required on decorator without arguments --- 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 25d11bfb..ad961636 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -108,7 +108,7 @@ For example: queryset = User.objects.all() serializer_class = UserSerializer - @action() + @action def set_password(self, request, pk=None): user = self.get_object() serializer = PasswordSerializer(data=request.DATA) -- cgit v1.2.3 From 4f7f93e20ef53fbc0b66766158bca75ebddce2ed Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 22:28:36 +0100 Subject: Added @freakydug, for changes in #941. Thanks :) --- docs/topics/credits.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 3f0ee429..a7c09b5b 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -142,6 +142,7 @@ The following people have helped make REST framework great. * Areski Belaid - [areski] * Ethan Freman - [mindlace] * David Sanders - [davesque] +* Philip Douglas - [freakydug] Many thanks to everyone who's contributed to the project. @@ -320,4 +321,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [areski]: https://github.com/areski [mindlace]: https://github.com/mindlace [davesque]: https://github.com/davesque - +[freakydug]: https://github.com/freakydug -- cgit v1.2.3 From 8cc63b09f6065e0197e060cc4d62b560196c8877 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 22:42:04 +0100 Subject: Add support for StreamingHttpResponse. Closes #939 --- docs/api-guide/responses.md | 2 +- rest_framework/compat.py | 6 ++++++ rest_framework/views.py | 11 ++++++----- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/api-guide/responses.md b/docs/api-guide/responses.md index f83b8194..399b7c23 100644 --- a/docs/api-guide/responses.md +++ b/docs/api-guide/responses.md @@ -10,7 +10,7 @@ REST framework supports HTTP content negotiation by providing a `Response` class The `Response` class subclasses Django's `SimpleTemplateResponse`. `Response` objects are initialised with data, which should consist of native Python primitives. REST framework then uses standard HTTP content negotiation to determine how it should render the final response content. -There's no requirement for you to use the `Response` class, you can also return regular `HttpResponse` objects from your views if you want, but it provides a nicer interface for returning Web API responses. +There's no requirement for you to use the `Response` class, you can also return regular `HttpResponse` or `StreamingHttpResponse` objects from your views if required. Using the `Response` class simply provides a nicer interface for returning content-negotiated Web API responses, that can be rendered to multiple formats. Unless you want to heavily customize REST framework for some reason, you should always use an `APIView` class or `@api_view` function for views that return `Response` objects. Doing so ensures that the view can perform content negotiation and select the appropriate renderer for the response, before it is returned from the view. diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 76dc0052..a19bd778 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -33,6 +33,12 @@ except ImportError: from django.utils.encoding import force_unicode as force_text +# HttpResponseBase only exists from 1.5 onwards +try: + from django.http.response import HttpResponseBase +except ImportError: + from django.http import HttpResponse as HttpResponseBase + # django-filter is optional try: import django_filters diff --git a/rest_framework/views.py b/rest_framework/views.py index c28d2835..37bba7f0 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -4,11 +4,11 @@ Provides an APIView class that is the base of all views in REST framework. from __future__ import unicode_literals from django.core.exceptions import PermissionDenied -from django.http import Http404, HttpResponse +from django.http import Http404 from django.utils.datastructures import SortedDict from django.views.decorators.csrf import csrf_exempt from rest_framework import status, exceptions -from rest_framework.compat import View +from rest_framework.compat import View, HttpResponseBase from rest_framework.request import Request from rest_framework.response import Response from rest_framework.settings import api_settings @@ -244,9 +244,10 @@ class APIView(View): Returns the final response object. """ # Make the error obvious if a proper response is not returned - assert isinstance(response, HttpResponse), ( - 'Expected a `Response` to be returned from the view, ' - 'but received a `%s`' % type(response) + assert isinstance(response, HttpResponseBase), ( + 'Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` ' + 'to be returned from the view, but received a `%s`' + % type(response) ) if isinstance(response, Response): -- cgit v1.2.3 From fb6bcd9f06daaca51441c6b851d6411621d32c26 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 22:43:01 +0100 Subject: Update release notes, noting support for StreamingHttpResponse. Refs #939 --- 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 f49dd5c8..b08ac058 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -43,6 +43,7 @@ You can determine your currently installed version using `pip freeze`: ### Master * Added `trailing_slash` option to routers. +* Include support for `HttpStreamingResponse`. * Support wider range of default serializer validation when used with custom model fields. * Bugfix: Return error correctly when OAuth non-existent consumer occurs. * Bugfix: Allow `FileUploadParser` to correctly filename if provided as URL kwarg. -- cgit v1.2.3 From 8d83ff8e6c8513d0a88d6b1fecb34ed86f1e2085 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 23:12:16 +0100 Subject: Add decorator brackets back. Refs #941 --- 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 ad961636..25d11bfb 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -108,7 +108,7 @@ For example: queryset = User.objects.all() serializer_class = UserSerializer - @action + @action() def set_password(self, request, pk=None): user = self.get_object() serializer = PasswordSerializer(data=request.DATA) -- cgit v1.2.3 From 2bf5f6305030d5ebbd5a8a0fd5c31586c08a558d Mon Sep 17 00:00:00 2001 From: Igor Kalat Date: Sat, 22 Jun 2013 13:43:45 +0200 Subject: Make browsable API views play nice with utf-8 --- rest_framework/tests/test_utils.py | 36 ++++++++++++++++++++++++++++++++++++ rest_framework/utils/formatting.py | 5 +++++ 2 files changed, 41 insertions(+) create mode 100644 rest_framework/tests/test_utils.py diff --git a/rest_framework/tests/test_utils.py b/rest_framework/tests/test_utils.py new file mode 100644 index 00000000..da508dbb --- /dev/null +++ b/rest_framework/tests/test_utils.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +from django.test import TestCase +from rest_framework.utils import formatting +import sys + + +class FormattingUnitTests(TestCase): + def setUp(self): + # test strings snatched from http://www.columbia.edu/~fdc/utf8/, + # http://winrus.com/utf8-jap.htm and memory + self.utf8_test_string = ( + 'zażółć gęślą jaźń' + 'Sîne klâwen durh die wolken sint geslagen' + 'Τη γλώσσα μου έδωσαν ελληνική' + 'யாமறிந்த மொழிகளிலே தமிழ்மொழி' + 'На берегу пустынных волн' + ' てすと' + 'アイウエオカキクケコサシスセソタチツテ' + ) + self.non_utf8_test_string = ('The quick brown fox jumps over the lazy ' + 'dog') + + def test_for_ascii_support_in_remove_leading_indent(self): + if sys.version_info < (3, 0): + # only Python 2.x is affected, so we skip the test entirely + # if on Python 3.x + self.assertEqual(formatting._remove_leading_indent( + self.non_utf8_test_string), self.non_utf8_test_string) + + def test_for_utf8_support_in_remove_leading_indent(self): + if sys.version_info < (3, 0): + # only Python 2.x is affected, so we skip the test entirely + # if on Python 3.x + self.assertEqual(formatting._remove_leading_indent( + self.utf8_test_string), self.utf8_test_string.decode('utf-8')) diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py index ebadb3a6..a2a5609c 100644 --- a/rest_framework/utils/formatting.py +++ b/rest_framework/utils/formatting.py @@ -24,6 +24,11 @@ def _remove_leading_indent(content): Remove leading indent from a block of text. Used when generating descriptions from docstrings. """ + try: + content = content.decode('utf-8') + except (AttributeError, UnicodeEncodeError): + pass # the string should keep the default 'ascii' encoding in + # Python 2.x or stay a unicode string in Python 3.x whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in content.splitlines()[1:] if line.lstrip()] -- cgit v1.2.3 From 13a3c993ab20e7af510d615a5eafaa87667b8efb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Jun 2013 11:30:27 +0100 Subject: Fix incorrect example --- docs/api-guide/generic-views.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index cd1bc7a1..67853ed0 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -92,7 +92,8 @@ May be overridden to provide dynamic behavior such as returning a queryset that For example: def get_queryset(self): - return self.user.accounts.all() + user = self.request.user + return user.accounts.all() #### `get_object(self)` -- cgit v1.2.3 From 715bd47dfababd39be9b3295ada99f2107d7c00c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Jun 2013 17:56:42 +0100 Subject: Use AUTH_USER_MODEL consistently between various Django versions. Closes #946 --- rest_framework/authtoken/models.py | 4 ++-- rest_framework/compat.py | 10 ++-------- rest_framework/runtests/settings.py | 2 ++ 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index 52c45ad1..7601f5b7 100644 --- a/rest_framework/authtoken/models.py +++ b/rest_framework/authtoken/models.py @@ -1,7 +1,7 @@ import uuid import hmac from hashlib import sha1 -from rest_framework.compat import User +from rest_framework.compat import AUTH_USER_MODEL from django.conf import settings from django.db import models @@ -11,7 +11,7 @@ class Token(models.Model): The default authorization token model. """ key = models.CharField(max_length=40, primary_key=True) - user = models.OneToOneField(User, related_name='auth_token') + user = models.OneToOneField(AUTH_USER_MODEL, related_name='auth_token') created = models.DateTimeField(auto_now_add=True) class Meta: diff --git a/rest_framework/compat.py b/rest_framework/compat.py index a19bd778..69853730 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -83,15 +83,9 @@ def get_concrete_model(model_cls): # Django 1.5 add support for custom auth user model if django.VERSION >= (1, 5): from django.conf import settings - if hasattr(settings, 'AUTH_USER_MODEL'): - User = settings.AUTH_USER_MODEL - else: - from django.contrib.auth.models import User + AUTH_USER_MODEL = settings.AUTH_USER_MODEL else: - try: - from django.contrib.auth.models import User - except ImportError: - raise ImportError("User model is not to be found.") + AUTH_USER_MODEL = 'auth.User' if django.VERSION >= (1, 5): diff --git a/rest_framework/runtests/settings.py b/rest_framework/runtests/settings.py index 9dd7b545..b3702d0b 100644 --- a/rest_framework/runtests/settings.py +++ b/rest_framework/runtests/settings.py @@ -134,6 +134,8 @@ PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.CryptPasswordHasher', ) +AUTH_USER_MODEL = 'auth.User' + import django if django.VERSION < (1, 3): -- cgit v1.2.3 From c8b0e6c40f6bcf447aa539ff98b9985aa53032ce Mon Sep 17 00:00:00 2001 From: Igor Kalat Date: Wed, 26 Jun 2013 22:12:02 +0200 Subject: Refactored get_view_description, moved appropriate tests to test_description.py --- rest_framework/tests/test_description.py | 13 ++++++------ rest_framework/tests/test_utils.py | 36 -------------------------------- rest_framework/tests/views.py | 25 ++++++++++++++++++++++ rest_framework/utils/formatting.py | 9 ++------ 4 files changed, 34 insertions(+), 49 deletions(-) delete mode 100644 rest_framework/tests/test_utils.py create mode 100644 rest_framework/tests/views.py diff --git a/rest_framework/tests/test_description.py b/rest_framework/tests/test_description.py index 52c1a34c..bc86e106 100644 --- a/rest_framework/tests/test_description.py +++ b/rest_framework/tests/test_description.py @@ -3,8 +3,10 @@ from __future__ import unicode_literals from django.test import TestCase from rest_framework.views import APIView -from rest_framework.compat import apply_markdown +from rest_framework.compat import apply_markdown, smart_text from rest_framework.utils.formatting import get_view_name, get_view_description +from rest_framework.tests.views import ( + ViewWithNonASCIICharactersInDocstring, UTF8_TEST_DOCSTRING) # We check that docstrings get nicely un-indented. DESCRIPTION = """an example docstring @@ -83,11 +85,10 @@ class TestViewNamesAndDescriptions(TestCase): Unicode in docstrings should be respected. """ - class MockView(APIView): - """Проверка""" - pass - - self.assertEqual(get_view_description(MockView), "Проверка") + self.assertEqual( + get_view_description(ViewWithNonASCIICharactersInDocstring), + smart_text(UTF8_TEST_DOCSTRING) + ) def test_view_description_can_be_empty(self): """ diff --git a/rest_framework/tests/test_utils.py b/rest_framework/tests/test_utils.py deleted file mode 100644 index da508dbb..00000000 --- a/rest_framework/tests/test_utils.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- - -from django.test import TestCase -from rest_framework.utils import formatting -import sys - - -class FormattingUnitTests(TestCase): - def setUp(self): - # test strings snatched from http://www.columbia.edu/~fdc/utf8/, - # http://winrus.com/utf8-jap.htm and memory - self.utf8_test_string = ( - 'zażółć gęślą jaźń' - 'Sîne klâwen durh die wolken sint geslagen' - 'Τη γλώσσα μου έδωσαν ελληνική' - 'யாமறிந்த மொழிகளிலே தமிழ்மொழி' - 'На берегу пустынных волн' - ' てすと' - 'アイウエオカキクケコサシスセソタチツテ' - ) - self.non_utf8_test_string = ('The quick brown fox jumps over the lazy ' - 'dog') - - def test_for_ascii_support_in_remove_leading_indent(self): - if sys.version_info < (3, 0): - # only Python 2.x is affected, so we skip the test entirely - # if on Python 3.x - self.assertEqual(formatting._remove_leading_indent( - self.non_utf8_test_string), self.non_utf8_test_string) - - def test_for_utf8_support_in_remove_leading_indent(self): - if sys.version_info < (3, 0): - # only Python 2.x is affected, so we skip the test entirely - # if on Python 3.x - self.assertEqual(formatting._remove_leading_indent( - self.utf8_test_string), self.utf8_test_string.decode('utf-8')) diff --git a/rest_framework/tests/views.py b/rest_framework/tests/views.py new file mode 100644 index 00000000..fc00cc0b --- /dev/null +++ b/rest_framework/tests/views.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +from rest_framework.views import APIView + + +# test strings snatched from http://www.columbia.edu/~fdc/utf8/, +# http://winrus.com/utf8-jap.htm and memory +UTF8_TEST_DOCSTRING = ( + 'zażółć gęślą jaźń' + 'Sîne klâwen durh die wolken sint geslagen' + 'Τη γλώσσα μου έδωσαν ελληνική' + 'யாமறிந்த மொழிகளிலே தமிழ்மொழி' + 'На берегу пустынных волн' + 'てすと' + 'アイウエオカキクケコサシスセソタチツテ' +) + + +# Apparently there is an issue where docstrings of imported view classes +# do not retain their encoding information even if a module has a proper +# encoding declaration at the top of its source file. Therefore for tests +# to catch unicode related errors, a mock view has to be declared in a separate +# module. +class ViewWithNonASCIICharactersInDocstring(APIView): + __doc__ = UTF8_TEST_DOCSTRING diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py index a2a5609c..4bec8387 100644 --- a/rest_framework/utils/formatting.py +++ b/rest_framework/utils/formatting.py @@ -5,7 +5,7 @@ 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.compat import apply_markdown, smart_text import re @@ -24,11 +24,6 @@ def _remove_leading_indent(content): Remove leading indent from a block of text. Used when generating descriptions from docstrings. """ - try: - content = content.decode('utf-8') - except (AttributeError, UnicodeEncodeError): - pass # the string should keep the default 'ascii' encoding in - # Python 2.x or stay a unicode string in Python 3.x whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in content.splitlines()[1:] if line.lstrip()] @@ -68,7 +63,7 @@ def get_view_description(cls, html=False): Return a description for an `APIView` class or `@api_view` function. """ description = cls.__doc__ or '' - description = _remove_leading_indent(description) + description = _remove_leading_indent(smart_text(description)) if html: return markup_description(description) return description -- cgit v1.2.3 From 69e5e3cc0db481e4fad7ac34bf28b73f4786e790 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Jun 2013 21:18:13 +0100 Subject: Use timezone aware datetimes with oauth2 provider, when supported. Closes #947. --- rest_framework/authentication.py | 9 ++++----- rest_framework/compat.py | 10 ++++++++++ rest_framework/serializers.py | 5 ++++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index f659a172..10298027 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -3,14 +3,13 @@ Provides various authentication policies. """ from __future__ import unicode_literals import base64 -from datetime import datetime from django.contrib.auth import authenticate from django.core.exceptions import ImproperlyConfigured 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 +from rest_framework.compat import oauth2_provider, provider_now from rest_framework.authtoken.models import Token @@ -320,9 +319,9 @@ class OAuth2Authentication(BaseAuthentication): try: token = oauth2_provider.models.AccessToken.objects.select_related('user') - # TODO: Change to timezone aware datetime when oauth2_provider add - # support to it. - token = token.get(token=access_token, expires__gt=datetime.now()) + # provider_now switches to timezone aware datetime when + # the oauth2_provider version supports to it. + token = token.get(token=access_token, expires__gt=provider_now()) except oauth2_provider.models.AccessToken.DoesNotExist: raise exceptions.AuthenticationFailed('Invalid token') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 69853730..b748dcc5 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -2,6 +2,7 @@ The `compat` module provides support for backwards compatibility with older versions of django/python, and compatibility wrappers around optional packages. """ + # flake8: noqa from __future__ import unicode_literals @@ -489,12 +490,21 @@ try: from provider.oauth2 import forms as oauth2_provider_forms from provider import scope as oauth2_provider_scope from provider import constants as oauth2_constants + from provider import __version__ as provider_version + 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 + else: + # Any other supported version does use timezone aware datetimes + from django.utils.timezone import now as provider_now except ImportError: oauth2_provider = None oauth2_provider_models = None oauth2_provider_forms = None oauth2_provider_scope = None oauth2_constants = None + provider_now = None # Handle lazy strings from django.utils.functional import Promise diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 5a8fd89f..023f7ccf 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -915,7 +915,10 @@ class HyperlinkedModelSerializer(ModelSerializer): view_name=self.opts.view_name, lookup_field=self.opts.lookup_field ) - fields.insert(0, 'url', url_field) + ret = self._dict_class() + ret['url'] = url_field + ret.update(fields) + fields = ret return fields -- cgit v1.2.3 From 494703fc8e916a9b7a318ec8bc7d774ef31de14e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Jun 2013 22:40:14 +0100 Subject: Update 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 b08ac058..ce4df83c 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -45,6 +45,7 @@ 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. +* OAuth2 provider usez timezone aware datetimes when supported. * Bugfix: Return error correctly when OAuth non-existent consumer occurs. * Bugfix: Allow `FileUploadParser` to correctly filename if provided as URL kwarg. * Bugfix: Fix `ScopedRateThrottle`. -- cgit v1.2.3 From 91b9fcb0ba2541b2752e2ab0706becad14bdda20 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Jun 2013 22:43:17 +0100 Subject: Minor test cleanup --- rest_framework/tests/test_description.py | 24 ++++++++++++++++++++++-- rest_framework/tests/views.py | 25 ------------------------- 2 files changed, 22 insertions(+), 27 deletions(-) delete mode 100644 rest_framework/tests/views.py diff --git a/rest_framework/tests/test_description.py b/rest_framework/tests/test_description.py index bc86e106..ea4b2c3a 100644 --- a/rest_framework/tests/test_description.py +++ b/rest_framework/tests/test_description.py @@ -5,8 +5,6 @@ from django.test import TestCase from rest_framework.views import APIView from rest_framework.compat import apply_markdown, smart_text from rest_framework.utils.formatting import get_view_name, get_view_description -from rest_framework.tests.views import ( - ViewWithNonASCIICharactersInDocstring, UTF8_TEST_DOCSTRING) # We check that docstrings get nicely un-indented. DESCRIPTION = """an example docstring @@ -51,6 +49,28 @@ MARKED_DOWN_gte_21 = """

an example docstring

hash style header

""" +# test strings snatched from http://www.columbia.edu/~fdc/utf8/, +# http://winrus.com/utf8-jap.htm and memory +UTF8_TEST_DOCSTRING = ( + 'zażółć gęślą jaźń' + 'Sîne klâwen durh die wolken sint geslagen' + 'Τη γλώσσα μου έδωσαν ελληνική' + 'யாமறிந்த மொழிகளிலே தமிழ்மொழி' + 'На берегу пустынных волн' + 'てすと' + 'アイウエオカキクケコサシスセソタチツテ' +) + + +# Apparently there is an issue where docstrings of imported view classes +# do not retain their encoding information even if a module has a proper +# encoding declaration at the top of its source file. Therefore for tests +# to catch unicode related errors, a mock view has to be declared in a separate +# module. +class ViewWithNonASCIICharactersInDocstring(APIView): + __doc__ = UTF8_TEST_DOCSTRING + + class TestViewNamesAndDescriptions(TestCase): def test_view_name_uses_class_name(self): """ diff --git a/rest_framework/tests/views.py b/rest_framework/tests/views.py deleted file mode 100644 index fc00cc0b..00000000 --- a/rest_framework/tests/views.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- - -from rest_framework.views import APIView - - -# test strings snatched from http://www.columbia.edu/~fdc/utf8/, -# http://winrus.com/utf8-jap.htm and memory -UTF8_TEST_DOCSTRING = ( - 'zażółć gęślą jaźń' - 'Sîne klâwen durh die wolken sint geslagen' - 'Τη γλώσσα μου έδωσαν ελληνική' - 'யாமறிந்த மொழிகளிலே தமிழ்மொழி' - 'На берегу пустынных волн' - 'てすと' - 'アイウエオカキクケコサシスセソタチツテ' -) - - -# Apparently there is an issue where docstrings of imported view classes -# do not retain their encoding information even if a module has a proper -# encoding declaration at the top of its source file. Therefore for tests -# to catch unicode related errors, a mock view has to be declared in a separate -# module. -class ViewWithNonASCIICharactersInDocstring(APIView): - __doc__ = UTF8_TEST_DOCSTRING -- cgit v1.2.3 From cb83bc373f8044ec21f5affabda0540ed0876357 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Jun 2013 22:44:44 +0100 Subject: Added @trwired for fix #943. Thanks :) --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index a7c09b5b..94760c74 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -143,6 +143,7 @@ The following people have helped make REST framework great. * Ethan Freman - [mindlace] * David Sanders - [davesque] * Philip Douglas - [freakydug] +* Igor Kalat - [trwired] Many thanks to everyone who's contributed to the project. @@ -322,3 +323,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [mindlace]: https://github.com/mindlace [davesque]: https://github.com/davesque [freakydug]: https://github.com/freakydug +[trwired]: https://github.com/trwired -- cgit v1.2.3 From af2fdc03a6f4cafe6e2f19b2adcf59c8918088f2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Jun 2013 22:45:39 +0100 Subject: Update 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 ce4df83c..4fecbf1f 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -45,6 +45,7 @@ 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. * OAuth2 provider usez timezone aware datetimes when supported. * Bugfix: Return error correctly when OAuth non-existent consumer occurs. * Bugfix: Allow `FileUploadParser` to correctly filename if provided as URL kwarg. -- cgit v1.2.3 From c127e63c32b2fb93d1a9422943005c1f6cc5328b Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Wed, 26 Jun 2013 23:00:42 +0100 Subject: Raise exception when attempting to dynamically route to a method that is already routed to. Fixes #940 --- rest_framework/routers.py | 14 ++++++++++++++ rest_framework/tests/test_routers.py | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/rest_framework/routers.py b/rest_framework/routers.py index c222f504..930011d3 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -15,7 +15,9 @@ For example, you might have a `urls.py` that looks something like this: """ from __future__ import unicode_literals +import itertools from collections import namedtuple +from django.core.exceptions import ImproperlyConfigured from rest_framework import views from rest_framework.compat import patterns, url from rest_framework.response import Response @@ -38,6 +40,13 @@ def replace_methodname(format_string, methodname): return ret +def flatten(list_of_lists): + """ + Takes an iterable of iterables, returns a single iterable containing all items + """ + return itertools.chain(*list_of_lists) + + class BaseRouter(object): def __init__(self): self.registry = [] @@ -130,12 +139,17 @@ class SimpleRouter(BaseRouter): Returns a list of the Route namedtuple. """ + known_actions = flatten([route.mapping.values() for route in self.routes]) + # Determine any `@action` or `@link` decorated methods on the viewset dynamic_routes = [] for methodname in dir(viewset): attr = getattr(viewset, methodname) httpmethods = getattr(attr, 'bind_to_methods', None) 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)) diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py index fe0711fa..d375f4a8 100644 --- a/rest_framework/tests/test_routers.py +++ b/rest_framework/tests/test_routers.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from django.db import models from django.test import TestCase from django.test.client import RequestFactory +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 @@ -191,3 +192,24 @@ class TestActionKeywordArgs(TestCase): response.data, {'permission_classes': [permissions.AllowAny]} ) + +class TestActionAppliedToExistingRoute(TestCase): + """ + Ensure `@action` 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() + def retrieve(self, request, *args, **kwargs): + return Response({ + 'hello': 'world' + }) + + self.router = SimpleRouter() + self.router.register(r'test', TestViewSet, base_name='test') + + with self.assertRaises(ImproperlyConfigured): + self.router.urls -- cgit v1.2.3 From 4d22a65e78432a2aa70ddc80395a014a7c9e299e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Jun 2013 23:26:35 +0100 Subject: Fix sidebar styling when browser window is too small --- docs/css/default.css | 4 ++++ docs/template.html | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/docs/css/default.css b/docs/css/default.css index a4f05daa..af6a9cc0 100644 --- a/docs/css/default.css +++ b/docs/css/default.css @@ -303,3 +303,7 @@ table { border-color: white; margin-bottom: 0.6em; } + +.side-nav { + overflow-y: scroll; +} diff --git a/docs/template.html b/docs/template.html index 53656e7d..14ecc9c7 100644 --- a/docs/template.html +++ b/docs/template.html @@ -198,5 +198,14 @@ $('.dropdown-menu').on('click touchstart', function(event) { event.stopPropagation(); }); + + // Dynamically force sidenav to no higher than browser window + $('.side-nav').css('max-height', window.innerHeight - 125); + + $(function(){ + $(window).resize(function(){ + $('.side-nav').css('max-height', window.innerHeight - 125); + }); + }); -- cgit v1.2.3 From 96f41fd12d376833a5822918cedcec5e74d59d02 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 27 Jun 2013 11:58:34 +0100 Subject: Use imported views to expose python 2.6 bug. Refs #943 --- rest_framework/tests/description.py | 26 ++++++++++++++++++++++++++ rest_framework/tests/test_description.py | 26 +++----------------------- 2 files changed, 29 insertions(+), 23 deletions(-) create mode 100644 rest_framework/tests/description.py diff --git a/rest_framework/tests/description.py b/rest_framework/tests/description.py new file mode 100644 index 00000000..b46d7f54 --- /dev/null +++ b/rest_framework/tests/description.py @@ -0,0 +1,26 @@ +# -- coding: utf-8 -- + +# Apparently there is a python 2.6 issue where docstrings of imported view classes +# do not retain their encoding information even if a module has a proper +# encoding declaration at the top of its source file. Therefore for tests +# to catch unicode related errors, a mock view has to be declared in a separate +# module. + +from rest_framework.views import APIView + + +# test strings snatched from http://www.columbia.edu/~fdc/utf8/, +# http://winrus.com/utf8-jap.htm and memory +UTF8_TEST_DOCSTRING = ( + 'zażółć gęślą jaźń' + 'Sîne klâwen durh die wolken sint geslagen' + 'Τη γλώσσα μου έδωσαν ελληνική' + 'யாமறிந்த மொழிகளிலே தமிழ்மொழி' + 'На берегу пустынных волн' + 'てすと' + 'アイウエオカキクケコサシスセソタチツテ' +) + + +class ViewWithNonASCIICharactersInDocstring(APIView): + __doc__ = UTF8_TEST_DOCSTRING diff --git a/rest_framework/tests/test_description.py b/rest_framework/tests/test_description.py index ea4b2c3a..8019f5ec 100644 --- a/rest_framework/tests/test_description.py +++ b/rest_framework/tests/test_description.py @@ -2,8 +2,10 @@ from __future__ import unicode_literals from django.test import TestCase -from rest_framework.views import APIView 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 rest_framework.utils.formatting import get_view_name, get_view_description # We check that docstrings get nicely un-indented. @@ -49,28 +51,6 @@ MARKED_DOWN_gte_21 = """

an example docstring

hash style header

""" -# test strings snatched from http://www.columbia.edu/~fdc/utf8/, -# http://winrus.com/utf8-jap.htm and memory -UTF8_TEST_DOCSTRING = ( - 'zażółć gęślą jaźń' - 'Sîne klâwen durh die wolken sint geslagen' - 'Τη γλώσσα μου έδωσαν ελληνική' - 'யாமறிந்த மொழிகளிலே தமிழ்மொழி' - 'На берегу пустынных волн' - 'てすと' - 'アイウエオカキクケコサシスセソタチツテ' -) - - -# Apparently there is an issue where docstrings of imported view classes -# do not retain their encoding information even if a module has a proper -# encoding declaration at the top of its source file. Therefore for tests -# to catch unicode related errors, a mock view has to be declared in a separate -# module. -class ViewWithNonASCIICharactersInDocstring(APIView): - __doc__ = UTF8_TEST_DOCSTRING - - class TestViewNamesAndDescriptions(TestCase): def test_view_name_uses_class_name(self): """ -- cgit v1.2.3 From 124ae8c2c88d48b67fbaee77e337e8a6f37d1b70 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 27 Jun 2013 12:58:38 +0100 Subject: Tweak styling for max-height of sidenav --- docs/template.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/template.html b/docs/template.html index 14ecc9c7..21771025 100644 --- a/docs/template.html +++ b/docs/template.html @@ -200,11 +200,11 @@ }); // Dynamically force sidenav to no higher than browser window - $('.side-nav').css('max-height', window.innerHeight - 125); + $('.side-nav').css('max-height', window.innerHeight - 130); $(function(){ $(window).resize(function(){ - $('.side-nav').css('max-height', window.innerHeight - 125); + $('.side-nav').css('max-height', window.innerHeight - 130); }); }); -- cgit v1.2.3 From 7ba2f44a0f0e5ed7bac0fbdbb0112bbfe43f6d24 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 27 Jun 2013 13:00:05 +0100 Subject: Version 2.3.6 --- docs/topics/release-notes.md | 6 ++++-- rest_framework/__init__.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 4fecbf1f..d379ab74 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -40,13 +40,15 @@ You can determine your currently installed version using `pip freeze`: ## 2.3.x series -### Master +### 2.3.6 + +**Date**: 27th June 2013 * 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. -* OAuth2 provider usez timezone aware datetimes when supported. +* OAuth2 provider uses timezone aware datetimes when supported. * Bugfix: Return error correctly when OAuth non-existent consumer occurs. * Bugfix: Allow `FileUploadParser` to correctly filename if provided as URL kwarg. * Bugfix: Fix `ScopedRateThrottle`. diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 0a210186..776618ac 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -1,4 +1,4 @@ -__version__ = '2.3.5' +__version__ = '2.3.6' VERSION = __version__ # synonym -- cgit v1.2.3 From 1f6a59d76da286e7a89e8e41317beb16a4aab7c7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 27 Jun 2013 13:41:42 +0100 Subject: Moar hyperlinks --- README.md | 23 ++++++++++++++++++----- docs/index.md | 16 +++++++++++----- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 12ed09f9..62883e32 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,11 @@ Django REST framework is a powerful and flexible toolkit that makes it easy to b Some reasons you might want to use REST framework: -* The Web browseable API is a huge useability win for your developers. -* Authentication policies including OAuth1a and OAuth2 out of the box. -* Serialization that supports both ORM and non-ORM data sources. -* Customizable all the way down - just use regular function-based views if you don't need the more powerful features. -* Extensive documentation, and great community support. +* The [Web browseable API][sandbox] is a huge useability win for your developers. +* [Authentication policies][authentication] including [OAuth1a][oauth1-section] and [OAuth2][oauth2-section] out of the box. +* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources. +* Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers]. +* [Extensive documentation][index], and [great community support][group]. There is a live example API for testing purposes, [available here][sandbox]. @@ -139,6 +139,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework [0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X [sandbox]: http://restframework.herokuapp.com/ + +[index]: http://django-rest-framework.org/ +[oauth1-section]: http://django-rest-framework.org/api-guide/authentication.html#oauthauthentication +[oauth2-section]: http://django-rest-framework.org/api-guide/authentication.html#oauth2authentication +[serializer-section]: http://django-rest-framework.org/api-guide/serializers.html#serializers +[modelserializer-section]: http://django-rest-framework.org/api-guide/serializers.html#modelserializer +[functionview-section]: http://django-rest-framework.org/api-guide/views.html#function-based-views +[generic-views]: http://django-rest-framework.org/api-guide/generic-views.html +[viewsets]: http://django-rest-framework.org/api-guide/viewsets.html +[routers]: http://django-rest-framework.org/api-guide/routers.html +[serializers]: http://django-rest-framework.org/api-guide/serializers.html +[authentication]: http://django-rest-framework.org/api-guide/authentication.html + [rest-framework-2-announcement]: http://django-rest-framework.org/topics/rest-framework-2-announcement.html [2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion [image]: http://django-rest-framework.org/img/quickstart.png diff --git a/docs/index.md b/docs/index.md index b04e2346..de4b01c6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,11 +15,11 @@ Django REST framework is a powerful and flexible toolkit that makes it easy to b Some reasons you might want to use REST framework: -* The Web browseable API is a huge usability win for your developers. -* Authentication policies including OAuth1a and OAuth2 out of the box. -* Serialization that supports both ORM and non-ORM data sources. -* Customizable all the way down - just use regular function-based views if you don't need the more powerful features. -* Extensive documentation, and great community support. +* The [Web browseable API][sandbox] is a huge usability win for your developers. +* [Authentication policies][authentication] including [OAuth1a][oauth1-section] and [OAuth2][oauth2-section] out of the box. +* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources. +* Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers]. +* [Extensive documentation][index], and [great community support][group]. There is a live example API for testing purposes, [available here][sandbox]. @@ -250,6 +250,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [django-oauth2-provider]: https://github.com/caffeinehit/django-oauth2-provider [0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X [image]: img/quickstart.png +[index]: . +[oauth1-section]: api-guide/authentication.html#oauthauthentication +[oauth2-section]: api-guide/authentication.html#oauth2authentication +[serializer-section]: api-guide/serializers.html#serializers +[modelserializer-section]: api-guide/serializers.html#modelserializer +[functionview-section]: api-guide/views.html#function-based-views [sandbox]: http://restframework.herokuapp.com/ [quickstart]: tutorial/quickstart.md -- cgit v1.2.3