From 050f07e7b1c53426d5234f9aa61d9a2461a6aeff Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 14 Dec 2011 19:23:48 +0000 Subject: [PATCH 01/53] Ensure that external projects do not need to add 'djangorestframework.tests' to INSTALLED_APPS during testing. --- djangorestframework/tests/testcases.py | 63 ++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 djangorestframework/tests/testcases.py diff --git a/djangorestframework/tests/testcases.py b/djangorestframework/tests/testcases.py new file mode 100644 index 000000000..ad0c7be64 --- /dev/null +++ b/djangorestframework/tests/testcases.py @@ -0,0 +1,63 @@ +# http://djangosnippets.org/snippets/1011/ +from django.conf import settings +from django.core.management import call_command +from django.db.models import loading +from django.test import TestCase + +NO_SETTING = ('!', None) + +class TestSettingsManager(object): + """ + A class which can modify some Django settings temporarily for a + test and then revert them to their original values later. + + Automatically handles resyncing the DB if INSTALLED_APPS is + modified. + + """ + def __init__(self): + self._original_settings = {} + + def set(self, **kwargs): + for k,v in kwargs.iteritems(): + self._original_settings.setdefault(k, getattr(settings, k, + NO_SETTING)) + setattr(settings, k, v) + if 'INSTALLED_APPS' in kwargs: + self.syncdb() + + def syncdb(self): + loading.cache.loaded = False + call_command('syncdb', verbosity=0) + + def revert(self): + for k,v in self._original_settings.iteritems(): + if v == NO_SETTING: + delattr(settings, k) + else: + setattr(settings, k, v) + if 'INSTALLED_APPS' in self._original_settings: + self.syncdb() + self._original_settings = {} + + +class SettingsTestCase(TestCase): + """ + A subclass of the Django TestCase with a settings_manager + attribute which is an instance of TestSettingsManager. + + Comes with a tearDown() method that calls + self.settings_manager.revert(). + + """ + def __init__(self, *args, **kwargs): + super(SettingsTestCase, self).__init__(*args, **kwargs) + self.settings_manager = TestSettingsManager() + + def tearDown(self): + self.settings_manager.revert() + +class TestModelsTestCase(SettingsTestCase): + def setUp(self, *args, **kwargs): + installed_apps = tuple(settings.INSTALLED_APPS) + ('djangorestframework.tests',) + self.settings_manager.set(INSTALLED_APPS=installed_apps) \ No newline at end of file From d53f7f45b4297aaf247aefd85093e2a0b413b4c4 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 14 Dec 2011 20:10:06 +0000 Subject: [PATCH 02/53] Use 1.4's CSRFMiddleware, so that PUT and DELETE get CSRF validation if session authentication is being used --- djangorestframework/authentication.py | 2 +- djangorestframework/compat.py | 225 ++++++++++++++++++++++- djangorestframework/runtests/settings.py | 1 - djangorestframework/tests/mixins.py | 4 +- djangorestframework/tests/models.py | 2 +- djangorestframework/tests/modelviews.py | 3 +- djangorestframework/tests/serializer.py | 4 +- 7 files changed, 229 insertions(+), 12 deletions(-) diff --git a/djangorestframework/authentication.py b/djangorestframework/authentication.py index be22103e6..806021fe7 100644 --- a/djangorestframework/authentication.py +++ b/djangorestframework/authentication.py @@ -8,7 +8,7 @@ The set of authentication methods which are used is then specified by setting th """ from django.contrib.auth import authenticate -from django.middleware.csrf import CsrfViewMiddleware +from djangorestframework.compat import CsrfViewMiddleware from djangorestframework.utils import as_tuple import base64 diff --git a/djangorestframework/compat.py b/djangorestframework/compat.py index 6147c3647..cae8c7b7e 100644 --- a/djangorestframework/compat.py +++ b/djangorestframework/compat.py @@ -1,24 +1,25 @@ """ The :mod:`compat` module provides support for backwards compatibility with older versions of django/python. """ +import django -# cStringIO only if it's available +# cStringIO only if it's available, otherwise StringIO try: import cStringIO as StringIO except ImportError: import StringIO -# parse_qs +# parse_qs from 'urlparse' module unless python 2.5, in which case from 'cgi' try: - # python >= ? + # python >= 2.6 from urlparse import parse_qs except ImportError: - # python <= ? + # python < 2.6 from cgi import parse_qs -# django.test.client.RequestFactory (Django >= 1.3) +# django.test.client.RequestFactory (Required for Django < 1.3) try: from django.test.client import RequestFactory except ImportError: @@ -156,6 +157,220 @@ except ImportError: def head(self, request, *args, **kwargs): return self.get(request, *args, **kwargs) +# PUT, DELETE do not require CSRF until 1.4. They should. Make it better. +if django.VERSION >= (1, 4): + from django.middleware.csrf import CsrfViewMiddleware +else: + import hashlib + import re + import random + import logging + import urlparse + + from django.conf import settings + from django.core.urlresolvers import get_callable + + try: + from logging import NullHandler + except ImportError: + class NullHandler(logging.Handler): + def emit(self, record): + pass + + logger = logging.getLogger('django.request') + + if not logger.handlers: + logger.addHandler(NullHandler()) + + def same_origin(url1, url2): + """ + Checks if two URLs are 'same-origin' + """ + p1, p2 = urlparse.urlparse(url1), urlparse.urlparse(url2) + return p1[0:2] == p2[0:2] + + def constant_time_compare(val1, val2): + """ + Returns True if the two strings are equal, False otherwise. + + The time taken is independent of the number of characters that match. + """ + if len(val1) != len(val2): + return False + result = 0 + for x, y in zip(val1, val2): + result |= ord(x) ^ ord(y) + return result == 0 + + # Use the system (hardware-based) random number generator if it exists. + if hasattr(random, 'SystemRandom'): + randrange = random.SystemRandom().randrange + else: + randrange = random.randrange + _MAX_CSRF_KEY = 18446744073709551616L # 2 << 63 + + REASON_NO_REFERER = "Referer checking failed - no Referer." + REASON_BAD_REFERER = "Referer checking failed - %s does not match %s." + REASON_NO_CSRF_COOKIE = "CSRF cookie not set." + REASON_BAD_TOKEN = "CSRF token missing or incorrect." + + + def _get_failure_view(): + """ + Returns the view to be used for CSRF rejections + """ + return get_callable(settings.CSRF_FAILURE_VIEW) + + + def _get_new_csrf_key(): + return hashlib.md5("%s%s" % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest() + + + def get_token(request): + """ + Returns the the CSRF token required for a POST form. The token is an + alphanumeric value. + + A side effect of calling this function is to make the the csrf_protect + decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie' + header to the outgoing response. For this reason, you may need to use this + function lazily, as is done by the csrf context processor. + """ + request.META["CSRF_COOKIE_USED"] = True + return request.META.get("CSRF_COOKIE", None) + + + def _sanitize_token(token): + # Allow only alphanum, and ensure we return a 'str' for the sake of the post + # processing middleware. + token = re.sub('[^a-zA-Z0-9]', '', str(token.decode('ascii', 'ignore'))) + if token == "": + # In case the cookie has been truncated to nothing at some point. + return _get_new_csrf_key() + else: + return token + + class CsrfViewMiddleware(object): + """ + Middleware that requires a present and correct csrfmiddlewaretoken + for POST requests that have a CSRF cookie, and sets an outgoing + CSRF cookie. + + This middleware should be used in conjunction with the csrf_token template + tag. + """ + # The _accept and _reject methods currently only exist for the sake of the + # requires_csrf_token decorator. + def _accept(self, request): + # Avoid checking the request twice by adding a custom attribute to + # request. This will be relevant when both decorator and middleware + # are used. + request.csrf_processing_done = True + return None + + def _reject(self, request, reason): + return _get_failure_view()(request, reason=reason) + + def process_view(self, request, callback, callback_args, callback_kwargs): + + if getattr(request, 'csrf_processing_done', False): + return None + + try: + csrf_token = _sanitize_token(request.COOKIES[settings.CSRF_COOKIE_NAME]) + # Use same token next time + request.META['CSRF_COOKIE'] = csrf_token + except KeyError: + csrf_token = None + # Generate token and store it in the request, so it's available to the view. + request.META["CSRF_COOKIE"] = _get_new_csrf_key() + + # Wait until request.META["CSRF_COOKIE"] has been manipulated before + # bailing out, so that get_token still works + if getattr(callback, 'csrf_exempt', False): + return None + + # Assume that anything not defined as 'safe' by RC2616 needs protection. + if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): + if getattr(request, '_dont_enforce_csrf_checks', False): + # Mechanism to turn off CSRF checks for test suite. It comes after + # the creation of CSRF cookies, so that everything else continues to + # work exactly the same (e.g. cookies are sent etc), but before the + # any branches that call reject() + return self._accept(request) + + if request.is_secure(): + # Suppose user visits http://example.com/ + # An active network attacker,(man-in-the-middle, MITM) sends a + # POST form which targets https://example.com/detonate-bomb/ and + # submits it via javascript. + # + # The attacker will need to provide a CSRF cookie and token, but + # that is no problem for a MITM and the session independent + # nonce we are using. So the MITM can circumvent the CSRF + # protection. This is true for any HTTP connection, but anyone + # using HTTPS expects better! For this reason, for + # https://example.com/ we need additional protection that treats + # http://example.com/ as completely untrusted. Under HTTPS, + # Barth et al. found that the Referer header is missing for + # same-domain requests in only about 0.2% of cases or less, so + # we can use strict Referer checking. + referer = request.META.get('HTTP_REFERER') + if referer is None: + logger.warning('Forbidden (%s): %s' % (REASON_NO_REFERER, request.path), + extra={ + 'status_code': 403, + 'request': request, + } + ) + return self._reject(request, REASON_NO_REFERER) + + # Note that request.get_host() includes the port + good_referer = 'https://%s/' % request.get_host() + if not same_origin(referer, good_referer): + reason = REASON_BAD_REFERER % (referer, good_referer) + logger.warning('Forbidden (%s): %s' % (reason, request.path), + extra={ + 'status_code': 403, + 'request': request, + } + ) + return self._reject(request, reason) + + if csrf_token is None: + # No CSRF cookie. For POST requests, we insist on a CSRF cookie, + # and in this way we can avoid all CSRF attacks, including login + # CSRF. + logger.warning('Forbidden (%s): %s' % (REASON_NO_CSRF_COOKIE, request.path), + extra={ + 'status_code': 403, + 'request': request, + } + ) + return self._reject(request, REASON_NO_CSRF_COOKIE) + + # check non-cookie token for match + request_csrf_token = "" + if request.method == "POST": + request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') + + if request_csrf_token == "": + # Fall back to X-CSRFToken, to make things easier for AJAX, + # and possible for PUT/DELETE + request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '') + + if not constant_time_compare(request_csrf_token, csrf_token): + logger.warning('Forbidden (%s): %s' % (REASON_BAD_TOKEN, request.path), + extra={ + 'status_code': 403, + 'request': request, + } + ) + return self._reject(request, REASON_BAD_TOKEN) + + return self._accept(request) + + # Markdown is optional try: import markdown diff --git a/djangorestframework/runtests/settings.py b/djangorestframework/runtests/settings.py index a38ba8edf..9b3c2c920 100644 --- a/djangorestframework/runtests/settings.py +++ b/djangorestframework/runtests/settings.py @@ -95,7 +95,6 @@ INSTALLED_APPS = ( # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', 'djangorestframework', - 'djangorestframework.tests', ) # OAuth support is optional, so we only test oauth if it's installed. diff --git a/djangorestframework/tests/mixins.py b/djangorestframework/tests/mixins.py index 65cf4a45a..3b814aa7e 100644 --- a/djangorestframework/tests/mixins.py +++ b/djangorestframework/tests/mixins.py @@ -8,13 +8,15 @@ from djangorestframework.mixins import CreateModelMixin, PaginatorMixin from djangorestframework.resources import ModelResource from djangorestframework.response import Response from djangorestframework.tests.models import CustomUser +from djangorestframework.tests.testcases import TestModelsTestCase from djangorestframework.views import View -class TestModelCreation(TestCase): +class TestModelCreation(TestModelsTestCase): """Tests on CreateModelMixin""" def setUp(self): + super(TestModelsTestCase, self).setUp() self.req = RequestFactory() def test_creation(self): diff --git a/djangorestframework/tests/models.py b/djangorestframework/tests/models.py index 61da1d457..39ac59e70 100644 --- a/djangorestframework/tests/models.py +++ b/djangorestframework/tests/models.py @@ -25,4 +25,4 @@ class UserGroupMap(models.Model): def get_absolute_url(self): return ('user_group_map', (), { 'pk': self.id - }) \ No newline at end of file + }) diff --git a/djangorestframework/tests/modelviews.py b/djangorestframework/tests/modelviews.py index 2fd1878ac..3fbc8ab61 100644 --- a/djangorestframework/tests/modelviews.py +++ b/djangorestframework/tests/modelviews.py @@ -5,6 +5,7 @@ from django.contrib.auth.models import Group, User from djangorestframework.resources import ModelResource from djangorestframework.views import ListOrCreateModelView, InstanceModelView from djangorestframework.tests.models import CustomUser +from djangorestframework.tests.testcases import TestModelsTestCase class GroupResource(ModelResource): model = Group @@ -31,7 +32,7 @@ urlpatterns = patterns('', ) -class ModelViewTests(TestCase): +class ModelViewTests(TestModelsTestCase): """Test the model views djangorestframework provides""" urls = 'djangorestframework.tests.modelviews' diff --git a/djangorestframework/tests/serializer.py b/djangorestframework/tests/serializer.py index fff72c024..5f42e7e2b 100644 --- a/djangorestframework/tests/serializer.py +++ b/djangorestframework/tests/serializer.py @@ -56,8 +56,8 @@ class TestFieldNesting(TestCase): self.serialize = self.serializer.serialize class M1(models.Model): - field1 = models.CharField() - field2 = models.CharField() + field1 = models.CharField(max_length=256) + field2 = models.CharField(max_length=256) class M2(models.Model): field = models.OneToOneField(M1) From 67b13bd1bc79590b5dcc3f9e2b99c43be91e00eb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 14 Dec 2011 20:11:02 +0000 Subject: [PATCH 03/53] More general requirements. --- requirements.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index da076b796..a89f66d82 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ # We need Django. Duh. # coverage isn't strictly a requirement, but it's useful. -Django==1.2.4 -wsgiref==0.1.2 -coverage==3.4 +Django +coverage From 8cabab2703be3a2e21e79b945659a7b14df89e71 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 14 Dec 2011 20:19:17 +0000 Subject: [PATCH 04/53] CSRF for non-dict like .DATA. Fixes #85 --- djangorestframework/authentication.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/djangorestframework/authentication.py b/djangorestframework/authentication.py index 806021fe7..0a3c1bd84 100644 --- a/djangorestframework/authentication.py +++ b/djangorestframework/authentication.py @@ -88,18 +88,27 @@ class UserLoggedInAuthentication(BaseAuthentication): Returns a :obj:`User` if the request session currently has a logged in user. Otherwise returns :const:`None`. """ - # TODO: Switch this back to request.POST, and let FormParser/MultiPartParser deal with the consequences. + # TODO: Might be cleaner to switch this back to using request.POST, + # and let FormParser/MultiPartParser deal with the consequences. if getattr(request, 'user', None) and request.user.is_active: - # If this is a POST request we enforce CSRF validation. + # Enforce CSRF validation for session based authentication. + + # Temporarily replace request.POST with .DATA, to use our generic parsing. + # If DATA is not dict-like, use an empty dict. + if request.method.upper() == 'POST': + if hasattr(self.view.DATA, 'get'): + request._post = self.view.DATA + else: + request._post = {} + + resp = CsrfViewMiddleware().process_view(request, None, (), {}) + + # Replace request.POST if request.method.upper() == 'POST': - # Temporarily replace request.POST with .DATA, - # so that we use our more generic request parsing - request._post = self.view.DATA - resp = CsrfViewMiddleware().process_view(request, None, (), {}) del(request._post) - if resp is not None: # csrf failed - return None - return request.user + + if resp is None: # csrf passed + return request.user return None From 4a60575132b2221e9abca432bbef57611b629a97 Mon Sep 17 00:00:00 2001 From: alazaro Date: Wed, 14 Dec 2011 23:36:13 +0100 Subject: [PATCH 05/53] Fixed test broken by markdown update Removed unused code. No longer needed with markdown 2.1.0. --- djangorestframework/compat.py | 37 ++---------------------- djangorestframework/tests/description.py | 6 ++-- 2 files changed, 6 insertions(+), 37 deletions(-) diff --git a/djangorestframework/compat.py b/djangorestframework/compat.py index cae8c7b7e..b7eedf85c 100644 --- a/djangorestframework/compat.py +++ b/djangorestframework/compat.py @@ -374,48 +374,17 @@ else: # Markdown is optional try: import markdown - import re - class CustomSetextHeaderProcessor(markdown.blockprocessors.BlockProcessor): - """ - Override `markdown`'s :class:`SetextHeaderProcessor`, so that ==== headers are

and ---- headers are

. - - We use

for the resource name. - """ - - # Detect Setext-style header. Must be first 2 lines of block. - RE = re.compile(r'^.*?\n[=-]{3,}', re.MULTILINE) - - def test(self, parent, block): - return bool(self.RE.match(block)) - - def run(self, parent, blocks): - lines = blocks.pop(0).split('\n') - # Determine level. ``=`` is 1 and ``-`` is 2. - if lines[1].startswith('='): - level = 2 - else: - level = 3 - h = markdown.etree.SubElement(parent, 'h%d' % level) - h.text = lines[0].strip() - if len(lines) > 2: - # Block contains additional lines. Add to master blocks for later. - blocks.insert(0, '\n'.join(lines[2:])) - def apply_markdown(text): """ - Simple wrapper around :func:`markdown.markdown` to apply our :class:`CustomSetextHeaderProcessor`, - and also set the base level of '#' style headers to

. + Simple wrapper around :func:`markdown.markdown` to set the base level + of '#' style headers to

. """ extensions = ['headerid(level=2)'] safe_mode = False, - output_format = markdown.DEFAULT_OUTPUT_FORMAT - md = markdown.Markdown(extensions=markdown.load_extensions(extensions), - safe_mode=safe_mode, - output_format=output_format) - md.parser.blockprocessors['setextheader'] = CustomSetextHeaderProcessor(md.parser) + md = markdown.Markdown(extensions=extensions, safe_mode=safe_mode) return md.convert(text) except ImportError: diff --git a/djangorestframework/tests/description.py b/djangorestframework/tests/description.py index 1ce291124..17bb43306 100644 --- a/djangorestframework/tests/description.py +++ b/djangorestframework/tests/description.py @@ -20,16 +20,16 @@ indented # hash style header #""" # If markdown is installed we also test it's working (and that our wrapped forces '=' to h2 and '-' to h3) -MARKED_DOWN = """

an example docstring

+MARKED_DOWN = """

an example docstring

  • list
  • list
-

another header

+

another header

code block
 

indented

-

hash style header

""" +

hash style header

