mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-28 12:34:00 +03:00
merged master into experimental
This commit is contained in:
commit
59e6cd9892
3
AUTHORS
3
AUTHORS
|
@ -19,6 +19,9 @@ Danilo Bargen <gwrtheyrn>
|
||||||
Andrew McCloud <amccloud>
|
Andrew McCloud <amccloud>
|
||||||
Thomas Steinacher <thomasst>
|
Thomas Steinacher <thomasst>
|
||||||
Meurig Freeman <meurig>
|
Meurig Freeman <meurig>
|
||||||
|
Anthony Nemitz <anemitz>
|
||||||
|
Ewoud Kohl van Wijngaarden <ekohl>
|
||||||
|
Michael Ding <yandy>
|
||||||
|
|
||||||
THANKS TO:
|
THANKS TO:
|
||||||
|
|
||||||
|
|
21
LICENSE
21
LICENSE
|
@ -1,9 +1,22 @@
|
||||||
Copyright (c) 2011, Tom Christie
|
Copyright (c) 2011, Tom Christie
|
||||||
All rights reserved.
|
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 of source code must retain the above copyright notice, this
|
||||||
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.
|
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.
|
||||||
|
|
|
@ -16,10 +16,12 @@ Full documentation for the project is available at http://django-rest-framework.
|
||||||
Issue tracking is on `GitHub <https://github.com/tomchristie/django-rest-framework/issues>`_.
|
Issue tracking is on `GitHub <https://github.com/tomchristie/django-rest-framework/issues>`_.
|
||||||
General questions should be taken to the `discussion group <http://groups.google.com/group/django-rest-framework>`_.
|
General questions should be taken to the `discussion group <http://groups.google.com/group/django-rest-framework>`_.
|
||||||
|
|
||||||
|
We also have a `Jenkins service <http://jenkins.tibold.nl/job/djangorestframework/>`_ which runs our test suite.
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
|
|
||||||
* Python (2.5, 2.6, 2.7 supported)
|
* Python (2.5, 2.6, 2.7 supported)
|
||||||
* Django (1.2, 1.3 supported)
|
* Django (1.2, 1.3, 1.4-alpha supported)
|
||||||
|
|
||||||
|
|
||||||
Installation Notes
|
Installation Notes
|
||||||
|
@ -33,7 +35,7 @@ To clone the project from GitHub using git::
|
||||||
To install django-rest-framework in a virtualenv environment::
|
To install django-rest-framework in a virtualenv environment::
|
||||||
|
|
||||||
cd django-rest-framework
|
cd django-rest-framework
|
||||||
virtualenv --no-site-packages --distribute --python=python2.6 env
|
virtualenv --no-site-packages --distribute env
|
||||||
source env/bin/activate
|
source env/bin/activate
|
||||||
pip install -r requirements.txt # django, coverage
|
pip install -r requirements.txt # django, coverage
|
||||||
|
|
||||||
|
|
5
RELEASES
5
RELEASES
|
@ -1,3 +1,8 @@
|
||||||
|
0.3.0
|
||||||
|
|
||||||
|
* JSONP Support
|
||||||
|
* Bugfixes, including support for latest markdown release
|
||||||
|
|
||||||
0.2.4
|
0.2.4
|
||||||
|
|
||||||
* Fix broken IsAdminUser permission.
|
* Fix broken IsAdminUser permission.
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
__version__ = '0.2.4'
|
__version__ = '0.3.1-dev'
|
||||||
|
|
||||||
VERSION = __version__ # synonym
|
VERSION = __version__ # synonym
|
||||||
|
|
|
@ -11,7 +11,8 @@ set of :class:`authentication` classes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.contrib.auth import authenticate
|
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
|
import base64
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
@ -67,7 +68,7 @@ class BasicAuthentication(BaseAuthentication):
|
||||||
supplied using HTTP Basic authentication.
|
supplied using HTTP Basic authentication.
|
||||||
Otherwise returns :const:`None`.
|
Otherwise returns :const:`None`.
|
||||||
"""
|
"""
|
||||||
from django.utils import encoding
|
from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError
|
||||||
|
|
||||||
if 'HTTP_AUTHORIZATION' in request.META:
|
if 'HTTP_AUTHORIZATION' in request.META:
|
||||||
auth = request.META['HTTP_AUTHORIZATION'].split()
|
auth = request.META['HTTP_AUTHORIZATION'].split()
|
||||||
|
@ -83,10 +84,9 @@ class BasicAuthentication(BaseAuthentication):
|
||||||
except encoding.DjangoUnicodeDecodeError:
|
except encoding.DjangoUnicodeDecodeError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
user = self._authenticate_user(username, password)
|
user = authenticate(username=username, password=password)
|
||||||
if user:
|
if user is not None and user.is_active:
|
||||||
return user
|
return user
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,18 +100,26 @@ class UserLoggedInAuthentication(BaseAuthentication):
|
||||||
Returns a :obj:`User` if the request session currently has a logged in
|
Returns a :obj:`User` if the request session currently has a logged in
|
||||||
user. Otherwise returns :const:`None`.
|
user. Otherwise returns :const:`None`.
|
||||||
"""
|
"""
|
||||||
# TODO: Switch this back to request.POST, and let
|
# TODO: Might be cleaner to switch this back to using request.POST,
|
||||||
# FormParser/MultiPartParser deal with the consequences.
|
# and let FormParser/MultiPartParser deal with the consequences.
|
||||||
if getattr(request, 'user', None) and request.user.is_active:
|
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 request.method.upper() == 'POST':
|
||||||
# Temporarily replace request.POST with .DATA,
|
if hasattr(self.view.DATA, 'get'):
|
||||||
# so that we use our more generic request parsing
|
|
||||||
request._post = self.view.DATA
|
request._post = self.view.DATA
|
||||||
|
else:
|
||||||
|
request._post = {}
|
||||||
|
|
||||||
resp = CsrfViewMiddleware().process_view(request, None, (), {})
|
resp = CsrfViewMiddleware().process_view(request, None, (), {})
|
||||||
|
|
||||||
|
# Replace request.POST
|
||||||
|
if request.method.upper() == 'POST':
|
||||||
del(request._post)
|
del(request._post)
|
||||||
if resp is not None: # csrf failed
|
|
||||||
return None
|
if resp is None: # csrf passed
|
||||||
return request.user
|
return request.user
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,25 @@
|
||||||
"""
|
"""
|
||||||
The :mod:`compat` module provides support for backwards compatibility with older versions of django/python.
|
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:
|
try:
|
||||||
import cStringIO as StringIO
|
import cStringIO as StringIO
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import StringIO
|
import StringIO
|
||||||
|
|
||||||
|
|
||||||
# parse_qs
|
# parse_qs from 'urlparse' module unless python 2.5, in which case from 'cgi'
|
||||||
try:
|
try:
|
||||||
# python >= ?
|
# python >= 2.6
|
||||||
from urlparse import parse_qs
|
from urlparse import parse_qs
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# python <= ?
|
# python < 2.6
|
||||||
from cgi import parse_qs
|
from cgi import parse_qs
|
||||||
|
|
||||||
|
|
||||||
# django.test.client.RequestFactory (Django >= 1.3)
|
# django.test.client.RequestFactory (Required for Django < 1.3)
|
||||||
try:
|
try:
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -156,18 +157,233 @@ except ImportError:
|
||||||
def head(self, request, *args, **kwargs):
|
def head(self, request, *args, **kwargs):
|
||||||
return self.get(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
|
# Markdown is optional
|
||||||
try:
|
try:
|
||||||
import markdown
|
import markdown
|
||||||
import re
|
|
||||||
|
|
||||||
class CustomSetextHeaderProcessor(markdown.blockprocessors.BlockProcessor):
|
class CustomSetextHeaderProcessor(markdown.blockprocessors.BlockProcessor):
|
||||||
"""
|
"""
|
||||||
Override `markdown`'s :class:`SetextHeaderProcessor`, so that ==== headers are <h2> and ---- headers are <h3>.
|
Class for markdown < 2.1
|
||||||
|
|
||||||
|
Override `markdown`'s :class:`SetextHeaderProcessor`, so that ==== headers are <h2> and ---- heade
|
||||||
|
|
||||||
We use <h1> for the resource name.
|
We use <h1> for the resource name.
|
||||||
"""
|
"""
|
||||||
|
import re
|
||||||
# Detect Setext-style header. Must be first 2 lines of block.
|
# Detect Setext-style header. Must be first 2 lines of block.
|
||||||
RE = re.compile(r'^.*?\n[=-]{3,}', re.MULTILINE)
|
RE = re.compile(r'^.*?\n[=-]{3,}', re.MULTILINE)
|
||||||
|
|
||||||
|
@ -189,18 +405,22 @@ try:
|
||||||
|
|
||||||
def apply_markdown(text):
|
def apply_markdown(text):
|
||||||
"""
|
"""
|
||||||
Simple wrapper around :func:`markdown.markdown` to apply our :class:`CustomSetextHeaderProcessor`,
|
Simple wrapper around :func:`markdown.markdown` to set the base level
|
||||||
and also set the base level of '#' style headers to <h2>.
|
of '#' style headers to <h2>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
extensions = ['headerid(level=2)']
|
extensions = ['headerid(level=2)']
|
||||||
safe_mode = False,
|
safe_mode = False,
|
||||||
|
|
||||||
|
if markdown.version_info < (2, 1):
|
||||||
output_format = markdown.DEFAULT_OUTPUT_FORMAT
|
output_format = markdown.DEFAULT_OUTPUT_FORMAT
|
||||||
|
|
||||||
md = markdown.Markdown(extensions=markdown.load_extensions(extensions),
|
md = markdown.Markdown(extensions=markdown.load_extensions(extensions),
|
||||||
safe_mode=safe_mode,
|
safe_mode=safe_mode,
|
||||||
output_format=output_format)
|
output_format=output_format)
|
||||||
md.parser.blockprocessors['setextheader'] = CustomSetextHeaderProcessor(md.parser)
|
md.parser.blockprocessors['setextheader'] = CustomSetextHeaderProcessor(md.parser)
|
||||||
|
else:
|
||||||
|
md = markdown.Markdown(extensions=extensions, safe_mode=safe_mode)
|
||||||
return md.convert(text)
|
return md.convert(text)
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -211,3 +431,4 @@ try:
|
||||||
import yaml
|
import yaml
|
||||||
except ImportError:
|
except ImportError:
|
||||||
yaml = None
|
yaml = None
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ __all__ = (
|
||||||
'BaseRenderer',
|
'BaseRenderer',
|
||||||
'TemplateRenderer',
|
'TemplateRenderer',
|
||||||
'JSONRenderer',
|
'JSONRenderer',
|
||||||
|
'JSONPRenderer',
|
||||||
'DocumentingHTMLRenderer',
|
'DocumentingHTMLRenderer',
|
||||||
'DocumentingXHTMLRenderer',
|
'DocumentingXHTMLRenderer',
|
||||||
'DocumentingPlainTextRenderer',
|
'DocumentingPlainTextRenderer',
|
||||||
|
@ -113,6 +114,28 @@ class JSONRenderer(BaseRenderer):
|
||||||
return json.dumps(obj, cls=DateTimeAwareJSONEncoder, indent=indent, sort_keys=sort_keys)
|
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):
|
class XMLRenderer(BaseRenderer):
|
||||||
"""
|
"""
|
||||||
Renderer which serializes to XML.
|
Renderer which serializes to XML.
|
||||||
|
@ -326,7 +349,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
|
||||||
'logout_url': logout_url,
|
'logout_url': logout_url,
|
||||||
'FORMAT_PARAM': self._FORMAT_QUERY_PARAM,
|
'FORMAT_PARAM': self._FORMAT_QUERY_PARAM,
|
||||||
'METHOD_PARAM': getattr(self.view, '_METHOD_PARAM', None),
|
'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)
|
ret = template.render(context)
|
||||||
|
@ -376,6 +399,7 @@ class DocumentingPlainTextRenderer(DocumentingTemplateRenderer):
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_RENDERERS = ( JSONRenderer,
|
DEFAULT_RENDERERS = ( JSONRenderer,
|
||||||
|
JSONPRenderer,
|
||||||
DocumentingHTMLRenderer,
|
DocumentingHTMLRenderer,
|
||||||
DocumentingXHTMLRenderer,
|
DocumentingXHTMLRenderer,
|
||||||
DocumentingPlainTextRenderer,
|
DocumentingPlainTextRenderer,
|
||||||
|
|
|
@ -78,6 +78,7 @@ class FormResource(Resource):
|
||||||
def validate_request(self, data, files=None):
|
def validate_request(self, data, files=None):
|
||||||
"""
|
"""
|
||||||
Given some content as input return some cleaned, validated content.
|
Given some content as input return some cleaned, validated content.
|
||||||
|
|
||||||
Raises a :exc:`response.ErrorResponse` with status code 400
|
Raises a :exc:`response.ErrorResponse` with status code 400
|
||||||
# (Bad Request) on failure.
|
# (Bad Request) on failure.
|
||||||
|
|
||||||
|
@ -125,16 +126,15 @@ class FormResource(Resource):
|
||||||
data = data and data or {}
|
data = data and data or {}
|
||||||
files = files and files or {}
|
files = files and files or {}
|
||||||
|
|
||||||
seen_fields = set(data.keys())
|
|
||||||
form_fields = set(bound_form.fields.keys())
|
|
||||||
allowed_extra_fields = set(allowed_extra_fields)
|
|
||||||
|
|
||||||
# In addition to regular validation we also ensure no additional fields
|
# In addition to regular validation we also ensure no additional fields
|
||||||
# are being passed in...
|
# are being passed in...
|
||||||
# TODO: Hardcoded ignore_fields here is pretty icky.
|
seen_fields_set = set(data.keys())
|
||||||
ignore_fields = set(('csrfmiddlewaretoken', '_accept', '_method'))
|
form_fields_set = set(bound_form.fields.keys())
|
||||||
allowed_fields = form_fields | allowed_extra_fields | ignore_fields
|
allowed_extra_fields_set = set(allowed_extra_fields)
|
||||||
unknown_fields = seen_fields - allowed_fields
|
|
||||||
|
# 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
|
# Check using both regular validation, and our stricter no additional fields rule
|
||||||
if bound_form.is_valid() and not unknown_fields:
|
if bound_form.is_valid() and not unknown_fields:
|
||||||
|
@ -178,7 +178,7 @@ class FormResource(Resource):
|
||||||
field_errors[key] = [u'This field does not exist.']
|
field_errors[key] = [u'This field does not exist.']
|
||||||
|
|
||||||
if field_errors:
|
if field_errors:
|
||||||
detail[u'field-errors'] = field_errors
|
detail[u'field_errors'] = field_errors
|
||||||
|
|
||||||
# Return HTTP 400 response (BAD REQUEST)
|
# Return HTTP 400 response (BAD REQUEST)
|
||||||
raise ErrorResponse(400, detail)
|
raise ErrorResponse(400, detail)
|
||||||
|
@ -204,6 +204,7 @@ class FormResource(Resource):
|
||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
|
||||||
def get_bound_form(self, data=None, files=None, method=None):
|
def get_bound_form(self, data=None, files=None, method=None):
|
||||||
"""
|
"""
|
||||||
Given some content return a Django form bound to that content.
|
Given some content return a Django form bound to that content.
|
||||||
|
@ -302,6 +303,7 @@ class ModelResource(FormResource):
|
||||||
def validate_request(self, data, files=None):
|
def validate_request(self, data, files=None):
|
||||||
"""
|
"""
|
||||||
Given some content as input return some cleaned, validated content.
|
Given some content as input return some cleaned, validated content.
|
||||||
|
|
||||||
Raises a :exc:`response.ErrorResponse` with status code 400
|
Raises a :exc:`response.ErrorResponse` with status code 400
|
||||||
(Bad Request) on failure.
|
(Bad Request) on failure.
|
||||||
|
|
||||||
|
@ -410,7 +412,8 @@ class ModelResource(FormResource):
|
||||||
if self.fields:
|
if self.fields:
|
||||||
return model_fields & set(self.fields)
|
return model_fields & set(self.fields)
|
||||||
|
|
||||||
return model_fields - set(self.exclude)
|
return model_fields - set(as_tuple(self.exclude))
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _property_fields_set(self):
|
def _property_fields_set(self):
|
||||||
|
|
|
@ -95,7 +95,6 @@ INSTALLED_APPS = (
|
||||||
# Uncomment the next line to enable admin documentation:
|
# Uncomment the next line to enable admin documentation:
|
||||||
# 'django.contrib.admindocs',
|
# 'django.contrib.admindocs',
|
||||||
'djangorestframework',
|
'djangorestframework',
|
||||||
'djangorestframework.tests',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# OAuth support is optional, so we only test oauth if it's installed.
|
# OAuth support is optional, so we only test oauth if it's installed.
|
||||||
|
|
|
@ -19,8 +19,11 @@ indented
|
||||||
|
|
||||||
# hash style header #"""
|
# hash style header #"""
|
||||||
|
|
||||||
# If markdown is installed we also test it's working (and that our wrapped forces '=' to h2 and '-' to h3)
|
# If markdown is installed we also test it's working
|
||||||
MARKED_DOWN = """<h2>an example docstring</h2>
|
# (and that our wrapped forces '=' to h2 and '-' to h3)
|
||||||
|
|
||||||
|
# We support markdown < 2.1 and markdown >= 2.1
|
||||||
|
MARKED_DOWN_lt_21 = """<h2>an example docstring</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>list</li>
|
<li>list</li>
|
||||||
<li>list</li>
|
<li>list</li>
|
||||||
|
@ -31,6 +34,17 @@ MARKED_DOWN = """<h2>an example docstring</h2>
|
||||||
<p>indented</p>
|
<p>indented</p>
|
||||||
<h2 id="hash_style_header">hash style header</h2>"""
|
<h2 id="hash_style_header">hash style header</h2>"""
|
||||||
|
|
||||||
|
MARKED_DOWN_gte_21 = """<h2 id="an-example-docstring">an example docstring</h2>
|
||||||
|
<ul>
|
||||||
|
<li>list</li>
|
||||||
|
<li>list</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="another-header">another header</h3>
|
||||||
|
<pre><code>code block
|
||||||
|
</code></pre>
|
||||||
|
<p>indented</p>
|
||||||
|
<h2 id="hash-style-header">hash style header</h2>"""
|
||||||
|
|
||||||
|
|
||||||
class TestViewNamesAndDescriptions(TestCase):
|
class TestViewNamesAndDescriptions(TestCase):
|
||||||
def test_resource_name_uses_classname_by_default(self):
|
def test_resource_name_uses_classname_by_default(self):
|
||||||
|
@ -92,4 +106,6 @@ class TestViewNamesAndDescriptions(TestCase):
|
||||||
def test_markdown(self):
|
def test_markdown(self):
|
||||||
"""Ensure markdown to HTML works as expected"""
|
"""Ensure markdown to HTML works as expected"""
|
||||||
if apply_markdown:
|
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)
|
||||||
|
|
|
@ -8,13 +8,15 @@ from djangorestframework.mixins import PaginatorMixin, ModelMixin
|
||||||
from djangorestframework.resources import ModelResource
|
from djangorestframework.resources import ModelResource
|
||||||
from djangorestframework.response import Response
|
from djangorestframework.response import Response
|
||||||
from djangorestframework.tests.models import CustomUser
|
from djangorestframework.tests.models import CustomUser
|
||||||
|
from djangorestframework.tests.testcases import TestModelsTestCase
|
||||||
from djangorestframework.views import View
|
from djangorestframework.views import View
|
||||||
|
|
||||||
|
|
||||||
class TestModelCreation(TestCase):
|
class TestModelCreation(TestModelsTestCase):
|
||||||
"""Tests on CreateModelMixin"""
|
"""Tests on CreateModelMixin"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
super(TestModelsTestCase, self).setUp()
|
||||||
self.req = RequestFactory()
|
self.req = RequestFactory()
|
||||||
|
|
||||||
def test_creation(self):
|
def test_creation(self):
|
||||||
|
|
|
@ -5,6 +5,7 @@ from django.contrib.auth.models import Group, User
|
||||||
from djangorestframework.resources import ModelResource
|
from djangorestframework.resources import ModelResource
|
||||||
from djangorestframework.views import ListOrCreateModelView, InstanceModelView
|
from djangorestframework.views import ListOrCreateModelView, InstanceModelView
|
||||||
from djangorestframework.tests.models import CustomUser
|
from djangorestframework.tests.models import CustomUser
|
||||||
|
from djangorestframework.tests.testcases import TestModelsTestCase
|
||||||
|
|
||||||
class GroupResource(ModelResource):
|
class GroupResource(ModelResource):
|
||||||
model = Group
|
model = Group
|
||||||
|
@ -31,7 +32,7 @@ urlpatterns = patterns('',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ModelViewTests(TestCase):
|
class ModelViewTests(TestModelsTestCase):
|
||||||
"""Test the model views djangorestframework provides"""
|
"""Test the model views djangorestframework provides"""
|
||||||
urls = 'djangorestframework.tests.modelviews'
|
urls = 'djangorestframework.tests.modelviews'
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,10 @@ from django.conf.urls.defaults import patterns, url
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from djangorestframework import status
|
from djangorestframework import status
|
||||||
|
from djangorestframework.views import View
|
||||||
from djangorestframework.compat import View as DjangoView
|
from djangorestframework.compat import View as DjangoView
|
||||||
from djangorestframework.renderers import BaseRenderer, JSONRenderer, \
|
from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
|
||||||
YAMLRenderer, XMLRenderer
|
XMLRenderer, JSONPRenderer
|
||||||
from djangorestframework.parsers import JSONParser, YAMLParser
|
from djangorestframework.parsers import JSONParser, YAMLParser
|
||||||
from djangorestframework.mixins import ResponseMixin
|
from djangorestframework.mixins import ResponseMixin
|
||||||
from djangorestframework.response import Response
|
from djangorestframework.response import Response
|
||||||
|
@ -44,9 +45,16 @@ class MockView(ResponseMixin, DjangoView):
|
||||||
return self.render(response)
|
return self.render(response)
|
||||||
|
|
||||||
|
|
||||||
|
class MockGETView(View):
|
||||||
|
def get(self, request, **kwargs):
|
||||||
|
return {'foo': ['bar', 'baz']}
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])),
|
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])),
|
||||||
url(r'^$', 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])),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -190,6 +198,45 @@ class JSONRendererTests(TestCase):
|
||||||
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:
|
if YAMLRenderer:
|
||||||
_yaml_repr = 'foo: [bar, baz]\n'
|
_yaml_repr = 'foo: [bar, baz]\n'
|
||||||
|
|
||||||
|
@ -203,7 +250,7 @@ if YAMLRenderer:
|
||||||
"""
|
"""
|
||||||
Test basic YAML rendering.
|
Test basic YAML rendering.
|
||||||
"""
|
"""
|
||||||
obj = {'foo':['bar','baz']}
|
obj = {'foo': ['bar', 'baz']}
|
||||||
renderer = YAMLRenderer(None)
|
renderer = YAMLRenderer(None)
|
||||||
content = renderer.render(obj, 'application/yaml')
|
content = renderer.render(obj, 'application/yaml')
|
||||||
self.assertEquals(content, _yaml_repr)
|
self.assertEquals(content, _yaml_repr)
|
||||||
|
@ -214,7 +261,7 @@ if YAMLRenderer:
|
||||||
Test rendering and then parsing returns the original object.
|
Test rendering and then parsing returns the original object.
|
||||||
IE obj -> render -> parse -> obj.
|
IE obj -> render -> parse -> obj.
|
||||||
"""
|
"""
|
||||||
obj = {'foo':['bar','baz']}
|
obj = {'foo': ['bar', 'baz']}
|
||||||
|
|
||||||
renderer = YAMLRenderer(None)
|
renderer = YAMLRenderer(None)
|
||||||
parser = YAMLParser(None)
|
parser = YAMLParser(None)
|
||||||
|
@ -224,7 +271,6 @@ if YAMLRenderer:
|
||||||
self.assertEquals(obj, data)
|
self.assertEquals(obj, data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class XMLRendererTestCase(TestCase):
|
class XMLRendererTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests specific to the XML Renderer
|
Tests specific to the XML Renderer
|
||||||
|
@ -280,7 +326,6 @@ class XMLRendererTestCase(TestCase):
|
||||||
content = renderer.render({'field': None}, 'application/xml')
|
content = renderer.render({'field': None}, 'application/xml')
|
||||||
self.assertXMLContains(content, '<field></field>')
|
self.assertXMLContains(content, '<field></field>')
|
||||||
|
|
||||||
|
|
||||||
def assertXMLContains(self, xml, string):
|
def assertXMLContains(self, xml, string):
|
||||||
self.assertTrue(xml.startswith('<?xml version="1.0" encoding="utf-8"?>\n<root>'))
|
self.assertTrue(xml.startswith('<?xml version="1.0" encoding="utf-8"?>\n<root>'))
|
||||||
self.assertTrue(xml.endswith('</root>'))
|
self.assertTrue(xml.endswith('</root>'))
|
||||||
|
|
|
@ -56,8 +56,8 @@ class TestFieldNesting(TestCase):
|
||||||
self.serialize = self.serializer.serialize
|
self.serialize = self.serializer.serialize
|
||||||
|
|
||||||
class M1(models.Model):
|
class M1(models.Model):
|
||||||
field1 = models.CharField()
|
field1 = models.CharField(max_length=256)
|
||||||
field2 = models.CharField()
|
field2 = models.CharField(max_length=256)
|
||||||
|
|
||||||
class M2(models.Model):
|
class M2(models.Model):
|
||||||
field = models.OneToOneField(M1)
|
field = models.OneToOneField(M1)
|
||||||
|
|
63
djangorestframework/tests/testcases.py
Normal file
63
djangorestframework/tests/testcases.py
Normal file
|
@ -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)
|
|
@ -149,7 +149,7 @@ class TestFormValidation(TestCase):
|
||||||
try:
|
try:
|
||||||
validator.validate_request(content, None)
|
validator.validate_request(content, None)
|
||||||
except ErrorResponse, exc:
|
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:
|
else:
|
||||||
self.fail('ResourceException was not raised') #pragma: no cover
|
self.fail('ResourceException was not raised') #pragma: no cover
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ class TestFormValidation(TestCase):
|
||||||
try:
|
try:
|
||||||
validator.validate_request(content, None)
|
validator.validate_request(content, None)
|
||||||
except ErrorResponse, exc:
|
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:
|
else:
|
||||||
self.fail('ResourceException was not raised') #pragma: no cover
|
self.fail('ResourceException was not raised') #pragma: no cover
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ class TestFormValidation(TestCase):
|
||||||
try:
|
try:
|
||||||
validator.validate_request(content, None)
|
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.']}})
|
self.assertEqual(exc.response.raw_content, {'field_errors': {'extra': ['This field does not exist.']}})
|
||||||
else:
|
else:
|
||||||
self.fail('ResourceException was not raised') #pragma: no cover
|
self.fail('ResourceException was not raised') #pragma: no cover
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ class TestFormValidation(TestCase):
|
||||||
try:
|
try:
|
||||||
validator.validate_request(content, None)
|
validator.validate_request(content, None)
|
||||||
except ErrorResponse, exc:
|
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.']}})
|
'extra': ['This field does not exist.']}})
|
||||||
else:
|
else:
|
||||||
self.fail('ResourceException was not raised') #pragma: no cover
|
self.fail('ResourceException was not raised') #pragma: no cover
|
||||||
|
|
|
@ -18,6 +18,24 @@ from mediatypes import add_media_type_param, get_media_type_params, order_by_pre
|
||||||
|
|
||||||
MSIE_USER_AGENT_REGEX = re.compile(r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )')
|
MSIE_USER_AGENT_REGEX = re.compile(r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )')
|
||||||
|
|
||||||
|
def as_tuple(obj):
|
||||||
|
"""
|
||||||
|
Given an object which may be a list/tuple, another object, or None,
|
||||||
|
return that object in list form.
|
||||||
|
|
||||||
|
IE:
|
||||||
|
If the object is already a list/tuple just return it.
|
||||||
|
If the object is not None, return it in a list with a single element.
|
||||||
|
If the object is None return an empty list.
|
||||||
|
"""
|
||||||
|
if obj is None:
|
||||||
|
return ()
|
||||||
|
elif isinstance(obj, list):
|
||||||
|
return tuple(obj)
|
||||||
|
elif isinstance(obj, tuple):
|
||||||
|
return obj
|
||||||
|
return (obj,)
|
||||||
|
|
||||||
|
|
||||||
def url_resolves(url):
|
def url_resolves(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from django.contrib.auth.views import *
|
from django.contrib.auth.views import *
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django.shortcuts import render_to_response
|
||||||
|
from django.template import RequestContext
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
def deny_robots(request):
|
def deny_robots(request):
|
||||||
|
|
9
docs/check_sphinx.py
Normal file
9
docs/check_sphinx.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import pytest
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
def test_build_docs(tmpdir):
|
||||||
|
doctrees = tmpdir.join("doctrees")
|
||||||
|
htmldir = "html" #we want to keep the docs
|
||||||
|
subprocess.check_call([
|
||||||
|
"sphinx-build", "-q", "-bhtml",
|
||||||
|
"-d", str(doctrees), ".", str(htmldir)])
|
16
docs/conf.py
16
docs/conf.py
|
@ -14,8 +14,8 @@
|
||||||
import sys, os
|
import sys, os
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
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__)), 'djangorestframework')) # for documenting the library
|
||||||
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__)), 'examples')) # for importing settings
|
||||||
import settings
|
import settings
|
||||||
from django.core.management import setup_environ
|
from django.core.management import setup_environ
|
||||||
setup_environ(settings)
|
setup_environ(settings)
|
||||||
|
@ -55,9 +55,13 @@ copyright = u'2011, Tom Christie'
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.1'
|
|
||||||
|
import djangorestframework
|
||||||
|
|
||||||
|
version = djangorestframework.__version__
|
||||||
|
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '0.1'
|
release = version
|
||||||
|
|
||||||
autodoc_member_order='bysource'
|
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
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# 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
|
# 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
|
# further. For a list of options available for each theme, see the
|
||||||
|
@ -220,3 +224,5 @@ html_static_path = []
|
||||||
#man_pages = [
|
#man_pages = [
|
||||||
# ()
|
# ()
|
||||||
#]
|
#]
|
||||||
|
|
||||||
|
linkcheck_timeout = 120 # seconds, set to extra large value for link_checks
|
||||||
|
|
10
docs/contents.rst
Normal file
10
docs/contents.rst
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
Documentation
|
||||||
|
=============
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
howto
|
||||||
|
library
|
||||||
|
examples
|
||||||
|
|
23
docs/examples.rst
Normal file
23
docs/examples.rst
Normal file
|
@ -0,0 +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/*
|
|
@ -1,9 +1,7 @@
|
||||||
.. _blogposts:
|
|
||||||
|
|
||||||
Blog Posts API
|
Blog Posts API
|
||||||
==============
|
==============
|
||||||
|
|
||||||
* http://api.django-rest-framework.org/blog-post/
|
* http://rest.ep.io/blog-post/
|
||||||
|
|
||||||
The models
|
The models
|
||||||
----------
|
----------
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
.. _modelviews:
|
|
||||||
|
|
||||||
Getting Started - Model Views
|
Getting Started - Model Views
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
|
@ -7,11 +5,11 @@ Getting Started - Model Views
|
||||||
|
|
||||||
A live sandbox instance of this API is available:
|
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::
|
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:
|
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 +41,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:
|
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:
|
Or access it from the command line using curl:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
# Demonstrates API's input validation using form input
|
# 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."]}}
|
{"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}}
|
||||||
|
|
||||||
# Demonstrates API's input validation using JSON input
|
# 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."]}}
|
{"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
.. _objectstore:
|
|
||||||
|
|
||||||
Object Store API
|
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.
|
This example shows an object store API that can be used to store arbitrary serializable content.
|
||||||
|
|
||||||
|
|
66
docs/examples/permissions.rst
Normal file
66
docs/examples/permissions.rst
Normal file
|
@ -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 :mod:`permissions` module.
|
||||||
|
|
||||||
|
If you want to implement your own authentication method, then refer to the :mod:`authentication`
|
||||||
|
module.
|
|
@ -1,5 +1,3 @@
|
||||||
.. _codehighlighting:
|
|
||||||
|
|
||||||
Code Highlighting API
|
Code Highlighting API
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
|
@ -8,11 +6,11 @@ We're going to provide a simple wrapper around the awesome `pygments <http://pyg
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
A live sandbox instance of this API is available at http://api.django-rest-framework.org/pygments/
|
A live sandbox instance of this API is available at http://rest.ep.io/pygments/
|
||||||
|
|
||||||
You can browse the API using a web browser, or from the command line::
|
You can browse the API using a web browser, or from the command line::
|
||||||
|
|
||||||
curl -X GET http://api.django-rest-framework.org/pygments/ -H 'Accept: text/plain'
|
curl -X GET http://rest.ep.io/pygments/ -H 'Accept: text/plain'
|
||||||
|
|
||||||
|
|
||||||
URL configuration
|
URL configuration
|
||||||
|
@ -79,13 +77,13 @@ For example if we make a POST request using form input:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
bash: curl -X POST --data 'code=print "hello, world!"' --data 'style=foobar' -H 'X-Requested-With: XMLHttpRequest' http://api.django-rest-framework.org/pygments/
|
bash: curl -X POST --data 'code=print "hello, world!"' --data 'style=foobar' -H 'X-Requested-With: XMLHttpRequest' http://rest.ep.io/pygments/
|
||||||
{"detail": {"style": ["Select a valid choice. foobar is not one of the available choices."], "lexer": ["This field is required."]}}
|
{"detail": {"style": ["Select a valid choice. foobar is not one of the available choices."], "lexer": ["This field is required."]}}
|
||||||
|
|
||||||
Or if we make the same request using JSON:
|
Or if we make the same request using JSON:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
bash: curl -X POST --data-binary '{"code":"print \"hello, world!\"", "style":"foobar"}' -H 'Content-Type: application/json' -H 'X-Requested-With: XMLHttpRequest' http://api.django-rest-framework.org/pygments/
|
bash: curl -X POST --data-binary '{"code":"print \"hello, world!\"", "style":"foobar"}' -H 'Content-Type: application/json' -H 'X-Requested-With: XMLHttpRequest' http://rest.ep.io/pygments/
|
||||||
{"detail": {"style": ["Select a valid choice. foobar is not one of the available choices."], "lexer": ["This field is required."]}}
|
{"detail": {"style": ["Select a valid choice. foobar is not one of the available choices."], "lexer": ["This field is required."]}}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
.. _sandbox:
|
|
||||||
|
|
||||||
Sandbox Root API
|
Sandbox Root API
|
||||||
================
|
================
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
.. _views:
|
|
||||||
|
|
||||||
Getting Started - Views
|
Getting Started - Views
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
@ -7,11 +5,11 @@ Getting Started - Views
|
||||||
|
|
||||||
A live sandbox instance of this API is available:
|
A live sandbox instance of this API is available:
|
||||||
|
|
||||||
http://api.django-rest-framework.org/resource-example/
|
http://rest.ep.io/resource-example/
|
||||||
|
|
||||||
You can browse the API using a web browser, or from the command line::
|
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'
|
||||||
|
|
||||||
We're going to start off with a simple example, that demonstrates a few things:
|
We're going to start off with a simple example, that demonstrates a few things:
|
||||||
|
|
||||||
|
@ -43,16 +41,16 @@ Now we'll write our views. The first is a read only view that links to three in
|
||||||
|
|
||||||
That's us done. Our API now provides both programmatic access using JSON and XML, as well a nice browseable HTML view, so we can now access it both from the browser:
|
That's us done. Our API now provides both programmatic access using JSON and XML, as well a nice browseable HTML view, so we can now access it both from the browser:
|
||||||
|
|
||||||
* http://api.django-rest-framework.org/resource-example/
|
* http://rest.ep.io/resource-example/
|
||||||
|
|
||||||
And from the command line:
|
And from the command line:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
# Demonstrates API's input validation using form input
|
# Demonstrates API's input validation using form input
|
||||||
bash: curl -X POST --data 'foo=true' http://api.django-rest-framework.org/resource-example/1/
|
bash: curl -X POST --data 'foo=true' http://rest.ep.io/resource-example/1/
|
||||||
{"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}}
|
{"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}}
|
||||||
|
|
||||||
# Demonstrates API's input validation using JSON input
|
# 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/resource-example/1/
|
bash: curl -X POST -H 'Content-Type: application/json' --data-binary '{"foo":true}' http://rest.ep.io/resource-example/1/
|
||||||
{"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}}
|
{"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}}
|
||||||
|
|
8
docs/howto.rst
Normal file
8
docs/howto.rst
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
How Tos, FAQs & Notes
|
||||||
|
=====================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
:glob:
|
||||||
|
|
||||||
|
howto/*
|
|
@ -1,19 +1,18 @@
|
||||||
Using Django REST framework Mixin classes
|
Using Django REST framework Mixin classes
|
||||||
=========================================
|
=========================================
|
||||||
|
|
||||||
This example demonstrates creating a REST API **without** using Django REST framework's :class:`.Resource` or :class:`.ModelResource`,
|
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,
|
||||||
but instead using Django :class:`View` class, and adding the :class:`EmitterMixin` 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.
|
a browseable Web API, and much of the other goodness that Django REST framework gives you for free.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
A live sandbox instance of this API is available for testing:
|
A live sandbox instance of this API is available for testing:
|
||||||
|
|
||||||
* http://api.django-rest-framework.org/mixin/
|
* http://rest.ep.io/mixin/
|
||||||
|
|
||||||
You can browse the API using a web browser, or from the command line::
|
You can browse the API using a web browser, or from the command line::
|
||||||
|
|
||||||
curl -X GET http://api.django-rest-framework.org/mixin/
|
curl -X GET http://rest.ep.io/mixin/
|
||||||
|
|
||||||
|
|
||||||
URL configuration
|
URL configuration
|
||||||
|
@ -26,5 +25,6 @@ Everything we need for this example can go straight into the URL conf...
|
||||||
.. include:: ../../examples/mixin/urls.py
|
.. include:: ../../examples/mixin/urls.py
|
||||||
:literal:
|
: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.
|
We even get a nice HTML view which can be used to self-document our API.
|
||||||
|
|
|
@ -13,7 +13,7 @@ If you need to manually install Django REST framework to your ``site-packages``
|
||||||
Template Loaders
|
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'``.
|
* 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
|
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 <http://docs.djangoproject.com/en/dev/howto/deployment/modwsgi/#serving-the-admin-files>`_.
|
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 <https://docs.djangoproject.com/en/dev/howto/deployment/modpython/#serving-the-admin-files>`_.
|
||||||
|
|
||||||
* 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
|
Markdown
|
||||||
--------
|
--------
|
||||||
|
|
||||||
The Python `markdown library <http://www.freewisdom.org/projects/python-markdown/>`_ is not required but comes recommended.
|
The Python `markdown library <http://www.freewisdom.org/projects/python-markdown/>`_ is not required but comes recommended.
|
||||||
|
|
||||||
If markdown is installed your :class:`.Resource` descriptions can include `markdown style formatting <http://daringfireball.net/projects/markdown/syntax>`_ which will be rendered by the HTML documenting emitter.
|
If markdown is installed your :class:`.Resource` descriptions can include `markdown style formatting
|
||||||
|
<http://daringfireball.net/projects/markdown/syntax>`_ which will be rendered by the HTML documenting renderer.
|
||||||
|
|
||||||
robots.txt, favicon, login/logout
|
robots.txt, favicon, login/logout
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
39
docs/howto/usingurllib2.rst
Normal file
39
docs/howto/usingurllib2.rst
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
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.
|
||||||
|
|
||||||
|
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')
|
||||||
|
>>> r.getcode() # Check if the response was ok
|
||||||
|
200
|
||||||
|
>>> print r.read() # Examin the response itself
|
||||||
|
[{"url": "http://rest.ep.io/model-resource-example/1/", "baz": "sdf", "foo": true, "bar": 123}]
|
||||||
|
|
||||||
|
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.::
|
||||||
|
|
||||||
|
>>> 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.
|
|
@ -11,11 +11,12 @@ 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.
|
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 <http://api.django-rest-framework.org/>`_
|
**Browse example APIs created with Django REST framework:** `The Sandbox <http://rest.ep.io/>`_
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
---------
|
||||||
|
|
||||||
* Automatically provides an awesome Django admin style `browse-able self-documenting API <http://api.django-rest-framework.org>`_.
|
* Automatically provides an awesome Django admin style `browse-able self-documenting API <http://rest.ep.io>`_.
|
||||||
* Clean, simple, views for Resources, using Django's new `class based views <http://docs.djangoproject.com/en/dev/topics/class-based-views/>`_.
|
* Clean, simple, views for Resources, using Django's new `class based views <http://docs.djangoproject.com/en/dev/topics/class-based-views/>`_.
|
||||||
* Support for ModelResources with out-of-the-box default implementations and input validation.
|
* 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.
|
* Pluggable :mod:`.parsers`, :mod:`renderers`, :mod:`authentication` and :mod:`permissions` - Easy to customise.
|
||||||
|
@ -39,18 +40,17 @@ Requirements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* Python (2.5, 2.6, 2.7 supported)
|
* Python (2.5, 2.6, 2.7 supported)
|
||||||
* Django (1.2, 1.3 supported)
|
* Django (1.2, 1.3, 1.4-alpha supported)
|
||||||
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
||||||
You can install Django REST framework using ``pip`` or ``easy_install``::
|
You can install Django REST framework using ``pip`` or ``easy_install``::
|
||||||
|
|
||||||
pip install djangorestframework
|
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
|
git clone git@github.com:tomchristie/django-rest-framework.git
|
||||||
|
|
||||||
|
@ -71,6 +71,13 @@ Getting Started
|
||||||
|
|
||||||
Using Django REST framework can be as simple as adding a few lines to your urlconf.
|
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``::
|
``urls.py``::
|
||||||
|
|
||||||
from django.conf.urls.defaults import patterns, url
|
from django.conf.urls.defaults import patterns, url
|
||||||
|
@ -86,70 +93,17 @@ Using Django REST framework can be as simple as adding a few lines to your urlco
|
||||||
url(r'^(?P<pk>[^/]+)/$', InstanceModelView.as_view(resource=MyResource)),
|
url(r'^(?P<pk>[^/]+)/$', InstanceModelView.as_view(resource=MyResource)),
|
||||||
)
|
)
|
||||||
|
|
||||||
Django REST framework comes with two "getting started" examples.
|
.. include:: howto.rst
|
||||||
|
|
||||||
#. :ref:`views`
|
.. include:: library.rst
|
||||||
#. :ref:`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.
|
|
||||||
|
|
||||||
All the examples are freely available for testing in the sandbox:
|
|
||||||
|
|
||||||
http://api.django-rest-framework.org
|
|
||||||
|
|
||||||
(The :ref:`sandbox` resource is also documented.)
|
|
||||||
|
|
||||||
|
|
||||||
|
.. include:: examples.rst
|
||||||
How Tos, FAQs & Notes
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:hidden:
|
||||||
|
|
||||||
howto/setup
|
contents
|
||||||
howto/usingcurl
|
|
||||||
howto/alternativeframeworks
|
|
||||||
howto/mixin
|
|
||||||
|
|
||||||
Library Reference
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
library/authentication
|
|
||||||
library/compat
|
|
||||||
library/mixins
|
|
||||||
library/parsers
|
|
||||||
library/permissions
|
|
||||||
library/renderers
|
|
||||||
library/resource
|
|
||||||
library/response
|
|
||||||
library/serializer
|
|
||||||
library/status
|
|
||||||
library/views
|
|
||||||
|
|
||||||
Examples Reference
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
examples/views
|
|
||||||
examples/modelviews
|
|
||||||
examples/objectstore
|
|
||||||
examples/pygments
|
|
||||||
examples/blogpost
|
|
||||||
examples/sandbox
|
|
||||||
howto/mixin
|
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
------------------
|
------------------
|
||||||
|
|
8
docs/library.rst
Normal file
8
docs/library.rst
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Library
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
:glob:
|
||||||
|
|
||||||
|
library/*
|
2
docs/templates/layout.html
vendored
2
docs/templates/layout.html
vendored
|
@ -24,3 +24,5 @@
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block footer %}
|
||||||
|
<div class="footer"> <p> Documentation version {{ version }} {% endblock %}</p></div>
|
||||||
|
|
1
examples/.epio-app
Normal file
1
examples/.epio-app
Normal file
|
@ -0,0 +1 @@
|
||||||
|
rest
|
62
examples/epio.ini
Normal file
62
examples/epio.ini
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
# 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
|
||||||
|
|
||||||
|
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 = .
|
||||||
|
#
|
||||||
|
# [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
|
||||||
|
|
|
@ -13,6 +13,9 @@ import operator
|
||||||
OBJECT_STORE_DIR = os.path.join(settings.MEDIA_ROOT, 'objectstore')
|
OBJECT_STORE_DIR = os.path.join(settings.MEDIA_ROOT, 'objectstore')
|
||||||
MAX_FILES = 10
|
MAX_FILES = 10
|
||||||
|
|
||||||
|
if not os.path.exists(OBJECT_STORE_DIR):
|
||||||
|
os.makedirs(OBJECT_STORE_DIR)
|
||||||
|
|
||||||
|
|
||||||
def remove_oldest_files(dir, max_files):
|
def remove_oldest_files(dir, max_files):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -31,8 +31,11 @@ class ThrottlingExampleView(View):
|
||||||
|
|
||||||
class LoggedInExampleView(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, )
|
permissions = (IsAuthenticated, )
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
return 'Logged in or not?'
|
return 'Logged in or not?'
|
|
@ -22,6 +22,9 @@ import operator
|
||||||
HIGHLIGHTED_CODE_DIR = os.path.join(settings.MEDIA_ROOT, 'pygments')
|
HIGHLIGHTED_CODE_DIR = os.path.join(settings.MEDIA_ROOT, 'pygments')
|
||||||
MAX_FILES = 10
|
MAX_FILES = 10
|
||||||
|
|
||||||
|
if not os.path.exists(HIGHLIGHTED_CODE_DIR):
|
||||||
|
os.makedirs(HIGHLIGHTED_CODE_DIR)
|
||||||
|
|
||||||
|
|
||||||
def list_dir_sorted_by_ctime(dir):
|
def list_dir_sorted_by_ctime(dir):
|
||||||
"""
|
"""
|
||||||
|
|
3
examples/requirements-epio.txt
Normal file
3
examples/requirements-epio.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Pygments==1.4
|
||||||
|
Markdown==2.0.3
|
||||||
|
djangorestframework
|
|
@ -1,4 +1,5 @@
|
||||||
# Settings for djangorestframework examples project
|
# Settings for djangorestframework examples project
|
||||||
|
import os
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
TEMPLATE_DEBUG = DEBUG
|
TEMPLATE_DEBUG = DEBUG
|
||||||
|
@ -46,7 +47,7 @@ USE_L10N = True
|
||||||
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
||||||
# Example: "/home/media/media.lawrence.com/"
|
# Example: "/home/media/media.lawrence.com/"
|
||||||
# NOTE: Some of the djangorestframework examples use MEDIA_ROOT to store content.
|
# 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
|
# 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).
|
# trailing slash if there is a path component (optional in other cases).
|
||||||
|
@ -61,7 +62,7 @@ MEDIA_URL = ''
|
||||||
# but it does require the admin media be served. Django's test server will do
|
# 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
|
# this for you automatically, but in production you'll want to make sure you
|
||||||
# serve the admin media from somewhere.
|
# 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.
|
# Make this unique, and don't share it with anybody.
|
||||||
SECRET_KEY = 't&9mru2_k$t8e2-9uq-wu2a1)9v*us&j3i#lsqkt(lbx*vh1cu'
|
SECRET_KEY = 't&9mru2_k$t8e2-9uq-wu2a1)9v*us&j3i#lsqkt(lbx*vh1cu'
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
# We need Django. Duh.
|
# We need Django. Duh.
|
||||||
# coverage isn't strictly a requirement, but it's useful.
|
# coverage isn't strictly a requirement, but it's useful.
|
||||||
|
|
||||||
Django==1.2.4
|
Django>=1.2
|
||||||
wsgiref==0.1.2
|
coverage>=3.4
|
||||||
coverage==3.4
|
|
||||||
|
|
127
tox.ini
127
tox.ini
|
@ -9,13 +9,18 @@ envlist=
|
||||||
py25-django13,
|
py25-django13,
|
||||||
py26-django13,
|
py26-django13,
|
||||||
py27-django13,
|
py27-django13,
|
||||||
|
py25-django14a1,
|
||||||
py25-django12e,
|
py26-django14a1,
|
||||||
py26-django12e,
|
py27-django14a1,
|
||||||
py27-django12e,
|
py25-django12-examples,
|
||||||
py25-django13e,
|
py26-django12-examples,
|
||||||
py26-django13e,
|
py27-django12-examples,
|
||||||
py27-django13e
|
py25-django13-examples,
|
||||||
|
py26-django13-examples,
|
||||||
|
py27-django13-examples,
|
||||||
|
py25-django14a1-examples,
|
||||||
|
py26-django14a1-examples,
|
||||||
|
py27-django14a1-examples
|
||||||
|
|
||||||
########################################### CORE TESTS ############################################
|
########################################### CORE TESTS ############################################
|
||||||
|
|
||||||
|
@ -30,6 +35,8 @@ deps=
|
||||||
coverage==3.4
|
coverage==3.4
|
||||||
unittest-xml-reporting==1.2
|
unittest-xml-reporting==1.2
|
||||||
Pyyaml==3.10
|
Pyyaml==3.10
|
||||||
|
# Optional packages:
|
||||||
|
markdown
|
||||||
|
|
||||||
[testenv:py26-django12]
|
[testenv:py26-django12]
|
||||||
basepython=python2.6
|
basepython=python2.6
|
||||||
|
@ -38,6 +45,8 @@ deps=
|
||||||
coverage==3.4
|
coverage==3.4
|
||||||
unittest-xml-reporting==1.2
|
unittest-xml-reporting==1.2
|
||||||
Pyyaml==3.10
|
Pyyaml==3.10
|
||||||
|
# Optional packages:
|
||||||
|
markdown
|
||||||
|
|
||||||
[testenv:py27-django12]
|
[testenv:py27-django12]
|
||||||
basepython=python2.7
|
basepython=python2.7
|
||||||
|
@ -46,6 +55,8 @@ deps=
|
||||||
coverage==3.4
|
coverage==3.4
|
||||||
unittest-xml-reporting==1.2
|
unittest-xml-reporting==1.2
|
||||||
Pyyaml==3.10
|
Pyyaml==3.10
|
||||||
|
# Optional packages:
|
||||||
|
markdown
|
||||||
|
|
||||||
[testenv:py25-django13]
|
[testenv:py25-django13]
|
||||||
basepython=python2.5
|
basepython=python2.5
|
||||||
|
@ -54,6 +65,8 @@ deps=
|
||||||
coverage==3.4
|
coverage==3.4
|
||||||
unittest-xml-reporting==1.2
|
unittest-xml-reporting==1.2
|
||||||
Pyyaml==3.10
|
Pyyaml==3.10
|
||||||
|
# Optional packages:
|
||||||
|
markdown
|
||||||
|
|
||||||
[testenv:py26-django13]
|
[testenv:py26-django13]
|
||||||
basepython=python2.6
|
basepython=python2.6
|
||||||
|
@ -62,6 +75,8 @@ deps=
|
||||||
coverage==3.4
|
coverage==3.4
|
||||||
unittest-xml-reporting==1.2
|
unittest-xml-reporting==1.2
|
||||||
Pyyaml==3.10
|
Pyyaml==3.10
|
||||||
|
# Optional packages:
|
||||||
|
markdown
|
||||||
|
|
||||||
[testenv:py27-django13]
|
[testenv:py27-django13]
|
||||||
basepython=python2.7
|
basepython=python2.7
|
||||||
|
@ -70,10 +85,42 @@ deps=
|
||||||
coverage==3.4
|
coverage==3.4
|
||||||
unittest-xml-reporting==1.2
|
unittest-xml-reporting==1.2
|
||||||
Pyyaml==3.10
|
Pyyaml==3.10
|
||||||
|
# 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 ################################################
|
####################################### EXAMPLES ################################################
|
||||||
|
|
||||||
[testenv:py25-django12e]
|
[testenv:py25-django12-examples]
|
||||||
basepython=python2.5
|
basepython=python2.5
|
||||||
commands=
|
commands=
|
||||||
python examples/runtests.py
|
python examples/runtests.py
|
||||||
|
@ -87,7 +134,7 @@ deps=
|
||||||
unittest-xml-reporting==1.2
|
unittest-xml-reporting==1.2
|
||||||
Pyyaml==3.10
|
Pyyaml==3.10
|
||||||
|
|
||||||
[testenv:py26-django12e]
|
[testenv:py26-django12-examples]
|
||||||
basepython=python2.6
|
basepython=python2.6
|
||||||
commands=
|
commands=
|
||||||
python examples/runtests.py
|
python examples/runtests.py
|
||||||
|
@ -101,7 +148,7 @@ deps=
|
||||||
unittest-xml-reporting==1.2
|
unittest-xml-reporting==1.2
|
||||||
Pyyaml==3.10
|
Pyyaml==3.10
|
||||||
|
|
||||||
[testenv:py27-django12e]
|
[testenv:py27-django12-examples]
|
||||||
basepython=python2.7
|
basepython=python2.7
|
||||||
commands=
|
commands=
|
||||||
python examples/runtests.py
|
python examples/runtests.py
|
||||||
|
@ -115,7 +162,7 @@ deps=
|
||||||
unittest-xml-reporting==1.2
|
unittest-xml-reporting==1.2
|
||||||
Pyyaml==3.10
|
Pyyaml==3.10
|
||||||
|
|
||||||
[testenv:py25-django13e]
|
[testenv:py25-django13-examples]
|
||||||
basepython=python2.5
|
basepython=python2.5
|
||||||
commands=
|
commands=
|
||||||
python examples/runtests.py
|
python examples/runtests.py
|
||||||
|
@ -129,7 +176,7 @@ deps=
|
||||||
unittest-xml-reporting==1.2
|
unittest-xml-reporting==1.2
|
||||||
Pyyaml==3.10
|
Pyyaml==3.10
|
||||||
|
|
||||||
[testenv:py26-django13e]
|
[testenv:py26-django13-examples]
|
||||||
basepython=python2.6
|
basepython=python2.6
|
||||||
commands=
|
commands=
|
||||||
python examples/runtests.py
|
python examples/runtests.py
|
||||||
|
@ -143,7 +190,7 @@ deps=
|
||||||
unittest-xml-reporting==1.2
|
unittest-xml-reporting==1.2
|
||||||
Pyyaml==3.10
|
Pyyaml==3.10
|
||||||
|
|
||||||
[testenv:py27-django13e]
|
[testenv:py27-django13-examples]
|
||||||
basepython=python2.7
|
basepython=python2.7
|
||||||
commands=
|
commands=
|
||||||
python examples/runtests.py
|
python examples/runtests.py
|
||||||
|
@ -156,3 +203,57 @@ deps=
|
||||||
Markdown==2.0.3
|
Markdown==2.0.3
|
||||||
unittest-xml-reporting==1.2
|
unittest-xml-reporting==1.2
|
||||||
Pyyaml==3.10
|
Pyyaml==3.10
|
||||||
|
|
||||||
|
[testenv:py25-django14a1-examples]
|
||||||
|
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-django14a1-examples]
|
||||||
|
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-django14a1-examples]
|
||||||
|
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]
|
||||||
|
basepython=python
|
||||||
|
changedir=docs
|
||||||
|
deps=
|
||||||
|
sphinx
|
||||||
|
pytest
|
||||||
|
django==1.3
|
||||||
|
commands=
|
||||||
|
py.test --tb=line -v --junitxml=junit-{envname}.xml check_sphinx.py
|
||||||
|
|
Loading…
Reference in New Issue
Block a user