""" class TestViewNamesAndDescriptions(TestCase): From e2b9359a2d903b2010dbf47565f63adebbdbc21b Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Thu, 15 Dec 2011 00:27:29 +0100 Subject: [PATCH 06/53] Adding a docs env to the tox project. This will run the linkcheck and build the html docs, turning any warnings into errors. Also changed the theme to sphinx-doc and added version to be displayed in the docs. --- docs/check_sphinx.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 docs/check_sphinx.py diff --git a/docs/check_sphinx.py b/docs/check_sphinx.py new file mode 100644 index 000000000..c4ce03177 --- /dev/null +++ b/docs/check_sphinx.py @@ -0,0 +1,16 @@ +import pytest +import subprocess + +def test_linkcheck(tmpdir): + doctrees = tmpdir.join("doctrees") + htmldir = tmpdir.join("html") + subprocess.check_call( + ["sphinx-build", "-W", "-blinkcheck", + "-d", str(doctrees), ".", str(htmldir)]) + +def test_build_docs(tmpdir): + doctrees = tmpdir.join("doctrees") + htmldir = tmpdir.join("html") + subprocess.check_call([ + "sphinx-build", "-W", "-bhtml", + "-d", str(doctrees), ".", str(htmldir)]) From 0f16db76146bed8c0c425fac862c43ccbe1fa7a7 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Thu, 15 Dec 2011 00:31:57 +0100 Subject: [PATCH 07/53] see previous commit message. --- docs/conf.py | 10 +++-- docs/index.rst | 1 - docs/templates/layout.html | 3 +- tox.ini | 90 +++++++++++++++++++++----------------- 4 files changed, 60 insertions(+), 44 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 503b40597..0a9fe068c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -55,9 +55,13 @@ copyright = u'2011, Tom Christie' # built documents. # # The short X.Y version. -version = '0.1' + +import djangorestframework + +version = djangorestframework.__version__ + # The full version, including alpha/beta/rc tags. -release = '0.1' +release = version autodoc_member_order='bysource' @@ -100,7 +104,7 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = 'sphinxdoc' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/docs/index.rst b/docs/index.rst index 309effab6..6038b7df5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -45,7 +45,6 @@ Requirements Installation ------------ - You can install Django REST framework using ``pip`` or ``easy_install``:: pip install djangorestframework diff --git a/docs/templates/layout.html b/docs/templates/layout.html index 6ba9e6ff0..705325542 100644 --- a/docs/templates/layout.html +++ b/docs/templates/layout.html @@ -23,4 +23,5 @@ })(); -{% endblock %} +{% endblock %}{% block footer %} + diff --git a/tox.ini b/tox.ini index 5a9065334..b126725ff 100644 --- a/tox.ini +++ b/tox.ini @@ -37,7 +37,7 @@ deps= django==1.2.4 coverage==3.4 unittest-xml-reporting==1.2 - Pyyaml==3.10 + Pyyaml==3.10 [testenv:py27-django12] basepython=python2.7 @@ -45,7 +45,7 @@ deps= django==1.2.4 coverage==3.4 unittest-xml-reporting==1.2 - Pyyaml==3.10 + Pyyaml==3.10 [testenv:py25-django13] basepython=python2.5 @@ -53,23 +53,23 @@ deps= django==1.3 coverage==3.4 unittest-xml-reporting==1.2 - Pyyaml==3.10 + Pyyaml==3.10 [testenv:py26-django13] basepython=python2.6 deps= django==1.3 coverage==3.4 - unittest-xml-reporting==1.2 - Pyyaml==3.10 + unittest-xml-reporting==1.2 + Pyyaml==3.10 [testenv:py27-django13] basepython=python2.7 deps= django==1.3 coverage==3.4 - unittest-xml-reporting==1.2 - Pyyaml==3.10 + unittest-xml-reporting==1.2 + Pyyaml==3.10 ####################################### EXAMPLES ################################################ @@ -80,12 +80,12 @@ commands= deps= django==1.2.4 coverage==3.4 - wsgiref==0.1.2 - Pygments==1.4 - httplib2==0.6.0 - Markdown==2.0.3 + wsgiref==0.1.2 + Pygments==1.4 + httplib2==0.6.0 + Markdown==2.0.3 unittest-xml-reporting==1.2 - Pyyaml==3.10 + Pyyaml==3.10 [testenv:py26-django12e] basepython=python2.6 @@ -94,12 +94,12 @@ commands= deps= django==1.2.4 coverage==3.4 - wsgiref==0.1.2 - Pygments==1.4 - httplib2==0.6.0 - Markdown==2.0.3 + wsgiref==0.1.2 + Pygments==1.4 + httplib2==0.6.0 + Markdown==2.0.3 unittest-xml-reporting==1.2 - Pyyaml==3.10 + Pyyaml==3.10 [testenv:py27-django12e] basepython=python2.7 @@ -108,12 +108,12 @@ commands= deps= django==1.2.4 coverage==3.4 - wsgiref==0.1.2 - Pygments==1.4 - httplib2==0.6.0 - Markdown==2.0.3 + wsgiref==0.1.2 + Pygments==1.4 + httplib2==0.6.0 + Markdown==2.0.3 unittest-xml-reporting==1.2 - Pyyaml==3.10 + Pyyaml==3.10 [testenv:py25-django13e] basepython=python2.5 @@ -122,12 +122,12 @@ commands= deps= django==1.3 coverage==3.4 - wsgiref==0.1.2 - Pygments==1.4 - httplib2==0.6.0 - Markdown==2.0.3 + wsgiref==0.1.2 + Pygments==1.4 + httplib2==0.6.0 + Markdown==2.0.3 unittest-xml-reporting==1.2 - Pyyaml==3.10 + Pyyaml==3.10 [testenv:py26-django13e] basepython=python2.6 @@ -136,12 +136,12 @@ commands= deps= django==1.3 coverage==3.4 - wsgiref==0.1.2 - Pygments==1.4 - httplib2==0.6.0 - Markdown==2.0.3 - unittest-xml-reporting==1.2 - Pyyaml==3.10 + wsgiref==0.1.2 + Pygments==1.4 + httplib2==0.6.0 + Markdown==2.0.3 + unittest-xml-reporting==1.2 + Pyyaml==3.10 [testenv:py27-django13e] basepython=python2.7 @@ -150,9 +150,21 @@ commands= deps= django==1.3 coverage==3.4 - wsgiref==0.1.2 - Pygments==1.4 - httplib2==0.6.0 - Markdown==2.0.3 - unittest-xml-reporting==1.2 - Pyyaml==3.10 \ No newline at end of file + wsgiref==0.1.2 + Pygments==1.4 + httplib2==0.6.0 + Markdown==2.0.3 + unittest-xml-reporting==1.2 + Pyyaml==3.10 + +##########################################DOCS################################################# + +[testenv:docs] +basepython=python +changedir=docs +deps= + sphinx + pytest + django==1.3 +commands= + py.test --tb=line -v --junitxml=junit-{envname}.xml check_sphinx.py From 670dbe6d75c96efe77b94146ec48fc9543109f1e Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Thu, 15 Dec 2011 01:16:09 +0100 Subject: [PATCH 08/53] Don't output html to a tempdir, but keep them. --- docs/check_sphinx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/check_sphinx.py b/docs/check_sphinx.py index c4ce03177..4a96a8b0f 100644 --- a/docs/check_sphinx.py +++ b/docs/check_sphinx.py @@ -10,7 +10,7 @@ def test_linkcheck(tmpdir): def test_build_docs(tmpdir): doctrees = tmpdir.join("doctrees") - htmldir = tmpdir.join("html") + htmldir = "html" #we want to keep the docs subprocess.check_call([ "sphinx-build", "-W", "-bhtml", "-d", str(doctrees), ".", str(htmldir)]) From 59904576a2dcdaae8a5537af0109ca23ae99b6bb Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Thu, 15 Dec 2011 12:25:56 +0100 Subject: [PATCH 09/53] reduced output to errors and warnings only --- docs/check_sphinx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/check_sphinx.py b/docs/check_sphinx.py index 4a96a8b0f..74874eede 100644 --- a/docs/check_sphinx.py +++ b/docs/check_sphinx.py @@ -5,12 +5,12 @@ def test_linkcheck(tmpdir): doctrees = tmpdir.join("doctrees") htmldir = tmpdir.join("html") subprocess.check_call( - ["sphinx-build", "-W", "-blinkcheck", + ["sphinx-build", "-q", "-blinkcheck", "-d", str(doctrees), ".", str(htmldir)]) def test_build_docs(tmpdir): doctrees = tmpdir.join("doctrees") htmldir = "html" #we want to keep the docs subprocess.check_call([ - "sphinx-build", "-W", "-bhtml", + "sphinx-build", "-q", "-bhtml", "-d", str(doctrees), ".", str(htmldir)]) From beccc54d114b2b707a47ce931561e60f714f0a8b Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Thu, 15 Dec 2011 14:56:01 +0100 Subject: [PATCH 10/53] Update docs/howto/setup.rst --- docs/howto/setup.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto/setup.rst b/docs/howto/setup.rst index b4fbc0372..a9053f733 100644 --- a/docs/howto/setup.rst +++ b/docs/howto/setup.rst @@ -22,7 +22,7 @@ This will be the case by default so you shouldn't normally need to do anything h Admin Styling ------------- -Django REST framework uses the admin media for styling. When running using Django's testserver this is automatically served for you, but once you move onto a production server, you'll want to make sure you serve the admin media separately, exactly as you would do `if using the Django admin `_. +Django REST framework uses the admin media for styling. When running using Django's testserver this is automatically served for you, but once you move onto a production server, you'll want to make sure you serve the admin media separately, exactly as you would do `if using the Django admin `_. * Ensure that the ``ADMIN_MEDIA_PREFIX`` is set appropriately and that you are serving the admin media. (Django's testserver will automatically serve the admin media for you) From 83910ff7a418e4eea1965cb597da32e7f0bd25a9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 15 Dec 2011 16:20:37 +0000 Subject: [PATCH 11/53] Update requirements. Refs #96 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a89f66d82..b39cd674d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # We need Django. Duh. # coverage isn't strictly a requirement, but it's useful. -Django +Django>=1.2 coverage From 28ccabf16565fcf733fe3a3bc0fbcd427d011f34 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 15 Dec 2011 16:22:10 +0000 Subject: [PATCH 12/53] Use coverage>=3.4, since that's what we've got in tox.ini at the moment. Refs #96 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b39cd674d..2b54c95b6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ # coverage isn't strictly a requirement, but it's useful. Django>=1.2 -coverage +coverage>=3.4 From 8af629f566f3973589256021501cb51b9a5383a7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 16 Dec 2011 10:06:02 +0000 Subject: [PATCH 13/53] Fold license text to 80 chars --- LICENSE | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index 272caf498..025dccf1a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,22 @@ Copyright (c) 2011, Tom Christie All rights reserved. -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 9f63e8dd1d23f9975e90e92d9aaf3def321b5e9b Mon Sep 17 00:00:00 2001 From: Anthony Nemitz Date: Mon, 19 Dec 2011 22:09:09 -0800 Subject: [PATCH 14/53] ADMIN_MEDIA_PREFIX is deprecated in django1.4 --- djangorestframework/renderers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangorestframework/renderers.py b/djangorestframework/renderers.py index aae2cab25..3f951ba1f 100644 --- a/djangorestframework/renderers.py +++ b/djangorestframework/renderers.py @@ -326,7 +326,7 @@ class DocumentingTemplateRenderer(BaseRenderer): 'logout_url': logout_url, 'FORMAT_PARAM': self._FORMAT_QUERY_PARAM, 'METHOD_PARAM': getattr(self.view, '_METHOD_PARAM', None), - 'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX + 'ADMIN_MEDIA_PREFIX': getattr(settings, 'ADMIN_MEDIA_PREFIX', None), }) ret = template.render(context) From 443694ebe6195547a9078b7d2586c652676e43e7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 20 Dec 2011 12:20:17 +0000 Subject: [PATCH 15/53] Added Anthony Nemitz --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 9ddcb9adc..4d03fdc75 100644 --- a/AUTHORS +++ b/AUTHORS @@ -19,6 +19,7 @@ Danilo Bargen Andrew McCloud Thomas Steinacher Meurig Freeman +Anthony Nemitz THANKS TO: From 90ddec03b7e6c3236d44eafe1a3c17db7a662780 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Wed, 21 Dec 2011 01:06:24 +0100 Subject: [PATCH 16/53] Fixes #94 Modified alazaro's commit sot that both markdown < 2.1 and >= 2.1 are supported The test checks if either matches the old or the new style. --- djangorestframework/compat.py | 41 ++++++++++++++++++++++-- djangorestframework/tests/description.py | 22 +++++++++++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/djangorestframework/compat.py b/djangorestframework/compat.py index b7eedf85c..ab0d80f3c 100644 --- a/djangorestframework/compat.py +++ b/djangorestframework/compat.py @@ -374,7 +374,35 @@ else: # Markdown is optional try: import markdown - + + class CustomSetextHeaderProcessor(markdown.blockprocessors.BlockProcessor): + """ + Class for markdown < 2.1 + + Override `markdown`'s :class:`SetextHeaderProcessor`, so that ==== headers are

and ---- heade + + We use

for the resource name. + """ + import re + # Detect Setext-style header. Must be first 2 lines of block. + RE = re.compile(r'^.*?\n[=-]{3,}', re.MULTILINE) + + def test(self, parent, block): + return bool(self.RE.match(block)) + + def run(self, parent, blocks): + lines = blocks.pop(0).split('\n') + # Determine level. ``=`` is 1 and ``-`` is 2. + if lines[1].startswith('='): + level = 2 + else: + level = 3 + h = markdown.etree.SubElement(parent, 'h%d' % level) + h.text = lines[0].strip() + if len(lines) > 2: + # Block contains additional lines. Add to master blocks for later. + blocks.insert(0, '\n'.join(lines[2:])) + def apply_markdown(text): """ Simple wrapper around :func:`markdown.markdown` to set the base level @@ -384,7 +412,15 @@ try: extensions = ['headerid(level=2)'] safe_mode = False, - md = markdown.Markdown(extensions=extensions, safe_mode=safe_mode) + if markdown.version < (2, 1): + output_format = markdown.DEFAULT_OUTPUT_FORMAT + + md = markdown.Markdown(extensions=markdown.load_extensions(extensions), + safe_mode=safe_mode, + output_format=output_format) + md.parser.blockprocessors['setextheader'] = CustomSetextHeaderProcessor(md.parser) + else: + md = markdown.Markdown(extensions=extensions, safe_mode=safe_mode) return md.convert(text) except ImportError: @@ -395,3 +431,4 @@ try: import yaml except ImportError: yaml = None + diff --git a/djangorestframework/tests/description.py b/djangorestframework/tests/description.py index 17bb43306..c0424c24a 100644 --- a/djangorestframework/tests/description.py +++ b/djangorestframework/tests/description.py @@ -19,8 +19,22 @@ indented # hash style header #""" -# If markdown is installed we also test it's working (and that our wrapped forces '=' to h2 and '-' to h3) -MARKED_DOWN = """

an example docstring

+# If markdown is installed we also test it's working +# (and that our wrapped forces '=' to h2 and '-' to h3) + +# We support markdown < 2.1 and markdown >= 2.1 +MARKED_DOWN_lt_21 = """

an example docstring

+
    +
  • list
  • +
  • list
  • +
+

another header

+
code block
+
+

indented

+

hash style header

""" + +MARKED_DOWN_gte_21 = """

an example docstring

  • list
  • list
  • @@ -92,4 +106,6 @@ class TestViewNamesAndDescriptions(TestCase): def test_markdown(self): """Ensure markdown to HTML works as expected""" if apply_markdown: - self.assertEquals(apply_markdown(DESCRIPTION), MARKED_DOWN) + gte_21_match = apply_markdown(DESCRIPTION) == MARKED_DOWN_gte_21 + lt_21_match = apply_markdown(DESCRIPTION) == MARKED_DOWN_lt_21 + self.assertTrue(gte_21_match or lt_21_match) From 4b3c63f1551ed40a7bc708250ee7fab4745cf74f Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Wed, 21 Dec 2011 01:22:10 +0100 Subject: [PATCH 17/53] Make sure the optional markdown functionality is also tested. --- tox.ini | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index b126725ff..e32f985ef 100644 --- a/tox.ini +++ b/tox.ini @@ -30,6 +30,8 @@ deps= coverage==3.4 unittest-xml-reporting==1.2 Pyyaml==3.10 + # Optional packages: + markdown [testenv:py26-django12] basepython=python2.6 @@ -38,6 +40,8 @@ deps= coverage==3.4 unittest-xml-reporting==1.2 Pyyaml==3.10 + # Optional packages: + markdown [testenv:py27-django12] basepython=python2.7 @@ -46,7 +50,9 @@ deps= coverage==3.4 unittest-xml-reporting==1.2 Pyyaml==3.10 - + # Optional packages: + markdown + [testenv:py25-django13] basepython=python2.5 deps= @@ -54,7 +60,8 @@ deps= coverage==3.4 unittest-xml-reporting==1.2 Pyyaml==3.10 - + markdown + [testenv:py26-django13] basepython=python2.6 deps= @@ -62,7 +69,9 @@ deps= coverage==3.4 unittest-xml-reporting==1.2 Pyyaml==3.10 - + # Optional packages: + markdown + [testenv:py27-django13] basepython=python2.7 deps= @@ -70,7 +79,8 @@ deps= coverage==3.4 unittest-xml-reporting==1.2 Pyyaml==3.10 - + # Optional packages: + markdown ####################################### EXAMPLES ################################################ [testenv:py25-django12e] From a3ddd9e4c945369d542e4559ad8b697bc6f5b9d3 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Wed, 21 Dec 2011 01:24:16 +0100 Subject: [PATCH 18/53] Fix style consistency. --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index e32f985ef..1375568cb 100644 --- a/tox.ini +++ b/tox.ini @@ -60,6 +60,7 @@ deps= coverage==3.4 unittest-xml-reporting==1.2 Pyyaml==3.10 + # Optional packages: markdown [testenv:py26-django13] @@ -81,6 +82,7 @@ deps= Pyyaml==3.10 # Optional packages: markdown + ####################################### EXAMPLES ################################################ [testenv:py25-django12e] From 6e6dc21376e781813e4faea7264d661c2e6d67f3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 21 Dec 2011 00:52:41 +0000 Subject: [PATCH 19/53] Fix version test. --- djangorestframework/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangorestframework/compat.py b/djangorestframework/compat.py index ab0d80f3c..e70b8730b 100644 --- a/djangorestframework/compat.py +++ b/djangorestframework/compat.py @@ -412,7 +412,7 @@ try: extensions = ['headerid(level=2)'] safe_mode = False, - if markdown.version < (2, 1): + if markdown.version_info < (2, 1): output_format = markdown.DEFAULT_OUTPUT_FORMAT md = markdown.Markdown(extensions=markdown.load_extensions(extensions), From 1096e06c2d9300658d702a7167b4ccf960992006 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Wed, 21 Dec 2011 02:23:26 +0100 Subject: [PATCH 20/53] set linkcheck_timeout to very large, to prevent timeouts on the CI. --- docs/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 0a9fe068c..78534ac3f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -224,3 +224,5 @@ html_static_path = [] #man_pages = [ # () #] + +linkcheck_timeout = 120 # seconds, set to extra large value for link_checks From ee4cba90814e083343c969af1a12d47ce7996b59 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 22 Dec 2011 10:30:13 +0000 Subject: [PATCH 21/53] Add CI link. --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 092010b64..38e22c111 100644 --- a/README.rst +++ b/README.rst @@ -16,6 +16,8 @@ Full documentation for the project is available at http://django-rest-framework. Issue tracking is on `GitHub `_. General questions should be taken to the `discussion group `_. +We also have a `Jenkins service `_ which runs our test suite. + Requirements: * Python (2.5, 2.6, 2.7 supported) From 500b0dcddc2d965a595eadadc70fb06d2648cc96 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Sat, 24 Dec 2011 13:18:48 +0100 Subject: [PATCH 22/53] Added epio configuration. --- examples/.epio-app | 1 + examples/epio.ini | 61 ++++++++++++++++++++++++++++++++++ examples/requirements-epio.txt | 3 ++ examples/settings.py | 4 +-- 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 examples/.epio-app create mode 100644 examples/epio.ini create mode 100644 examples/requirements-epio.txt diff --git a/examples/.epio-app b/examples/.epio-app new file mode 100644 index 000000000..47c4439d0 --- /dev/null +++ b/examples/.epio-app @@ -0,0 +1 @@ +rest diff --git a/examples/epio.ini b/examples/epio.ini new file mode 100644 index 000000000..00a90bce1 --- /dev/null +++ b/examples/epio.ini @@ -0,0 +1,61 @@ +# This is an example epio.ini file. +# We suggest you edit it to fit your application's needs. +# Documentation for the options is available at www.ep.io/docs/epioini/ + +[wsgi] + +# Location of your requirements file +requirements = requirements-epio.txt + + +[static] + +# Serve the static directory directly as /static +/static/admin = ../shortcuts/django-admin-media/ + + +[services] + +# Uncomment to enable the PostgreSQL service. +postgres = true + +# Uncomment to enable the Redis service +# redis = true + + +[checkout] + +# By default your code is put in a directory called 'app'. +# You can change that here. +# directory_name = my_project + + +[env] + +# Set any additional environment variables here. For example: +# IN_PRODUCTION = true + + +[symlinks] + +# Any symlinks you'd like to add. As an example, link the symlink 'config.py' +# to the real file 'configs/epio.py': +# config.py = configs/epio.py + + +# #### If you're using Django, you'll want to uncomment some or all of these lines #### +# [django] +# # Path to your project root, relative to this directory. +# base = . +# +# [static] +# Serve the admin media +# # Django 1.3 +# /static/admin = ../shortcuts/django-admin-media/ +# # Django 1.2 and below +# /media = ../shortcuts/django-admin-media/ +# +# [env] +# # Use a different settings module for ep.io (i.e. with DEBUG=False) +# DJANGO_SETTINGS_MODULE = production_settings + diff --git a/examples/requirements-epio.txt b/examples/requirements-epio.txt new file mode 100644 index 000000000..76ab5bd23 --- /dev/null +++ b/examples/requirements-epio.txt @@ -0,0 +1,3 @@ +Pygments==1.4 +Markdown==2.0.3 +djangorestframework diff --git a/examples/settings.py b/examples/settings.py index 2eab2cfa0..c47011fdb 100644 --- a/examples/settings.py +++ b/examples/settings.py @@ -61,7 +61,7 @@ MEDIA_URL = '' # but it does require the admin media be served. Django's test server will do # this for you automatically, but in production you'll want to make sure you # serve the admin media from somewhere. -ADMIN_MEDIA_PREFIX = '/admin-media/' +ADMIN_MEDIA_PREFIX = '/static/admin' # Make this unique, and don't share it with anybody. SECRET_KEY = 't&9mru2_k$t8e2-9uq-wu2a1)9v*us&j3i#lsqkt(lbx*vh1cu' @@ -118,4 +118,4 @@ if os.environ.get('HUDSON_URL', None): TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner' TEST_OUTPUT_VERBOSE = True TEST_OUTPUT_DESCRIPTIONS = True - TEST_OUTPUT_DIR = 'xmlrunner' \ No newline at end of file + TEST_OUTPUT_DIR = 'xmlrunner' From 1bdc5eacc6290c486796eb5ab8fa29092137dab6 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 29 Dec 2011 13:24:52 +0000 Subject: [PATCH 23/53] Add JSONP. Fixes #82 --- djangorestframework/renderers.py | 24 ++++++ djangorestframework/tests/renderers.py | 105 +++++++++++++++++-------- 2 files changed, 95 insertions(+), 34 deletions(-) diff --git a/djangorestframework/renderers.py b/djangorestframework/renderers.py index 3f951ba1f..2affd7f29 100644 --- a/djangorestframework/renderers.py +++ b/djangorestframework/renderers.py @@ -26,6 +26,7 @@ __all__ = ( 'BaseRenderer', 'TemplateRenderer', 'JSONRenderer', + 'JSONPRenderer', 'DocumentingHTMLRenderer', 'DocumentingXHTMLRenderer', 'DocumentingPlainTextRenderer', @@ -113,6 +114,28 @@ class JSONRenderer(BaseRenderer): return json.dumps(obj, cls=DateTimeAwareJSONEncoder, indent=indent, sort_keys=sort_keys) +class JSONPRenderer(JSONRenderer): + """ + Renderer which serializes to JSONP + """ + + media_type = 'application/json-p' + format = 'json-p' + renderer_class = JSONRenderer + callback_parameter = 'callback' + + def _get_callback(self): + return self.view.request.GET.get(self.callback_parameter, self.callback_parameter) + + def _get_renderer(self): + return self.renderer_class(self.view) + + def render(self, obj=None, media_type=None): + callback = self._get_callback() + json = self._get_renderer().render(obj, media_type) + return "%s(%s);" % (callback, json) + + class XMLRenderer(BaseRenderer): """ Renderer which serializes to XML. @@ -376,6 +399,7 @@ class DocumentingPlainTextRenderer(DocumentingTemplateRenderer): DEFAULT_RENDERERS = ( JSONRenderer, + JSONPRenderer, DocumentingHTMLRenderer, DocumentingXHTMLRenderer, DocumentingPlainTextRenderer, diff --git a/djangorestframework/tests/renderers.py b/djangorestframework/tests/renderers.py index 997fd5103..bd0d360cb 100644 --- a/djangorestframework/tests/renderers.py +++ b/djangorestframework/tests/renderers.py @@ -1,15 +1,14 @@ from django.conf.urls.defaults import patterns, url -from django import http from django.test import TestCase from djangorestframework import status +from djangorestframework.views import View from djangorestframework.compat import View as DjangoView -from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer,\ - XMLRenderer +from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \ + XMLRenderer, JSONPRenderer from djangorestframework.parsers import JSONParser, YAMLParser from djangorestframework.mixins import ResponseMixin from djangorestframework.response import Response -from djangorestframework.utils.mediatypes import add_media_type_param from StringIO import StringIO import datetime @@ -21,31 +20,41 @@ DUMMYCONTENT = 'dummycontent' RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x + class RendererA(BaseRenderer): media_type = 'mock/renderera' - format="formata" + format = "formata" def render(self, obj=None, media_type=None): return RENDERER_A_SERIALIZER(obj) + class RendererB(BaseRenderer): media_type = 'mock/rendererb' - format="formatb" + format = "formatb" def render(self, obj=None, media_type=None): return RENDERER_B_SERIALIZER(obj) + class MockView(ResponseMixin, DjangoView): renderers = (RendererA, RendererB) def get(self, request, **kwargs): response = Response(DUMMYSTATUS, DUMMYCONTENT) return self.render(response) - + + +class MockGETView(View): + def get(self, request, **kwargs): + return {'foo': ['bar', 'baz']} + urlpatterns = patterns('', url(r'^.*\.(?P.+)$', MockView.as_view(renderers=[RendererA, RendererB])), url(r'^$', MockView.as_view(renderers=[RendererA, RendererB])), + url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderers=[JSONRenderer, JSONPRenderer])), + url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderers=[JSONPRenderer])), ) @@ -92,7 +101,7 @@ class RendererIntegrationTests(TestCase): self.assertEquals(resp['Content-Type'], RendererB.media_type) self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT)) self.assertEquals(resp.status_code, DUMMYSTATUS) - + def test_specified_renderer_serializes_content_on_accept_query(self): """The '_accept' query string should behave in the same way as the Accept header.""" resp = self.client.get('/?_accept=%s' % RendererB.media_type) @@ -147,13 +156,7 @@ class RendererIntegrationTests(TestCase): self.assertEquals(resp.status_code, DUMMYSTATUS) _flat_repr = '{"foo": ["bar", "baz"]}' - -_indented_repr = """{ - "foo": [ - "bar", - "baz" - ] -}""" +_indented_repr = '{\n "foo": [\n "bar", \n "baz"\n ]\n}' class JSONRendererTests(TestCase): @@ -165,71 +168,106 @@ class JSONRendererTests(TestCase): """ Test basic JSON rendering. """ - obj = {'foo':['bar','baz']} + obj = {'foo': ['bar', 'baz']} renderer = JSONRenderer(None) content = renderer.render(obj, 'application/json') self.assertEquals(content, _flat_repr) def test_with_content_type_args(self): """ - Test JSON rendering with additional content type arguments supplied. + Test JSON rendering with additional content type arguments supplied. """ - obj = {'foo':['bar','baz']} + obj = {'foo': ['bar', 'baz']} renderer = JSONRenderer(None) content = renderer.render(obj, 'application/json; indent=2') self.assertEquals(content, _indented_repr) - + def test_render_and_parse(self): """ Test rendering and then parsing returns the original object. IE obj -> render -> parse -> obj. """ - obj = {'foo':['bar','baz']} + obj = {'foo': ['bar', 'baz']} renderer = JSONRenderer(None) parser = JSONParser(None) content = renderer.render(obj, 'application/json') (data, files) = parser.parse(StringIO(content)) - self.assertEquals(obj, data) + self.assertEquals(obj, data) +class JSONPRendererTests(TestCase): + """ + Tests specific to the JSONP Renderer + """ + + urls = 'djangorestframework.tests.renderers' + + def test_without_callback_with_json_renderer(self): + """ + Test JSONP rendering with View JSON Renderer. + """ + resp = self.client.get('/jsonp/jsonrenderer', + HTTP_ACCEPT='application/json-p') + self.assertEquals(resp.status_code, 200) + self.assertEquals(resp['Content-Type'], 'application/json-p') + self.assertEquals(resp.content, 'callback(%s);' % _flat_repr) + + def test_without_callback_without_json_renderer(self): + """ + Test JSONP rendering without View JSON Renderer. + """ + resp = self.client.get('/jsonp/nojsonrenderer', + HTTP_ACCEPT='application/json-p') + self.assertEquals(resp.status_code, 200) + self.assertEquals(resp['Content-Type'], 'application/json-p') + self.assertEquals(resp.content, 'callback(%s);' % _flat_repr) + + def test_with_callback(self): + """ + Test JSONP rendering with callback function name. + """ + callback_func = 'myjsonpcallback' + resp = self.client.get('/jsonp/nojsonrenderer?callback=' + callback_func, + HTTP_ACCEPT='application/json-p') + self.assertEquals(resp.status_code, 200) + self.assertEquals(resp['Content-Type'], 'application/json-p') + self.assertEquals(resp.content, '%s(%s);' % (callback_func, _flat_repr)) + if YAMLRenderer: _yaml_repr = 'foo: [bar, baz]\n' - - + class YAMLRendererTests(TestCase): """ Tests specific to the JSON Renderer """ - + def test_render(self): """ Test basic YAML rendering. """ - obj = {'foo':['bar','baz']} + obj = {'foo': ['bar', 'baz']} renderer = YAMLRenderer(None) content = renderer.render(obj, 'application/yaml') self.assertEquals(content, _yaml_repr) - - + def test_render_and_parse(self): """ Test rendering and then parsing returns the original object. IE obj -> render -> parse -> obj. """ - obj = {'foo':['bar','baz']} - + obj = {'foo': ['bar', 'baz']} + renderer = YAMLRenderer(None) parser = YAMLParser(None) - + content = renderer.render(obj, 'application/yaml') (data, files) = parser.parse(StringIO(content)) - self.assertEquals(obj, data) + self.assertEquals(obj, data) - class XMLRendererTestCase(TestCase): """ Tests specific to the XML Renderer @@ -285,8 +323,7 @@ class XMLRendererTestCase(TestCase): content = renderer.render({'field': None}, 'application/xml') self.assertXMLContains(content, '') - def assertXMLContains(self, xml, string): self.assertTrue(xml.startswith('\n')) self.assertTrue(xml.endswith('')) - self.assertTrue(string in xml, '%r not in %r' % (string, xml)) + self.assertTrue(string in xml, '%r not in %r' % (string, xml)) From 07349597ab936dc9887caa70b5d7d2860c897b12 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 29 Dec 2011 13:31:12 +0000 Subject: [PATCH 24/53] whitespace fixes --- djangorestframework/authentication.py | 18 ++--- djangorestframework/compat.py | 46 ++++++------ djangorestframework/parsers.py | 24 +++---- djangorestframework/renderers.py | 18 ++--- djangorestframework/resources.py | 46 ++++++------ djangorestframework/response.py | 8 +-- djangorestframework/runtests/runcoverage.py | 4 +- djangorestframework/runtests/runtests.py | 6 +- djangorestframework/runtests/settings.py | 2 +- djangorestframework/runtests/urls.py | 2 +- djangorestframework/serializer.py | 24 +++---- djangorestframework/status.py | 2 +- .../templatetags/urlize_quoted_links.py | 2 +- djangorestframework/tests/accept.py | 2 +- djangorestframework/tests/authentication.py | 4 +- djangorestframework/tests/breadcrumbs.py | 2 +- djangorestframework/tests/description.py | 12 ++-- djangorestframework/tests/files.py | 2 +- djangorestframework/tests/methods.py | 4 +- djangorestframework/tests/modelviews.py | 22 +++--- djangorestframework/tests/oauthentication.py | 46 ++++++------ djangorestframework/tests/package.py | 2 +- djangorestframework/tests/parsers.py | 66 ++++++++--------- djangorestframework/tests/response.py | 2 +- djangorestframework/tests/serializer.py | 2 +- djangorestframework/tests/status.py | 2 +- djangorestframework/tests/testcases.py | 4 +- djangorestframework/tests/throttling.py | 38 +++++----- djangorestframework/tests/validators.py | 72 +++++++++---------- djangorestframework/tests/views.py | 66 ++++++++--------- djangorestframework/urls.py | 2 +- djangorestframework/utils/__init__.py | 16 ++--- djangorestframework/utils/breadcrumbs.py | 12 ++-- djangorestframework/utils/description.py | 14 ++-- djangorestframework/utils/mediatypes.py | 4 +- examples/blogpost/resources.py | 8 +-- examples/blogpost/tests.py | 44 ++++++------ examples/mixin/urls.py | 2 +- examples/objectstore/urls.py | 4 +- examples/objectstore/views.py | 2 +- examples/permissionsexample/models.py | 2 +- examples/permissionsexample/views.py | 16 ++--- examples/pygments_api/forms.py | 2 +- examples/pygments_api/tests.py | 2 +- examples/pygments_api/urls.py | 2 +- examples/pygments_api/views.py | 4 +- examples/resourceexample/views.py | 2 +- examples/runtests.py | 4 +- examples/sandbox/views.py | 4 +- examples/settings.py | 6 +- 50 files changed, 351 insertions(+), 351 deletions(-) diff --git a/djangorestframework/authentication.py b/djangorestframework/authentication.py index 0a3c1bd84..8317af9e2 100644 --- a/djangorestframework/authentication.py +++ b/djangorestframework/authentication.py @@ -33,13 +33,13 @@ class BaseAuthentication(object): def authenticate(self, request): """ Authenticate the :obj:`request` and return a :obj:`User` or :const:`None`. [*]_ - + .. [*] The authentication context *will* typically be a :obj:`User`, but it need not be. It can be any user-like object so long as the permissions classes (see the :mod:`permissions` module) on the view can handle the object and use it to determine if the request has the required - permissions or not. - + permissions or not. + This can be an important distinction if you're implementing some token based authentication mechanism, where the authentication context may be more involved than simply mapping to a :obj:`User`. @@ -55,10 +55,10 @@ class BasicAuthentication(BaseAuthentication): def authenticate(self, request): """ Returns a :obj:`User` if a correct username and password have been supplied - using HTTP Basic authentication. Otherwise returns :const:`None`. + using HTTP Basic authentication. Otherwise returns :const:`None`. """ from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError - + if 'HTTP_AUTHORIZATION' in request.META: auth = request.META['HTTP_AUTHORIZATION'].split() if len(auth) == 2 and auth[0].lower() == "basic": @@ -66,17 +66,17 @@ class BasicAuthentication(BaseAuthentication): auth_parts = base64.b64decode(auth[1]).partition(':') except TypeError: return None - + try: uname, passwd = smart_unicode(auth_parts[0]), smart_unicode(auth_parts[2]) except DjangoUnicodeDecodeError: return None - + user = authenticate(username=uname, password=passwd) if user is not None and user.is_active: return user return None - + class UserLoggedInAuthentication(BaseAuthentication): """ @@ -92,7 +92,7 @@ class UserLoggedInAuthentication(BaseAuthentication): # and let FormParser/MultiPartParser deal with the consequences. if getattr(request, 'user', None) and request.user.is_active: # Enforce CSRF validation for session based authentication. - + # Temporarily replace request.POST with .DATA, to use our generic parsing. # If DATA is not dict-like, use an empty dict. if request.method.upper() == 'POST': diff --git a/djangorestframework/compat.py b/djangorestframework/compat.py index e70b8730b..381198116 100644 --- a/djangorestframework/compat.py +++ b/djangorestframework/compat.py @@ -18,32 +18,32 @@ except ImportError: # python < 2.6 from cgi import parse_qs - -# django.test.client.RequestFactory (Required for Django < 1.3) + +# django.test.client.RequestFactory (Required for Django < 1.3) try: from django.test.client import RequestFactory except ImportError: from django.test import Client from django.core.handlers.wsgi import WSGIRequest - + # From: http://djangosnippets.org/snippets/963/ # Lovely stuff class RequestFactory(Client): """ Class that lets you create mock :obj:`Request` objects for use in testing. - + Usage:: - + rf = RequestFactory() get_request = rf.get('/hello/') post_request = rf.post('/submit/', {'foo': 'bar'}) - + This class re-uses the :class:`django.test.client.Client` interface. Of which you can find the docs here__. - + __ http://www.djangoproject.com/documentation/testing/#the-test-client - - Once you have a `request` object you can pass it to any :func:`view` function, + + Once you have a `request` object you can pass it to any :func:`view` function, just as if that :func:`view` had been hooked up using a URLconf. """ def request(self, **request): @@ -69,30 +69,30 @@ except ImportError: try: from django.views.generic import View if not hasattr(View, 'head'): - # First implementation of Django class-based views did not include head method + # First implementation of Django class-based views did not include head method # in base View class - https://code.djangoproject.com/ticket/15668 class ViewPlusHead(View): def head(self, request, *args, **kwargs): return self.get(request, *args, **kwargs) View = ViewPlusHead - + except ImportError: from django import http from django.utils.functional import update_wrapper # from django.utils.log import getLogger # from django.utils.decorators import classonlymethod - + # logger = getLogger('django.request') - We'll just drop support for logger if running Django <= 1.2 # Might be nice to fix this up sometime to allow djangorestframework.compat.View to match 1.3's View more closely - + class View(object): """ Intentionally simple parent class for all views. Only implements dispatch-by-method and simple sanity checking. """ - + http_method_names = ['get', 'post', 'put', 'delete', 'head', 'options', 'trace'] - + def __init__(self, **kwargs): """ Constructor. Called in the URLconf; can contain helpful extra @@ -102,7 +102,7 @@ except ImportError: # instance, or raise an error. for key, value in kwargs.iteritems(): setattr(self, key, value) - + # @classonlymethod - We'll just us classmethod instead if running Django <= 1.2 @classmethod def as_view(cls, **initkwargs): @@ -118,19 +118,19 @@ except ImportError: if not hasattr(cls, key): raise TypeError(u"%s() received an invalid keyword %r" % ( cls.__name__, key)) - + def view(request, *args, **kwargs): self = cls(**initkwargs) return self.dispatch(request, *args, **kwargs) - + # take name and docstring from class update_wrapper(view, cls, updated=()) - + # and possible attributes set by decorators # like csrf_exempt from dispatch update_wrapper(view, cls.dispatch, assigned=()) return view - + def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the @@ -143,7 +143,7 @@ except ImportError: self.args = args self.kwargs = kwargs return handler(request, *args, **kwargs) - + def http_method_not_allowed(self, request, *args, **kwargs): allowed_methods = [m for m in self.http_method_names if hasattr(self, m)] #logger.warning('Method Not Allowed (%s): %s' % (request.method, request.path), @@ -374,7 +374,7 @@ else: # Markdown is optional try: import markdown - + class CustomSetextHeaderProcessor(markdown.blockprocessors.BlockProcessor): """ Class for markdown < 2.1 @@ -408,7 +408,7 @@ try: Simple wrapper around :func:`markdown.markdown` to set the base level of '#' style headers to

    . """ - + extensions = ['headerid(level=2)'] safe_mode = False, diff --git a/djangorestframework/parsers.py b/djangorestframework/parsers.py index 09e7f4d01..7d7650221 100644 --- a/djangorestframework/parsers.py +++ b/djangorestframework/parsers.py @@ -49,15 +49,15 @@ class BaseParser(object): in case the parser needs to access any metadata on the :obj:`View` object. """ self.view = view - + def can_handle_request(self, content_type): """ Returns :const:`True` if this parser is able to deal with the given *content_type*. - + The default implementation for this function is to check the *content_type* argument against the :attr:`media_type` attribute set on the class to see if they match. - + This may be overridden to provide for other behavior, but typically you'll instead want to just set the :attr:`media_type` attribute on the class. """ @@ -97,13 +97,13 @@ if yaml: """ Parses YAML-serialized data. """ - + media_type = 'application/yaml' - + def parse(self, stream): """ 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`. """ @@ -125,7 +125,7 @@ class PlainTextParser(BaseParser): def parse(self, stream): """ Returns a 2-tuple of `(data, files)`. - + `data` will simply be a string representing the body of the request. `files` will always be `None`. """ @@ -142,7 +142,7 @@ class FormParser(BaseParser): def parse(self, stream): """ Returns a 2-tuple of `(data, files)`. - + `data` will be a :class:`QueryDict` containing all the form parameters. `files` will always be :const:`None`. """ @@ -160,7 +160,7 @@ class MultiPartParser(BaseParser): def parse(self, stream): """ Returns a 2-tuple of `(data, files)`. - + `data` will be a :class:`QueryDict` containing all the form parameters. `files` will be a :class:`QueryDict` containing all the form files. """ @@ -194,9 +194,9 @@ class XMLParser(BaseParser): return (data, None) - def _type_convert(self, value): + def _type_convert(self, value): """ - Converts the value returned by the XMl parse into the equivalent + Converts the value returned by the XMl parse into the equivalent Python type """ if value is None: @@ -227,4 +227,4 @@ DEFAULT_PARSERS = ( JSONParser, ) if YAMLParser: - DEFAULT_PARSERS += ( YAMLParser, ) \ No newline at end of file + DEFAULT_PARSERS += ( YAMLParser, ) diff --git a/djangorestframework/renderers.py b/djangorestframework/renderers.py index 2affd7f29..0c80f07e8 100644 --- a/djangorestframework/renderers.py +++ b/djangorestframework/renderers.py @@ -3,7 +3,7 @@ Renderers are used to serialize a View's output into specific media types. Django REST framework also provides HTML and PlainText renderers that help self-document the API, by serializing the output along with documentation regarding the View, output status and headers, -and providing forms and links depending on the allowed methods, renderers and parsers on the View. +and providing forms and links depending on the allowed methods, renderers and parsers on the View. """ from django import forms from django.conf import settings @@ -40,7 +40,7 @@ class BaseRenderer(object): All renderers must extend this class, set the :attr:`media_type` attribute, and override the :meth:`render` method. """ - + _FORMAT_QUERY_PARAM = 'format' media_type = None @@ -82,7 +82,7 @@ class BaseRenderer(object): """ if obj is None: return '' - + return str(obj) @@ -118,7 +118,7 @@ class JSONPRenderer(JSONRenderer): """ Renderer which serializes to JSONP """ - + media_type = 'application/json-p' format = 'json-p' renderer_class = JSONRenderer @@ -158,10 +158,10 @@ if yaml: """ Renderer which serializes to YAML. """ - + media_type = 'application/yaml' format = 'yaml' - + def render(self, obj=None, media_type=None): """ Renders *obj* into serialized YAML. @@ -223,7 +223,7 @@ class DocumentingTemplateRenderer(BaseRenderer): content = renderers[0](view).render(obj, media_type) if not all(char in string.printable for char in content): return '[%d bytes of binary content]' - + return content @@ -259,7 +259,7 @@ class DocumentingTemplateRenderer(BaseRenderer): # If we still don't have a form instance then try to get an unbound form which can tunnel arbitrary content types if not form_instance: form_instance = self._get_generic_content_form(view) - + return form_instance @@ -351,7 +351,7 @@ class DocumentingTemplateRenderer(BaseRenderer): 'METHOD_PARAM': getattr(self.view, '_METHOD_PARAM', None), 'ADMIN_MEDIA_PREFIX': getattr(settings, 'ADMIN_MEDIA_PREFIX', None), }) - + ret = template.render(context) # Munge DELETE Response code to allow us to return content diff --git a/djangorestframework/resources.py b/djangorestframework/resources.py index 5770d07f9..bf9af6f04 100644 --- a/djangorestframework/resources.py +++ b/djangorestframework/resources.py @@ -34,7 +34,7 @@ class BaseResource(Serializer): Typically raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure. """ return data - + def filter_response(self, obj): """ Given the response content, filter it into a serializable object. @@ -47,11 +47,11 @@ class Resource(BaseResource): A Resource determines how a python object maps to some serializable data. Objects that a resource can act on include plain Python object instances, Django Models, and Django QuerySets. """ - + # The model attribute refers to the Django Model which this Resource maps to. # (The Model's class, rather than an instance of the Model) model = None - + # By default the set of returned fields will be the set of: # # 0. All the fields on the model, excluding 'id'. @@ -83,7 +83,7 @@ class FormResource(Resource): """ Given some content as input return some cleaned, validated content. Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure. - + Validation is standard form validation, with an additional constraint that *no extra unknown fields* may be supplied. On failure the :exc:`response.ErrorResponse` content is a dict which may contain :obj:`'errors'` and :obj:`'field-errors'` keys. @@ -99,27 +99,27 @@ class FormResource(Resource): allowed_extra_fields is a list of fields which are not defined by the form, but which we still expect to see on the input. - + fake_data is a string that should be used as an extra key, as a kludge to force .errors to be populated when an empty dict is supplied in `data` """ - + # We'd like nice error messages even if no content is supplied. # Typically if an empty dict is given to a form Django will # return .is_valid() == False, but .errors == {} # - # To get around this case we revalidate with some fake data. + # To get around this case we revalidate with some fake data. if fake_data: data[fake_data] = '_fake_data' allowed_extra_fields = tuple(allowed_extra_fields) + ('_fake_data',) - + bound_form = self.get_bound_form(data, files) if bound_form is None: return data - + self.view.bound_form_instance = bound_form - + data = data and data or {} files = files and files or {} @@ -130,7 +130,7 @@ class FormResource(Resource): # In addition to regular validation we also ensure no additional fields are being passed in... unknown_fields = seen_fields_set - (form_fields_set | allowed_extra_fields_set) unknown_fields = unknown_fields - set(('csrfmiddlewaretoken', '_accept', '_method')) # TODO: Ugh. - + # Check using both regular validation, and our stricter no additional fields rule if bound_form.is_valid() and not unknown_fields: # Validation succeeded... @@ -155,7 +155,7 @@ class FormResource(Resource): # If we've already set fake_dict and we're still here, fallback gracefully. detail = {u'errors': [u'No content was supplied.']} - else: + else: # Add any non-field errors if bound_form.non_field_errors(): detail[u'errors'] = bound_form.non_field_errors() @@ -171,7 +171,7 @@ class FormResource(Resource): # Add any unknown field errors for key in unknown_fields: field_errors[key] = [u'This field does not exist.'] - + if field_errors: detail[u'field-errors'] = field_errors @@ -199,7 +199,7 @@ class FormResource(Resource): form = getattr(self.view, '%s_form' % method.lower(), form) return form - + def get_bound_form(self, data=None, files=None, method=None): """ @@ -237,7 +237,7 @@ class ModelResource(FormResource): Also provides a :meth:`get_bound_form` method which may be used by some renderers. """ - # Auto-register new ModelResource classes into _model_to_resource + # Auto-register new ModelResource classes into _model_to_resource #__metaclass__ = _RegisterModelResource form = None @@ -258,21 +258,21 @@ class ModelResource(FormResource): fields = None """ The list of fields to use on the output. - + May be any of: - + The name of a model field. To view nested resources, give the field as a tuple of ("fieldName", resource) where `resource` may be any of ModelResource reference, the name of a ModelResourc reference as a string or a tuple of strings representing fields on the nested model. The name of an attribute on the model. The name of an attribute on the resource. The name of a method on the model, with a signature like ``func(self)``. The name of a method on the resource, with a signature like ``func(self, instance)``. """ - + exclude = ('id', 'pk') """ The list of fields to exclude. This is only used if :attr:`fields` is not set. """ - + include = ('url',) """ @@ -294,7 +294,7 @@ class ModelResource(FormResource): """ Given some content as input return some cleaned, validated content. Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure. - + Validation is standard form or model form validation, with an additional constraint that no extra unknown fields may be supplied, and that all fields specified by the fields class attribute must be supplied, @@ -345,12 +345,12 @@ class ModelResource(FormResource): Attempts to reverse resolve the url of the given model *instance* for this resource. Requires a ``View`` with :class:`mixins.InstanceMixin` to have been created for this resource. - + This method can be overridden if you need to set the resource url reversing explicitly. """ if not hasattr(self, 'view_callable'): - raise _SkipField + raise _SkipField # dis does teh magicks... urlconf = get_urlconf() @@ -393,7 +393,7 @@ class ModelResource(FormResource): return model_fields & set(as_tuple(self.fields)) return model_fields - set(as_tuple(self.exclude)) - + @property def _property_fields_set(self): """ diff --git a/djangorestframework/response.py b/djangorestframework/response.py index 311e0bb71..0090b0706 100644 --- a/djangorestframework/response.py +++ b/djangorestframework/response.py @@ -1,8 +1,8 @@ """ -The :mod:`response` module provides Response classes you can use in your -views to return a certain HTTP response. Typically a response is *rendered* +The :mod:`response` module provides Response classes you can use in your +views to return a certain HTTP response. Typically a response is *rendered* into a HTTP response depending on what renderers are set on your view and -als depending on the accept header of the request. +als depending on the accept header of the request. """ from django.core.handlers.wsgi import STATUS_CODE_TEXT @@ -23,7 +23,7 @@ class Response(object): self.raw_content = content # content prior to filtering self.cleaned_content = content # content after filtering self.headers = headers or {} - + @property def status_text(self): """ diff --git a/djangorestframework/runtests/runcoverage.py b/djangorestframework/runtests/runcoverage.py index 6bae02df3..ab1b0498b 100644 --- a/djangorestframework/runtests/runcoverage.py +++ b/djangorestframework/runtests/runcoverage.py @@ -51,10 +51,10 @@ def main(): # Drop the compat module from coverage, since we're not interested in the coverage # of a module which is specifically for resolving environment dependant imports. - # (Because we'll end up getting different coverage reports for it for each environment) + # (Because we'll end up getting different coverage reports for it for each environment) if 'compat.py' in files: files.remove('compat.py') - + cov_files.extend([os.path.join(path, file) for file in files if file.endswith('.py')]) cov.report(cov_files) diff --git a/djangorestframework/runtests/runtests.py b/djangorestframework/runtests/runtests.py index 1da918f53..9f5cc7aa6 100644 --- a/djangorestframework/runtests/runtests.py +++ b/djangorestframework/runtests/runtests.py @@ -16,12 +16,12 @@ from django.test.utils import get_runner def usage(): return """ Usage: python runtests.py [UnitTestClass].[method] - + You can pass the Class name of the `UnitTestClass` you want to test. - + Append a method name if you only want to test a specific method of that class. """ - + def main(): TestRunner = get_runner(settings) diff --git a/djangorestframework/runtests/settings.py b/djangorestframework/runtests/settings.py index 9b3c2c920..07855fa7a 100644 --- a/djangorestframework/runtests/settings.py +++ b/djangorestframework/runtests/settings.py @@ -55,7 +55,7 @@ MEDIA_URL = '' # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a # trailing slash. -# Examples: "http://foo.com/media/", "/media/". +# Examples: "http://foo.com/media/", "/media/". ADMIN_MEDIA_PREFIX = '/media/' # Make this unique, and don't share it with anybody. diff --git a/djangorestframework/runtests/urls.py b/djangorestframework/runtests/urls.py index 455558138..4b7da7871 100644 --- a/djangorestframework/runtests/urls.py +++ b/djangorestframework/runtests/urls.py @@ -4,4 +4,4 @@ Blank URLConf just to keep runtests.py happy. from django.conf.urls.defaults import * urlpatterns = patterns('', -) \ No newline at end of file +) diff --git a/djangorestframework/serializer.py b/djangorestframework/serializer.py index bb77ef7dc..429adea2c 100644 --- a/djangorestframework/serializer.py +++ b/djangorestframework/serializer.py @@ -18,7 +18,7 @@ _serializers = {} def _field_to_tuple(field): """ - Convert an item in the `fields` attribute into a 2-tuple. + Convert an item in the `fields` attribute into a 2-tuple. """ if isinstance(field, (tuple, list)): return (field[0], field[1]) @@ -52,7 +52,7 @@ class _RegisterSerializer(type): """ def __new__(cls, name, bases, attrs): # Build the class and register it. - ret = super(_RegisterSerializer, cls).__new__(cls, name, bases, attrs) + ret = super(_RegisterSerializer, cls).__new__(cls, name, bases, attrs) _serializers[name] = ret return ret @@ -61,19 +61,19 @@ class Serializer(object): """ Converts python objects into plain old native types suitable for serialization. In particular it handles models and querysets. - + The output format is specified by setting a number of attributes on the class. You may also override any of the serialization methods, to provide for more flexible behavior. - + Valid output types include anything that may be directly rendered into json, xml etc... """ __metaclass__ = _RegisterSerializer - fields = () + fields = () """ Specify the fields to be serialized on a model or dict. Overrides `include` and `exclude`. @@ -109,7 +109,7 @@ class Serializer(object): if depth is not None: self.depth = depth self.stack = stack - + def get_fields(self, obj): """ @@ -168,7 +168,7 @@ class Serializer(object): # Similar to what Django does for cyclically related models. elif isinstance(info, str) and info in _serializers: return _serializers[info] - + # Otherwise use `related_serializer` or fall back to `Serializer` return getattr(self, 'related_serializer') or Serializer @@ -186,7 +186,7 @@ class Serializer(object): Convert a model field or dict value into a serializable representation. """ related_serializer = self.get_related_serializer(key) - + if self.depth is None: depth = None elif self.depth <= 0: @@ -227,7 +227,7 @@ class Serializer(object): fields = self.get_fields(instance) - # serialize each required field + # serialize each required field for fname in fields: try: if hasattr(self, smart_str(fname)): @@ -279,13 +279,13 @@ class Serializer(object): Convert any unhandled object into a serializable representation. """ return smart_unicode(obj, strings_only=True) - - + + def serialize(self, obj): """ Convert any object into a serializable representation. """ - + if isinstance(obj, (dict, models.Model)): # Model instances & dictionaries return self.serialize_model(obj) diff --git a/djangorestframework/status.py b/djangorestframework/status.py index 67704b8e9..93c2a392f 100644 --- a/djangorestframework/status.py +++ b/djangorestframework/status.py @@ -89,4 +89,4 @@ NOT_IMPLEMENTED = 501 BAD_GATEWAY = 502 SERVICE_UNAVAILABLE = 503 GATEWAY_TIMEOUT = 504 -HTTP_VERSION_NOT_SUPPORTED = 505 \ No newline at end of file +HTTP_VERSION_NOT_SUPPORTED = 505 diff --git a/djangorestframework/templatetags/urlize_quoted_links.py b/djangorestframework/templatetags/urlize_quoted_links.py index bd3cbcf59..ffe859c98 100644 --- a/djangorestframework/templatetags/urlize_quoted_links.py +++ b/djangorestframework/templatetags/urlize_quoted_links.py @@ -1,6 +1,6 @@ """Adds the custom filter 'urlize_quoted_links' -This is identical to the built-in filter 'urlize' with the exception that +This is identical to the built-in filter 'urlize' with the exception that single and double quotes are permitted as leading or trailing punctuation. """ diff --git a/djangorestframework/tests/accept.py b/djangorestframework/tests/accept.py index f26de633f..4f9dbb328 100644 --- a/djangorestframework/tests/accept.py +++ b/djangorestframework/tests/accept.py @@ -50,7 +50,7 @@ class UserAgentMungingTest(TestCase): req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent) resp = view(req) self.assertEqual(resp['Content-Type'], 'application/json') - + def test_dont_munge_nice_browsers_accept_header(self): """Send Non-MSIE user agent strings and ensure that we get a JSON response, if we set a */* Accept header. (Other browsers will correctly set the Accept header)""" diff --git a/djangorestframework/tests/authentication.py b/djangorestframework/tests/authentication.py index 8254403c4..e6da42174 100644 --- a/djangorestframework/tests/authentication.py +++ b/djangorestframework/tests/authentication.py @@ -31,7 +31,7 @@ class BasicAuthTests(TestCase): self.username = 'john' self.email = 'lennon@thebeatles.com' self.password = 'password' - self.user = User.objects.create_user(self.username, self.email, self.password) + self.user = User.objects.create_user(self.username, self.email, self.password) def test_post_form_passing_basic_auth(self): """Ensure POSTing json over basic auth with correct credentials passes and does not require CSRF""" @@ -66,7 +66,7 @@ class SessionAuthTests(TestCase): self.username = 'john' self.email = 'lennon@thebeatles.com' self.password = 'password' - self.user = User.objects.create_user(self.username, self.email, self.password) + self.user = User.objects.create_user(self.username, self.email, self.password) def tearDown(self): self.csrf_client.logout() diff --git a/djangorestframework/tests/breadcrumbs.py b/djangorestframework/tests/breadcrumbs.py index dc1a02bf9..d7473c6ac 100644 --- a/djangorestframework/tests/breadcrumbs.py +++ b/djangorestframework/tests/breadcrumbs.py @@ -64,4 +64,4 @@ class BreadcrumbTests(TestCase): def test_broken_url_breadcrumbs_handled_gracefully(self): url = '/foobar' - self.assertEqual(get_breadcrumbs(url), [('Root', '/')]) \ No newline at end of file + self.assertEqual(get_breadcrumbs(url), [('Root', '/')]) diff --git a/djangorestframework/tests/description.py b/djangorestframework/tests/description.py index c0424c24a..56dcdfabb 100644 --- a/djangorestframework/tests/description.py +++ b/djangorestframework/tests/description.py @@ -19,7 +19,7 @@ indented # hash style header #""" -# If markdown is installed we also test it's working +# If markdown is installed we also test it's working # (and that our wrapped forces '=' to h2 and '-' to h3) # We support markdown < 2.1 and markdown >= 2.1 @@ -69,16 +69,16 @@ class TestViewNamesAndDescriptions(TestCase): * list * list - + another header -------------- code block indented - + # hash style header #""" - + self.assertEquals(get_description(MockView()), DESCRIPTION) # This has been turned off now @@ -89,7 +89,7 @@ class TestViewNamesAndDescriptions(TestCase): # """docstring""" # description = example # self.assertEquals(get_description(MockView()), example) - + #def test_resource_description_does_not_require_docstring(self): # """Ensure that empty docstrings do not affect the Resource's description if it has been set using the 'description' class attribute.""" # example = 'Some other description' @@ -102,7 +102,7 @@ class TestViewNamesAndDescriptions(TestCase): class MockView(View): pass self.assertEquals(get_description(MockView()), '') - + def test_markdown(self): """Ensure markdown to HTML works as expected""" if apply_markdown: diff --git a/djangorestframework/tests/files.py b/djangorestframework/tests/files.py index 992d3cbab..91f460615 100644 --- a/djangorestframework/tests/files.py +++ b/djangorestframework/tests/files.py @@ -22,7 +22,7 @@ class UploadFilesTests(TestCase): def post(self, request, *args, **kwargs): return {'FILE_NAME': self.CONTENT['file'].name, 'FILE_CONTENT': self.CONTENT['file'].read()} - + file = StringIO.StringIO('stuff') file.name = 'stuff.txt' request = self.factory.post('/', {'file': file}) diff --git a/djangorestframework/tests/methods.py b/djangorestframework/tests/methods.py index c3a3a28d5..4b90a21f1 100644 --- a/djangorestframework/tests/methods.py +++ b/djangorestframework/tests/methods.py @@ -3,7 +3,7 @@ from djangorestframework.compat import RequestFactory from djangorestframework.mixins import RequestMixin -class TestMethodOverloading(TestCase): +class TestMethodOverloading(TestCase): def setUp(self): self.req = RequestFactory() @@ -18,7 +18,7 @@ class TestMethodOverloading(TestCase): view = RequestMixin() view.request = self.req.post('/') self.assertEqual(view.method, 'POST') - + def test_overloaded_POST_behaviour_determines_overloaded_method(self): """POST requests can be overloaded to another method by setting a reserved form field""" view = RequestMixin() diff --git a/djangorestframework/tests/modelviews.py b/djangorestframework/tests/modelviews.py index 3fbc8ab61..031e65c50 100644 --- a/djangorestframework/tests/modelviews.py +++ b/djangorestframework/tests/modelviews.py @@ -18,9 +18,9 @@ class UserForm(ModelForm): class UserResource(ModelResource): model = User form = UserForm - + class CustomUserResource(ModelResource): - model = CustomUser + model = CustomUser urlpatterns = patterns('', url(r'^users/$', ListOrCreateModelView.as_view(resource=UserResource), name='users'), @@ -34,7 +34,7 @@ urlpatterns = patterns('', class ModelViewTests(TestModelsTestCase): """Test the model views djangorestframework provides""" - urls = 'djangorestframework.tests.modelviews' + urls = 'djangorestframework.tests.modelviews' def test_creation(self): """Ensure that a model object can be created""" @@ -53,18 +53,18 @@ class ModelViewTests(TestModelsTestCase): self.assertEqual(0, User.objects.count()) response = self.client.post('/users/', {'username': 'bar', 'password': 'baz', 'groups': [group.id]}) - + self.assertEqual(response.status_code, 201) self.assertEqual(1, User.objects.count()) - + user = User.objects.all()[0] self.assertEqual('bar', user.username) self.assertEqual('baz', user.password) self.assertEqual(1, user.groups.count()) - + group = user.groups.all()[0] self.assertEqual('foo', group.name) - + def test_creation_with_m2m_relation_through(self): """ Ensure that a model object with a m2m relation can be created where that @@ -75,13 +75,13 @@ class ModelViewTests(TestModelsTestCase): self.assertEqual(0, User.objects.count()) response = self.client.post('/customusers/', {'username': 'bar', 'groups': [group.id]}) - + self.assertEqual(response.status_code, 201) self.assertEqual(1, CustomUser.objects.count()) - + user = CustomUser.objects.all()[0] self.assertEqual('bar', user.username) self.assertEqual(1, user.groups.count()) - + group = user.groups.all()[0] - self.assertEqual('foo', group.name) + self.assertEqual('foo', group.name) diff --git a/djangorestframework/tests/oauthentication.py b/djangorestframework/tests/oauthentication.py index 109d9a72b..b4bcf2fa8 100644 --- a/djangorestframework/tests/oauthentication.py +++ b/djangorestframework/tests/oauthentication.py @@ -23,14 +23,14 @@ else: class ClientView(View): def get(self, request): return {'resource': 'Protected!'} - + urlpatterns = patterns('', url(r'^$', oauth_required(ClientView.as_view())), url(r'^oauth/', include('oauth_provider.urls')), url(r'^accounts/login/$', 'djangorestframework.utils.staticviews.api_login'), ) - - + + class OAuthTests(TestCase): """ OAuth authentication: @@ -42,23 +42,23 @@ else: * the third-party website is able to retrieve data from the API """ urls = 'djangorestframework.tests.oauthentication' - + def setUp(self): self.client = Client() self.username = 'john' self.email = 'lennon@thebeatles.com' self.password = 'password' self.user = User.objects.create_user(self.username, self.email, self.password) - + # OAuth requirements self.resource = Resource(name='data', url='/') self.resource.save() self.CONSUMER_KEY = 'dpf43f3p2l4k3l03' self.CONSUMER_SECRET = 'kd94hf93k423kf44' - self.consumer = Consumer(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, + self.consumer = Consumer(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, name='api.example.com', user=self.user) self.consumer.save() - + def test_oauth_invalid_and_anonymous_access(self): """ Verify that the resource is protected and the OAuth authorization view @@ -69,16 +69,16 @@ else: self.assertEqual(response.status_code, 401) response = self.client.get('/oauth/authorize/', follow=True) self.assertRedirects(response, '/accounts/login/?next=/oauth/authorize/') - + def test_oauth_authorize_access(self): """ - Verify that once logged in, the user can access the authorization page + Verify that once logged in, the user can access the authorization page but can't display the page because the request token is not specified. """ self.client.login(username=self.username, password=self.password) response = self.client.get('/oauth/authorize/', follow=True) self.assertEqual(response.content, 'No request token specified.') - + def _create_request_token_parameters(self): """ A shortcut to create request's token parameters. @@ -93,28 +93,28 @@ else: 'oauth_callback': 'http://api.example.com/request_token_ready', 'scope': 'data', } - + def test_oauth_request_token_retrieval(self): """ Verify that the request token can be retrieved by the server. """ - response = self.client.get("/oauth/request_token/", + response = self.client.get("/oauth/request_token/", self._create_request_token_parameters()) self.assertEqual(response.status_code, 200) token = list(Token.objects.all())[-1] self.failIf(token.key not in response.content) self.failIf(token.secret not in response.content) - + def test_oauth_user_request_authorization(self): """ Verify that the user can access the authorization page once logged in and the request token has been retrieved. """ # Setup - response = self.client.get("/oauth/request_token/", + response = self.client.get("/oauth/request_token/", self._create_request_token_parameters()) token = list(Token.objects.all())[-1] - + # Starting the test here self.client.login(username=self.username, password=self.password) parameters = {'oauth_token': token.key,} @@ -129,7 +129,7 @@ else: token = Token.objects.get(key=token.key) self.failIf(token.key not in response['Location']) self.assertEqual(token.is_approved, 1) - + def _create_access_token_parameters(self, token): """ A shortcut to create access' token parameters. @@ -145,13 +145,13 @@ else: 'oauth_verifier': token.verifier, 'scope': 'data', } - + def test_oauth_access_token_retrieval(self): """ Verify that the request token can be retrieved by the server. """ # Setup - response = self.client.get("/oauth/request_token/", + response = self.client.get("/oauth/request_token/", self._create_request_token_parameters()) token = list(Token.objects.all())[-1] self.client.login(username=self.username, password=self.password) @@ -160,7 +160,7 @@ else: parameters['authorize_access'] = 1 # fake authorization by the user response = self.client.post("/oauth/authorize/", parameters) token = Token.objects.get(key=token.key) - + # Starting the test here response = self.client.get("/oauth/access_token/", self._create_access_token_parameters(token)) self.assertEqual(response.status_code, 200) @@ -169,7 +169,7 @@ else: self.failIf(access_token.key not in response.content) self.failIf(access_token.secret not in response.content) self.assertEqual(access_token.user.username, 'john') - + def _create_access_parameters(self, access_token): """ A shortcut to create access' parameters. @@ -188,13 +188,13 @@ else: signature = signature_method.sign(oauth_request, self.consumer, access_token) parameters['oauth_signature'] = signature return parameters - + def test_oauth_protected_resource_access(self): """ Verify that the request token can be retrieved by the server. """ # Setup - response = self.client.get("/oauth/request_token/", + response = self.client.get("/oauth/request_token/", self._create_request_token_parameters()) token = list(Token.objects.all())[-1] self.client.login(username=self.username, password=self.password) @@ -205,7 +205,7 @@ else: token = Token.objects.get(key=token.key) response = self.client.get("/oauth/access_token/", self._create_access_token_parameters(token)) access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1] - + # Starting the test here response = self.client.get("/", self._create_access_token_parameters(access_token)) self.assertEqual(response.status_code, 200) diff --git a/djangorestframework/tests/package.py b/djangorestframework/tests/package.py index 4128333cf..d18128f7e 100644 --- a/djangorestframework/tests/package.py +++ b/djangorestframework/tests/package.py @@ -2,7 +2,7 @@ from django.test import TestCase import djangorestframework -class TestVersion(TestCase): +class TestVersion(TestCase): """Simple sanity test to check the VERSION exists""" def test_version(self): diff --git a/djangorestframework/tests/parsers.py b/djangorestframework/tests/parsers.py index b21acd4d8..e4e7e09a4 100644 --- a/djangorestframework/tests/parsers.py +++ b/djangorestframework/tests/parsers.py @@ -8,76 +8,76 @@ # >>> req = RequestFactory().get('/') # >>> some_view = View() # >>> some_view.request = req # Make as if this request had been dispatched -# +# # FormParser # ============ -# +# # Data flatening # ---------------- -# +# # Here is some example data, which would eventually be sent along with a post request : -# +# # >>> inpt = urlencode([ # ... ('key1', 'bla1'), # ... ('key2', 'blo1'), ('key2', 'blo2'), # ... ]) -# +# # Default behaviour for :class:`parsers.FormParser`, is to return a single value for each parameter : -# +# # >>> (data, files) = FormParser(some_view).parse(StringIO(inpt)) # >>> data == {'key1': 'bla1', 'key2': 'blo1'} # True -# +# # However, you can customize this behaviour by subclassing :class:`parsers.FormParser`, and overriding :meth:`parsers.FormParser.is_a_list` : -# +# # >>> class MyFormParser(FormParser): -# ... +# ... # ... def is_a_list(self, key, val_list): # ... return len(val_list) > 1 -# +# # This new parser only flattens the lists of parameters that contain a single value. -# +# # >>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt)) # >>> data == {'key1': 'bla1', 'key2': ['blo1', 'blo2']} # True -# +# # .. note:: The same functionality is available for :class:`parsers.MultiPartParser`. -# +# # Submitting an empty list # -------------------------- -# +# # When submitting an empty select multiple, like this one :: -# +# # -# +# # The browsers usually strip the parameter completely. A hack to avoid this, and therefore being able to submit an empty select multiple, is to submit a value that tells the server that the list is empty :: -# +# # -# +# # :class:`parsers.FormParser` provides the server-side implementation for this hack. Considering the following posted data : -# +# # >>> inpt = urlencode([ # ... ('key1', 'blo1'), ('key1', '_empty'), # ... ('key2', '_empty'), # ... ]) -# +# # :class:`parsers.FormParser` strips the values ``_empty`` from all the lists. -# +# # >>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt)) # >>> data == {'key1': 'blo1'} # True -# +# # Oh ... but wait a second, the parameter ``key2`` isn't even supposed to be a list, so the parser just stripped it. -# +# # >>> class MyFormParser(FormParser): -# ... +# ... # ... def is_a_list(self, key, val_list): # ... return key == 'key2' -# ... +# ... # >>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt)) # >>> data == {'key1': 'blo1', 'key2': []} # True -# +# # Better like that. Note that you can configure something else than ``_empty`` for the empty value by setting :attr:`parsers.FormParser.EMPTY_VALUE`. # """ # import httplib, mimetypes @@ -87,7 +87,7 @@ # from djangorestframework.parsers import MultiPartParser # from djangorestframework.views import View # from StringIO import StringIO -# +# # def encode_multipart_formdata(fields, files): # """For testing multipart parser. # fields is a sequence of (name, value) elements for regular form fields. @@ -112,10 +112,10 @@ # body = CRLF.join(L) # content_type = 'multipart/form-data; boundary=%s' % BOUNDARY # return content_type, body -# +# # def get_content_type(filename): # return mimetypes.guess_type(filename)[0] or 'application/octet-stream' -# +# #class TestMultiPartParser(TestCase): # def setUp(self): # self.req = RequestFactory() @@ -145,12 +145,12 @@ class Form(forms.Form): class TestFormParser(TestCase): def setUp(self): - self.string = "field1=abc&field2=defghijk" - + self.string = "field1=abc&field2=defghijk" + def test_parse(self): """ Make sure the `QueryDict` works OK """ parser = FormParser(None) - + stream = StringIO(self.string) (data, files) = parser.parse(stream) @@ -179,4 +179,4 @@ class TestXMLParser(TestCase): def test_parse(self): parser = XMLParser(None) (data, files) = parser.parse(self.input) - self.assertEqual(data, self.data) \ No newline at end of file + self.assertEqual(data, self.data) diff --git a/djangorestframework/tests/response.py b/djangorestframework/tests/response.py index 6f225fa87..d973deb45 100644 --- a/djangorestframework/tests/response.py +++ b/djangorestframework/tests/response.py @@ -5,7 +5,7 @@ #from djangorestframework.response import Response # # -#class TestResponse(TestCase): +#class TestResponse(TestCase): # # # Interface tests # diff --git a/djangorestframework/tests/serializer.py b/djangorestframework/tests/serializer.py index 5f42e7e2b..8e370fa13 100644 --- a/djangorestframework/tests/serializer.py +++ b/djangorestframework/tests/serializer.py @@ -7,7 +7,7 @@ from django.db import models import datetime import decimal -class TestObjectToData(TestCase): +class TestObjectToData(TestCase): """ Tests for the Serializer class. """ diff --git a/djangorestframework/tests/status.py b/djangorestframework/tests/status.py index 9456ca9ef..04aed740f 100644 --- a/djangorestframework/tests/status.py +++ b/djangorestframework/tests/status.py @@ -3,7 +3,7 @@ from django.test import TestCase from djangorestframework import status -class TestStatus(TestCase): +class TestStatus(TestCase): """Simple sanity test to check the status module""" def test_status(self): diff --git a/djangorestframework/tests/testcases.py b/djangorestframework/tests/testcases.py index ad0c7be64..51b4afbc0 100644 --- a/djangorestframework/tests/testcases.py +++ b/djangorestframework/tests/testcases.py @@ -53,11 +53,11 @@ class SettingsTestCase(TestCase): def __init__(self, *args, **kwargs): super(SettingsTestCase, self).__init__(*args, **kwargs) self.settings_manager = TestSettingsManager() - + def tearDown(self): self.settings_manager.revert() class TestModelsTestCase(SettingsTestCase): def setUp(self, *args, **kwargs): installed_apps = tuple(settings.INSTALLED_APPS) + ('djangorestframework.tests',) - self.settings_manager.set(INSTALLED_APPS=installed_apps) \ No newline at end of file + self.settings_manager.set(INSTALLED_APPS=installed_apps) diff --git a/djangorestframework/tests/throttling.py b/djangorestframework/tests/throttling.py index b620ee244..7fdc6491a 100644 --- a/djangorestframework/tests/throttling.py +++ b/djangorestframework/tests/throttling.py @@ -21,25 +21,25 @@ class MockView(View): class MockView_PerViewThrottling(MockView): permissions = ( PerViewThrottling, ) -class MockView_PerResourceThrottling(MockView): +class MockView_PerResourceThrottling(MockView): permissions = ( PerResourceThrottling, ) resource = FormResource class MockView_MinuteThrottling(MockView): throttle = '3/min' - - - + + + class ThrottlingTests(TestCase): - urls = 'djangorestframework.tests.throttling' - + urls = 'djangorestframework.tests.throttling' + def setUp(self): """ Reset the cache so that no throttles will be active """ cache.clear() self.factory = RequestFactory() - + def test_requests_are_throttled(self): """ Ensure request rate is limited @@ -48,7 +48,7 @@ class ThrottlingTests(TestCase): for dummy in range(4): response = MockView.as_view()(request) self.assertEqual(503, response.status_code) - + def set_throttle_timer(self, view, value): """ Explicitly set the timer, overriding time.time() @@ -71,7 +71,7 @@ class ThrottlingTests(TestCase): response = MockView.as_view()(request) self.assertEqual(200, response.status_code) - + def ensure_is_throttled(self, view, expect): request = self.factory.get('/') request.user = User.objects.create(username='a') @@ -80,27 +80,27 @@ class ThrottlingTests(TestCase): request.user = User.objects.create(username='b') response = view.as_view()(request) self.assertEqual(expect, response.status_code) - + def test_request_throttling_is_per_user(self): """ - Ensure request rate is only limited per user, not globally for + Ensure request rate is only limited per user, not globally for PerUserThrottles """ self.ensure_is_throttled(MockView, 200) - + def test_request_throttling_is_per_view(self): """ Ensure request rate is limited globally per View for PerViewThrottles """ self.ensure_is_throttled(MockView_PerViewThrottling, 503) - + def test_request_throttling_is_per_resource(self): """ Ensure request rate is limited globally per Resource for PerResourceThrottles - """ + """ self.ensure_is_throttled(MockView_PerResourceThrottling, 503) - - + + def ensure_response_header_contains_proper_throttle_field(self, view, expected_headers): """ Ensure the response returns an X-Throttle field with status and next attributes @@ -111,7 +111,7 @@ class ThrottlingTests(TestCase): self.set_throttle_timer(view, timer) response = view.as_view()(request) self.assertEquals(response['X-Throttle'], expect) - + def test_seconds_fields(self): """ Ensure for second based throttles. @@ -122,7 +122,7 @@ class ThrottlingTests(TestCase): (0, 'status=SUCCESS; next=1.00 sec'), (0, 'status=FAILURE; next=1.00 sec') )) - + def test_minutes_fields(self): """ Ensure for minute based throttles. @@ -133,7 +133,7 @@ class ThrottlingTests(TestCase): (0, 'status=SUCCESS; next=60.00 sec'), (0, 'status=FAILURE; next=60.00 sec') )) - + def test_next_rate_remains_constant_if_followed(self): """ If a client follows the recommended next request rate, diff --git a/djangorestframework/tests/validators.py b/djangorestframework/tests/validators.py index a1e5d2d71..1f696422e 100644 --- a/djangorestframework/tests/validators.py +++ b/djangorestframework/tests/validators.py @@ -22,7 +22,7 @@ class TestDisabledValidations(TestCase): resource = DisabledFormResource view = MockView() - content = {'qwerty':'uiop'} + content = {'qwerty':'uiop'} self.assertEqual(FormResource(view).validate_request(content, None), content) def test_disabled_form_validator_get_bound_form_returns_none(self): @@ -33,12 +33,12 @@ class TestDisabledValidations(TestCase): class MockView(View): resource = DisabledFormResource - + view = MockView() - content = {'qwerty':'uiop'} + content = {'qwerty':'uiop'} self.assertEqual(FormResource(view).get_bound_form(content), None) - + def test_disabled_model_form_validator_returns_content_unchanged(self): """If the view's form is None and does not have a Resource with a model set then ModelFormValidator(view).validate_request(content, None) should just return the content unmodified.""" @@ -47,17 +47,17 @@ class TestDisabledValidations(TestCase): resource = ModelResource view = DisabledModelFormView() - content = {'qwerty':'uiop'} + content = {'qwerty':'uiop'} self.assertEqual(ModelResource(view).get_bound_form(content), None)# def test_disabled_model_form_validator_get_bound_form_returns_none(self): """If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None.""" class DisabledModelFormView(View): resource = ModelResource - + view = DisabledModelFormView() - content = {'qwerty':'uiop'} - self.assertEqual(ModelResource(view).get_bound_form(content), None) + content = {'qwerty':'uiop'} + self.assertEqual(ModelResource(view).get_bound_form(content), None) class TestNonFieldErrors(TestCase): """Tests against form validation errors caused by non-field errors. (eg as might be caused by some custom form validation)""" @@ -68,15 +68,15 @@ class TestNonFieldErrors(TestCase): field1 = forms.CharField(required=False) field2 = forms.CharField(required=False) ERROR_TEXT = 'You may not supply both field1 and field2' - + def clean(self): if 'field1' in self.cleaned_data and 'field2' in self.cleaned_data: raise forms.ValidationError(self.ERROR_TEXT) return self.cleaned_data #pragma: no cover - + class MockResource(FormResource): form = MockForm - + class MockView(View): pass @@ -84,7 +84,7 @@ class TestNonFieldErrors(TestCase): content = {'field1': 'example1', 'field2': 'example2'} try: MockResource(view).validate_request(content, None) - except ErrorResponse, exc: + except ErrorResponse, exc: self.assertEqual(exc.response.raw_content, {'errors': [MockForm.ERROR_TEXT]}) else: self.fail('ErrorResponse was not raised') #pragma: no cover @@ -94,26 +94,26 @@ class TestFormValidation(TestCase): """Tests which check basic form validation. Also includes the same set of tests with a ModelFormValidator for which the form has been explicitly set. (ModelFormValidator should behave as FormValidator if a form is set rather than relying on the default ModelForm)""" - def setUp(self): + def setUp(self): class MockForm(forms.Form): qwerty = forms.CharField(required=True) class MockFormResource(FormResource): form = MockForm - + class MockModelResource(ModelResource): form = MockForm - + class MockFormView(View): resource = MockFormResource - + class MockModelFormView(View): resource = MockModelResource self.MockFormResource = MockFormResource - self.MockModelResource = MockModelResource + self.MockModelResource = MockModelResource self.MockFormView = MockFormView - self.MockModelFormView = MockModelFormView + self.MockModelFormView = MockModelFormView def validation_returns_content_unchanged_if_already_valid_and_clean(self, validator): @@ -130,22 +130,22 @@ class TestFormValidation(TestCase): """If some (otherwise valid) content includes fields that are not in the form then validation should fail. It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up broken clients more easily (eg submitting content with a misnamed field)""" - content = {'qwerty': 'uiop', 'extra': 'extra'} + content = {'qwerty': 'uiop', 'extra': 'extra'} self.assertRaises(ErrorResponse, validator.validate_request, content, None) def validation_allows_extra_fields_if_explicitly_set(self, validator): """If we include an allowed_extra_fields paramater on _validate, then allow fields with those names.""" - content = {'qwerty': 'uiop', 'extra': 'extra'} + content = {'qwerty': 'uiop', 'extra': 'extra'} validator._validate(content, None, allowed_extra_fields=('extra',)) def validation_does_not_require_extra_fields_if_explicitly_set(self, validator): """If we include an allowed_extra_fields paramater on _validate, then do not fail if we do not have fields with those names.""" - content = {'qwerty': 'uiop'} + content = {'qwerty': 'uiop'} self.assertEqual(validator._validate(content, None, allowed_extra_fields=('extra',)), content) def validation_failed_due_to_no_content_returns_appropriate_message(self, validator): """If validation fails due to no content, ensure the response contains a single non-field error""" - content = {} + content = {} try: validator.validate_request(content, None) except ErrorResponse, exc: @@ -158,7 +158,7 @@ class TestFormValidation(TestCase): content = {'qwerty': ''} try: validator.validate_request(content, None) - except ErrorResponse, exc: + except ErrorResponse, exc: self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.']}}) else: self.fail('ResourceException was not raised') #pragma: no cover @@ -168,17 +168,17 @@ class TestFormValidation(TestCase): content = {'qwerty': 'uiop', 'extra': 'extra'} try: validator.validate_request(content, None) - except ErrorResponse, exc: + except ErrorResponse, exc: self.assertEqual(exc.response.raw_content, {'field-errors': {'extra': ['This field does not exist.']}}) else: self.fail('ResourceException was not raised') #pragma: no cover - + def validation_failed_due_to_multiple_errors_returns_appropriate_message(self, validator): """If validation for multiple reasons, ensure the response contains each error""" content = {'qwerty': '', 'extra': 'extra'} try: validator.validate_request(content, None) - except ErrorResponse, exc: + except ErrorResponse, exc: self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.'], 'extra': ['This field does not exist.']}}) else: @@ -263,23 +263,23 @@ class TestFormValidation(TestCase): class TestModelFormValidator(TestCase): """Tests specific to ModelFormValidatorMixin""" - + def setUp(self): - """Create a validator for a model with two fields and a property.""" + """Create a validator for a model with two fields and a property.""" class MockModel(models.Model): qwerty = models.CharField(max_length=256) uiop = models.CharField(max_length=256, blank=True) - + @property def readonly(self): return 'read only' - + class MockResource(ModelResource): model = MockModel - + class MockView(View): resource = MockResource - + self.validator = MockResource(MockView) @@ -299,19 +299,19 @@ class TestModelFormValidator(TestCase): broken clients more easily (eg submitting content with a misnamed field)""" content = {'qwerty': 'example', 'uiop':'example', 'readonly': 'read only', 'extra': 'extra'} self.assertRaises(ErrorResponse, self.validator.validate_request, content, None) - + def test_validate_requires_fields_on_model_forms(self): """If some (otherwise valid) content includes fields that are not in the form then validation should fail. It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up broken clients more easily (eg submitting content with a misnamed field)""" - content = {'readonly': 'read only'} + content = {'readonly': 'read only'} self.assertRaises(ErrorResponse, self.validator.validate_request, content, None) - + def test_validate_does_not_require_blankable_fields_on_model_forms(self): """Test standard ModelForm validation behaviour - fields with blank=True are not required.""" content = {'qwerty':'example', 'readonly': 'read only'} self.validator.validate_request(content, None) - + def test_model_form_validator_uses_model_forms(self): self.assertTrue(isinstance(self.validator.get_bound_form(), forms.ModelForm)) diff --git a/djangorestframework/tests/views.py b/djangorestframework/tests/views.py index b0f9d6d47..e03f6615f 100644 --- a/djangorestframework/tests/views.py +++ b/djangorestframework/tests/views.py @@ -23,17 +23,17 @@ class ResourceMockView(View): foo = forms.BooleanField(required=False) bar = forms.IntegerField(help_text='Must be an integer.') baz = forms.CharField(max_length=32) - + form = MockForm class MockResource(ModelResource): """This is a mock model-based resource""" - + class MockResourceModel(models.Model): foo = models.BooleanField() bar = models.IntegerField(help_text='Must be an integer.') baz = models.CharField(max_length=32, help_text='Free text. Max length 32 chars.') - + model = MockResourceModel fields = ('foo', 'bar', 'baz') @@ -50,62 +50,62 @@ urlpatterns = patterns('djangorestframework.utils.staticviews', class BaseViewTests(TestCase): """Test the base view class of djangorestframework""" - urls = 'djangorestframework.tests.views' - + urls = 'djangorestframework.tests.views' + def test_options_method_simple_view(self): response = self.client.options('/mock/') - self._verify_options_response(response, - name='Mock', + self._verify_options_response(response, + name='Mock', description='This is a basic mock view') - + def test_options_method_resource_view(self): response = self.client.options('/resourcemock/') - self._verify_options_response(response, - name='Resource Mock', - description='This is a resource-based mock view', - fields={'foo':'BooleanField', - 'bar':'IntegerField', - 'baz':'CharField', + self._verify_options_response(response, + name='Resource Mock', + description='This is a resource-based mock view', + fields={'foo':'BooleanField', + 'bar':'IntegerField', + 'baz':'CharField', }) - + def test_options_method_model_resource_list_view(self): response = self.client.options('/model/') - self._verify_options_response(response, - name='Mock List', - description='This is a mock model-based resource', - fields={'foo':'BooleanField', - 'bar':'IntegerField', - 'baz':'CharField', + self._verify_options_response(response, + name='Mock List', + description='This is a mock model-based resource', + fields={'foo':'BooleanField', + 'bar':'IntegerField', + 'baz':'CharField', }) def test_options_method_model_resource_detail_view(self): response = self.client.options('/model/0/') - self._verify_options_response(response, - name='Mock Instance', - description='This is a mock model-based resource', - fields={'foo':'BooleanField', - 'bar':'IntegerField', - 'baz':'CharField', + self._verify_options_response(response, + name='Mock Instance', + description='This is a mock model-based resource', + fields={'foo':'BooleanField', + 'bar':'IntegerField', + 'baz':'CharField', }) - def _verify_options_response(self, response, name, description, fields=None, status=200, + def _verify_options_response(self, response, name, description, fields=None, status=200, mime_type='application/json'): self.assertEqual(response.status_code, status) self.assertEqual(response['Content-Type'].split(';')[0], mime_type) parser = JSONParser(None) (data, files) = parser.parse(StringIO(response.content)) - self.assertTrue('application/json' in data['renders']) - self.assertEqual(name, data['name']) - self.assertEqual(description, data['description']) + self.assertTrue('application/json' in data['renders']) + self.assertEqual(name, data['name']) + self.assertEqual(description, data['description']) if fields is None: self.assertFalse(hasattr(data, 'fields')) else: - self.assertEqual(data['fields'], fields) + self.assertEqual(data['fields'], fields) class ExtraViewsTests(TestCase): """Test the extra views djangorestframework provides""" - urls = 'djangorestframework.tests.views' + urls = 'djangorestframework.tests.views' def test_robots_view(self): """Ensure the robots view exists""" diff --git a/djangorestframework/urls.py b/djangorestframework/urls.py index 271b02159..827aac9cd 100644 --- a/djangorestframework/urls.py +++ b/djangorestframework/urls.py @@ -13,4 +13,4 @@ urlpatterns = patterns('djangorestframework.utils.staticviews', if not settings.DEBUG: urlpatterns += patterns('djangorestframework.utils.staticviews', (r'favicon.ico', 'favicon'), - ) \ No newline at end of file + ) diff --git a/djangorestframework/utils/__init__.py b/djangorestframework/utils/__init__.py index 04baea78f..305311f44 100644 --- a/djangorestframework/utils/__init__.py +++ b/djangorestframework/utils/__init__.py @@ -36,7 +36,7 @@ def as_tuple(obj): return obj return (obj,) - + def url_resolves(url): """ Return True if the given URL is mapped to a view in the urlconf, False otherwise. @@ -50,7 +50,7 @@ def url_resolves(url): # From http://www.koders.com/python/fidB6E125C586A6F49EAC38992CF3AFDAAE35651975.aspx?s=mdef:xml #class object_dict(dict): -# """object view of dict, you can +# """object view of dict, you can # >>> a = object_dict() # >>> a.fish = 'fish' # >>> a['fish'] @@ -103,8 +103,8 @@ class XML2Dict(object): old = node_tree[tag] if not isinstance(old, list): node_tree.pop(tag) - node_tree[tag] = [old] # multi times, so change old dict to a list - node_tree[tag].append(tree) # add the new one + node_tree[tag] = [old] # multi times, so change old dict to a list + node_tree[tag].append(tree) # add the new one return node_tree @@ -117,13 +117,13 @@ class XML2Dict(object): """ result = re.compile("\{(.*)\}(.*)").search(tag) if result: - value.namespace, tag = result.groups() + value.namespace, tag = result.groups() return (tag, value) def parse(self, file): """parse a xml file to a dict""" f = open(file, 'r') - return self.fromstring(f.read()) + return self.fromstring(f.read()) def fromstring(self, s): """parse a string""" @@ -159,7 +159,7 @@ class XMLRenderer(): xml.characters(smart_unicode(data)) def dict2xml(self, data): - stream = StringIO.StringIO() + stream = StringIO.StringIO() xml = SimplerXMLGenerator(stream, "utf-8") xml.startDocument() @@ -172,4 +172,4 @@ class XMLRenderer(): return stream.getvalue() def dict2xml(input): - return XMLRenderer().dict2xml(input) \ No newline at end of file + return XMLRenderer().dict2xml(input) diff --git a/djangorestframework/utils/breadcrumbs.py b/djangorestframework/utils/breadcrumbs.py index 0b043c78c..6cf978edb 100644 --- a/djangorestframework/utils/breadcrumbs.py +++ b/djangorestframework/utils/breadcrumbs.py @@ -3,12 +3,12 @@ from djangorestframework.utils.description import get_name def get_breadcrumbs(url): """Given a url returns a list of breadcrumbs, which are each a tuple of (name, url).""" - + from djangorestframework.views import View - + def breadcrumbs_recursive(url, breadcrumbs_list): """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: @@ -17,15 +17,15 @@ def get_breadcrumbs(url): # Check if this is a REST framework view, and if so add it to the breadcrumbs if isinstance(getattr(view, 'cls_instance', None), View): breadcrumbs_list.insert(0, (get_name(view), url)) - + if url == '': # All done return breadcrumbs_list - + elif url.endswith('/'): # Drop trailing slash off the end and continue to try to resolve more breadcrumbs return breadcrumbs_recursive(url.rstrip('/'), breadcrumbs_list) - + # Drop trailing non-slash off the end and continue to try to resolve more breadcrumbs return breadcrumbs_recursive(url[:url.rfind('/') + 1], breadcrumbs_list) diff --git a/djangorestframework/utils/description.py b/djangorestframework/utils/description.py index 25bef80bd..ce61e5580 100644 --- a/djangorestframework/utils/description.py +++ b/djangorestframework/utils/description.py @@ -10,7 +10,7 @@ from djangorestframework.resources import Resource, FormResource, ModelResource def get_name(view): """ Return a name for the view. - + If view has a name attribute, use that, otherwise use the view's class name, with 'CamelCaseNames' converted to 'Camel Case Names'. """ @@ -22,7 +22,7 @@ def get_name(view): # If this view has a resource that's been overridden, then use that resource for the name if getattr(view, 'resource', None) not in (None, Resource, FormResource, ModelResource): name = view.resource.__name__ - + # Chomp of any non-descriptive trailing part of the resource class name if name.endswith('Resource') and name != 'Resource': name = name[:-len('Resource')] @@ -30,7 +30,7 @@ def get_name(view): # If the view has a descriptive suffix, eg '*** List', '*** Instance' if getattr(view, '_suffix', None): name += view._suffix - + # Otherwise if it's a function view use the function's name elif getattr(view, '__name__', None) is not None: name = view.__name__ @@ -62,12 +62,12 @@ def get_description(view): # grok the class instance that we stored when as_view was called. if getattr(view, 'cls_instance', None): view = view.cls_instance - + # If this view has a resource that's been overridden, then use the resource's doctring if getattr(view, 'resource', None) not in (None, Resource, FormResource, ModelResource): doc = view.resource.__doc__ - + # Otherwise use the view doctring elif getattr(view, '__doc__', None): doc = view.__doc__ @@ -81,11 +81,11 @@ def get_description(view): whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in doc.splitlines()[1:] if line.lstrip()] - # unindent the docstring if needed + # unindent the docstring if needed if whitespace_counts: whitespace_pattern = '^' + (' ' * min(whitespace_counts)) return re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', doc) # otherwise return it as-is return doc - + diff --git a/djangorestframework/utils/mediatypes.py b/djangorestframework/utils/mediatypes.py index ae734e62d..3c0eefc4e 100644 --- a/djangorestframework/utils/mediatypes.py +++ b/djangorestframework/utils/mediatypes.py @@ -111,7 +111,7 @@ class _MediaType(object): # return Decimal(self.params.get('q', '1.0')) # except: # return Decimal(0) - + #def score(self): # """ # Return an overall score for a given media type given it's quality and precedence. @@ -119,7 +119,7 @@ class _MediaType(object): # # NB. quality values should only have up to 3 decimal points # # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.9 # return self.quality * 10000 + self.precedence - + #def as_tuple(self): # return (self.main_type, self.sub_type, self.params) diff --git a/examples/blogpost/resources.py b/examples/blogpost/resources.py index 9b91ed731..5a3c1ce2b 100644 --- a/examples/blogpost/resources.py +++ b/examples/blogpost/resources.py @@ -12,16 +12,16 @@ class BlogPostResource(ModelResource): ordering = ('-created',) def comments(self, instance): - return reverse('comments', kwargs={'blogpost': instance.key}) + return reverse('comments', kwargs={'blogpost': instance.key}) class CommentResource(ModelResource): """ - A Comment is associated with a given Blog Post and has a *username* and *comment*, and optionally a *rating*. + A Comment is associated with a given Blog Post and has a *username* and *comment*, and optionally a *rating*. """ model = Comment fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost') ordering = ('-created',) - + def blogpost(self, instance): - return reverse('blog-post', kwargs={'key': instance.blogpost.key}) \ No newline at end of file + return reverse('blog-post', kwargs={'key': instance.blogpost.key}) diff --git a/examples/blogpost/tests.py b/examples/blogpost/tests.py index e55f0f90f..5aa4f89f4 100644 --- a/examples/blogpost/tests.py +++ b/examples/blogpost/tests.py @@ -15,68 +15,68 @@ from blogpost import models, urls # class AcceptHeaderTests(TestCase): # """Test correct behaviour of the Accept header as specified by RFC 2616: -# +# # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1""" -# +# # def assert_accept_mimetype(self, mimetype, expect=None): # """Assert that a request with given mimetype in the accept header, # gives a response with the appropriate content-type.""" # if expect is None: # expect = mimetype -# +# # resp = self.client.get(reverse(views.RootResource), HTTP_ACCEPT=mimetype) -# +# # self.assertEquals(resp['content-type'], expect) -# -# +# +# # def dont_test_accept_json(self): # """Ensure server responds with Content-Type of JSON when requested.""" # self.assert_accept_mimetype('application/json') -# +# # def dont_test_accept_xml(self): # """Ensure server responds with Content-Type of XML when requested.""" # self.assert_accept_mimetype('application/xml') -# +# # def dont_test_accept_json_when_prefered_to_xml(self): # """Ensure server responds with Content-Type of JSON when it is the client's prefered choice.""" # self.assert_accept_mimetype('application/json;q=0.9, application/xml;q=0.1', expect='application/json') -# +# # def dont_test_accept_xml_when_prefered_to_json(self): # """Ensure server responds with Content-Type of XML when it is the client's prefered choice.""" # self.assert_accept_mimetype('application/json;q=0.1, application/xml;q=0.9', expect='application/xml') -# +# # def dont_test_default_json_prefered(self): # """Ensure server responds with JSON in preference to XML.""" # self.assert_accept_mimetype('application/json,application/xml', expect='application/json') -# +# # def dont_test_accept_generic_subtype_format(self): # """Ensure server responds with an appropriate type, when the subtype is left generic.""" # self.assert_accept_mimetype('text/*', expect='text/html') -# +# # def dont_test_accept_generic_type_format(self): # """Ensure server responds with an appropriate type, when the type and subtype are left generic.""" # self.assert_accept_mimetype('*/*', expect='application/json') -# +# # def dont_test_invalid_accept_header_returns_406(self): # """Ensure server returns a 406 (not acceptable) response if we set the Accept header to junk.""" # resp = self.client.get(reverse(views.RootResource), HTTP_ACCEPT='invalid/invalid') # self.assertNotEquals(resp['content-type'], 'invalid/invalid') # self.assertEquals(resp.status_code, 406) -# +# # def dont_test_prefer_specific_over_generic(self): # This test is broken right now # """More specific accept types have precedence over less specific types.""" # self.assert_accept_mimetype('application/xml, */*', expect='application/xml') # self.assert_accept_mimetype('*/*, application/xml', expect='application/xml') -# -# +# +# # class AllowedMethodsTests(TestCase): # """Basic tests to check that only allowed operations may be performed on a Resource""" -# +# # def dont_test_reading_a_read_only_resource_is_allowed(self): # """GET requests on a read only resource should default to a 200 (OK) response""" # resp = self.client.get(reverse(views.RootResource)) # self.assertEquals(resp.status_code, 200) -# +# # def dont_test_writing_to_read_only_resource_is_not_allowed(self): # """PUT requests on a read only resource should default to a 405 (method not allowed) response""" # resp = self.client.put(reverse(views.RootResource), {}) @@ -171,7 +171,7 @@ from blogpost import models, urls class TestRotation(TestCase): - """For the example the maximum amount of Blogposts is capped off at views.MAX_POSTS. + """For the example the maximum amount of Blogposts is capped off at views.MAX_POSTS. Whenever a new Blogpost is posted the oldest one should be popped.""" def setUp(self): @@ -193,7 +193,7 @@ class TestRotation(TestCase): view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource) view(request) self.assertEquals(len(models.BlogPost.objects.all()),models.MAX_POSTS) - + def test_fifo_behaviour(self): '''It's fine that the Blogposts are capped off at MAX_POSTS. But we want to make sure we see FIFO behaviour.''' for post in range(15): @@ -201,11 +201,11 @@ class TestRotation(TestCase): request = self.factory.post('/blog-post', data=form_data) view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource) view(request) - request = self.factory.get('/blog-post') + request = self.factory.get('/blog-post') view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource) response = view(request) response_posts = json.loads(response.content) response_titles = [d['title'] for d in response_posts] response_titles.reverse() self.assertEquals(response_titles, ['%s' % i for i in range(models.MAX_POSTS - 5, models.MAX_POSTS + 5)]) - \ No newline at end of file + diff --git a/examples/mixin/urls.py b/examples/mixin/urls.py index 1d25f6c7d..a3da3b2cc 100644 --- a/examples/mixin/urls.py +++ b/examples/mixin/urls.py @@ -1,4 +1,4 @@ -from djangorestframework.compat import View # Use Django 1.3's django.views.generic.View, or fall back to a clone of that if Django < 1.3 +from djangorestframework.compat import View # Use Django 1.3's django.views.generic.View, or fall back to a clone of that if Django < 1.3 from djangorestframework.mixins import ResponseMixin from djangorestframework.renderers import DEFAULT_RENDERERS from djangorestframework.response import Response diff --git a/examples/objectstore/urls.py b/examples/objectstore/urls.py index 2c685f592..0a3effa76 100644 --- a/examples/objectstore/urls.py +++ b/examples/objectstore/urls.py @@ -1,7 +1,7 @@ from django.conf.urls.defaults import patterns, url from objectstore.views import ObjectStoreRoot, StoredObject - + urlpatterns = patterns('objectstore.views', - url(r'^$', ObjectStoreRoot.as_view(), name='object-store-root'), + url(r'^$', ObjectStoreRoot.as_view(), name='object-store-root'), url(r'^(?P[A-Za-z0-9_-]{1,64})/$', StoredObject.as_view(), name='stored-object'), ) diff --git a/examples/objectstore/views.py b/examples/objectstore/views.py index 19999aa95..2a2225293 100644 --- a/examples/objectstore/views.py +++ b/examples/objectstore/views.py @@ -39,7 +39,7 @@ class ObjectStoreRoot(View): ctime_sorted_basenames = [item[0] for item in sorted([(os.path.basename(path), os.path.getctime(path)) for path in filepaths], key=operator.itemgetter(1), reverse=True)] return [reverse('stored-object', kwargs={'key':key}) for key in ctime_sorted_basenames] - + def post(self, request): """ Create a new stored object, with a unique key. diff --git a/examples/permissionsexample/models.py b/examples/permissionsexample/models.py index 232085ad3..790bbaf8e 100644 --- a/examples/permissionsexample/models.py +++ b/examples/permissionsexample/models.py @@ -1 +1 @@ -#for fixture loading \ No newline at end of file +#for fixture loading diff --git a/examples/permissionsexample/views.py b/examples/permissionsexample/views.py index f95c2c842..54a1fdd56 100644 --- a/examples/permissionsexample/views.py +++ b/examples/permissionsexample/views.py @@ -6,33 +6,33 @@ class PermissionsExampleView(View): """ A container view for permissions examples. """ - + def get(self, request): return [{'name': 'Throttling Example', 'url': reverse('throttled-resource')}, {'name': 'Logged in example', 'url': reverse('loggedin-resource')},] - + class ThrottlingExampleView(View): """ A basic read-only View that has a **per-user throttle** of 10 requests per minute. - + If a user exceeds the 10 requests limit within a period of one minute, the throttle will be applied until 60 seconds have passed since the first request. """ - + permissions = ( PerUserThrottling, ) throttle = '10/min' - + def get(self, request): """ Handle GET requests. """ return "Successful response to GET request because throttle is not yet active." - + class LoggedInExampleView(View): """ - You can login with **'test', 'test'.** + You can login with **'test', 'test'.** """ permissions = (IsAuthenticated, ) def get(self, request): - return 'Logged in or not?' \ No newline at end of file + return 'Logged in or not?' diff --git a/examples/pygments_api/forms.py b/examples/pygments_api/forms.py index 8488db062..30a59a845 100644 --- a/examples/pygments_api/forms.py +++ b/examples/pygments_api/forms.py @@ -13,7 +13,7 @@ class PygmentsForm(forms.Form): code = forms.CharField(widget=forms.Textarea, label='Code Text', - max_length=1000000, + max_length=1000000, help_text='(Copy and paste the code text here.)') title = forms.CharField(required=False, help_text='(Optional)', diff --git a/examples/pygments_api/tests.py b/examples/pygments_api/tests.py index 6eb69da5e..98139ce25 100644 --- a/examples/pygments_api/tests.py +++ b/examples/pygments_api/tests.py @@ -46,4 +46,4 @@ class TestPygmentsExample(TestCase): self.assertEquals(locations, response_locations) - \ No newline at end of file + diff --git a/examples/pygments_api/urls.py b/examples/pygments_api/urls.py index 905e31c5f..e0d44ece0 100644 --- a/examples/pygments_api/urls.py +++ b/examples/pygments_api/urls.py @@ -2,6 +2,6 @@ from django.conf.urls.defaults import patterns, url from pygments_api.views import PygmentsRoot, PygmentsInstance urlpatterns = patterns('', - url(r'^$', PygmentsRoot.as_view(), name='pygments-root'), + url(r'^$', PygmentsRoot.as_view(), name='pygments-root'), url(r'^([a-zA-Z0-9-]+)/$', PygmentsInstance.as_view(), name='pygments-instance'), ) diff --git a/examples/pygments_api/views.py b/examples/pygments_api/views.py index e50029f6e..884cff3af 100644 --- a/examples/pygments_api/views.py +++ b/examples/pygments_api/views.py @@ -72,10 +72,10 @@ class PygmentsRoot(View): linenos = 'table' if self.CONTENT['linenos'] else False options = {'title': self.CONTENT['title']} if self.CONTENT['title'] else {} formatter = HtmlFormatter(style=self.CONTENT['style'], linenos=linenos, full=True, **options) - + with open(pathname, 'w') as outfile: highlight(self.CONTENT['code'], lexer, formatter, outfile) - + remove_oldest_files(HIGHLIGHTED_CODE_DIR, MAX_FILES) return Response(status.HTTP_201_CREATED, headers={'Location': reverse('pygments-instance', args=[unique_id])}) diff --git a/examples/resourceexample/views.py b/examples/resourceexample/views.py index 990c78348..e6b5eeb89 100644 --- a/examples/resourceexample/views.py +++ b/examples/resourceexample/views.py @@ -34,7 +34,7 @@ class AnotherExampleView(View): if int(num) > 2: return Response(status.HTTP_404_NOT_FOUND) return "GET request to AnotherExampleResource %s" % num - + def post(self, request, num): """ Handle POST requests, with form validation. diff --git a/examples/runtests.py b/examples/runtests.py index fdd358395..a62d9a9ac 100644 --- a/examples/runtests.py +++ b/examples/runtests.py @@ -8,7 +8,7 @@ from coverage import coverage def main(): """Run the tests for the examples and generate a coverage report.""" - + # Discover the list of all modules that we should test coverage for project_dir = os.path.dirname(__file__) cov_files = [] @@ -18,7 +18,7 @@ def main(): continue cov_files.extend([os.path.join(path, file) for file in files if file.endswith('.py')]) TestRunner = get_runner(settings) - + cov = coverage() cov.erase() cov.start() diff --git a/examples/sandbox/views.py b/examples/sandbox/views.py index ecf62165b..f7a3542d7 100644 --- a/examples/sandbox/views.py +++ b/examples/sandbox/views.py @@ -14,8 +14,8 @@ class Sandbox(View): bash: curl -X GET http://api.django-rest-framework.org/ # (Use default renderer) bash: curl -X GET http://api.django-rest-framework.org/ -H 'Accept: text/plain' # (Use plaintext documentation renderer) - The examples provided: - + The examples provided: + 1. A basic example using the [Resource](http://django-rest-framework.org/library/resource.html) class. 2. A basic example using the [ModelResource](http://django-rest-framework.org/library/modelresource.html) class. 3. An basic example using Django 1.3's [class based views](http://docs.djangoproject.com/en/dev/topics/class-based-views/) and djangorestframework's [RendererMixin](http://django-rest-framework.org/library/renderers.html). diff --git a/examples/settings.py b/examples/settings.py index c47011fdb..4438bb849 100644 --- a/examples/settings.py +++ b/examples/settings.py @@ -51,7 +51,7 @@ MEDIA_ROOT = 'media/' # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash if there is a path component (optional in other cases). # Examples: "http://media.lawrence.com", "http://example.com/media/" -# NOTE: None of the djangorestframework examples serve media content via MEDIA_URL. +# NOTE: None of the djangorestframework examples serve media content via MEDIA_URL. MEDIA_URL = '' # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a @@ -90,10 +90,10 @@ TEMPLATE_DIRS = ( ) # for loading initial data -##SERIALIZATION_MODULES = { +##SERIALIZATION_MODULES = { # 'yml': "django.core.serializers.pyyaml" -#} +#} INSTALLED_APPS = ( From 833bcbf69d96ba6ddbb69defb6be256f34e5412e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 29 Dec 2011 13:35:09 +0000 Subject: [PATCH 25/53] Added ekohl --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 4d03fdc75..c1ed36617 100644 --- a/AUTHORS +++ b/AUTHORS @@ -20,6 +20,7 @@ Andrew McCloud Thomas Steinacher Meurig Freeman Anthony Nemitz +Ewoud Kohl van Wijngaarden THANKS TO: From d87113ff60e0a48c85eb0c8d37b86416312b18a2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 29 Dec 2011 13:54:13 +0000 Subject: [PATCH 26/53] For now just point the examples at rest.ep.io Would be nice to keep the custom subdomain, but too much of a PITA right now. --- docs/examples/blogpost.rst | 2 +- docs/examples/modelviews.rst | 10 +++++----- docs/examples/objectstore.rst | 2 +- docs/examples/pygments.rst | 8 ++++---- docs/examples/views.rst | 10 +++++----- docs/howto/mixin.rst | 4 ++-- docs/howto/usingcurl.rst | 2 +- docs/index.rst | 6 +++--- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/examples/blogpost.rst b/docs/examples/blogpost.rst index be91913dc..66d3a3bbd 100644 --- a/docs/examples/blogpost.rst +++ b/docs/examples/blogpost.rst @@ -3,7 +3,7 @@ Blog Posts API ============== -* http://api.django-rest-framework.org/blog-post/ +* http://rest.ep.io/blog-post/ The models ---------- diff --git a/docs/examples/modelviews.rst b/docs/examples/modelviews.rst index c60c9f246..a98ac279b 100644 --- a/docs/examples/modelviews.rst +++ b/docs/examples/modelviews.rst @@ -7,11 +7,11 @@ Getting Started - Model Views A live sandbox instance of this API is available: - http://api.django-rest-framework.org/model-resource-example/ + http://rest.ep.io/model-resource-example/ You can browse the API using a web browser, or from the command line:: - curl -X GET http://api.django-rest-framework.org/resource-example/ -H 'Accept: text/plain' + curl -X GET http://rest.ep.io/resource-example/ -H 'Accept: text/plain' Often you'll want parts of your API to directly map to existing django models. Django REST framework handles this nicely for you in a couple of ways: @@ -43,16 +43,16 @@ And we're done. We've now got a fully browseable API, which supports multiple i We can visit the API in our browser: -* http://api.django-rest-framework.org/model-resource-example/ +* http://rest.ep.io/model-resource-example/ Or access it from the command line using curl: .. code-block:: bash # Demonstrates API's input validation using form input - bash: curl -X POST --data 'foo=true' http://api.django-rest-framework.org/model-resource-example/ + bash: curl -X POST --data 'foo=true' http://rest.ep.io/model-resource-example/ {"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}} # Demonstrates API's input validation using JSON input - bash: curl -X POST -H 'Content-Type: application/json' --data-binary '{"foo":true}' http://api.django-rest-framework.org/model-resource-example/ + bash: curl -X POST -H 'Content-Type: application/json' --data-binary '{"foo":true}' http://rest.ep.io/model-resource-example/ {"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}} diff --git a/docs/examples/objectstore.rst b/docs/examples/objectstore.rst index 38bdbc983..65d613273 100644 --- a/docs/examples/objectstore.rst +++ b/docs/examples/objectstore.rst @@ -3,7 +3,7 @@ Object Store API ================ -* http://api.django-rest-framework.org/object-store/ +* http://rest.ep.io/object-store/ This example shows an object store API that can be used to store arbitrary serializable content. diff --git a/docs/examples/pygments.rst b/docs/examples/pygments.rst index b4bc2c166..62ca9e521 100644 --- a/docs/examples/pygments.rst +++ b/docs/examples/pygments.rst @@ -8,11 +8,11 @@ We're going to provide a simple wrapper around the awesome `pygments : http://example.com/my-api/ \ No newline at end of file + curl -X GET -H 'Accept: application/json' -u : http://example.com/my-api/ diff --git a/docs/index.rst b/docs/index.rst index 6038b7df5..94032daa9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,11 +11,11 @@ Introduction Django REST framework is a lightweight REST framework for Django, that aims to make it easy to build well-connected, self-describing RESTful Web APIs. -**Browse example APIs created with Django REST framework:** `The Sandbox `_ +**Browse example APIs created with Django REST framework:** `The Sandbox `_ Features: -* Automatically provides an awesome Django admin style `browse-able self-documenting API `_. +* Automatically provides an awesome Django admin style `browse-able self-documenting API `_. * Clean, simple, views for Resources, using Django's new `class based views `_. * Support for ModelResources with out-of-the-box default implementations and input validation. * Pluggable :mod:`.parsers`, :mod:`renderers`, :mod:`authentication` and :mod:`permissions` - Easy to customise. @@ -101,7 +101,7 @@ There are a few real world web API examples included with Django REST framework. All the examples are freely available for testing in the sandbox: - http://api.django-rest-framework.org + http://rest.ep.io (The :ref:`sandbox` resource is also documented.) From 3cf6fab3d9b21ad50d7a115a0c917c0e23a1a738 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 29 Dec 2011 13:57:41 +0000 Subject: [PATCH 27/53] Version 0.3.0 --- RELEASES | 5 +++++ djangorestframework/__init__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/RELEASES b/RELEASES index cd05173e6..85d26c711 100644 --- a/RELEASES +++ b/RELEASES @@ -1,3 +1,8 @@ +0.3.0 + +* JSONP Support +* Bugfixes, including support for latest markdown release + 0.2.4 * Fix broken IsAdminUser permission. diff --git a/djangorestframework/__init__.py b/djangorestframework/__init__.py index 0bcc04c06..f67a2d6af 100644 --- a/djangorestframework/__init__.py +++ b/djangorestframework/__init__.py @@ -1,3 +1,3 @@ -__version__ = '0.2.4' +__version__ = '0.3.0' VERSION = __version__ # synonym From a0c4dca9462c738aac3c712607997b9bd1f7b2c8 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 29 Dec 2011 14:00:31 +0000 Subject: [PATCH 28/53] Version: 0.3.1-dev --- djangorestframework/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangorestframework/__init__.py b/djangorestframework/__init__.py index f67a2d6af..62edc0708 100644 --- a/djangorestframework/__init__.py +++ b/djangorestframework/__init__.py @@ -1,3 +1,3 @@ -__version__ = '0.3.0' +__version__ = '0.3.1-dev' VERSION = __version__ # synonym From 91cee26ad89c04a70f33ae334150dd127f900474 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Fri, 30 Dec 2011 01:36:43 +0100 Subject: [PATCH 29/53] Some initial refactoring of the docs. --- docs/conf.py | 4 ++-- docs/examples.rst | 9 +++++++++ docs/examples/blogpost.rst | 2 -- docs/examples/modelviews.rst | 2 -- docs/examples/objectstore.rst | 2 -- docs/examples/pygments.rst | 2 -- docs/examples/sandbox.rst | 2 -- docs/examples/views.rst | 2 -- docs/index.rst | 27 ++++++++++----------------- 9 files changed, 21 insertions(+), 31 deletions(-) create mode 100644 docs/examples.rst diff --git a/docs/conf.py b/docs/conf.py index 78534ac3f..163888142 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,8 +14,8 @@ import sys, os sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) -sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'djangorestframework')) -sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'examples')) +sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'djangorestframework')) # for documenting the library +sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'examples')) # for importing settings import settings from django.core.management import setup_environ setup_environ(settings) diff --git a/docs/examples.rst b/docs/examples.rst new file mode 100644 index 000000000..911ebdf30 --- /dev/null +++ b/docs/examples.rst @@ -0,0 +1,9 @@ +.. toctree:: + :maxdepth: 1 + + examples/views + examples/modelviews + examples/objectstore + examples/pygments + examples/blogpost + examples/sandbox diff --git a/docs/examples/blogpost.rst b/docs/examples/blogpost.rst index be91913dc..597d715a6 100644 --- a/docs/examples/blogpost.rst +++ b/docs/examples/blogpost.rst @@ -1,5 +1,3 @@ -.. _blogposts: - Blog Posts API ============== diff --git a/docs/examples/modelviews.rst b/docs/examples/modelviews.rst index c60c9f246..24e35f457 100644 --- a/docs/examples/modelviews.rst +++ b/docs/examples/modelviews.rst @@ -1,5 +1,3 @@ -.. _modelviews: - Getting Started - Model Views ----------------------------- diff --git a/docs/examples/objectstore.rst b/docs/examples/objectstore.rst index 38bdbc983..601eeabbf 100644 --- a/docs/examples/objectstore.rst +++ b/docs/examples/objectstore.rst @@ -1,5 +1,3 @@ -.. _objectstore: - Object Store API ================ diff --git a/docs/examples/pygments.rst b/docs/examples/pygments.rst index b4bc2c166..82a2ceed9 100644 --- a/docs/examples/pygments.rst +++ b/docs/examples/pygments.rst @@ -1,5 +1,3 @@ -.. _codehighlighting: - Code Highlighting API ===================== diff --git a/docs/examples/sandbox.rst b/docs/examples/sandbox.rst index b6658ad9c..ec465aafe 100644 --- a/docs/examples/sandbox.rst +++ b/docs/examples/sandbox.rst @@ -1,5 +1,3 @@ -.. _sandbox: - Sandbox Root API ================ diff --git a/docs/examples/views.rst b/docs/examples/views.rst index 59e13976f..0e02aa29b 100644 --- a/docs/examples/views.rst +++ b/docs/examples/views.rst @@ -1,5 +1,3 @@ -.. _views: - Getting Started - Views ----------------------- diff --git a/docs/index.rst b/docs/index.rst index 6038b7df5..704055dee 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -87,24 +87,23 @@ Using Django REST framework can be as simple as adding a few lines to your urlco Django REST framework comes with two "getting started" examples. -#. :ref:`views` -#. :ref:`modelviews` +#. :doc:`examples/views` +#. :doc:`examples/modelviews` Examples -------- There are a few real world web API examples included with Django REST framework. -#. :ref:`objectstore` - Using :class:`views.View` classes for APIs that do not map to models. -#. :ref:`codehighlighting` - Using :class:`views.View` classes with forms for input validation. -#. :ref:`blogposts` - Using :class:`views.ModelView` classes for APIs that map directly to models. +#. :doc:`examples/objectstore` - Using :class:`views.View` classes for APIs that do not map to models. +#. :doc:`examples/pygments` - Using :class:`views.View` classes with forms for input validation. +#. :doc:`examples/blogpost` - Using :class:`views.ModelView` classes for APIs that map directly to models. All the examples are freely available for testing in the sandbox: http://api.django-rest-framework.org -(The :ref:`sandbox` resource is also documented.) - +(The :doc:`examples/sandbox` resource is also documented.) How Tos, FAQs & Notes @@ -136,19 +135,13 @@ Library Reference library/status library/views -Examples Reference ------------------- +Example Reference +----------------- .. toctree:: - :maxdepth: 1 + :maxdepth: 2 - examples/views - examples/modelviews - examples/objectstore - examples/pygments - examples/blogpost - examples/sandbox - howto/mixin + examples.rst Indices and tables ------------------ From 08a1fb9a7093c2dab1759216e00e3acb00a0f671 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 30 Dec 2011 12:21:33 +0000 Subject: [PATCH 30/53] Use 2.7, fool. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 38e22c111..694584d7e 100644 --- a/README.rst +++ b/README.rst @@ -35,7 +35,7 @@ To clone the project from GitHub using git:: To install django-rest-framework in a virtualenv environment:: cd django-rest-framework - virtualenv --no-site-packages --distribute --python=python2.6 env + virtualenv --no-site-packages --distribute --python=python2.7 env source env/bin/activate pip install -r requirements.txt # django, coverage From 23979a9953f5dc7d0a5ffcb6c5d1e7573dabd3f3 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Fri, 30 Dec 2011 13:42:01 +0100 Subject: [PATCH 31/53] Added django 1.4 aplha 1. --- tox.ini | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1375568cb..015b5d2e4 100644 --- a/tox.ini +++ b/tox.ini @@ -9,13 +9,18 @@ envlist= py25-django13, py26-django13, py27-django13, - + py25-django14a1, + py26-django14a1, + py27-django14a1, py25-django12e, py26-django12e, py27-django12e, py25-django13e, py26-django13e, py27-django13e + py25-django14a1e, + py26-django14a1e, + py27-django14a1e ########################################### CORE TESTS ############################################ @@ -83,6 +88,36 @@ deps= # Optional packages: markdown +[testenv:py25-django14a1] +basepython=python2.5 +deps= + http://www.djangoproject.com/download/1.4-alpha-1/tarball/ + coverage==3.4 + unittest-xml-reporting==1.2 + Pyyaml==3.10 + # Optional packages: + markdown + +[testenv:py26-django14a1] +basepython=python2.6 +deps= + http://www.djangoproject.com/download/1.4-alpha-1/tarball/ + coverage==3.4 + unittest-xml-reporting==1.2 + Pyyaml==3.10 + # Optional packages: + markdown + +[testenv:py27-django14a1] +basepython=python2.7 +deps= + http://www.djangoproject.com/download/1.4-alpha-1/tarball/ + coverage==3.4 + unittest-xml-reporting==1.2 + Pyyaml==3.10 + # Optional packages: + markdown + ####################################### EXAMPLES ################################################ [testenv:py25-django12e] @@ -169,6 +204,48 @@ deps= unittest-xml-reporting==1.2 Pyyaml==3.10 +[testenv:py25-django14a1e] +basepython=python2.5 +commands= + python examples/runtests.py +deps= + http://www.djangoproject.com/download/1.4-alpha-1/tarball/ + coverage==3.4 + wsgiref==0.1.2 + Pygments==1.4 + httplib2==0.6.0 + Markdown==2.0.3 + unittest-xml-reporting==1.2 + Pyyaml==3.10 + +[testenv:py26-django14a1e] +basepython=python2.6 +commands= + python examples/runtests.py +deps= + http://www.djangoproject.com/download/1.4-alpha-1/tarball/ + coverage==3.4 + wsgiref==0.1.2 + Pygments==1.4 + httplib2==0.6.0 + Markdown==2.0.3 + unittest-xml-reporting==1.2 + Pyyaml==3.10 + +[testenv:py27-django14a1e] +basepython=python2.7 +commands= + python examples/runtests.py +deps= + http://www.djangoproject.com/download/1.4-alpha-1/tarball/ + coverage==3.4 + wsgiref==0.1.2 + Pygments==1.4 + httplib2==0.6.0 + Markdown==2.0.3 + unittest-xml-reporting==1.2 + Pyyaml==3.10 + ##########################################DOCS################################################# [testenv:docs] From ff9cb862d1b1c670629b26158c62c2eec90f0a28 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 30 Dec 2011 13:06:57 +0000 Subject: [PATCH 32/53] Keep it simple. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 694584d7e..97aae8230 100644 --- a/README.rst +++ b/README.rst @@ -35,7 +35,7 @@ To clone the project from GitHub using git:: To install django-rest-framework in a virtualenv environment:: cd django-rest-framework - virtualenv --no-site-packages --distribute --python=python2.7 env + virtualenv --no-site-packages --distribute env source env/bin/activate pip install -r requirements.txt # django, coverage From c7b9100f19d7a3701b03cfca6715a9341439b9a8 Mon Sep 17 00:00:00 2001 From: Michael Ding Date: Fri, 30 Dec 2011 22:52:01 +0800 Subject: [PATCH 33/53] change the key name "field-errors" to "field_errors". as "-" is not allowed as a part of key name in javascript --- djangorestframework/resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangorestframework/resources.py b/djangorestframework/resources.py index bf9af6f04..68b285b91 100644 --- a/djangorestframework/resources.py +++ b/djangorestframework/resources.py @@ -173,7 +173,7 @@ class FormResource(Resource): field_errors[key] = [u'This field does not exist.'] if field_errors: - detail[u'field-errors'] = field_errors + detail[u'field_errors'] = field_errors # Return HTTP 400 response (BAD REQUEST) raise ErrorResponse(400, detail) From a1d31c8a3a9e694da7cea8a03579ff938000bf96 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Fri, 30 Dec 2011 16:01:15 +0100 Subject: [PATCH 34/53] drop linkchecking --- docs/check_sphinx.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/check_sphinx.py b/docs/check_sphinx.py index 74874eede..feb04abd2 100644 --- a/docs/check_sphinx.py +++ b/docs/check_sphinx.py @@ -1,13 +1,6 @@ import pytest import subprocess -def test_linkcheck(tmpdir): - doctrees = tmpdir.join("doctrees") - htmldir = tmpdir.join("html") - subprocess.check_call( - ["sphinx-build", "-q", "-blinkcheck", - "-d", str(doctrees), ".", str(htmldir)]) - def test_build_docs(tmpdir): doctrees = tmpdir.join("doctrees") htmldir = "html" #we want to keep the docs From abf0ee8b97a995f3b5d91beccb36acfdf976f0f6 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Fri, 30 Dec 2011 16:13:24 +0100 Subject: [PATCH 35/53] some more refactoring and some style enhancements. Just committing to stay in sync. --- docs/contents.rst | 9 ++++++ docs/examples.rst | 26 ++++++++++++++---- docs/index.rst | 56 +++++++++----------------------------- docs/library.rst | 8 ++++++ docs/templates/layout.html | 3 +- 5 files changed, 52 insertions(+), 50 deletions(-) create mode 100644 docs/contents.rst create mode 100644 docs/library.rst diff --git a/docs/contents.rst b/docs/contents.rst new file mode 100644 index 000000000..4dbe5baa5 --- /dev/null +++ b/docs/contents.rst @@ -0,0 +1,9 @@ +Documentation +============= + +.. toctree:: + :maxdepth: 2 + + library + examples + diff --git a/docs/examples.rst b/docs/examples.rst index 911ebdf30..640883454 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -1,9 +1,23 @@ +Examples +======== + +There are a few real world web API examples included with Django REST framework. + +#. :doc:`examples/objectstore` - Using :class:`views.View` classes for APIs that do not map to models. +#. :doc:`examples/pygments` - Using :class:`views.View` classes with forms for input validation. +#. :doc:`examples/blogpost` - Using :class:`views.ModelView` classes for APIs that map directly to models. + +All the examples are freely available for testing in the sandbox: + + http://rest.ep.io + +(The :doc:`examples/sandbox` resource is also documented.) + +Example Reference +----------------- + .. toctree:: :maxdepth: 1 + :glob: - examples/views - examples/modelviews - examples/objectstore - examples/pygments - examples/blogpost - examples/sandbox + examples/* diff --git a/docs/index.rst b/docs/index.rst index be2e9cab5..bec9e67f3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -70,6 +70,13 @@ Getting Started Using Django REST framework can be as simple as adding a few lines to your urlconf. +The following example exposes your `MyModel` model through an api. It will provide two views: + + * A view which lists your model instances and simultaniously allows creation of instances + from that view. + + * Another view which lets you view, update or delete your model instances individually. + ``urls.py``:: from django.conf.urls.defaults import patterns, url @@ -85,27 +92,6 @@ Using Django REST framework can be as simple as adding a few lines to your urlco url(r'^(?P[^/]+)/$', InstanceModelView.as_view(resource=MyResource)), ) -Django REST framework comes with two "getting started" examples. - -#. :doc:`examples/views` -#. :doc:`examples/modelviews` - -Examples --------- - -There are a few real world web API examples included with Django REST framework. - -#. :doc:`examples/objectstore` - Using :class:`views.View` classes for APIs that do not map to models. -#. :doc:`examples/pygments` - Using :class:`views.View` classes with forms for input validation. -#. :doc:`examples/blogpost` - Using :class:`views.ModelView` classes for APIs that map directly to models. - -All the examples are freely available for testing in the sandbox: - - http://rest.ep.io - -(The :doc:`examples/sandbox` resource is also documented.) - - How Tos, FAQs & Notes --------------------- @@ -117,31 +103,15 @@ How Tos, FAQs & Notes howto/alternativeframeworks howto/mixin -Library Reference ------------------ +.. include:: library.rst + + +.. include:: examples.rst .. toctree:: - :maxdepth: 1 + :hidden: - library/authentication - library/compat - library/mixins - library/parsers - library/permissions - library/renderers - library/resource - library/response - library/serializer - library/status - library/views - -Example Reference ------------------ - -.. toctree:: - :maxdepth: 2 - - examples.rst + contents Indices and tables ------------------ diff --git a/docs/library.rst b/docs/library.rst new file mode 100644 index 000000000..b0309da0c --- /dev/null +++ b/docs/library.rst @@ -0,0 +1,8 @@ +Library +======= + +.. toctree:: + :maxdepth: 1 + :glob: + + library/* diff --git a/docs/templates/layout.html b/docs/templates/layout.html index 705325542..a59645f2a 100644 --- a/docs/templates/layout.html +++ b/docs/templates/layout.html @@ -23,5 +23,6 @@ })(); -{% endblock %}{% block footer %} +{% endblock %} +{% block footer %} From 904f197474ad3561d3d273961a70ab19d8175aaf Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 30 Dec 2011 15:32:49 +0000 Subject: [PATCH 36/53] Replace field-errors with field_errors --- djangorestframework/tests/validators.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/djangorestframework/tests/validators.py b/djangorestframework/tests/validators.py index 1f696422e..18c8c3137 100644 --- a/djangorestframework/tests/validators.py +++ b/djangorestframework/tests/validators.py @@ -149,7 +149,7 @@ class TestFormValidation(TestCase): try: validator.validate_request(content, None) except ErrorResponse, exc: - self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.']}}) + self.assertEqual(exc.response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}}) else: self.fail('ResourceException was not raised') #pragma: no cover @@ -159,7 +159,7 @@ class TestFormValidation(TestCase): try: validator.validate_request(content, None) except ErrorResponse, exc: - self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.']}}) + self.assertEqual(exc.response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}}) else: self.fail('ResourceException was not raised') #pragma: no cover @@ -169,7 +169,7 @@ class TestFormValidation(TestCase): try: validator.validate_request(content, None) except ErrorResponse, exc: - self.assertEqual(exc.response.raw_content, {'field-errors': {'extra': ['This field does not exist.']}}) + self.assertEqual(exc.response.raw_content, {'field_errors': {'extra': ['This field does not exist.']}}) else: self.fail('ResourceException was not raised') #pragma: no cover @@ -179,7 +179,7 @@ class TestFormValidation(TestCase): try: validator.validate_request(content, None) except ErrorResponse, exc: - self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.'], + self.assertEqual(exc.response.raw_content, {'field_errors': {'qwerty': ['This field is required.'], 'extra': ['This field does not exist.']}}) else: self.fail('ResourceException was not raised') #pragma: no cover From b8bfebc83719a7c286fee413bb44fffedac1a8d0 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 30 Dec 2011 15:33:08 +0000 Subject: [PATCH 37/53] ep.io fixes --- examples/epio.ini | 3 ++- examples/objectstore/views.py | 3 +++ examples/settings.py | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/epio.ini b/examples/epio.ini index 00a90bce1..4e61a42d0 100644 --- a/examples/epio.ini +++ b/examples/epio.ini @@ -42,11 +42,12 @@ postgres = true # to the real file 'configs/epio.py': # config.py = configs/epio.py +media/ = %(data_directory)s/ # #### If you're using Django, you'll want to uncomment some or all of these lines #### # [django] # # Path to your project root, relative to this directory. -# base = . +# base = . # # [static] # Serve the admin media diff --git a/examples/objectstore/views.py b/examples/objectstore/views.py index 2a2225293..8f997d7ef 100644 --- a/examples/objectstore/views.py +++ b/examples/objectstore/views.py @@ -13,6 +13,9 @@ import operator OBJECT_STORE_DIR = os.path.join(settings.MEDIA_ROOT, 'objectstore') MAX_FILES = 10 +if not os.path.exists(OBJECT_STORE_DIR): + os.makedirs(OBJECT_STORE_DIR) + def remove_oldest_files(dir, max_files): """ diff --git a/examples/settings.py b/examples/settings.py index 4438bb849..e12b7f3fe 100644 --- a/examples/settings.py +++ b/examples/settings.py @@ -1,4 +1,5 @@ # Settings for djangorestframework examples project +import os DEBUG = True TEMPLATE_DEBUG = DEBUG @@ -46,7 +47,7 @@ USE_L10N = True # Absolute filesystem path to the directory that will hold user-uploaded files. # Example: "/home/media/media.lawrence.com/" # NOTE: Some of the djangorestframework examples use MEDIA_ROOT to store content. -MEDIA_ROOT = 'media/' +MEDIA_ROOT = os.path.join(os.getenv('EPIO_DATA_DIRECTORY', '.'), 'media') # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash if there is a path component (optional in other cases). From 615e8d7e77764d26cf1e6f305b2362b2cafd4f29 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 30 Dec 2011 15:34:39 +0000 Subject: [PATCH 38/53] Added @yandy. Thanks! --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index c1ed36617..5a2bc165f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -21,6 +21,7 @@ Thomas Steinacher Meurig Freeman Anthony Nemitz Ewoud Kohl van Wijngaarden +Michael Ding THANKS TO: From fd449204c1e2987bd2f05b43ab3ed03e7569f99a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 30 Dec 2011 15:51:00 +0000 Subject: [PATCH 39/53] Fixup for epio --- examples/pygments_api/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/pygments_api/views.py b/examples/pygments_api/views.py index 884cff3af..ffea60ae3 100644 --- a/examples/pygments_api/views.py +++ b/examples/pygments_api/views.py @@ -22,6 +22,9 @@ import operator HIGHLIGHTED_CODE_DIR = os.path.join(settings.MEDIA_ROOT, 'pygments') MAX_FILES = 10 +if not os.path.exists(HIGHLIGHTED_CODE_DIR): + os.makedirs(HIGHLIGHTED_CODE_DIR) + def list_dir_sorted_by_ctime(dir): """ From c81c0d843ec555c4cc4b71f58852d2281a9cb5b2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 30 Dec 2011 16:30:55 +0000 Subject: [PATCH 40/53] Fixes #102 --- djangorestframework/utils/staticviews.py | 1 + 1 file changed, 1 insertion(+) diff --git a/djangorestframework/utils/staticviews.py b/djangorestframework/utils/staticviews.py index de2cb5d8b..7f5cb2e58 100644 --- a/djangorestframework/utils/staticviews.py +++ b/djangorestframework/utils/staticviews.py @@ -1,6 +1,7 @@ from django.contrib.auth.views import * from django.conf import settings from django.http import HttpResponse +from django.shortcuts import render_to_response import base64 def deny_robots(request): From 2394fc70d1c33def374014226e7648db12562af1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 30 Dec 2011 16:38:03 +0000 Subject: [PATCH 41/53] And add RequestContext too --- djangorestframework/utils/staticviews.py | 1 + 1 file changed, 1 insertion(+) diff --git a/djangorestframework/utils/staticviews.py b/djangorestframework/utils/staticviews.py index 7f5cb2e58..825ea6b5c 100644 --- a/djangorestframework/utils/staticviews.py +++ b/djangorestframework/utils/staticviews.py @@ -2,6 +2,7 @@ from django.contrib.auth.views import * from django.conf import settings from django.http import HttpResponse from django.shortcuts import render_to_response +from django.template import RequestContext import base64 def deny_robots(request): From e82ab8ea31de5a89470dd039e426af0e16df54f8 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Fri, 30 Dec 2011 18:46:18 +0100 Subject: [PATCH 42/53] Added a simple example of how to use urllib and urllib2 to interact with an api. --- docs/contents.rst | 3 ++- docs/howto.rst | 8 ++++++++ docs/howto/usingurllib2.rst | 33 +++++++++++++++++++++++++++++++++ docs/index.rst | 11 +---------- 4 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 docs/howto.rst create mode 100644 docs/howto/usingurllib2.rst diff --git a/docs/contents.rst b/docs/contents.rst index 4dbe5baa5..d8e6e7428 100644 --- a/docs/contents.rst +++ b/docs/contents.rst @@ -3,7 +3,8 @@ Documentation .. toctree:: :maxdepth: 2 - + + howto library examples diff --git a/docs/howto.rst b/docs/howto.rst new file mode 100644 index 000000000..8fdc09267 --- /dev/null +++ b/docs/howto.rst @@ -0,0 +1,8 @@ +How Tos, FAQs & Notes +===================== + +.. toctree:: + :maxdepth: 1 + :glob: + + howto/* diff --git a/docs/howto/usingurllib2.rst b/docs/howto/usingurllib2.rst new file mode 100644 index 000000000..8b655d455 --- /dev/null +++ b/docs/howto/usingurllib2.rst @@ -0,0 +1,33 @@ +Using urllib2 +============= + +Python's standard library comes with some nice modules +you can use to test your api or even write a full client. + +Here's an example which does a 'GET' on the `model-resource` examle +in the sandbox.:: + + import urllib2 + >>> r = urllib2.urlopen('htpp://rest.ep.io/model-resource-example') + # You can check if the response was ok: + >>> r.getcode() + 200 + # Or examin the resonse itself: + >>> print r.read() + [{"url": "http://rest.ep.io/model-resource-example/1/", "baz": "sdf", "foo": true, "bar": 123}] + +And here's an example which does a 'POST' to create a new instance:: + + # First encode tha data we want to POST, we'll use urllib for encoding + # and the time module to send the current time as as a string value for our POST + >>> import urllib, time + >>> d = urllib.urlencode((('bar', 123), ('baz', time.asctime()))) + # Now use the Request class and specify the 'Content-type' + >>> req = urllib2.Request('http://rest.ep.io/model-resource-example/', data=d, headers={'Content-Type':'application/x-www-form-urlencoded'}) + >>> resp = urllib2.urlopen(req) + >>> resp.getcode() + 201 + >>> resp.read() + '{"url": "http://rest.ep.io/model-resource-example/4/", "baz": "Fri Dec 30 18:22:52 2011", "foo": false, "bar": 123}' + +That should get you started to write a client for your own api. diff --git a/docs/index.rst b/docs/index.rst index bec9e67f3..3311bfa99 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -92,16 +92,7 @@ The following example exposes your `MyModel` model through an api. It will provi url(r'^(?P[^/]+)/$', InstanceModelView.as_view(resource=MyResource)), ) -How Tos, FAQs & Notes ---------------------- - -.. toctree:: - :maxdepth: 1 - - howto/setup - howto/usingcurl - howto/alternativeframeworks - howto/mixin +.. include:: howto.rst .. include:: library.rst From e44f182fcf51d0f169461c4a36647adb7324e8a9 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Fri, 30 Dec 2011 18:52:15 +0100 Subject: [PATCH 43/53] typos --- docs/howto/usingurllib2.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/howto/usingurllib2.rst b/docs/howto/usingurllib2.rst index 8b655d455..a31bcc9bb 100644 --- a/docs/howto/usingurllib2.rst +++ b/docs/howto/usingurllib2.rst @@ -4,15 +4,15 @@ Using urllib2 Python's standard library comes with some nice modules you can use to test your api or even write a full client. -Here's an example which does a 'GET' on the `model-resource` examle +Here's an example which does a 'GET' on the `model-resource` example in the sandbox.:: - import urllib2 + >>> import urllib2 >>> r = urllib2.urlopen('htpp://rest.ep.io/model-resource-example') # You can check if the response was ok: >>> r.getcode() 200 - # Or examin the resonse itself: + # Or examin the response itself: >>> print r.read() [{"url": "http://rest.ep.io/model-resource-example/1/", "baz": "sdf", "foo": true, "bar": 123}] From e419e2066fad135d161ae833b8973026f811706b Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Fri, 30 Dec 2011 19:03:05 +0100 Subject: [PATCH 44/53] Colouring of literal code Get the nice colouring sphinx provides and fix some more typos. --- docs/howto/usingurllib2.rst | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/howto/usingurllib2.rst b/docs/howto/usingurllib2.rst index a31bcc9bb..139bf7173 100644 --- a/docs/howto/usingurllib2.rst +++ b/docs/howto/usingurllib2.rst @@ -4,25 +4,31 @@ Using urllib2 Python's standard library comes with some nice modules you can use to test your api or even write a full client. +Using the 'GET' method +---------------------- + Here's an example which does a 'GET' on the `model-resource` example in the sandbox.:: >>> import urllib2 >>> r = urllib2.urlopen('htpp://rest.ep.io/model-resource-example') - # You can check if the response was ok: - >>> r.getcode() + >>> r.getcode() # Check if the response was ok 200 - # Or examin the response itself: - >>> print r.read() + >>> print r.read() # Examin the response itself [{"url": "http://rest.ep.io/model-resource-example/1/", "baz": "sdf", "foo": true, "bar": 123}] -And here's an example which does a 'POST' to create a new instance:: +Using the 'POST' method +----------------------- + +And here's an example which does a 'POST' to create a new instance. First let's encode +the data we want to POST. We'll use `urllib` for encoding and the `time` module +to send the current time as as a string value for our POST.:: - # First encode tha data we want to POST, we'll use urllib for encoding - # and the time module to send the current time as as a string value for our POST >>> import urllib, time >>> d = urllib.urlencode((('bar', 123), ('baz', time.asctime()))) - # Now use the Request class and specify the 'Content-type' + +Now use the `Request` class and specify the 'Content-type':: + >>> req = urllib2.Request('http://rest.ep.io/model-resource-example/', data=d, headers={'Content-Type':'application/x-www-form-urlencoded'}) >>> resp = urllib2.urlopen(req) >>> resp.getcode() From 412727440beb678ba3beef78ee0b934d412afe64 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Mon, 2 Jan 2012 14:55:19 +0100 Subject: [PATCH 45/53] Add an extra explenation on how to use curl on this view. --- examples/permissionsexample/views.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/permissionsexample/views.py b/examples/permissionsexample/views.py index 54a1fdd56..3f71e67bb 100644 --- a/examples/permissionsexample/views.py +++ b/examples/permissionsexample/views.py @@ -31,8 +31,11 @@ class ThrottlingExampleView(View): class LoggedInExampleView(View): """ - You can login with **'test', 'test'.** - """ + You can login with **'test', 'test'.** or use curl: + + `curl -X GET -H 'Accept: application/json' -u test:test http://localhost:8000/permissions-example` + """ + permissions = (IsAuthenticated, ) def get(self, request): return 'Logged in or not?' From 9871532746aee7e0f5796ae24cfb44c878759c38 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Mon, 2 Jan 2012 18:12:22 +0100 Subject: [PATCH 46/53] Added an example of how to use authentication and throttling. --- docs/examples/permissions.rst | 66 +++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 docs/examples/permissions.rst diff --git a/docs/examples/permissions.rst b/docs/examples/permissions.rst new file mode 100644 index 000000000..cfd7b4461 --- /dev/null +++ b/docs/examples/permissions.rst @@ -0,0 +1,66 @@ +Permissions +=========== + +This example will show how you can protect your api by using authentication +and how you can limit the amount of requests a user can do to a resource by setting +a throttle to your view. + +Authentication +-------------- + +If you want to protect your api from unauthorized users, Django REST Framework +offers you two default authentication methods: + + * Basic Authentication + * Django's session-based authentication + +These authentication methods are by default enabled. But they are not used unless +you specifically state that your view requires authentication. + +To do this you just need to import the `Isauthenticated` class from the frameworks' `permissions` module.:: + + from djangorestframework.permissions import IsAuthenticated + +Then you enable authentication by setting the right 'permission requirement' to the `permissions` class attribute of your View like +the example View below.: + + +.. literalinclude:: ../../examples/permissionsexample/views.py + :pyobject: LoggedInExampleView + +The `IsAuthenticated` permission will only let a user do a 'GET' if he is authenticated. Try it +yourself on the live sandbox__ + +__ http://rest.ep.io/permissions-example/loggedin + + +Throttling +---------- + +If you want to limit the amount of requests a client is allowed to do on +a resource, then you can set a 'throttle' to achieve this. + +For this to work you'll need to import the `PerUserThrottling` class from the `permissions` +module.:: + + from djangorestframework.permissions import PerUserThrottling + +In the example below we have limited the amount of requests one 'client' or 'user' +may do on our view to 10 requests per minute.: + +.. literalinclude:: ../../examples/permissionsexample/views.py + :pyobject: ThrottlingExampleView + +Try it yourself on the live sandbox__. + +__ http://rest.ep.io/permissions-example/throttling + +Now if you want a view to require both aurhentication and throttling, you simply declare them +both:: + + permissions = (PerUserThrottling, Isauthenticated) + +To see what other throttles are available, have a look at the :doc:`../library/permissions` module. + +If you want to implement your own authentication method, then refer to the :doc:`../library/authentication` +module. From 7a3d97a2fc3abeb8a22d7ddb0eafbd4cf34e9f4e Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Mon, 2 Jan 2012 18:42:22 +0100 Subject: [PATCH 47/53] :mod: directive is nicer here --- docs/examples/permissions.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/examples/permissions.rst b/docs/examples/permissions.rst index cfd7b4461..eafc32555 100644 --- a/docs/examples/permissions.rst +++ b/docs/examples/permissions.rst @@ -60,7 +60,7 @@ both:: permissions = (PerUserThrottling, Isauthenticated) -To see what other throttles are available, have a look at the :doc:`../library/permissions` module. +To see what other throttles are available, have a look at the :mod:`permissions` module. -If you want to implement your own authentication method, then refer to the :doc:`../library/authentication` +If you want to implement your own authentication method, then refer to the :mod:`authentication` module. From 0b21af938825830a85a9cbb6515b985e72f19453 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 2 Jan 2012 18:31:46 +0000 Subject: [PATCH 48/53] Drop mercurial reference. --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 3311bfa99..4141ccf5b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -49,7 +49,7 @@ You can install Django REST framework using ``pip`` or ``easy_install``:: pip install djangorestframework -Or get the latest development version using mercurial or git:: +Or get the latest development version using git:: git clone git@github.com:tomchristie/django-rest-framework.git From 1a72836edce52d50f2775af85a1de4280913c7fb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 2 Jan 2012 18:36:53 +0000 Subject: [PATCH 49/53] Rename examples in tox. --- tox.ini | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tox.ini b/tox.ini index 015b5d2e4..5e07e0f21 100644 --- a/tox.ini +++ b/tox.ini @@ -120,7 +120,7 @@ deps= ####################################### EXAMPLES ################################################ -[testenv:py25-django12e] +[testenv:py25-django12-examples] basepython=python2.5 commands= python examples/runtests.py @@ -134,7 +134,7 @@ deps= unittest-xml-reporting==1.2 Pyyaml==3.10 -[testenv:py26-django12e] +[testenv:py26-django12-examples] basepython=python2.6 commands= python examples/runtests.py @@ -148,7 +148,7 @@ deps= unittest-xml-reporting==1.2 Pyyaml==3.10 -[testenv:py27-django12e] +[testenv:py27-django12-examples] basepython=python2.7 commands= python examples/runtests.py @@ -162,7 +162,7 @@ deps= unittest-xml-reporting==1.2 Pyyaml==3.10 -[testenv:py25-django13e] +[testenv:py25-django13-examples] basepython=python2.5 commands= python examples/runtests.py @@ -176,7 +176,7 @@ deps= unittest-xml-reporting==1.2 Pyyaml==3.10 -[testenv:py26-django13e] +[testenv:py26-django13-examples] basepython=python2.6 commands= python examples/runtests.py @@ -190,7 +190,7 @@ deps= unittest-xml-reporting==1.2 Pyyaml==3.10 -[testenv:py27-django13e] +[testenv:py27-django13-examples] basepython=python2.7 commands= python examples/runtests.py @@ -204,7 +204,7 @@ deps= unittest-xml-reporting==1.2 Pyyaml==3.10 -[testenv:py25-django14a1e] +[testenv:py25-django14a1-examples] basepython=python2.5 commands= python examples/runtests.py @@ -218,7 +218,7 @@ deps= unittest-xml-reporting==1.2 Pyyaml==3.10 -[testenv:py26-django14a1e] +[testenv:py26-django14a1-examples] basepython=python2.6 commands= python examples/runtests.py @@ -232,7 +232,7 @@ deps= unittest-xml-reporting==1.2 Pyyaml==3.10 -[testenv:py27-django14a1e] +[testenv:py27-django14a1-examples] basepython=python2.7 commands= python examples/runtests.py From 9f349ca6fee18c0b1973173f6ce5fd1f163485dc Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 2 Jan 2012 18:37:41 +0000 Subject: [PATCH 50/53] Rename examples in tox. --- tox.ini | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tox.ini b/tox.ini index 5e07e0f21..6c51cc071 100644 --- a/tox.ini +++ b/tox.ini @@ -12,15 +12,15 @@ envlist= py25-django14a1, py26-django14a1, py27-django14a1, - py25-django12e, - py26-django12e, - py27-django12e, - py25-django13e, - py26-django13e, - py27-django13e - py25-django14a1e, - py26-django14a1e, - py27-django14a1e + py25-django12-examples, + py26-django12-examples, + py27-django12-examples, + py25-django13-examples, + py26-django13-examples, + py27-django13-examples, + py25-django14a1-examples, + py26-django14a1-examples, + py27-django14a1-examples ########################################### CORE TESTS ############################################ From 48a79a99e35057d4ddfeddeeaf1288ff1e44377a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 2 Jan 2012 18:39:29 +0000 Subject: [PATCH 51/53] Add 1.4. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 97aae8230..454674403 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ We also have a `Jenkins service Date: Mon, 2 Jan 2012 18:39:55 +0000 Subject: [PATCH 52/53] Add 1.4 --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 4141ccf5b..b105e1ab7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -39,7 +39,7 @@ Requirements ------------ * Python (2.5, 2.6, 2.7 supported) -* Django (1.2, 1.3 supported) +* Django (1.2, 1.3, 1.4-alpha supported) Installation From e0b82ce76de261c8f530cb488afb390c4aec13a8 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Mon, 2 Jan 2012 21:16:44 +0100 Subject: [PATCH 53/53] minor style edits --- docs/howto/mixin.rst | 6 +++--- docs/howto/setup.rst | 12 ++++++++---- docs/howto/usingurllib2.rst | 4 ++-- docs/index.rst | 1 + 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/howto/mixin.rst b/docs/howto/mixin.rst index d8b1c4f03..1a84f2ad4 100644 --- a/docs/howto/mixin.rst +++ b/docs/howto/mixin.rst @@ -1,8 +1,7 @@ Using Django REST framework Mixin classes ========================================= -This example demonstrates creating a REST API **without** using Django REST framework's :class:`.Resource` or :class:`.ModelResource`, -but instead using Django :class:`View` class, and adding the :class:`EmitterMixin` class to provide full HTTP Accept header content negotiation, +This example demonstrates creating a REST API **without** using Django REST framework's :class:`.Resource` or :class:`.ModelResource`, but instead using Django's :class:`View` class, and adding the :class:`ResponseMixin` class to provide full HTTP Accept header content negotiation, a browseable Web API, and much of the other goodness that Django REST framework gives you for free. .. note:: @@ -26,5 +25,6 @@ Everything we need for this example can go straight into the URL conf... .. include:: ../../examples/mixin/urls.py :literal: -That's it. Auto-magically our API now supports multiple output formats, specified either by using standard HTTP Accept header content negotiation, or by using the `&_accept=application/json` style parameter overrides. +That's it. Auto-magically our API now supports multiple output formats, specified either by using +standard HTTP Accept header content negotiation, or by using the `&_accept=application/json` style parameter overrides. We even get a nice HTML view which can be used to self-document our API. diff --git a/docs/howto/setup.rst b/docs/howto/setup.rst index a9053f733..f14e04999 100644 --- a/docs/howto/setup.rst +++ b/docs/howto/setup.rst @@ -13,7 +13,7 @@ If you need to manually install Django REST framework to your ``site-packages`` Template Loaders ---------------- -Django REST framework uses a few templates for the HTML and plain text documenting emitters. +Django REST framework uses a few templates for the HTML and plain text documenting renderers. * Ensure ``TEMPLATE_LOADERS`` setting contains ``'django.template.loaders.app_directories.Loader'``. @@ -22,16 +22,20 @@ This will be the case by default so you shouldn't normally need to do anything h Admin Styling ------------- -Django REST framework uses the admin media for styling. When running using Django's testserver this is automatically served for you, but once you move onto a production server, you'll want to make sure you serve the admin media separately, exactly as you would do `if using the Django admin `_. +Django REST framework uses the admin media for styling. When running using Django's testserver this is automatically served for you, +but once you move onto a production server, you'll want to make sure you serve the admin media separately, exactly as you would do +`if using the Django admin `_. -* Ensure that the ``ADMIN_MEDIA_PREFIX`` is set appropriately and that you are serving the admin media. (Django's testserver will automatically serve the admin media for you) +* Ensure that the ``ADMIN_MEDIA_PREFIX`` is set appropriately and that you are serving the admin media. + (Django's testserver will automatically serve the admin media for you) Markdown -------- The Python `markdown library `_ is not required but comes recommended. -If markdown is installed your :class:`.Resource` descriptions can include `markdown style formatting `_ which will be rendered by the HTML documenting emitter. +If markdown is installed your :class:`.Resource` descriptions can include `markdown style formatting +`_ which will be rendered by the HTML documenting renderer. robots.txt, favicon, login/logout --------------------------------- diff --git a/docs/howto/usingurllib2.rst b/docs/howto/usingurllib2.rst index 139bf7173..6320dc208 100644 --- a/docs/howto/usingurllib2.rst +++ b/docs/howto/usingurllib2.rst @@ -1,5 +1,5 @@ -Using urllib2 -============= +Using urllib2 with Django REST Framework +======================================== Python's standard library comes with some nice modules you can use to test your api or even write a full client. diff --git a/docs/index.rst b/docs/index.rst index 3311bfa99..c8a58f1f7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,6 +14,7 @@ Django REST framework is a lightweight REST framework for Django, that aims to m **Browse example APIs created with Django REST framework:** `The Sandbox `_ Features: +--------- * Automatically provides an awesome Django admin style `browse-able self-documenting API `_. * Clean, simple, views for Resources, using Django's new `class based views `_.