Remove old 'djangorestframework directories
|
@ -1,5 +1,2 @@
|
|||
recursive-include rest_framework/static *.ico *.txt *.css
|
||||
recursive-include rest_framework/static *.js *.css *.png
|
||||
recursive-include rest_framework/templates *.txt *.html
|
||||
recursive-include examples .keep *.py *.txt
|
||||
recursive-include docs *.py *.rst *.html *.txt
|
||||
include AUTHORS LICENSE CHANGELOG.rst requirements.txt tox.ini
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
__version__ = '2.0.0'
|
||||
|
||||
VERSION = __version__ # synonym
|
|
@ -1,132 +0,0 @@
|
|||
"""
|
||||
The :mod:`authentication` module provides a set of pluggable authentication classes.
|
||||
|
||||
Authentication behavior is provided by mixing the :class:`mixins.RequestMixin` class into a :class:`View` class.
|
||||
"""
|
||||
|
||||
from django.contrib.auth import authenticate
|
||||
from djangorestframework.compat import CsrfViewMiddleware
|
||||
from djangorestframework.authtoken.models import Token
|
||||
import base64
|
||||
|
||||
|
||||
class BaseAuthentication(object):
|
||||
"""
|
||||
All authentication classes should extend BaseAuthentication.
|
||||
"""
|
||||
|
||||
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.
|
||||
|
||||
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`.
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
class BasicAuthentication(BaseAuthentication):
|
||||
"""
|
||||
Base class for HTTP Basic authentication.
|
||||
Subclasses should implement `.authenticate_credentials()`.
|
||||
"""
|
||||
|
||||
def authenticate(self, request):
|
||||
"""
|
||||
Returns a `User` if a correct username and password have been supplied
|
||||
using HTTP Basic authentication. Otherwise returns `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":
|
||||
try:
|
||||
auth_parts = base64.b64decode(auth[1]).partition(':')
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
try:
|
||||
userid, password = smart_unicode(auth_parts[0]), smart_unicode(auth_parts[2])
|
||||
except DjangoUnicodeDecodeError:
|
||||
return None
|
||||
|
||||
return self.authenticate_credentials(userid, password)
|
||||
|
||||
def authenticate_credentials(self, userid, password):
|
||||
"""
|
||||
Given the Basic authentication userid and password, authenticate
|
||||
and return a user instance.
|
||||
"""
|
||||
raise NotImplementedError('.authenticate_credentials() must be overridden')
|
||||
|
||||
|
||||
class UserBasicAuthentication(BasicAuthentication):
|
||||
def authenticate_credentials(self, userid, password):
|
||||
"""
|
||||
Authenticate the userid and password against username and password.
|
||||
"""
|
||||
user = authenticate(username=userid, password=password)
|
||||
if user is not None and user.is_active:
|
||||
return (user, None)
|
||||
|
||||
|
||||
class SessionAuthentication(BaseAuthentication):
|
||||
"""
|
||||
Use Django's session framework for authentication.
|
||||
"""
|
||||
|
||||
def authenticate(self, request):
|
||||
"""
|
||||
Returns a :obj:`User` if the request session currently has a logged in user.
|
||||
Otherwise returns :const:`None`.
|
||||
"""
|
||||
user = getattr(request._request, 'user', None)
|
||||
|
||||
if user and user.is_active:
|
||||
# Enforce CSRF validation for session based authentication.
|
||||
resp = CsrfViewMiddleware().process_view(request, None, (), {})
|
||||
|
||||
if resp is None: # csrf passed
|
||||
return (user, None)
|
||||
|
||||
|
||||
class TokenAuthentication(BaseAuthentication):
|
||||
"""
|
||||
Simple token based authentication.
|
||||
|
||||
Clients should authenticate by passing the token key in the "Authorization"
|
||||
HTTP header, prepended with the string "Token ". For example:
|
||||
|
||||
Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a
|
||||
"""
|
||||
|
||||
model = Token
|
||||
"""
|
||||
A custom token model may be used, but must have the following properties.
|
||||
|
||||
* key -- The string identifying the token
|
||||
* user -- The user to which the token belongs
|
||||
"""
|
||||
|
||||
def authenticate(self, request):
|
||||
auth = request.META.get('HTTP_AUTHORIZATION', '').split()
|
||||
|
||||
if len(auth) == 2 and auth[0].lower() == "token":
|
||||
key = auth[1]
|
||||
try:
|
||||
token = self.model.objects.get(key=key)
|
||||
except self.model.DoesNotExist:
|
||||
return None
|
||||
|
||||
if token.user.is_active and not getattr(token, 'revoked', False):
|
||||
return (token.user, token)
|
||||
|
||||
# TODO: OAuthAuthentication
|
|
@ -1,72 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'Token'
|
||||
db.create_table('authtoken_token', (
|
||||
('key', self.gf('django.db.models.fields.CharField')(max_length=40, primary_key=True)),
|
||||
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
|
||||
('revoked', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
|
||||
))
|
||||
db.send_create_signal('authtoken', ['Token'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'Token'
|
||||
db.delete_table('authtoken_token')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'authtoken.token': {
|
||||
'Meta': {'object_name': 'Token'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'primary_key': 'True'}),
|
||||
'revoked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['authtoken']
|
|
@ -1,23 +0,0 @@
|
|||
import uuid
|
||||
import hmac
|
||||
from hashlib import sha1
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Token(models.Model):
|
||||
"""
|
||||
The default authorization token model.
|
||||
"""
|
||||
key = models.CharField(max_length=40, primary_key=True)
|
||||
user = models.ForeignKey('auth.User')
|
||||
revoked = models.BooleanField(default=False)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.key:
|
||||
self.key = self.generate_key()
|
||||
return super(Token, self).save(*args, **kwargs)
|
||||
|
||||
def generate_key(self):
|
||||
unique = str(uuid.uuid4())
|
||||
return hmac.new(unique, digestmod=sha1).hexdigest()
|
|
@ -1,480 +0,0 @@
|
|||
"""
|
||||
The :mod:`compat` module provides support for backwards compatibility with older versions of django/python.
|
||||
"""
|
||||
import django
|
||||
|
||||
# cStringIO only if it's available, otherwise StringIO
|
||||
try:
|
||||
import cStringIO as StringIO
|
||||
except ImportError:
|
||||
import StringIO
|
||||
|
||||
|
||||
# parse_qs from 'urlparse' module unless python 2.5, in which case from 'cgi'
|
||||
try:
|
||||
# python >= 2.6
|
||||
from urlparse import parse_qs
|
||||
except ImportError:
|
||||
# python < 2.6
|
||||
from cgi import parse_qs
|
||||
|
||||
|
||||
# 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,
|
||||
just as if that :func:`view` had been hooked up using a URLconf.
|
||||
"""
|
||||
def request(self, **request):
|
||||
"""
|
||||
Similar to parent class, but returns the :obj:`request` object as soon as it
|
||||
has created it.
|
||||
"""
|
||||
environ = {
|
||||
'HTTP_COOKIE': self.cookies,
|
||||
'PATH_INFO': '/',
|
||||
'QUERY_STRING': '',
|
||||
'REQUEST_METHOD': 'GET',
|
||||
'SCRIPT_NAME': '',
|
||||
'SERVER_NAME': 'testserver',
|
||||
'SERVER_PORT': 80,
|
||||
'SERVER_PROTOCOL': 'HTTP/1.1',
|
||||
}
|
||||
environ.update(self.defaults)
|
||||
environ.update(request)
|
||||
return WSGIRequest(environ)
|
||||
|
||||
# django.views.generic.View (Django >= 1.3)
|
||||
try:
|
||||
from django.views.generic import View
|
||||
if not hasattr(View, 'head'):
|
||||
# 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
|
||||
keyword arguments, and other things.
|
||||
"""
|
||||
# Go through keyword arguments, and either save their values to our
|
||||
# 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):
|
||||
"""
|
||||
Main entry point for a request-response process.
|
||||
"""
|
||||
# sanitize keyword arguments
|
||||
for key in initkwargs:
|
||||
if key in cls.http_method_names:
|
||||
raise TypeError(u"You tried to pass in the %s method name as a "
|
||||
u"keyword argument to %s(). Don't do that."
|
||||
% (key, cls.__name__))
|
||||
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
|
||||
# request method isn't on the approved list.
|
||||
if request.method.lower() in self.http_method_names:
|
||||
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
|
||||
else:
|
||||
handler = self.http_method_not_allowed
|
||||
self.request = request
|
||||
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),
|
||||
# extra={
|
||||
# 'status_code': 405,
|
||||
# 'request': self.request
|
||||
# }
|
||||
#)
|
||||
return http.HttpResponseNotAllowed(allowed_methods)
|
||||
|
||||
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)
|
||||
|
||||
# timezone support is new in Django 1.4
|
||||
try:
|
||||
from django.utils import timezone
|
||||
except ImportError:
|
||||
timezone = None
|
||||
|
||||
# dateparse is ALSO new in Django 1.4
|
||||
try:
|
||||
from django.utils.dateparse import parse_date, parse_datetime
|
||||
except ImportError:
|
||||
import datetime
|
||||
import re
|
||||
|
||||
date_re = re.compile(
|
||||
r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$'
|
||||
)
|
||||
|
||||
datetime_re = re.compile(
|
||||
r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
|
||||
r'[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
|
||||
r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
|
||||
r'(?P<tzinfo>Z|[+-]\d{1,2}:\d{1,2})?$'
|
||||
)
|
||||
|
||||
time_re = re.compile(
|
||||
r'(?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
|
||||
r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
|
||||
)
|
||||
|
||||
def parse_date(value):
|
||||
match = date_re.match(value)
|
||||
if match:
|
||||
kw = dict((k, int(v)) for k, v in match.groupdict().iteritems())
|
||||
return datetime.date(**kw)
|
||||
|
||||
def parse_time(value):
|
||||
match = time_re.match(value)
|
||||
if match:
|
||||
kw = match.groupdict()
|
||||
if kw['microsecond']:
|
||||
kw['microsecond'] = kw['microsecond'].ljust(6, '0')
|
||||
kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None)
|
||||
return datetime.time(**kw)
|
||||
|
||||
def parse_datetime(value):
|
||||
"""Parse datetime, but w/o the timezone awareness in 1.4"""
|
||||
match = datetime_re.match(value)
|
||||
if match:
|
||||
kw = match.groupdict()
|
||||
if kw['microsecond']:
|
||||
kw['microsecond'] = kw['microsecond'].ljust(6, '0')
|
||||
kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None)
|
||||
return datetime.datetime(**kw)
|
||||
|
||||
# Markdown is optional
|
||||
try:
|
||||
import markdown
|
||||
|
||||
def apply_markdown(text):
|
||||
"""
|
||||
Simple wrapper around :func:`markdown.markdown` to set the base level
|
||||
of '#' style headers to <h2>.
|
||||
"""
|
||||
|
||||
extensions = ['headerid(level=2)']
|
||||
safe_mode = False,
|
||||
md = markdown.Markdown(extensions=extensions, safe_mode=safe_mode)
|
||||
return md.convert(text)
|
||||
|
||||
except ImportError:
|
||||
apply_markdown = None
|
||||
|
||||
|
||||
# Yaml is optional
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
yaml = None
|
||||
|
||||
|
||||
import unittest
|
||||
try:
|
||||
import unittest.skip
|
||||
except ImportError: # python < 2.7
|
||||
from unittest import TestCase
|
||||
import functools
|
||||
|
||||
def skip(reason):
|
||||
# Pasted from py27/lib/unittest/case.py
|
||||
"""
|
||||
Unconditionally skip a test.
|
||||
"""
|
||||
def decorator(test_item):
|
||||
if not (isinstance(test_item, type) and issubclass(test_item, TestCase)):
|
||||
@functools.wraps(test_item)
|
||||
def skip_wrapper(*args, **kwargs):
|
||||
pass
|
||||
test_item = skip_wrapper
|
||||
|
||||
test_item.__unittest_skip__ = True
|
||||
test_item.__unittest_skip_why__ = reason
|
||||
return test_item
|
||||
return decorator
|
||||
|
||||
unittest.skip = skip
|
||||
|
||||
|
||||
# xml.etree.parse only throws ParseError for python >= 2.7
|
||||
try:
|
||||
from xml.etree import ParseError as ETParseError
|
||||
except ImportError: # python < 2.7
|
||||
ETParseError = None
|
|
@ -1,53 +0,0 @@
|
|||
from functools import wraps
|
||||
from django.http import Http404
|
||||
from django.utils.decorators import available_attrs
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from djangorestframework import exceptions
|
||||
from djangorestframework import status
|
||||
from djangorestframework.response import Response
|
||||
from djangorestframework.request import Request
|
||||
from djangorestframework.settings import api_settings
|
||||
|
||||
|
||||
def api_view(allowed_methods):
|
||||
"""
|
||||
Decorator for function based views.
|
||||
|
||||
@api_view(['GET', 'POST'])
|
||||
def my_view(request):
|
||||
# request will be an instance of `Request`
|
||||
# `Response` objects will have .request set automatically
|
||||
# APIException instances will be handled
|
||||
"""
|
||||
allowed_methods = [method.upper() for method in allowed_methods]
|
||||
|
||||
def decorator(func):
|
||||
@wraps(func, assigned=available_attrs(func))
|
||||
def inner(request, *args, **kwargs):
|
||||
try:
|
||||
|
||||
request = Request(request)
|
||||
|
||||
if request.method not in allowed_methods:
|
||||
raise exceptions.MethodNotAllowed(request.method)
|
||||
|
||||
response = func(request, *args, **kwargs)
|
||||
|
||||
if isinstance(response, Response):
|
||||
response.request = request
|
||||
if api_settings.FORMAT_SUFFIX_KWARG:
|
||||
response.format = kwargs.get(api_settings.FORMAT_SUFFIX_KWARG, None)
|
||||
return response
|
||||
|
||||
except exceptions.APIException as exc:
|
||||
return Response({'detail': exc.detail}, status=exc.status_code)
|
||||
|
||||
except Http404 as exc:
|
||||
return Response({'detail': 'Not found'},
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
except PermissionDenied as exc:
|
||||
return Response({'detail': 'Permission denied'},
|
||||
status=status.HTTP_403_FORBIDDEN)
|
||||
return inner
|
||||
return decorator
|
|
@ -1,79 +0,0 @@
|
|||
"""
|
||||
Handled exceptions raised by REST framework.
|
||||
|
||||
In addition Django's built in 403 and 404 exceptions are handled.
|
||||
(`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
|
||||
"""
|
||||
from djangorestframework import status
|
||||
|
||||
|
||||
class APIException(Exception):
|
||||
"""
|
||||
Base class for REST framework exceptions.
|
||||
Subclasses should provide `.status_code` and `.detail` properties.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ParseError(APIException):
|
||||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
default_detail = 'Malformed request.'
|
||||
|
||||
def __init__(self, detail=None):
|
||||
self.detail = detail or self.default_detail
|
||||
|
||||
|
||||
class PermissionDenied(APIException):
|
||||
status_code = status.HTTP_403_FORBIDDEN
|
||||
default_detail = 'You do not have permission to perform this action.'
|
||||
|
||||
def __init__(self, detail=None):
|
||||
self.detail = detail or self.default_detail
|
||||
|
||||
|
||||
class InvalidFormat(APIException):
|
||||
status_code = status.HTTP_404_NOT_FOUND
|
||||
default_detail = "Format suffix '.%s' not found."
|
||||
|
||||
def __init__(self, format, detail=None):
|
||||
self.detail = (detail or self.default_detail) % format
|
||||
|
||||
|
||||
class MethodNotAllowed(APIException):
|
||||
status_code = status.HTTP_405_METHOD_NOT_ALLOWED
|
||||
default_detail = "Method '%s' not allowed."
|
||||
|
||||
def __init__(self, method, detail=None):
|
||||
self.detail = (detail or self.default_detail) % method
|
||||
|
||||
|
||||
class NotAcceptable(APIException):
|
||||
status_code = status.HTTP_406_NOT_ACCEPTABLE
|
||||
default_detail = "Could not satisfy the request's Accept header"
|
||||
|
||||
def __init__(self, detail=None, available_renderers=None):
|
||||
self.detail = detail or self.default_detail
|
||||
self.available_renderers = available_renderers
|
||||
|
||||
|
||||
class UnsupportedMediaType(APIException):
|
||||
status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
|
||||
default_detail = "Unsupported media type '%s' in request."
|
||||
|
||||
def __init__(self, media_type, detail=None):
|
||||
self.detail = (detail or self.default_detail) % media_type
|
||||
|
||||
|
||||
class Throttled(APIException):
|
||||
status_code = status.HTTP_429_TOO_MANY_REQUESTS
|
||||
default_detail = "Request was throttled."
|
||||
extra_detail = "Expected available in %d second%s."
|
||||
|
||||
def __init__(self, wait=None, detail=None):
|
||||
import math
|
||||
self.wait = wait and math.ceil(wait) or None
|
||||
if wait is not None:
|
||||
format = detail or self.default_detail + self.extra_detail
|
||||
self.detail = format % (self.wait, self.wait != 1 and 's' or '')
|
||||
else:
|
||||
self.detail = detail or self.default_detail
|
|
@ -1,446 +0,0 @@
|
|||
import copy
|
||||
import datetime
|
||||
import inspect
|
||||
import warnings
|
||||
|
||||
from django.core import validators
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.conf import settings
|
||||
from django.db import DEFAULT_DB_ALIAS
|
||||
from django.db.models.related import RelatedObject
|
||||
from django.utils.encoding import is_protected_type, smart_unicode
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from djangorestframework.compat import parse_date, parse_datetime
|
||||
from djangorestframework.compat import timezone
|
||||
|
||||
|
||||
def is_simple_callable(obj):
|
||||
"""
|
||||
True if the object is a callable that takes no arguments.
|
||||
"""
|
||||
return (
|
||||
(inspect.isfunction(obj) and not inspect.getargspec(obj)[0]) or
|
||||
(inspect.ismethod(obj) and len(inspect.getargspec(obj)[0]) <= 1)
|
||||
)
|
||||
|
||||
|
||||
class Field(object):
|
||||
creation_counter = 0
|
||||
default_validators = []
|
||||
default_error_messages = {
|
||||
'required': _('This field is required.'),
|
||||
'invalid': _('Invalid value.'),
|
||||
}
|
||||
empty = ''
|
||||
|
||||
def __init__(self, source=None, readonly=False, required=None,
|
||||
validators=[], error_messages=None):
|
||||
self.parent = None
|
||||
|
||||
self.creation_counter = Field.creation_counter
|
||||
Field.creation_counter += 1
|
||||
|
||||
self.source = source
|
||||
self.readonly = readonly
|
||||
self.required = not(readonly)
|
||||
|
||||
messages = {}
|
||||
for c in reversed(self.__class__.__mro__):
|
||||
messages.update(getattr(c, 'default_error_messages', {}))
|
||||
messages.update(error_messages or {})
|
||||
self.error_messages = messages
|
||||
|
||||
self.validators = self.default_validators + validators
|
||||
|
||||
def initialize(self, parent, model_field=None):
|
||||
"""
|
||||
Called to set up a field prior to field_to_native or field_from_native.
|
||||
|
||||
parent - The parent serializer.
|
||||
model_field - The model field this field corrosponds to, if one exists.
|
||||
"""
|
||||
self.parent = parent
|
||||
self.root = parent.root or parent
|
||||
self.context = self.root.context
|
||||
if model_field:
|
||||
self.model_field = model_field
|
||||
|
||||
def validate(self, value):
|
||||
pass
|
||||
# if value in validators.EMPTY_VALUES and self.required:
|
||||
# raise ValidationError(self.error_messages['required'])
|
||||
|
||||
def run_validators(self, value):
|
||||
if value in validators.EMPTY_VALUES:
|
||||
return
|
||||
errors = []
|
||||
for v in self.validators:
|
||||
try:
|
||||
v(value)
|
||||
except ValidationError as e:
|
||||
if hasattr(e, 'code') and e.code in self.error_messages:
|
||||
message = self.error_messages[e.code]
|
||||
if e.params:
|
||||
message = message % e.params
|
||||
errors.append(message)
|
||||
else:
|
||||
errors.extend(e.messages)
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
||||
def field_from_native(self, data, field_name, into):
|
||||
"""
|
||||
Given a dictionary and a field name, updates the dictionary `into`,
|
||||
with the field and it's deserialized value.
|
||||
"""
|
||||
if self.readonly:
|
||||
return
|
||||
|
||||
try:
|
||||
native = data[field_name]
|
||||
except KeyError:
|
||||
return # TODO Consider validation behaviour, 'required' opt etc...
|
||||
|
||||
value = self.from_native(native)
|
||||
if self.source == '*':
|
||||
if value:
|
||||
into.update(value)
|
||||
else:
|
||||
self.validate(value)
|
||||
self.run_validators(value)
|
||||
into[self.source or field_name] = value
|
||||
|
||||
def from_native(self, value):
|
||||
"""
|
||||
Reverts a simple representation back to the field's value.
|
||||
"""
|
||||
if hasattr(self, 'model_field'):
|
||||
try:
|
||||
return self.model_field.rel.to._meta.get_field(self.model_field.rel.field_name).to_python(value)
|
||||
except:
|
||||
return self.model_field.to_python(value)
|
||||
return value
|
||||
|
||||
def field_to_native(self, obj, field_name):
|
||||
"""
|
||||
Given and object and a field name, returns the value that should be
|
||||
serialized for that field.
|
||||
"""
|
||||
if obj is None:
|
||||
return self.empty
|
||||
|
||||
if self.source == '*':
|
||||
return self.to_native(obj)
|
||||
|
||||
self.obj = obj # Need to hang onto this in the case of model fields
|
||||
if hasattr(self, 'model_field'):
|
||||
return self.to_native(self.model_field._get_val_from_obj(obj))
|
||||
|
||||
return self.to_native(getattr(obj, self.source or field_name))
|
||||
|
||||
def to_native(self, value):
|
||||
"""
|
||||
Converts the field's value into it's simple representation.
|
||||
"""
|
||||
if is_simple_callable(value):
|
||||
value = value()
|
||||
|
||||
if is_protected_type(value):
|
||||
return value
|
||||
elif hasattr(self, 'model_field'):
|
||||
return self.model_field.value_to_string(self.obj)
|
||||
return smart_unicode(value)
|
||||
|
||||
def attributes(self):
|
||||
"""
|
||||
Returns a dictionary of attributes to be used when serializing to xml.
|
||||
"""
|
||||
try:
|
||||
return {
|
||||
"type": self.model_field.get_internal_type()
|
||||
}
|
||||
except AttributeError:
|
||||
return {}
|
||||
|
||||
|
||||
class RelatedField(Field):
|
||||
"""
|
||||
A base class for model related fields or related managers.
|
||||
|
||||
Subclass this and override `convert` to define custom behaviour when
|
||||
serializing related objects.
|
||||
"""
|
||||
|
||||
def field_to_native(self, obj, field_name):
|
||||
obj = getattr(obj, field_name)
|
||||
if obj.__class__.__name__ in ('RelatedManager', 'ManyRelatedManager'):
|
||||
return [self.to_native(item) for item in obj.all()]
|
||||
return self.to_native(obj)
|
||||
|
||||
def attributes(self):
|
||||
try:
|
||||
return {
|
||||
"rel": self.model_field.rel.__class__.__name__,
|
||||
"to": smart_unicode(self.model_field.rel.to._meta)
|
||||
}
|
||||
except AttributeError:
|
||||
return {}
|
||||
|
||||
|
||||
class PrimaryKeyRelatedField(RelatedField):
|
||||
"""
|
||||
Serializes a model related field or related manager to a pk value.
|
||||
"""
|
||||
|
||||
# Note the we use ModelRelatedField's implementation, as we want to get the
|
||||
# raw database value directly, since that won't involve another
|
||||
# database lookup.
|
||||
#
|
||||
# An alternative implementation would simply be this...
|
||||
#
|
||||
# class PrimaryKeyRelatedField(RelatedField):
|
||||
# def to_native(self, obj):
|
||||
# return obj.pk
|
||||
|
||||
def to_native(self, pk):
|
||||
"""
|
||||
Simply returns the object's pk. You can subclass this method to
|
||||
provide different serialization behavior of the pk.
|
||||
(For example returning a URL based on the model's pk.)
|
||||
"""
|
||||
return pk
|
||||
|
||||
def field_to_native(self, obj, field_name):
|
||||
try:
|
||||
obj = obj.serializable_value(field_name)
|
||||
except AttributeError:
|
||||
field = obj._meta.get_field_by_name(field_name)[0]
|
||||
obj = getattr(obj, field_name)
|
||||
if obj.__class__.__name__ == 'RelatedManager':
|
||||
return [self.to_native(item.pk) for item in obj.all()]
|
||||
elif isinstance(field, RelatedObject):
|
||||
return self.to_native(obj.pk)
|
||||
raise
|
||||
if obj.__class__.__name__ == 'ManyRelatedManager':
|
||||
return [self.to_native(item.pk) for item in obj.all()]
|
||||
return self.to_native(obj)
|
||||
|
||||
def field_from_native(self, data, field_name, into):
|
||||
value = data.get(field_name)
|
||||
if hasattr(value, '__iter__'):
|
||||
into[field_name] = [self.from_native(item) for item in value]
|
||||
else:
|
||||
into[field_name + '_id'] = self.from_native(value)
|
||||
|
||||
|
||||
class NaturalKeyRelatedField(RelatedField):
|
||||
"""
|
||||
Serializes a model related field or related manager to a natural key value.
|
||||
"""
|
||||
is_natural_key = True # XML renderer handles these differently
|
||||
|
||||
def to_native(self, obj):
|
||||
if hasattr(obj, 'natural_key'):
|
||||
return obj.natural_key()
|
||||
return obj
|
||||
|
||||
def field_from_native(self, data, field_name, into):
|
||||
value = data.get(field_name)
|
||||
into[self.model_field.attname] = self.from_native(value)
|
||||
|
||||
def from_native(self, value):
|
||||
# TODO: Support 'using' : db = options.pop('using', DEFAULT_DB_ALIAS)
|
||||
manager = self.model_field.rel.to._default_manager
|
||||
manager = manager.db_manager(DEFAULT_DB_ALIAS)
|
||||
return manager.get_by_natural_key(*value).pk
|
||||
|
||||
|
||||
class BooleanField(Field):
|
||||
default_error_messages = {
|
||||
'invalid': _(u"'%s' value must be either True or False."),
|
||||
}
|
||||
|
||||
def from_native(self, value):
|
||||
if value in (True, False):
|
||||
# if value is 1 or 0 than it's equal to True or False, but we want
|
||||
# to return a true bool for semantic reasons.
|
||||
return bool(value)
|
||||
if value in ('t', 'True', '1'):
|
||||
return True
|
||||
if value in ('f', 'False', '0'):
|
||||
return False
|
||||
raise ValidationError(self.error_messages['invalid'] % value)
|
||||
|
||||
|
||||
class CharField(Field):
|
||||
def __init__(self, max_length=None, min_length=None, *args, **kwargs):
|
||||
self.max_length, self.min_length = max_length, min_length
|
||||
super(CharField, self).__init__(*args, **kwargs)
|
||||
if min_length is not None:
|
||||
self.validators.append(validators.MinLengthValidator(min_length))
|
||||
if max_length is not None:
|
||||
self.validators.append(validators.MaxLengthValidator(max_length))
|
||||
|
||||
def from_native(self, value):
|
||||
if isinstance(value, basestring) or value is None:
|
||||
return value
|
||||
return smart_unicode(value)
|
||||
|
||||
|
||||
class EmailField(CharField):
|
||||
default_error_messages = {
|
||||
'invalid': _('Enter a valid e-mail address.'),
|
||||
}
|
||||
default_validators = [validators.validate_email]
|
||||
|
||||
def from_native(self, value):
|
||||
return super(EmailField, self).from_native(value).strip()
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
result = copy.copy(self)
|
||||
memo[id(self)] = result
|
||||
#result.widget = copy.deepcopy(self.widget, memo)
|
||||
result.validators = self.validators[:]
|
||||
return result
|
||||
|
||||
|
||||
class DateField(Field):
|
||||
default_error_messages = {
|
||||
'invalid': _(u"'%s' value has an invalid date format. It must be "
|
||||
u"in YYYY-MM-DD format."),
|
||||
'invalid_date': _(u"'%s' value has the correct format (YYYY-MM-DD) "
|
||||
u"but it is an invalid date."),
|
||||
}
|
||||
empty = None
|
||||
|
||||
def from_native(self, value):
|
||||
if value is None:
|
||||
return value
|
||||
if isinstance(value, datetime.datetime):
|
||||
if timezone and settings.USE_TZ and timezone.is_aware(value):
|
||||
# Convert aware datetimes to the default time zone
|
||||
# before casting them to dates (#17742).
|
||||
default_timezone = timezone.get_default_timezone()
|
||||
value = timezone.make_naive(value, default_timezone)
|
||||
return value.date()
|
||||
if isinstance(value, datetime.date):
|
||||
return value
|
||||
|
||||
try:
|
||||
parsed = parse_date(value)
|
||||
if parsed is not None:
|
||||
return parsed
|
||||
except ValueError:
|
||||
msg = self.error_messages['invalid_date'] % value
|
||||
raise ValidationError(msg)
|
||||
|
||||
msg = self.error_messages['invalid'] % value
|
||||
raise ValidationError(msg)
|
||||
|
||||
|
||||
class DateTimeField(Field):
|
||||
default_error_messages = {
|
||||
'invalid': _(u"'%s' value has an invalid format. It must be in "
|
||||
u"YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."),
|
||||
'invalid_date': _(u"'%s' value has the correct format "
|
||||
u"(YYYY-MM-DD) but it is an invalid date."),
|
||||
'invalid_datetime': _(u"'%s' value has the correct format "
|
||||
u"(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) "
|
||||
u"but it is an invalid date/time."),
|
||||
}
|
||||
empty = None
|
||||
|
||||
def from_native(self, value):
|
||||
if value is None:
|
||||
return value
|
||||
if isinstance(value, datetime.datetime):
|
||||
return value
|
||||
if isinstance(value, datetime.date):
|
||||
value = datetime.datetime(value.year, value.month, value.day)
|
||||
if settings.USE_TZ:
|
||||
# For backwards compatibility, interpret naive datetimes in
|
||||
# local time. This won't work during DST change, but we can't
|
||||
# do much about it, so we let the exceptions percolate up the
|
||||
# call stack.
|
||||
warnings.warn(u"DateTimeField received a naive datetime (%s)"
|
||||
u" while time zone support is active." % value,
|
||||
RuntimeWarning)
|
||||
default_timezone = timezone.get_default_timezone()
|
||||
value = timezone.make_aware(value, default_timezone)
|
||||
return value
|
||||
|
||||
try:
|
||||
parsed = parse_datetime(value)
|
||||
if parsed is not None:
|
||||
return parsed
|
||||
except ValueError:
|
||||
msg = self.error_messages['invalid_datetime'] % value
|
||||
raise ValidationError(msg)
|
||||
|
||||
try:
|
||||
parsed = parse_date(value)
|
||||
if parsed is not None:
|
||||
return datetime.datetime(parsed.year, parsed.month, parsed.day)
|
||||
except ValueError:
|
||||
msg = self.error_messages['invalid_date'] % value
|
||||
raise ValidationError(msg)
|
||||
|
||||
msg = self.error_messages['invalid'] % value
|
||||
raise ValidationError(msg)
|
||||
|
||||
|
||||
class IntegerField(Field):
|
||||
default_error_messages = {
|
||||
'invalid': _('Enter a whole number.'),
|
||||
'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'),
|
||||
'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'),
|
||||
}
|
||||
|
||||
def __init__(self, max_value=None, min_value=None, *args, **kwargs):
|
||||
self.max_value, self.min_value = max_value, min_value
|
||||
super(IntegerField, self).__init__(*args, **kwargs)
|
||||
|
||||
if max_value is not None:
|
||||
self.validators.append(validators.MaxValueValidator(max_value))
|
||||
if min_value is not None:
|
||||
self.validators.append(validators.MinValueValidator(min_value))
|
||||
|
||||
def from_native(self, value):
|
||||
if value in validators.EMPTY_VALUES:
|
||||
return None
|
||||
try:
|
||||
value = int(str(value))
|
||||
except (ValueError, TypeError):
|
||||
raise ValidationError(self.error_messages['invalid'])
|
||||
return value
|
||||
|
||||
|
||||
class FloatField(Field):
|
||||
default_error_messages = {
|
||||
'invalid': _("'%s' value must be a float."),
|
||||
}
|
||||
|
||||
def from_native(self, value):
|
||||
if value is None:
|
||||
return value
|
||||
try:
|
||||
return float(value)
|
||||
except (TypeError, ValueError):
|
||||
msg = self.error_messages['invalid'] % value
|
||||
raise ValidationError(msg)
|
||||
|
||||
# field_mapping = {
|
||||
# models.AutoField: IntegerField,
|
||||
# models.BooleanField: BooleanField,
|
||||
# models.CharField: CharField,
|
||||
# models.DateTimeField: DateTimeField,
|
||||
# models.DateField: DateField,
|
||||
# models.BigIntegerField: IntegerField,
|
||||
# models.IntegerField: IntegerField,
|
||||
# models.PositiveIntegerField: IntegerField,
|
||||
# models.FloatField: FloatField
|
||||
# }
|
||||
|
||||
|
||||
# def modelfield_to_serializerfield(field):
|
||||
# return field_mapping.get(type(field), Field)
|
|
@ -1,113 +0,0 @@
|
|||
"""
|
||||
Generic views that provide commmonly needed behaviour.
|
||||
"""
|
||||
|
||||
from djangorestframework import views, mixins
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.views.generic.list import MultipleObjectMixin
|
||||
|
||||
|
||||
### Base classes for the generic views ###
|
||||
|
||||
class BaseView(views.APIView):
|
||||
"""
|
||||
Base class for all other generic views.
|
||||
"""
|
||||
serializer_class = None
|
||||
|
||||
def get_serializer(self, data=None, files=None, instance=None):
|
||||
# TODO: add support for files
|
||||
# TODO: add support for seperate serializer/deserializer
|
||||
context = {
|
||||
'request': self.request,
|
||||
'format': self.kwargs.get('format', None)
|
||||
}
|
||||
return self.serializer_class(data, instance=instance, context=context)
|
||||
|
||||
|
||||
class MultipleObjectBaseView(MultipleObjectMixin, BaseView):
|
||||
"""
|
||||
Base class for generic views onto a queryset.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class SingleObjectBaseView(SingleObjectMixin, BaseView):
|
||||
"""
|
||||
Base class for generic views onto a model instance.
|
||||
"""
|
||||
|
||||
def get_object(self):
|
||||
"""
|
||||
Override default to add support for object-level permissions.
|
||||
"""
|
||||
obj = super(SingleObjectBaseView, self).get_object()
|
||||
self.check_permissions(self.request, obj)
|
||||
return obj
|
||||
|
||||
|
||||
### Concrete view classes that provide method handlers ###
|
||||
### by composing the mixin classes with a base view. ###
|
||||
|
||||
class ListAPIView(mixins.ListModelMixin,
|
||||
mixins.MetadataMixin,
|
||||
MultipleObjectBaseView):
|
||||
"""
|
||||
Concrete view for listing a queryset.
|
||||
"""
|
||||
def get(self, request, *args, **kwargs):
|
||||
return self.list(request, *args, **kwargs)
|
||||
|
||||
def options(self, request, *args, **kwargs):
|
||||
return self.metadata(request, *args, **kwargs)
|
||||
|
||||
|
||||
class RootAPIView(mixins.ListModelMixin,
|
||||
mixins.CreateModelMixin,
|
||||
mixins.MetadataMixin,
|
||||
MultipleObjectBaseView):
|
||||
"""
|
||||
Concrete view for listing a queryset or creating a model instance.
|
||||
"""
|
||||
def get(self, request, *args, **kwargs):
|
||||
return self.list(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
return self.create(request, *args, **kwargs)
|
||||
|
||||
def options(self, request, *args, **kwargs):
|
||||
return self.metadata(request, *args, **kwargs)
|
||||
|
||||
|
||||
class DetailAPIView(mixins.RetrieveModelMixin,
|
||||
mixins.MetadataMixin,
|
||||
SingleObjectBaseView):
|
||||
"""
|
||||
Concrete view for retrieving a model instance.
|
||||
"""
|
||||
def get(self, request, *args, **kwargs):
|
||||
return self.retrieve(request, *args, **kwargs)
|
||||
|
||||
def options(self, request, *args, **kwargs):
|
||||
return self.metadata(request, *args, **kwargs)
|
||||
|
||||
|
||||
class InstanceAPIView(mixins.RetrieveModelMixin,
|
||||
mixins.UpdateModelMixin,
|
||||
mixins.DestroyModelMixin,
|
||||
mixins.MetadataMixin,
|
||||
SingleObjectBaseView):
|
||||
"""
|
||||
Concrete view for retrieving, updating or deleting a model instance.
|
||||
"""
|
||||
def get(self, request, *args, **kwargs):
|
||||
return self.retrieve(request, *args, **kwargs)
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
return self.update(request, *args, **kwargs)
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
return self.destroy(request, *args, **kwargs)
|
||||
|
||||
def options(self, request, *args, **kwargs):
|
||||
return self.metadata(request, *args, **kwargs)
|
|
@ -1,97 +0,0 @@
|
|||
"""
|
||||
Basic building blocks for generic class based views.
|
||||
|
||||
We don't bind behaviour to http method handlers yet,
|
||||
which allows mixin classes to be composed in interesting ways.
|
||||
|
||||
Eg. Use mixins to build a Resource class, and have a Router class
|
||||
perform the binding of http methods to actions for us.
|
||||
"""
|
||||
from djangorestframework import status
|
||||
from djangorestframework.response import Response
|
||||
|
||||
|
||||
class CreateModelMixin(object):
|
||||
"""
|
||||
Create a model instance.
|
||||
Should be mixed in with any `BaseView`.
|
||||
"""
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.DATA)
|
||||
if serializer.is_valid():
|
||||
self.object = serializer.object
|
||||
self.object.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class ListModelMixin(object):
|
||||
"""
|
||||
List a queryset.
|
||||
Should be mixed in with `MultipleObjectBaseView`.
|
||||
"""
|
||||
def list(self, request, *args, **kwargs):
|
||||
self.object_list = self.get_queryset()
|
||||
serializer = self.get_serializer(instance=self.object_list)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class RetrieveModelMixin(object):
|
||||
"""
|
||||
Retrieve a model instance.
|
||||
Should be mixed in with `SingleObjectBaseView`.
|
||||
"""
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
serializer = self.get_serializer(instance=self.object)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class UpdateModelMixin(object):
|
||||
"""
|
||||
Update a model instance.
|
||||
Should be mixed in with `SingleObjectBaseView`.
|
||||
"""
|
||||
def update(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
serializer = self.get_serializer(data=request.DATA, instance=self.object)
|
||||
if serializer.is_valid():
|
||||
self.object = serializer.object
|
||||
self.object.save()
|
||||
return Response(serializer.data)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class DestroyModelMixin(object):
|
||||
"""
|
||||
Destroy a model instance.
|
||||
Should be mixed in with `SingleObjectBaseView`.
|
||||
"""
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
self.object.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class MetadataMixin(object):
|
||||
"""
|
||||
Return a dicitonary of view metadata.
|
||||
Should be mixed in with any `BaseView`.
|
||||
|
||||
This mixin is typically used for the HTTP 'OPTIONS' method.
|
||||
"""
|
||||
def metadata(self, request, *args, **kwargs):
|
||||
content = {
|
||||
'name': self.get_name(),
|
||||
'description': self.get_description(),
|
||||
'renders': self._rendered_media_types,
|
||||
'parses': self._parsed_media_types,
|
||||
}
|
||||
# TODO: Add 'fields', from serializer info.
|
||||
# form = self.get_bound_form()
|
||||
# if form is not None:
|
||||
# field_name_types = {}
|
||||
# for name, field in form.fields.iteritems():
|
||||
# field_name_types[name] = field.__class__.__name__
|
||||
# content['fields'] = field_name_types
|
||||
return Response(content, status=status.HTTP_200_OK)
|
|
@ -1 +0,0 @@
|
|||
# Just to keep things like ./manage.py test happy
|
|
@ -1,74 +0,0 @@
|
|||
from djangorestframework import exceptions
|
||||
from djangorestframework.settings import api_settings
|
||||
from djangorestframework.utils.mediatypes import order_by_precedence
|
||||
|
||||
|
||||
class BaseContentNegotiation(object):
|
||||
def negotiate(self, request, renderers, format=None, force=False):
|
||||
raise NotImplementedError('.negotiate() must be implemented')
|
||||
|
||||
|
||||
class DefaultContentNegotiation(object):
|
||||
settings = api_settings
|
||||
|
||||
def negotiate(self, request, renderers, format=None, force=False):
|
||||
"""
|
||||
Given a request and a list of renderers, return a two-tuple of:
|
||||
(renderer, media type).
|
||||
|
||||
If force is set, then suppress exceptions, and forcibly return a
|
||||
fallback renderer and media_type.
|
||||
"""
|
||||
try:
|
||||
return self.unforced_negotiate(request, renderers, format)
|
||||
except (exceptions.InvalidFormat, exceptions.NotAcceptable):
|
||||
if force:
|
||||
return (renderers[0], renderers[0].media_type)
|
||||
raise
|
||||
|
||||
def unforced_negotiate(self, request, renderers, format=None):
|
||||
"""
|
||||
As `.negotiate()`, but does not take the optional `force` agument,
|
||||
or suppress exceptions.
|
||||
"""
|
||||
# Allow URL style format override. eg. "?format=json
|
||||
format = format or request.GET.get(self.settings.URL_FORMAT_OVERRIDE)
|
||||
|
||||
if format:
|
||||
renderers = self.filter_renderers(renderers, format)
|
||||
|
||||
accepts = self.get_accept_list(request)
|
||||
|
||||
# Check the acceptable media types against each renderer,
|
||||
# attempting more specific media types first
|
||||
# NB. The inner loop here isn't as bad as it first looks :)
|
||||
# Worst case is we're looping over len(accept_list) * len(self.renderers)
|
||||
for media_type_set in order_by_precedence(accepts):
|
||||
for renderer in renderers:
|
||||
for media_type in media_type_set:
|
||||
if renderer.can_handle_media_type(media_type):
|
||||
return renderer, media_type
|
||||
|
||||
raise exceptions.NotAcceptable(available_renderers=renderers)
|
||||
|
||||
def filter_renderers(self, renderers, format):
|
||||
"""
|
||||
If there is a '.json' style format suffix, filter the renderers
|
||||
so that we only negotiation against those that accept that format.
|
||||
"""
|
||||
renderers = [renderer for renderer in renderers
|
||||
if renderer.can_handle_format(format)]
|
||||
if not renderers:
|
||||
raise exceptions.InvalidFormat(format)
|
||||
return renderers
|
||||
|
||||
def get_accept_list(self, request):
|
||||
"""
|
||||
Given the incoming request, return a tokenised list of media
|
||||
type strings.
|
||||
|
||||
Allows URL style accept override. eg. "?accept=application/json"
|
||||
"""
|
||||
header = request.META.get('HTTP_ACCEPT', '*/*')
|
||||
header = request.GET.get(self.settings.URL_ACCEPT_OVERRIDE, header)
|
||||
return [token.strip() for token in header.split(',')]
|
|
@ -1,260 +0,0 @@
|
|||
"""
|
||||
Django supports parsing the content of an HTTP request, but only for form POST requests.
|
||||
That behavior is sufficient for dealing with standard HTML forms, but it doesn't map well
|
||||
to general HTTP requests.
|
||||
|
||||
We need a method to be able to:
|
||||
|
||||
1.) Determine the parsed content on a request for methods other than POST (eg typically also PUT)
|
||||
|
||||
2.) Determine the parsed content on a request for media types other than application/x-www-form-urlencoded
|
||||
and multipart/form-data. (eg also handle multipart/json)
|
||||
"""
|
||||
|
||||
from django.http import QueryDict
|
||||
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
|
||||
from django.http.multipartparser import MultiPartParserError
|
||||
from django.utils import simplejson as json
|
||||
from djangorestframework.compat import yaml
|
||||
from djangorestframework.exceptions import ParseError
|
||||
from djangorestframework.utils.mediatypes import media_type_matches
|
||||
from xml.etree import ElementTree as ET
|
||||
from djangorestframework.compat import ETParseError
|
||||
from xml.parsers.expat import ExpatError
|
||||
import datetime
|
||||
import decimal
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
__all__ = (
|
||||
'BaseParser',
|
||||
'JSONParser',
|
||||
'PlainTextParser',
|
||||
'FormParser',
|
||||
'MultiPartParser',
|
||||
'YAMLParser',
|
||||
'XMLParser'
|
||||
)
|
||||
|
||||
|
||||
class DataAndFiles(object):
|
||||
def __init__(self, data, files):
|
||||
self.data = data
|
||||
self.files = files
|
||||
|
||||
|
||||
class BaseParser(object):
|
||||
"""
|
||||
All parsers should extend :class:`BaseParser`, specifying a :attr:`media_type` attribute,
|
||||
and overriding the :meth:`parse` method.
|
||||
"""
|
||||
|
||||
media_type = None
|
||||
|
||||
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.
|
||||
"""
|
||||
return media_type_matches(self.media_type, content_type)
|
||||
|
||||
def parse(self, string_or_stream, **opts):
|
||||
"""
|
||||
The main entry point to parsers. This is a light wrapper around
|
||||
`parse_stream`, that instead handles both string and stream objects.
|
||||
"""
|
||||
if isinstance(string_or_stream, basestring):
|
||||
stream = BytesIO(string_or_stream)
|
||||
else:
|
||||
stream = string_or_stream
|
||||
return self.parse_stream(stream, **opts)
|
||||
|
||||
def parse_stream(self, stream, **opts):
|
||||
"""
|
||||
Given a *stream* to read from, return the deserialized output.
|
||||
Should return parsed data, or a DataAndFiles object consisting of the
|
||||
parsed data and files.
|
||||
"""
|
||||
raise NotImplementedError(".parse_stream() must be overridden.")
|
||||
|
||||
|
||||
class JSONParser(BaseParser):
|
||||
"""
|
||||
Parses JSON-serialized data.
|
||||
"""
|
||||
|
||||
media_type = 'application/json'
|
||||
|
||||
def parse_stream(self, stream, **opts):
|
||||
"""
|
||||
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`.
|
||||
"""
|
||||
try:
|
||||
return json.load(stream)
|
||||
except ValueError, exc:
|
||||
raise ParseError('JSON parse error - %s' % unicode(exc))
|
||||
|
||||
|
||||
class YAMLParser(BaseParser):
|
||||
"""
|
||||
Parses YAML-serialized data.
|
||||
"""
|
||||
|
||||
media_type = 'application/yaml'
|
||||
|
||||
def parse_stream(self, stream, **opts):
|
||||
"""
|
||||
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`.
|
||||
"""
|
||||
try:
|
||||
return yaml.safe_load(stream)
|
||||
except (ValueError, yaml.parser.ParserError), exc:
|
||||
raise ParseError('YAML parse error - %s' % unicode(exc))
|
||||
|
||||
|
||||
class PlainTextParser(BaseParser):
|
||||
"""
|
||||
Plain text parser.
|
||||
"""
|
||||
|
||||
media_type = 'text/plain'
|
||||
|
||||
def parse_stream(self, stream, **opts):
|
||||
"""
|
||||
Returns a 2-tuple of `(data, files)`.
|
||||
|
||||
`data` will simply be a string representing the body of the request.
|
||||
`files` will always be `None`.
|
||||
"""
|
||||
return stream.read()
|
||||
|
||||
|
||||
class FormParser(BaseParser):
|
||||
"""
|
||||
Parser for form data.
|
||||
"""
|
||||
|
||||
media_type = 'application/x-www-form-urlencoded'
|
||||
|
||||
def parse_stream(self, stream, **opts):
|
||||
"""
|
||||
Returns a 2-tuple of `(data, files)`.
|
||||
|
||||
`data` will be a :class:`QueryDict` containing all the form parameters.
|
||||
`files` will always be :const:`None`.
|
||||
"""
|
||||
data = QueryDict(stream.read())
|
||||
return data
|
||||
|
||||
|
||||
class MultiPartParser(BaseParser):
|
||||
"""
|
||||
Parser for multipart form data, which may include file data.
|
||||
"""
|
||||
|
||||
media_type = 'multipart/form-data'
|
||||
|
||||
def parse_stream(self, stream, **opts):
|
||||
"""
|
||||
Returns a DataAndFiles object.
|
||||
|
||||
`.data` will be a `QueryDict` containing all the form parameters.
|
||||
`.files` will be a `QueryDict` containing all the form files.
|
||||
"""
|
||||
meta = opts['meta']
|
||||
upload_handlers = opts['upload_handlers']
|
||||
try:
|
||||
parser = DjangoMultiPartParser(meta, stream, upload_handlers)
|
||||
data, files = parser.parse()
|
||||
return DataAndFiles(data, files)
|
||||
except MultiPartParserError, exc:
|
||||
raise ParseError('Multipart form parse error - %s' % unicode(exc))
|
||||
|
||||
|
||||
class XMLParser(BaseParser):
|
||||
"""
|
||||
XML parser.
|
||||
"""
|
||||
|
||||
media_type = 'application/xml'
|
||||
|
||||
def parse_stream(self, stream, **opts):
|
||||
try:
|
||||
tree = ET.parse(stream)
|
||||
except (ExpatError, ETParseError, ValueError), exc:
|
||||
raise ParseError('XML parse error - %s' % unicode(exc))
|
||||
data = self._xml_convert(tree.getroot())
|
||||
|
||||
return data
|
||||
|
||||
def _xml_convert(self, element):
|
||||
"""
|
||||
convert the xml `element` into the corresponding python object
|
||||
"""
|
||||
|
||||
children = element.getchildren()
|
||||
|
||||
if len(children) == 0:
|
||||
return self._type_convert(element.text)
|
||||
else:
|
||||
# if the fist child tag is list-item means all children are list-item
|
||||
if children[0].tag == "list-item":
|
||||
data = []
|
||||
for child in children:
|
||||
data.append(self._xml_convert(child))
|
||||
else:
|
||||
data = {}
|
||||
for child in children:
|
||||
data[child.tag] = self._xml_convert(child)
|
||||
|
||||
return data
|
||||
|
||||
def _type_convert(self, value):
|
||||
"""
|
||||
Converts the value returned by the XMl parse into the equivalent
|
||||
Python type
|
||||
"""
|
||||
if value is None:
|
||||
return value
|
||||
|
||||
try:
|
||||
return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return decimal.Decimal(value)
|
||||
except decimal.InvalidOperation:
|
||||
pass
|
||||
|
||||
return value
|
||||
|
||||
|
||||
DEFAULT_PARSERS = (
|
||||
JSONParser,
|
||||
FormParser,
|
||||
MultiPartParser,
|
||||
XMLParser
|
||||
)
|
||||
|
||||
if yaml:
|
||||
DEFAULT_PARSERS += (YAMLParser, )
|
||||
else:
|
||||
YAMLParser = None
|
|
@ -1,116 +0,0 @@
|
|||
"""
|
||||
The :mod:`permissions` module bundles a set of permission classes that are used
|
||||
for checking if a request passes a certain set of constraints.
|
||||
|
||||
Permission behavior is provided by mixing the :class:`mixins.PermissionsMixin` class into a :class:`View` class.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
'BasePermission',
|
||||
'FullAnonAccess',
|
||||
'IsAuthenticated',
|
||||
'IsAdminUser',
|
||||
'IsUserOrIsAnonReadOnly',
|
||||
'PerUserThrottling',
|
||||
'PerViewThrottling',
|
||||
)
|
||||
|
||||
SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
|
||||
|
||||
|
||||
class BasePermission(object):
|
||||
"""
|
||||
A base class from which all permission classes should inherit.
|
||||
"""
|
||||
def __init__(self, view):
|
||||
"""
|
||||
Permission classes are always passed the current view on creation.
|
||||
"""
|
||||
self.view = view
|
||||
|
||||
def has_permission(self, request, obj=None):
|
||||
"""
|
||||
Should simply return, or raise an :exc:`response.ImmediateResponse`.
|
||||
"""
|
||||
raise NotImplementedError(".has_permission() must be overridden.")
|
||||
|
||||
|
||||
class IsAuthenticated(BasePermission):
|
||||
"""
|
||||
Allows access only to authenticated users.
|
||||
"""
|
||||
|
||||
def has_permission(self, request, obj=None):
|
||||
if request.user and request.user.is_authenticated():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class IsAdminUser(BasePermission):
|
||||
"""
|
||||
Allows access only to admin users.
|
||||
"""
|
||||
|
||||
def has_permission(self, request, obj=None):
|
||||
if request.user and request.user.is_staff:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class IsAuthenticatedOrReadOnly(BasePermission):
|
||||
"""
|
||||
The request is authenticated as a user, or is a read-only request.
|
||||
"""
|
||||
|
||||
def has_permission(self, request, obj=None):
|
||||
if (request.method in SAFE_METHODS or
|
||||
request.user and
|
||||
request.user.is_authenticated()):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class DjangoModelPermissions(BasePermission):
|
||||
"""
|
||||
The request is authenticated using `django.contrib.auth` permissions.
|
||||
See: https://docs.djangoproject.com/en/dev/topics/auth/#permissions
|
||||
|
||||
It ensures that the user is authenticated, and has the appropriate
|
||||
`add`/`change`/`delete` permissions on the model.
|
||||
|
||||
This permission should only be used on views with a `ModelResource`.
|
||||
"""
|
||||
|
||||
# Map methods into required permission codes.
|
||||
# Override this if you need to also provide 'view' permissions,
|
||||
# or if you want to provide custom permission codes.
|
||||
perms_map = {
|
||||
'GET': [],
|
||||
'OPTIONS': [],
|
||||
'HEAD': [],
|
||||
'POST': ['%(app_label)s.add_%(model_name)s'],
|
||||
'PUT': ['%(app_label)s.change_%(model_name)s'],
|
||||
'PATCH': ['%(app_label)s.change_%(model_name)s'],
|
||||
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
|
||||
}
|
||||
|
||||
def get_required_permissions(self, method, model_cls):
|
||||
"""
|
||||
Given a model and an HTTP method, return the list of permission
|
||||
codes that the user is required to have.
|
||||
"""
|
||||
kwargs = {
|
||||
'app_label': model_cls._meta.app_label,
|
||||
'model_name': model_cls._meta.module_name
|
||||
}
|
||||
return [perm % kwargs for perm in self.perms_map[method]]
|
||||
|
||||
def has_permission(self, request, obj=None):
|
||||
model_cls = self.view.model
|
||||
perms = self.get_required_permissions(request.method, model_cls)
|
||||
|
||||
if (request.user and
|
||||
request.user.is_authenticated() and
|
||||
request.user.has_perms(perms, obj)):
|
||||
return True
|
||||
return False
|
|
@ -1,402 +0,0 @@
|
|||
"""
|
||||
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.
|
||||
"""
|
||||
from django import forms
|
||||
from django.template import RequestContext, loader
|
||||
from django.utils import simplejson as json
|
||||
|
||||
from djangorestframework.compat import yaml
|
||||
from djangorestframework.settings import api_settings
|
||||
from djangorestframework.utils import dict2xml
|
||||
from djangorestframework.utils import encoders
|
||||
from djangorestframework.utils.breadcrumbs import get_breadcrumbs
|
||||
from djangorestframework.utils.mediatypes import get_media_type_params, add_media_type_param, media_type_matches
|
||||
from djangorestframework import VERSION
|
||||
from djangorestframework.fields import FloatField, IntegerField, DateTimeField, DateField, EmailField, CharField, BooleanField
|
||||
|
||||
import string
|
||||
|
||||
|
||||
__all__ = (
|
||||
'BaseRenderer',
|
||||
'TemplateRenderer',
|
||||
'JSONRenderer',
|
||||
'JSONPRenderer',
|
||||
'DocumentingHTMLRenderer',
|
||||
'DocumentingXHTMLRenderer',
|
||||
'DocumentingPlainTextRenderer',
|
||||
'XMLRenderer',
|
||||
'YAMLRenderer'
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
format = None
|
||||
|
||||
def __init__(self, view=None):
|
||||
self.view = view
|
||||
|
||||
def can_handle_format(self, format):
|
||||
return format == self.format
|
||||
|
||||
def can_handle_media_type(self, media_type):
|
||||
"""
|
||||
Returns `True` if this renderer is able to deal with the given
|
||||
media type.
|
||||
|
||||
The default implementation for this function is to check the media type
|
||||
argument against the 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 `media_type` attribute on the class.
|
||||
"""
|
||||
return media_type_matches(self.media_type, media_type)
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
"""
|
||||
Given an object render it into a string.
|
||||
|
||||
The requested media type is also passed to this method,
|
||||
as it may contain parameters relevant to how the parser
|
||||
should render the output.
|
||||
EG: ``application/json; indent=4``
|
||||
|
||||
By default render simply returns the output as-is.
|
||||
Override this method to provide for other behavior.
|
||||
"""
|
||||
if obj is None:
|
||||
return ''
|
||||
|
||||
return str(obj)
|
||||
|
||||
|
||||
class JSONRenderer(BaseRenderer):
|
||||
"""
|
||||
Renderer which serializes to JSON
|
||||
"""
|
||||
|
||||
media_type = 'application/json'
|
||||
format = 'json'
|
||||
encoder_class = encoders.JSONEncoder
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
"""
|
||||
Renders *obj* into serialized JSON.
|
||||
"""
|
||||
if obj is None:
|
||||
return ''
|
||||
|
||||
# If the media type looks like 'application/json; indent=4', then
|
||||
# pretty print the result.
|
||||
indent = get_media_type_params(media_type).get('indent', None)
|
||||
sort_keys = False
|
||||
try:
|
||||
indent = max(min(int(indent), 8), 0)
|
||||
sort_keys = True
|
||||
except (ValueError, TypeError):
|
||||
indent = None
|
||||
|
||||
return json.dumps(obj, cls=self.encoder_class, indent=indent, sort_keys=sort_keys)
|
||||
|
||||
|
||||
class JSONPRenderer(JSONRenderer):
|
||||
"""
|
||||
Renderer which serializes to JSONP
|
||||
"""
|
||||
|
||||
media_type = 'application/javascript'
|
||||
format = 'jsonp'
|
||||
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.
|
||||
"""
|
||||
|
||||
media_type = 'application/xml'
|
||||
format = 'xml'
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
"""
|
||||
Renders *obj* into serialized XML.
|
||||
"""
|
||||
if obj is None:
|
||||
return ''
|
||||
return dict2xml(obj)
|
||||
|
||||
|
||||
class YAMLRenderer(BaseRenderer):
|
||||
"""
|
||||
Renderer which serializes to YAML.
|
||||
"""
|
||||
|
||||
media_type = 'application/yaml'
|
||||
format = 'yaml'
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
"""
|
||||
Renders *obj* into serialized YAML.
|
||||
"""
|
||||
if obj is None:
|
||||
return ''
|
||||
|
||||
return yaml.safe_dump(obj)
|
||||
|
||||
|
||||
class TemplateRenderer(BaseRenderer):
|
||||
"""
|
||||
A Base class provided for convenience.
|
||||
|
||||
Render the object simply by using the given template.
|
||||
To create a template renderer, subclass this class, and set
|
||||
the :attr:`media_type` and :attr:`template` attributes.
|
||||
"""
|
||||
|
||||
media_type = None
|
||||
template = None
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
"""
|
||||
Renders *obj* using the :attr:`template` specified on the class.
|
||||
"""
|
||||
if obj is None:
|
||||
return ''
|
||||
|
||||
template = loader.get_template(self.template)
|
||||
context = RequestContext(self.view.request, {'object': obj})
|
||||
return template.render(context)
|
||||
|
||||
|
||||
class DocumentingTemplateRenderer(BaseRenderer):
|
||||
"""
|
||||
Base class for renderers used to self-document the API.
|
||||
Implementing classes should extend this class and set the template attribute.
|
||||
"""
|
||||
|
||||
template = None
|
||||
|
||||
def _get_content(self, view, request, obj, media_type):
|
||||
"""
|
||||
Get the content as if it had been rendered by a non-documenting renderer.
|
||||
|
||||
(Typically this will be the content as it would have been if the Resource had been
|
||||
requested with an 'Accept: */*' header, although with verbose style formatting if appropriate.)
|
||||
"""
|
||||
|
||||
# Find the first valid renderer and render the content. (Don't use another documenting renderer.)
|
||||
renderers = [renderer for renderer in view.renderer_classes
|
||||
if not issubclass(renderer, DocumentingTemplateRenderer)]
|
||||
if not renderers:
|
||||
return '[No renderers were found]'
|
||||
|
||||
media_type = add_media_type_param(media_type, 'indent', '4')
|
||||
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
|
||||
|
||||
def _get_form_instance(self, view, method):
|
||||
"""
|
||||
Get a form, possibly bound to either the input or output data.
|
||||
In the absence on of the Resource having an associated form then
|
||||
provide a form that can be used to submit arbitrary content.
|
||||
"""
|
||||
if not hasattr(self.view, 'get_serializer'): # No serializer, no form.
|
||||
return
|
||||
# We need to map our Fields to Django's Fields.
|
||||
field_mapping = dict([
|
||||
[FloatField.__name__, forms.FloatField],
|
||||
[IntegerField.__name__, forms.IntegerField],
|
||||
[DateTimeField.__name__, forms.DateTimeField],
|
||||
[DateField.__name__, forms.DateField],
|
||||
[EmailField.__name__, forms.EmailField],
|
||||
[CharField.__name__, forms.CharField],
|
||||
[BooleanField.__name__, forms.BooleanField]
|
||||
])
|
||||
|
||||
# Creating an on the fly form see: http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python
|
||||
fields = {}
|
||||
object, data = None, None
|
||||
if hasattr(self.view, 'object'):
|
||||
object = self.view.object
|
||||
serializer = self.view.get_serializer(instance=object)
|
||||
for k, v in serializer.fields.items():
|
||||
fields[k] = field_mapping[v.__class__.__name__]()
|
||||
OnTheFlyForm = type("OnTheFlyForm", (forms.Form,), fields)
|
||||
if object and not self.view.request.method == 'DELETE': # Don't fill in the form when the object is deleted
|
||||
data = serializer.data
|
||||
form_instance = OnTheFlyForm(data)
|
||||
return form_instance
|
||||
|
||||
def _get_generic_content_form(self, view):
|
||||
"""
|
||||
Returns a form that allows for arbitrary content types to be tunneled via standard HTML forms
|
||||
(Which are typically application/x-www-form-urlencoded)
|
||||
"""
|
||||
|
||||
# If we're not using content overloading there's no point in supplying a generic form,
|
||||
# as the view won't treat the form's value as the content of the request.
|
||||
if not getattr(view.request, '_USE_FORM_OVERLOADING', False):
|
||||
return None
|
||||
|
||||
# NB. http://jacobian.org/writing/dynamic-form-generation/
|
||||
class GenericContentForm(forms.Form):
|
||||
def __init__(self, view, request):
|
||||
"""We don't know the names of the fields we want to set until the point the form is instantiated,
|
||||
as they are determined by the Resource the form is being created against.
|
||||
Add the fields dynamically."""
|
||||
super(GenericContentForm, self).__init__()
|
||||
|
||||
contenttype_choices = [(media_type, media_type) for media_type in view._parsed_media_types]
|
||||
initial_contenttype = view._default_parser.media_type
|
||||
|
||||
self.fields[request._CONTENTTYPE_PARAM] = forms.ChoiceField(label='Content Type',
|
||||
choices=contenttype_choices,
|
||||
initial=initial_contenttype)
|
||||
self.fields[request._CONTENT_PARAM] = forms.CharField(label='Content',
|
||||
widget=forms.Textarea)
|
||||
|
||||
# If either of these reserved parameters are turned off then content tunneling is not possible
|
||||
if self.view.request._CONTENTTYPE_PARAM is None or self.view.request._CONTENT_PARAM is None:
|
||||
return None
|
||||
|
||||
# Okey doke, let's do it
|
||||
return GenericContentForm(view, view.request)
|
||||
|
||||
def get_name(self):
|
||||
try:
|
||||
return self.view.get_name()
|
||||
except AttributeError:
|
||||
return self.view.__doc__
|
||||
|
||||
def get_description(self, html=None):
|
||||
if html is None:
|
||||
html = bool('html' in self.format)
|
||||
try:
|
||||
return self.view.get_description(html)
|
||||
except AttributeError:
|
||||
return self.view.__doc__
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
"""
|
||||
Renders *obj* using the :attr:`template` set on the class.
|
||||
|
||||
The context used in the template contains all the information
|
||||
needed to self-document the response to this request.
|
||||
"""
|
||||
|
||||
content = self._get_content(self.view, self.view.request, obj, media_type)
|
||||
|
||||
put_form_instance = self._get_form_instance(self.view, 'put')
|
||||
post_form_instance = self._get_form_instance(self.view, 'post')
|
||||
|
||||
name = self.get_name()
|
||||
description = self.get_description()
|
||||
|
||||
breadcrumb_list = get_breadcrumbs(self.view.request.path)
|
||||
|
||||
template = loader.get_template(self.template)
|
||||
context = RequestContext(self.view.request, {
|
||||
'content': content,
|
||||
'view': self.view,
|
||||
'request': self.view.request,
|
||||
'response': self.view.response,
|
||||
'description': description,
|
||||
'name': name,
|
||||
'version': VERSION,
|
||||
'breadcrumblist': breadcrumb_list,
|
||||
'allowed_methods': self.view.allowed_methods,
|
||||
'available_formats': self.view._rendered_formats,
|
||||
'put_form': put_form_instance,
|
||||
'post_form': post_form_instance,
|
||||
'FORMAT_PARAM': self._FORMAT_QUERY_PARAM,
|
||||
'METHOD_PARAM': getattr(self.view, '_METHOD_PARAM', None),
|
||||
'api_settings': api_settings
|
||||
})
|
||||
|
||||
ret = template.render(context)
|
||||
|
||||
# Munge DELETE Response code to allow us to return content
|
||||
# (Do this *after* we've rendered the template so that we include
|
||||
# the normal deletion response code in the output)
|
||||
if self.view.response.status_code == 204:
|
||||
self.view.response.status_code = 200
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
class DocumentingHTMLRenderer(DocumentingTemplateRenderer):
|
||||
"""
|
||||
Renderer which provides a browsable HTML interface for an API.
|
||||
See the examples at http://api.django-rest-framework.org to see this in action.
|
||||
"""
|
||||
|
||||
media_type = 'text/html'
|
||||
format = 'html'
|
||||
template = 'djangorestframework/api.html'
|
||||
|
||||
|
||||
class DocumentingXHTMLRenderer(DocumentingTemplateRenderer):
|
||||
"""
|
||||
Identical to DocumentingHTMLRenderer, except with an xhtml media type.
|
||||
We need this to be listed in preference to xml in order to return HTML to WebKit based browsers,
|
||||
given their Accept headers.
|
||||
"""
|
||||
|
||||
media_type = 'application/xhtml+xml'
|
||||
format = 'xhtml'
|
||||
template = 'djangorestframework/api.html'
|
||||
|
||||
|
||||
class DocumentingPlainTextRenderer(DocumentingTemplateRenderer):
|
||||
"""
|
||||
Renderer that serializes the object with the default renderer, but also provides plain-text
|
||||
documentation of the returned status and headers, and of the resource's name and description.
|
||||
Useful for browsing an API with command line tools.
|
||||
"""
|
||||
|
||||
media_type = 'text/plain'
|
||||
format = 'txt'
|
||||
template = 'djangorestframework/api.txt'
|
||||
|
||||
|
||||
DEFAULT_RENDERERS = (
|
||||
JSONRenderer,
|
||||
JSONPRenderer,
|
||||
DocumentingHTMLRenderer,
|
||||
DocumentingXHTMLRenderer,
|
||||
DocumentingPlainTextRenderer,
|
||||
XMLRenderer
|
||||
)
|
||||
|
||||
if yaml:
|
||||
DEFAULT_RENDERERS += (YAMLRenderer, )
|
||||
else:
|
||||
YAMLRenderer = None
|
|
@ -1,284 +0,0 @@
|
|||
"""
|
||||
The :mod:`request` module provides a :class:`Request` class used to wrap the standard `request`
|
||||
object received in all the views.
|
||||
|
||||
The wrapped request then offers a richer API, in particular :
|
||||
|
||||
- content automatically parsed according to `Content-Type` header,
|
||||
and available as :meth:`.DATA<Request.DATA>`
|
||||
- full support of PUT method, including support for file uploads
|
||||
- form overloading of HTTP method, content type and content
|
||||
"""
|
||||
from StringIO import StringIO
|
||||
|
||||
from djangorestframework import exceptions
|
||||
from djangorestframework.settings import api_settings
|
||||
from djangorestframework.utils.mediatypes import is_form_media_type
|
||||
|
||||
|
||||
__all__ = ('Request',)
|
||||
|
||||
|
||||
class Empty(object):
|
||||
"""
|
||||
Placeholder for unset attributes.
|
||||
Cannot use `None`, as that may be a valid value.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def _hasattr(obj, name):
|
||||
return not getattr(obj, name) is Empty
|
||||
|
||||
|
||||
class Request(object):
|
||||
"""
|
||||
Wrapper allowing to enhance a standard `HttpRequest` instance.
|
||||
|
||||
Kwargs:
|
||||
- request(HttpRequest). The original request instance.
|
||||
- parsers_classes(list/tuple). The parsers to use for parsing the
|
||||
request content.
|
||||
- authentication_classes(list/tuple). The authentications used to try
|
||||
authenticating the request's user.
|
||||
"""
|
||||
|
||||
_METHOD_PARAM = api_settings.FORM_METHOD_OVERRIDE
|
||||
_CONTENT_PARAM = api_settings.FORM_CONTENT_OVERRIDE
|
||||
_CONTENTTYPE_PARAM = api_settings.FORM_CONTENTTYPE_OVERRIDE
|
||||
|
||||
def __init__(self, request, parser_classes=None, authentication_classes=None):
|
||||
self._request = request
|
||||
self.parser_classes = parser_classes or ()
|
||||
self.authentication_classes = authentication_classes or ()
|
||||
self._data = Empty
|
||||
self._files = Empty
|
||||
self._method = Empty
|
||||
self._content_type = Empty
|
||||
self._stream = Empty
|
||||
|
||||
def get_parsers(self):
|
||||
"""
|
||||
Instantiates and returns the list of parsers the request will use.
|
||||
"""
|
||||
return [parser() for parser in self.parser_classes]
|
||||
|
||||
def get_authentications(self):
|
||||
"""
|
||||
Instantiates and returns the list of parsers the request will use.
|
||||
"""
|
||||
return [authentication() for authentication in self.authentication_classes]
|
||||
|
||||
@property
|
||||
def method(self):
|
||||
"""
|
||||
Returns the HTTP method.
|
||||
|
||||
This allows the `method` to be overridden by using a hidden `form`
|
||||
field on a form POST request.
|
||||
"""
|
||||
if not _hasattr(self, '_method'):
|
||||
self._load_method_and_content_type()
|
||||
return self._method
|
||||
|
||||
@property
|
||||
def content_type(self):
|
||||
"""
|
||||
Returns the content type header.
|
||||
|
||||
This should be used instead of ``request.META.get('HTTP_CONTENT_TYPE')``,
|
||||
as it allows the content type to be overridden by using a hidden form
|
||||
field on a form POST request.
|
||||
"""
|
||||
if not _hasattr(self, '_content_type'):
|
||||
self._load_method_and_content_type()
|
||||
return self._content_type
|
||||
|
||||
@property
|
||||
def stream(self):
|
||||
"""
|
||||
Returns an object that may be used to stream the request content.
|
||||
"""
|
||||
if not _hasattr(self, '_stream'):
|
||||
self._load_stream()
|
||||
return self._stream
|
||||
|
||||
@property
|
||||
def DATA(self):
|
||||
"""
|
||||
Parses the request body and returns the data.
|
||||
|
||||
Similar to usual behaviour of `request.POST`, except that it handles
|
||||
arbitrary parsers, and also works on methods other than POST (eg PUT).
|
||||
"""
|
||||
if not _hasattr(self, '_data'):
|
||||
self._load_data_and_files()
|
||||
return self._data
|
||||
|
||||
@property
|
||||
def FILES(self):
|
||||
"""
|
||||
Parses the request body and returns any files uploaded in the request.
|
||||
|
||||
Similar to usual behaviour of `request.FILES`, except that it handles
|
||||
arbitrary parsers, and also works on methods other than POST (eg PUT).
|
||||
"""
|
||||
if not _hasattr(self, '_files'):
|
||||
self._load_data_and_files()
|
||||
return self._files
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
"""
|
||||
Returns the user associated with the current request, as authenticated
|
||||
by the authentication classes provided to the request.
|
||||
"""
|
||||
if not hasattr(self, '_user'):
|
||||
self._user, self._auth = self._authenticate()
|
||||
return self._user
|
||||
|
||||
@property
|
||||
def auth(self):
|
||||
"""
|
||||
Returns any non-user authentication information associated with the
|
||||
request, such as an authentication token.
|
||||
"""
|
||||
if not hasattr(self, '_auth'):
|
||||
self._user, self._auth = self._authenticate()
|
||||
return self._auth
|
||||
|
||||
def _load_data_and_files(self):
|
||||
"""
|
||||
Parses the request content into self.DATA and self.FILES.
|
||||
"""
|
||||
if not _hasattr(self, '_content_type'):
|
||||
self._load_method_and_content_type()
|
||||
|
||||
if not _hasattr(self, '_data'):
|
||||
self._data, self._files = self._parse()
|
||||
|
||||
def _load_method_and_content_type(self):
|
||||
"""
|
||||
Sets the method and content_type, and then check if they've
|
||||
been overridden.
|
||||
"""
|
||||
self._content_type = self.META.get('HTTP_CONTENT_TYPE',
|
||||
self.META.get('CONTENT_TYPE', ''))
|
||||
self._perform_form_overloading()
|
||||
# if the HTTP method was not overloaded, we take the raw HTTP method
|
||||
if not _hasattr(self, '_method'):
|
||||
self._method = self._request.method
|
||||
|
||||
def _load_stream(self):
|
||||
"""
|
||||
Return the content body of the request, as a stream.
|
||||
"""
|
||||
try:
|
||||
content_length = int(self.META.get('CONTENT_LENGTH',
|
||||
self.META.get('HTTP_CONTENT_LENGTH')))
|
||||
except (ValueError, TypeError):
|
||||
content_length = 0
|
||||
|
||||
if content_length == 0:
|
||||
self._stream = None
|
||||
elif hasattr(self._request, 'read'):
|
||||
self._stream = self._request
|
||||
else:
|
||||
self._stream = StringIO(self.raw_post_data)
|
||||
|
||||
def _perform_form_overloading(self):
|
||||
"""
|
||||
If this is a form POST request, then we need to check if the method and
|
||||
content/content_type have been overridden by setting them in hidden
|
||||
form fields or not.
|
||||
"""
|
||||
|
||||
USE_FORM_OVERLOADING = (
|
||||
self._METHOD_PARAM or
|
||||
(self._CONTENT_PARAM and self._CONTENTTYPE_PARAM)
|
||||
)
|
||||
|
||||
# We only need to use form overloading on form POST requests.
|
||||
if (not USE_FORM_OVERLOADING
|
||||
or self._request.method != 'POST'
|
||||
or not is_form_media_type(self._content_type)):
|
||||
return
|
||||
|
||||
# At this point we're committed to parsing the request as form data.
|
||||
self._data = self._request.POST
|
||||
self._files = self._request.FILES
|
||||
|
||||
# Method overloading - change the method and remove the param from the content.
|
||||
if (self._METHOD_PARAM and
|
||||
self._METHOD_PARAM in self._data):
|
||||
self._method = self._data[self._METHOD_PARAM].upper()
|
||||
self._data.pop(self._METHOD_PARAM)
|
||||
|
||||
# Content overloading - modify the content type, and re-parse.
|
||||
if (self._CONTENT_PARAM and
|
||||
self._CONTENTTYPE_PARAM and
|
||||
self._CONTENT_PARAM in self._data and
|
||||
self._CONTENTTYPE_PARAM in self._data):
|
||||
self._content_type = self._data[self._CONTENTTYPE_PARAM]
|
||||
self._stream = StringIO(self._data[self._CONTENT_PARAM])
|
||||
self._data.pop(self._CONTENTTYPE_PARAM)
|
||||
self._data.pop(self._CONTENT_PARAM)
|
||||
self._data, self._files = self._parse()
|
||||
|
||||
def _parse(self):
|
||||
"""
|
||||
Parse the request content, returning a two-tuple of (data, files)
|
||||
|
||||
May raise an `UnsupportedMediaType`, or `ParseError` exception.
|
||||
"""
|
||||
if self.stream is None or self.content_type is None:
|
||||
return (None, None)
|
||||
|
||||
for parser in self.get_parsers():
|
||||
if parser.can_handle_request(self.content_type):
|
||||
parsed = parser.parse(self.stream, meta=self.META,
|
||||
upload_handlers=self.upload_handlers)
|
||||
# Parser classes may return the raw data, or a
|
||||
# DataAndFiles object. Unpack the result as required.
|
||||
try:
|
||||
return (parsed.data, parsed.files)
|
||||
except AttributeError:
|
||||
return (parsed, None)
|
||||
|
||||
raise exceptions.UnsupportedMediaType(self._content_type)
|
||||
|
||||
def _authenticate(self):
|
||||
"""
|
||||
Attempt to authenticate the request using each authentication instance in turn.
|
||||
Returns a two-tuple of (user, authtoken).
|
||||
"""
|
||||
for authentication in self.get_authentications():
|
||||
user_auth_tuple = authentication.authenticate(self)
|
||||
if not user_auth_tuple is None:
|
||||
return user_auth_tuple
|
||||
return self._not_authenticated()
|
||||
|
||||
def _not_authenticated(self):
|
||||
"""
|
||||
Return a two-tuple of (user, authtoken), representing an
|
||||
unauthenticated request.
|
||||
|
||||
By default this will be (AnonymousUser, None).
|
||||
"""
|
||||
if api_settings.UNAUTHENTICATED_USER:
|
||||
user = api_settings.UNAUTHENTICATED_USER()
|
||||
else:
|
||||
user = None
|
||||
|
||||
if api_settings.UNAUTHENTICATED_TOKEN:
|
||||
auth = api_settings.UNAUTHENTICATED_TOKEN()
|
||||
else:
|
||||
auth = None
|
||||
|
||||
return (user, auth)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
"""
|
||||
Proxy other attributes to the underlying HttpRequest object.
|
||||
"""
|
||||
return getattr(self._request, attr)
|
|
@ -1,87 +0,0 @@
|
|||
from functools import update_wrapper
|
||||
import inspect
|
||||
from django.utils.decorators import classonlymethod
|
||||
from djanorestframework import views, generics
|
||||
|
||||
|
||||
def wrapped(source, dest):
|
||||
"""
|
||||
Copy public, non-method attributes from source to dest, and return dest.
|
||||
"""
|
||||
for attr in [attr for attr in dir(source)
|
||||
if not attr.startswith('_') and not inspect.ismethod(attr)]:
|
||||
setattr(dest, attr, getattr(source, attr))
|
||||
return dest
|
||||
|
||||
|
||||
class ResourceMixin(object):
|
||||
"""
|
||||
Clone Django's `View.as_view()` behaviour *except* using REST framework's
|
||||
'method -> action' binding for resources.
|
||||
"""
|
||||
|
||||
@classonlymethod
|
||||
def as_view(cls, actions, **initkwargs):
|
||||
"""
|
||||
Main entry point for a request-response process.
|
||||
"""
|
||||
# sanitize keyword arguments
|
||||
for key in initkwargs:
|
||||
if key in cls.http_method_names:
|
||||
raise TypeError("You tried to pass in the %s method name as a "
|
||||
"keyword argument to %s(). Don't do that."
|
||||
% (key, cls.__name__))
|
||||
if not hasattr(cls, key):
|
||||
raise TypeError("%s() received an invalid keyword %r" % (
|
||||
cls.__name__, key))
|
||||
|
||||
def view(request, *args, **kwargs):
|
||||
self = cls(**initkwargs)
|
||||
|
||||
# Bind methods to actions
|
||||
for method, action in actions.items():
|
||||
handler = getattr(self, action)
|
||||
setattr(self, method, handler)
|
||||
|
||||
# As you were, solider.
|
||||
if hasattr(self, 'get') and not hasattr(self, 'head'):
|
||||
self.head = self.get
|
||||
return self.dispatch(request, *args, **kwargs)
|
||||
|
||||
# take name and docstring from class
|
||||
update_wrapper(view, cls, updated=())
|
||||
|
||||
# and possible attributes set by decorators
|
||||
# like csrf_exempt from dispatch
|
||||
update_wrapper(view, cls.dispatch, assigned=())
|
||||
return view
|
||||
|
||||
|
||||
class Resource(ResourceMixin, views.APIView):
|
||||
pass
|
||||
|
||||
|
||||
class ModelResource(ResourceMixin, views.APIView):
|
||||
root_class = generics.RootAPIView
|
||||
detail_class = generics.InstanceAPIView
|
||||
|
||||
def root_view(self):
|
||||
return wrapped(self, self.root_class())
|
||||
|
||||
def detail_view(self):
|
||||
return wrapped(self, self.detail_class())
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
return self.root_view().list(request, args, kwargs)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
return self.root_view().create(request, args, kwargs)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
return self.detail_view().retrieve(request, args, kwargs)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
return self.detail_view().update(request, args, kwargs)
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
return self.detail_view().destroy(request, args, kwargs)
|
|
@ -1,39 +0,0 @@
|
|||
from django.template.response import SimpleTemplateResponse
|
||||
from django.core.handlers.wsgi import STATUS_CODE_TEXT
|
||||
|
||||
|
||||
class Response(SimpleTemplateResponse):
|
||||
"""
|
||||
An HttpResponse that allows it's data to be rendered into
|
||||
arbitrary media types.
|
||||
"""
|
||||
|
||||
def __init__(self, data=None, status=None, headers=None,
|
||||
renderer=None, media_type=None):
|
||||
"""
|
||||
Alters the init arguments slightly.
|
||||
For example, drop 'template_name', and instead use 'data'.
|
||||
|
||||
Setting 'renderer' and 'media_type' will typically be defered,
|
||||
For example being set automatically by the `APIView`.
|
||||
"""
|
||||
super(Response, self).__init__(None, status=status)
|
||||
self.data = data
|
||||
self.headers = headers and headers[:] or []
|
||||
self.renderer = renderer
|
||||
self.media_type = media_type
|
||||
|
||||
@property
|
||||
def rendered_content(self):
|
||||
self['Content-Type'] = self.renderer.media_type
|
||||
if self.data is None:
|
||||
return self.renderer.render()
|
||||
return self.renderer.render(self.data, self.media_type)
|
||||
|
||||
@property
|
||||
def status_text(self):
|
||||
"""
|
||||
Returns reason text corresponding to our HTTP response status code.
|
||||
Provided for convenience.
|
||||
"""
|
||||
return STATUS_CODE_TEXT.get(self.status_code, '')
|
|
@ -1,20 +0,0 @@
|
|||
"""
|
||||
Provide reverse functions that return fully qualified URLs
|
||||
"""
|
||||
from django.core.urlresolvers import reverse as django_reverse
|
||||
from django.utils.functional import lazy
|
||||
|
||||
|
||||
def reverse(viewname, *args, **kwargs):
|
||||
"""
|
||||
Same as `django.core.urlresolvers.reverse`, but optionally takes a request
|
||||
and returns a fully qualified URL, using the request to get the base URL.
|
||||
"""
|
||||
request = kwargs.pop('request', None)
|
||||
url = django_reverse(viewname, *args, **kwargs)
|
||||
if request:
|
||||
return request.build_absolute_uri(url)
|
||||
return url
|
||||
|
||||
|
||||
reverse_lazy = lazy(reverse, str)
|
|
@ -1,66 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
Useful tool to run the test suite for djangorestframework and generate a coverage report.
|
||||
"""
|
||||
|
||||
# http://ericholscher.com/blog/2009/jun/29/enable-setuppy-test-your-django-apps/
|
||||
# http://www.travisswicegood.com/2010/01/17/django-virtualenv-pip-and-fabric/
|
||||
# http://code.djangoproject.com/svn/django/trunk/tests/runtests.py
|
||||
import os
|
||||
import sys
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'djangorestframework.runtests.settings'
|
||||
|
||||
from coverage import coverage
|
||||
|
||||
|
||||
def main():
|
||||
"""Run the tests for djangorestframework and generate a coverage report."""
|
||||
|
||||
cov = coverage()
|
||||
cov.erase()
|
||||
cov.start()
|
||||
|
||||
from django.conf import settings
|
||||
from django.test.utils import get_runner
|
||||
TestRunner = get_runner(settings)
|
||||
|
||||
if hasattr(TestRunner, 'func_name'):
|
||||
# Pre 1.2 test runners were just functions,
|
||||
# and did not support the 'failfast' option.
|
||||
import warnings
|
||||
warnings.warn(
|
||||
'Function-based test runners are deprecated. Test runners should be classes with a run_tests() method.',
|
||||
DeprecationWarning
|
||||
)
|
||||
failures = TestRunner(['djangorestframework'])
|
||||
else:
|
||||
test_runner = TestRunner()
|
||||
failures = test_runner.run_tests(['djangorestframework'])
|
||||
cov.stop()
|
||||
|
||||
# Discover the list of all modules that we should test coverage for
|
||||
import djangorestframework
|
||||
|
||||
project_dir = os.path.dirname(djangorestframework.__file__)
|
||||
cov_files = []
|
||||
|
||||
for (path, dirs, files) in os.walk(project_dir):
|
||||
# Drop tests and runtests directories from the test coverage report
|
||||
if os.path.basename(path) == 'tests' or os.path.basename(path) == 'runtests':
|
||||
continue
|
||||
|
||||
# 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)
|
||||
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)
|
||||
if '--html' in sys.argv:
|
||||
cov.html_report(cov_files, directory='coverage')
|
||||
sys.exit(failures)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,40 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# http://ericholscher.com/blog/2009/jun/29/enable-setuppy-test-your-django-apps/
|
||||
# http://www.travisswicegood.com/2010/01/17/django-virtualenv-pip-and-fabric/
|
||||
# http://code.djangoproject.com/svn/django/trunk/tests/runtests.py
|
||||
import os
|
||||
import sys
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'djangorestframework.runtests.settings'
|
||||
|
||||
from django.conf import settings
|
||||
from django.test.utils import get_runner
|
||||
|
||||
|
||||
def usage():
|
||||
return """
|
||||
Usage: python runtests.py [UnitTestClass].[method]
|
||||
|
||||
You can pass the Class name of the `UnitTestClass` you want to test.
|
||||
|
||||
Append a method name if you only want to test a specific method of that class.
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
TestRunner = get_runner(settings)
|
||||
|
||||
test_runner = TestRunner()
|
||||
if len(sys.argv) == 2:
|
||||
test_case = '.' + sys.argv[1]
|
||||
elif len(sys.argv) == 1:
|
||||
test_case = ''
|
||||
else:
|
||||
print usage()
|
||||
sys.exit(1)
|
||||
failures = test_runner.run_tests(['djangorestframework' + test_case])
|
||||
|
||||
sys.exit(failures)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,118 +0,0 @@
|
|||
# Django settings for testproject project.
|
||||
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
DEBUG_PROPAGATE_EXCEPTIONS = True
|
||||
|
||||
ADMINS = (
|
||||
# ('Your Name', 'your_email@domain.com'),
|
||||
)
|
||||
|
||||
MANAGERS = ADMINS
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
|
||||
'NAME': 'sqlite.db', # Or path to database file if using sqlite3.
|
||||
'USER': '', # Not used with sqlite3.
|
||||
'PASSWORD': '', # Not used with sqlite3.
|
||||
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
|
||||
'PORT': '', # Set to empty string for default. Not used with sqlite3.
|
||||
}
|
||||
}
|
||||
|
||||
# Local time zone for this installation. Choices can be found here:
|
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||
# although not all choices may be available on all operating systems.
|
||||
# On Unix systems, a value of None will cause Django to use the same
|
||||
# timezone as the operating system.
|
||||
# If running in a Windows environment this must be set to the same as your
|
||||
# system time zone.
|
||||
TIME_ZONE = 'Europe/London'
|
||||
|
||||
# Language code for this installation. All choices can be found here:
|
||||
# http://www.i18nguy.com/unicode/language-identifiers.html
|
||||
LANGUAGE_CODE = 'en-uk'
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
# If you set this to False, Django will make some optimizations so as not
|
||||
# to load the internationalization machinery.
|
||||
USE_I18N = True
|
||||
|
||||
# If you set this to False, Django will not format dates, numbers and
|
||||
# calendars according to the current locale
|
||||
USE_L10N = True
|
||||
|
||||
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
||||
# Example: "/home/media/media.lawrence.com/"
|
||||
MEDIA_ROOT = ''
|
||||
|
||||
# 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/"
|
||||
MEDIA_URL = ''
|
||||
|
||||
# Make this unique, and don't share it with anybody.
|
||||
SECRET_KEY = 'u@x-aj9(hoh#rb-^ymf#g2jx_hp0vj7u5#b@ag1n^seu9e!%cy'
|
||||
|
||||
# List of callables that know how to import templates from various sources.
|
||||
TEMPLATE_LOADERS = (
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
# 'django.template.loaders.eggs.Loader',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'urls'
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
|
||||
# Always use forward slashes, even on Windows.
|
||||
# Don't forget to use absolute paths, not relative paths.
|
||||
)
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.messages',
|
||||
# Uncomment the next line to enable the admin:
|
||||
# 'django.contrib.admin',
|
||||
# Uncomment the next line to enable admin documentation:
|
||||
# 'django.contrib.admindocs',
|
||||
'djangorestframework',
|
||||
'djangorestframework.authtoken',
|
||||
)
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
import django
|
||||
|
||||
if django.VERSION < (1, 3):
|
||||
INSTALLED_APPS += ('staticfiles',)
|
||||
|
||||
|
||||
# OAuth support is optional, so we only test oauth if it's installed.
|
||||
try:
|
||||
import oauth_provider
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
INSTALLED_APPS += ('oauth_provider',)
|
||||
|
||||
# If we're running on the Jenkins server we want to archive the coverage reports as XML.
|
||||
import os
|
||||
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'
|
|
@ -1,7 +0,0 @@
|
|||
"""
|
||||
Blank URLConf just to keep runtests.py happy.
|
||||
"""
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('',
|
||||
)
|
|
@ -1,348 +0,0 @@
|
|||
from decimal import Decimal
|
||||
from django.core.serializers.base import DeserializedObject
|
||||
from django.utils.datastructures import SortedDict
|
||||
import copy
|
||||
import datetime
|
||||
import types
|
||||
from djangorestframework.fields import *
|
||||
|
||||
|
||||
class DictWithMetadata(dict):
|
||||
"""
|
||||
A dict-like object, that can have additional properties attached.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class SortedDictWithMetadata(SortedDict, DictWithMetadata):
|
||||
"""
|
||||
A sorted dict-like object, that can have additional properties attached.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class RecursionOccured(BaseException):
|
||||
pass
|
||||
|
||||
|
||||
def _is_protected_type(obj):
|
||||
"""
|
||||
True if the object is a native datatype that does not need to
|
||||
be serialized further.
|
||||
"""
|
||||
return isinstance(obj, (
|
||||
types.NoneType,
|
||||
int, long,
|
||||
datetime.datetime, datetime.date, datetime.time,
|
||||
float, Decimal,
|
||||
basestring)
|
||||
)
|
||||
|
||||
|
||||
def _get_declared_fields(bases, attrs):
|
||||
"""
|
||||
Create a list of serializer field instances from the passed in 'attrs',
|
||||
plus any fields on the base classes (in 'bases').
|
||||
|
||||
Note that all fields from the base classes are used.
|
||||
"""
|
||||
fields = [(field_name, attrs.pop(field_name))
|
||||
for field_name, obj in attrs.items()
|
||||
if isinstance(obj, Field)]
|
||||
fields.sort(key=lambda x: x[1].creation_counter)
|
||||
|
||||
# If this class is subclassing another Serializer, add that Serializer's
|
||||
# fields. Note that we loop over the bases in *reverse*. This is necessary
|
||||
# in order to the correct order of fields.
|
||||
for base in bases[::-1]:
|
||||
if hasattr(base, 'base_fields'):
|
||||
fields = base.base_fields.items() + fields
|
||||
|
||||
return SortedDict(fields)
|
||||
|
||||
|
||||
class SerializerMetaclass(type):
|
||||
def __new__(cls, name, bases, attrs):
|
||||
attrs['base_fields'] = _get_declared_fields(bases, attrs)
|
||||
return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs)
|
||||
|
||||
|
||||
class SerializerOptions(object):
|
||||
"""
|
||||
Meta class options for ModelSerializer
|
||||
"""
|
||||
def __init__(self, meta):
|
||||
self.nested = getattr(meta, 'nested', False)
|
||||
self.fields = getattr(meta, 'fields', ())
|
||||
self.exclude = getattr(meta, 'exclude', ())
|
||||
|
||||
|
||||
class BaseSerializer(Field):
|
||||
class Meta(object):
|
||||
pass
|
||||
|
||||
_options_class = SerializerOptions
|
||||
_dict_class = SortedDictWithMetadata # Set to unsorted dict for backwards compatability with unsorted implementations.
|
||||
|
||||
def __init__(self, data=None, instance=None, context=None, **kwargs):
|
||||
super(BaseSerializer, self).__init__(**kwargs)
|
||||
self.fields = copy.deepcopy(self.base_fields)
|
||||
self.opts = self._options_class(self.Meta)
|
||||
self.parent = None
|
||||
self.root = None
|
||||
|
||||
self.stack = []
|
||||
self.context = context or {}
|
||||
|
||||
self.init_data = data
|
||||
self.instance = instance
|
||||
|
||||
self._data = None
|
||||
self._errors = None
|
||||
|
||||
#####
|
||||
# Methods to determine which fields to use when (de)serializing objects.
|
||||
|
||||
def default_fields(self, serialize, obj=None, data=None, nested=False):
|
||||
"""
|
||||
Return the complete set of default fields for the object, as a dict.
|
||||
"""
|
||||
return {}
|
||||
|
||||
def get_fields(self, serialize, obj=None, data=None, nested=False):
|
||||
"""
|
||||
Returns the complete set of fields for the object as a dict.
|
||||
|
||||
This will be the set of any explicitly declared fields,
|
||||
plus the set of fields returned by get_default_fields().
|
||||
"""
|
||||
ret = SortedDict()
|
||||
|
||||
# Get the explicitly declared fields
|
||||
for key, field in self.fields.items():
|
||||
ret[key] = field
|
||||
# Determine if the declared field corrosponds to a model field.
|
||||
try:
|
||||
if key == 'pk':
|
||||
model_field = obj._meta.pk
|
||||
else:
|
||||
model_field = obj._meta.get_field_by_name(key)[0]
|
||||
except:
|
||||
model_field = None
|
||||
# Set up the field
|
||||
field.initialize(parent=self, model_field=model_field)
|
||||
|
||||
# Add in the default fields
|
||||
fields = self.default_fields(serialize, obj, data, nested)
|
||||
for key, val in fields.items():
|
||||
if key not in ret:
|
||||
ret[key] = val
|
||||
|
||||
# If 'fields' is specified, use those fields, in that order.
|
||||
if self.opts.fields:
|
||||
new = SortedDict()
|
||||
for key in self.opts.fields:
|
||||
new[key] = ret[key]
|
||||
ret = new
|
||||
|
||||
# Remove anything in 'exclude'
|
||||
if self.opts.exclude:
|
||||
for key in self.opts.exclude:
|
||||
ret.pop(key, None)
|
||||
|
||||
return ret
|
||||
|
||||
#####
|
||||
# Field methods - used when the serializer class is itself used as a field.
|
||||
|
||||
def initialize(self, parent, model_field=None):
|
||||
"""
|
||||
Same behaviour as usual Field, except that we need to keep track
|
||||
of state so that we can deal with handling maximum depth and recursion.
|
||||
"""
|
||||
super(BaseSerializer, self).initialize(parent, model_field)
|
||||
self.stack = parent.stack[:]
|
||||
if parent.opts.nested and not isinstance(parent.opts.nested, bool):
|
||||
self.opts.nested = parent.opts.nested - 1
|
||||
else:
|
||||
self.opts.nested = parent.opts.nested
|
||||
|
||||
#####
|
||||
# Methods to convert or revert from objects <--> primative representations.
|
||||
|
||||
def get_field_key(self, field_name):
|
||||
"""
|
||||
Return the key that should be used for a given field.
|
||||
"""
|
||||
return field_name
|
||||
|
||||
def convert_object(self, obj):
|
||||
"""
|
||||
Core of serialization.
|
||||
Convert an object into a dictionary of serialized field values.
|
||||
"""
|
||||
if obj in self.stack and not self.source == '*':
|
||||
raise RecursionOccured()
|
||||
self.stack.append(obj)
|
||||
|
||||
ret = self._dict_class()
|
||||
ret.fields = {}
|
||||
|
||||
fields = self.get_fields(serialize=True, obj=obj, nested=self.opts.nested)
|
||||
for field_name, field in fields.items():
|
||||
key = self.get_field_key(field_name)
|
||||
try:
|
||||
value = field.field_to_native(obj, field_name)
|
||||
except RecursionOccured:
|
||||
field = self.get_fields(serialize=True, obj=obj, nested=False)[field_name]
|
||||
value = field.field_to_native(obj, field_name)
|
||||
ret[key] = value
|
||||
ret.fields[key] = field
|
||||
return ret
|
||||
|
||||
def restore_fields(self, data):
|
||||
"""
|
||||
Core of deserialization, together with `restore_object`.
|
||||
Converts a dictionary of data into a dictionary of deserialized fields.
|
||||
"""
|
||||
fields = self.get_fields(serialize=False, data=data, nested=self.opts.nested)
|
||||
reverted_data = {}
|
||||
for field_name, field in fields.items():
|
||||
try:
|
||||
field.field_from_native(data, field_name, reverted_data)
|
||||
except ValidationError as err:
|
||||
self._errors[field_name] = list(err.messages)
|
||||
|
||||
return reverted_data
|
||||
|
||||
def restore_object(self, attrs, instance=None):
|
||||
"""
|
||||
Deserialize a dictionary of attributes into an object instance.
|
||||
You should override this method to control how deserialized objects
|
||||
are instantiated.
|
||||
"""
|
||||
if instance is not None:
|
||||
instance.update(attrs)
|
||||
return instance
|
||||
return attrs
|
||||
|
||||
def to_native(self, obj):
|
||||
"""
|
||||
Serialize objects -> primatives.
|
||||
"""
|
||||
if isinstance(obj, dict):
|
||||
return dict([(key, self.to_native(val))
|
||||
for (key, val) in obj.items()])
|
||||
elif hasattr(obj, '__iter__'):
|
||||
return (self.to_native(item) for item in obj)
|
||||
return self.convert_object(obj)
|
||||
|
||||
def from_native(self, data):
|
||||
"""
|
||||
Deserialize primatives -> objects.
|
||||
"""
|
||||
if hasattr(data, '__iter__') and not isinstance(data, dict):
|
||||
# TODO: error data when deserializing lists
|
||||
return (self.from_native(item) for item in data)
|
||||
self._errors = {}
|
||||
attrs = self.restore_fields(data)
|
||||
if not self._errors:
|
||||
return self.restore_object(attrs, instance=getattr(self, 'instance', None))
|
||||
|
||||
@property
|
||||
def errors(self):
|
||||
"""
|
||||
Run deserialization and return error data,
|
||||
setting self.object if no errors occured.
|
||||
"""
|
||||
if self._errors is None:
|
||||
obj = self.from_native(self.init_data)
|
||||
if not self._errors:
|
||||
self.object = obj
|
||||
return self._errors
|
||||
|
||||
def is_valid(self):
|
||||
return not self.errors
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
if self._data is None:
|
||||
self._data = self.to_native(self.instance)
|
||||
return self._data
|
||||
|
||||
|
||||
class Serializer(BaseSerializer):
|
||||
__metaclass__ = SerializerMetaclass
|
||||
|
||||
|
||||
class ModelSerializerOptions(SerializerOptions):
|
||||
"""
|
||||
Meta class options for ModelSerializer
|
||||
"""
|
||||
def __init__(self, meta):
|
||||
super(ModelSerializerOptions, self).__init__(meta)
|
||||
self.model = getattr(meta, 'model', None)
|
||||
|
||||
|
||||
class ModelSerializer(RelatedField, Serializer):
|
||||
"""
|
||||
A serializer that deals with model instances and querysets.
|
||||
"""
|
||||
_options_class = ModelSerializerOptions
|
||||
|
||||
def default_fields(self, serialize, obj=None, data=None, nested=False):
|
||||
"""
|
||||
Return all the fields that should be serialized for the model.
|
||||
"""
|
||||
if serialize:
|
||||
cls = obj.__class__
|
||||
else:
|
||||
cls = self.opts.model
|
||||
|
||||
opts = cls._meta.concrete_model._meta
|
||||
pk_field = opts.pk
|
||||
while pk_field.rel:
|
||||
pk_field = pk_field.rel.to._meta.pk
|
||||
fields = [pk_field]
|
||||
fields += [field for field in opts.fields if field.serialize]
|
||||
fields += [field for field in opts.many_to_many if field.serialize]
|
||||
|
||||
ret = SortedDict()
|
||||
for model_field in fields:
|
||||
if model_field.rel and nested:
|
||||
field = self.get_nested_field(model_field)
|
||||
elif model_field.rel:
|
||||
field = self.get_related_field(model_field)
|
||||
else:
|
||||
field = self.get_field(model_field)
|
||||
field.initialize(parent=self, model_field=model_field)
|
||||
ret[model_field.name] = field
|
||||
return ret
|
||||
|
||||
def get_nested_field(self, model_field):
|
||||
"""
|
||||
Creates a default instance of a nested relational field.
|
||||
"""
|
||||
return ModelSerializer()
|
||||
|
||||
def get_related_field(self, model_field):
|
||||
"""
|
||||
Creates a default instance of a flat relational field.
|
||||
"""
|
||||
return PrimaryKeyRelatedField()
|
||||
|
||||
def get_field(self, model_field):
|
||||
"""
|
||||
Creates a default instance of a basic field.
|
||||
"""
|
||||
return Field()
|
||||
|
||||
def restore_object(self, attrs, instance=None):
|
||||
"""
|
||||
Restore the model instance.
|
||||
"""
|
||||
m2m_data = {}
|
||||
for field in self.opts.model._meta.many_to_many:
|
||||
if field.name in attrs:
|
||||
m2m_data[field.name] = attrs.pop(field.name)
|
||||
return DeserializedObject(self.opts.model(**attrs), m2m_data)
|
|
@ -1,125 +0,0 @@
|
|||
"""
|
||||
Settings for REST framework are all namespaced in the API_SETTINGS setting.
|
||||
For example your project's `settings.py` file might look like this:
|
||||
|
||||
API_SETTINGS = {
|
||||
'DEFAULT_RENDERERS': (
|
||||
'djangorestframework.renderers.JSONRenderer',
|
||||
'djangorestframework.renderers.YAMLRenderer',
|
||||
)
|
||||
'DEFAULT_PARSERS': (
|
||||
'djangorestframework.parsers.JSONParser',
|
||||
'djangorestframework.parsers.YAMLParser',
|
||||
)
|
||||
}
|
||||
|
||||
This module provides the `api_setting` object, that is used to access
|
||||
REST framework settings, checking for user settings first, then falling
|
||||
back to the defaults.
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.utils import importlib
|
||||
|
||||
|
||||
DEFAULTS = {
|
||||
'DEFAULT_RENDERERS': (
|
||||
'djangorestframework.renderers.JSONRenderer',
|
||||
'djangorestframework.renderers.JSONPRenderer',
|
||||
'djangorestframework.renderers.DocumentingHTMLRenderer',
|
||||
'djangorestframework.renderers.DocumentingPlainTextRenderer',
|
||||
),
|
||||
'DEFAULT_PARSERS': (
|
||||
'djangorestframework.parsers.JSONParser',
|
||||
'djangorestframework.parsers.FormParser'
|
||||
),
|
||||
'DEFAULT_AUTHENTICATION': (
|
||||
'djangorestframework.authentication.SessionAuthentication',
|
||||
'djangorestframework.authentication.UserBasicAuthentication'
|
||||
),
|
||||
'DEFAULT_PERMISSIONS': (),
|
||||
'DEFAULT_THROTTLES': (),
|
||||
'DEFAULT_CONTENT_NEGOTIATION': 'djangorestframework.negotiation.DefaultContentNegotiation',
|
||||
|
||||
'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
|
||||
'UNAUTHENTICATED_TOKEN': None,
|
||||
|
||||
'FORM_METHOD_OVERRIDE': '_method',
|
||||
'FORM_CONTENT_OVERRIDE': '_content',
|
||||
'FORM_CONTENTTYPE_OVERRIDE': '_content_type',
|
||||
'URL_ACCEPT_OVERRIDE': '_accept',
|
||||
'URL_FORMAT_OVERRIDE': 'format',
|
||||
|
||||
'FORMAT_SUFFIX_KWARG': 'format'
|
||||
}
|
||||
|
||||
|
||||
# List of settings that may be in string import notation.
|
||||
IMPORT_STRINGS = (
|
||||
'DEFAULT_RENDERERS',
|
||||
'DEFAULT_PARSERS',
|
||||
'DEFAULT_AUTHENTICATION',
|
||||
'DEFAULT_PERMISSIONS',
|
||||
'DEFAULT_THROTTLES',
|
||||
'DEFAULT_CONTENT_NEGOTIATION',
|
||||
'UNAUTHENTICATED_USER',
|
||||
'UNAUTHENTICATED_TOKEN',
|
||||
)
|
||||
|
||||
|
||||
def perform_import(val, setting):
|
||||
"""
|
||||
If the given setting is a string import notation,
|
||||
then perform the necessary import or imports.
|
||||
"""
|
||||
if val is None or not setting in IMPORT_STRINGS:
|
||||
return val
|
||||
|
||||
if isinstance(val, basestring):
|
||||
return import_from_string(val, setting)
|
||||
elif isinstance(val, (list, tuple)):
|
||||
return [import_from_string(item, setting) for item in val]
|
||||
return val
|
||||
|
||||
|
||||
def import_from_string(val, setting):
|
||||
"""
|
||||
Attempt to import a class from a string representation.
|
||||
"""
|
||||
try:
|
||||
# Nod to tastypie's use of importlib.
|
||||
parts = val.split('.')
|
||||
module_path, class_name = '.'.join(parts[:-1]), parts[-1]
|
||||
module = importlib.import_module(module_path)
|
||||
return getattr(module, class_name)
|
||||
except:
|
||||
msg = "Could not import '%s' for API setting '%s'" % (val, setting)
|
||||
raise ImportError(msg)
|
||||
|
||||
|
||||
class APISettings(object):
|
||||
"""
|
||||
A settings object, that allows API settings to be accessed as properties.
|
||||
For example:
|
||||
|
||||
from djangorestframework.settings import api_settings
|
||||
print api_settings.DEFAULT_RENDERERS
|
||||
|
||||
Any setting with string import paths will be automatically resolved
|
||||
and return the class, rather than the string literal.
|
||||
"""
|
||||
def __getattr__(self, attr):
|
||||
if attr not in DEFAULTS.keys():
|
||||
raise AttributeError("Invalid API setting: '%s'" % attr)
|
||||
|
||||
try:
|
||||
# Check if present in user settings
|
||||
val = perform_import(settings.API_SETTINGS[attr], attr)
|
||||
except (AttributeError, KeyError):
|
||||
# Fall back to defaults
|
||||
val = perform_import(DEFAULTS[attr], attr)
|
||||
|
||||
# Cache the result
|
||||
setattr(self, attr, val)
|
||||
return val
|
||||
|
||||
api_settings = APISettings()
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
|
||||
This CSS file contains some tweaks specific to the included Bootstrap theme.
|
||||
It's separate from `style.css` so that it can be easily overridden by replacing
|
||||
a single block in the template.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
.form-actions {
|
||||
background: transparent;
|
||||
border-top-color: transparent;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.navbar-inverse .brand a {
|
||||
color: #999;
|
||||
}
|
||||
.navbar-inverse .brand:hover a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
.com { color: #93a1a1; }
|
||||
.lit { color: #195f91; }
|
||||
.pun, .opn, .clo { color: #93a1a1; }
|
||||
.fun { color: #dc322f; }
|
||||
.str, .atv { color: #D14; }
|
||||
.kwd, .prettyprint .tag { color: #1e347b; }
|
||||
.typ, .atn, .dec, .var { color: teal; }
|
||||
.pln { color: #48484c; }
|
||||
|
||||
.prettyprint {
|
||||
padding: 8px;
|
||||
background-color: #f7f7f9;
|
||||
border: 1px solid #e1e1e8;
|
||||
}
|
||||
.prettyprint.linenums {
|
||||
-webkit-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
|
||||
-moz-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
|
||||
box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
|
||||
}
|
||||
|
||||
/* Specify class=linenums on a pre to get line numbering */
|
||||
ol.linenums {
|
||||
margin: 0 0 0 33px; /* IE indents via margin-left */
|
||||
}
|
||||
ol.linenums li {
|
||||
padding-left: 12px;
|
||||
color: #bebec5;
|
||||
line-height: 20px;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
body {
|
||||
padding-top: 0;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
/* The navbar is fixed at >= 980px wide, so add padding to the body to prevent
|
||||
content running up underneath it. */
|
||||
@media (min-width:980px) {
|
||||
body {
|
||||
padding-top: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h2, h3 {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.resource-description, .response-info {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
#footer {
|
||||
border-top: 1px solid #eee;
|
||||
margin-top: 2em;
|
||||
padding-top: 1em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.version:before {
|
||||
content: "v";
|
||||
opacity: 0.6;
|
||||
padding-right: 0.25em;
|
||||
}
|
||||
|
||||
.format-option {
|
||||
font-family: Menlo, Consolas, "Andale Mono", "Lucida Console", monospace;
|
||||
}
|
||||
|
||||
#options-form {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* To allow tooltips to work on disabled elements */
|
||||
.disabled-tooltip-shield {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#options-form {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.errorlist {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
word-wrap: normal;
|
||||
white-space: pre;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0px;
|
||||
}
|
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 12 KiB |
|
@ -1,28 +0,0 @@
|
|||
var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
|
||||
(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
|
||||
[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
|
||||
f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
|
||||
(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
|
||||
{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
|
||||
t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
|
||||
"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
|
||||
l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
|
||||
q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
|
||||
q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
|
||||
"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
|
||||
a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
|
||||
for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
|
||||
m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
|
||||
a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
|
||||
j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
|
||||
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
|
||||
H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
|
||||
J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
|
||||
I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
|
||||
["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
|
||||
/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
|
||||
["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
|
||||
hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
|
||||
!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
|
||||
250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
|
||||
PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();
|
|
@ -1,52 +0,0 @@
|
|||
"""
|
||||
Descriptive HTTP status codes, for code readability.
|
||||
|
||||
See RFC 2616 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
||||
And RFC 6585 - http://tools.ietf.org/html/rfc6585
|
||||
"""
|
||||
|
||||
HTTP_100_CONTINUE = 100
|
||||
HTTP_101_SWITCHING_PROTOCOLS = 101
|
||||
HTTP_200_OK = 200
|
||||
HTTP_201_CREATED = 201
|
||||
HTTP_202_ACCEPTED = 202
|
||||
HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203
|
||||
HTTP_204_NO_CONTENT = 204
|
||||
HTTP_205_RESET_CONTENT = 205
|
||||
HTTP_206_PARTIAL_CONTENT = 206
|
||||
HTTP_300_MULTIPLE_CHOICES = 300
|
||||
HTTP_301_MOVED_PERMANENTLY = 301
|
||||
HTTP_302_FOUND = 302
|
||||
HTTP_303_SEE_OTHER = 303
|
||||
HTTP_304_NOT_MODIFIED = 304
|
||||
HTTP_305_USE_PROXY = 305
|
||||
HTTP_306_RESERVED = 306
|
||||
HTTP_307_TEMPORARY_REDIRECT = 307
|
||||
HTTP_400_BAD_REQUEST = 400
|
||||
HTTP_401_UNAUTHORIZED = 401
|
||||
HTTP_402_PAYMENT_REQUIRED = 402
|
||||
HTTP_403_FORBIDDEN = 403
|
||||
HTTP_404_NOT_FOUND = 404
|
||||
HTTP_405_METHOD_NOT_ALLOWED = 405
|
||||
HTTP_406_NOT_ACCEPTABLE = 406
|
||||
HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407
|
||||
HTTP_408_REQUEST_TIMEOUT = 408
|
||||
HTTP_409_CONFLICT = 409
|
||||
HTTP_410_GONE = 410
|
||||
HTTP_411_LENGTH_REQUIRED = 411
|
||||
HTTP_412_PRECONDITION_FAILED = 412
|
||||
HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413
|
||||
HTTP_414_REQUEST_URI_TOO_LONG = 414
|
||||
HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415
|
||||
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416
|
||||
HTTP_417_EXPECTATION_FAILED = 417
|
||||
HTTP_428_PRECONDITION_REQUIRED = 428
|
||||
HTTP_429_TOO_MANY_REQUESTS = 429
|
||||
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431
|
||||
HTTP_500_INTERNAL_SERVER_ERROR = 500
|
||||
HTTP_501_NOT_IMPLEMENTED = 501
|
||||
HTTP_502_BAD_GATEWAY = 502
|
||||
HTTP_503_SERVICE_UNAVAILABLE = 503
|
||||
HTTP_504_GATEWAY_TIMEOUT = 504
|
||||
HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505
|
||||
HTTP_511_NETWORD_AUTHENTICATION_REQUIRED = 511
|
|
@ -1,3 +0,0 @@
|
|||
{% extends "djangorestframework/base.html" %}
|
||||
|
||||
{# Override this template in your own templates directory to customize #}
|
|
@ -1,8 +0,0 @@
|
|||
{% autoescape off %}{{ name }}
|
||||
|
||||
{{ description }}
|
||||
|
||||
HTTP {{ response.status_code }} {{ response.status_text }}
|
||||
{% for key, val in response.headers.items %}{{ key }}: {{ val }}
|
||||
{% endfor %}
|
||||
{{ content }}{% endautoescape %}
|
|
@ -1,214 +0,0 @@
|
|||
{% load url from future %}
|
||||
{% load urlize_quoted_links %}
|
||||
{% load add_query_param %}
|
||||
{% load add_class %}
|
||||
{% load optional_login %}
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
|
||||
{% block bootstrap_theme %}
|
||||
<link rel="stylesheet" type="text/css" href="{% get_static_prefix %}djangorestframework/css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="{% get_static_prefix %}djangorestframework/css/bootstrap-tweaks.css"/>
|
||||
{% endblock %}
|
||||
<link rel="stylesheet" type="text/css" href='{% get_static_prefix %}djangorestframework/css/prettify.css'/>
|
||||
<link rel="stylesheet" type="text/css" href='{% get_static_prefix %}djangorestframework/css/style.css'/>
|
||||
{% block extrastyle %}{% endblock %}
|
||||
|
||||
<title>{% block title %}Django REST framework - {{ name }}{% endblock %}</title>
|
||||
|
||||
{% block extrahead %}{% endblock %}
|
||||
|
||||
{% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE" />{% endblock %}
|
||||
|
||||
</head>
|
||||
|
||||
<body class="{% block bodyclass %}{% endblock %} container">
|
||||
|
||||
<div class="navbar navbar-fixed-top {% block bootstrap_navbar_variant %}navbar-inverse{% endblock %}">
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<span class="brand" href="/">
|
||||
{% block branding %}<a href='http://django-rest-framework.org'>Django REST framework <span class="version">{{ version }}</span></a>{% endblock %}
|
||||
</span>
|
||||
<ul class="nav pull-right">
|
||||
{% block userlinks %}
|
||||
{% if user.is_active %}
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
Welcome, {{ user }}
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li>{% optional_logout %}</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>{% optional_login %}</li>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block global_heading %}{% endblock %}
|
||||
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<ul class="breadcrumb">
|
||||
|
||||
{% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
|
||||
<li>
|
||||
<a href="{{ breadcrumb_url }}" {% if forloop.last %}class="active"{% endif %}>{{ breadcrumb_name }}</a> {% if not forloop.last %}<span class="divider">›</span>{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
<!-- Content -->
|
||||
<div id="content">
|
||||
|
||||
{% if 'GET' in allowed_methods %}
|
||||
<form id="get-form" class="pull-right">
|
||||
<fieldset>
|
||||
<div class="btn-group format-selection">
|
||||
<a class="btn btn-primary js-tooltip" href='{{ request.get_full_path }}' rel="nofollow" title="Do a GET request on the {{ name }} resource">GET</a>
|
||||
|
||||
<button class="btn btn-primary dropdown-toggle js-tooltip" data-toggle="dropdown" title="Specify a format for the GET request">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{% for format in available_formats %}
|
||||
{% with FORMAT_PARAM|add:"="|add:format as param %}
|
||||
<li>
|
||||
<a class="js-tooltip format-option" href='{{ request.get_full_path|add_query_param:param }}' rel="nofollow" title="Do a GET request on the {{ name }} resource with the format set to `{{ format }}`">{{ format }}</a>
|
||||
</li>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if api_settings.FORM_METHOD_OVERRIDE %}
|
||||
<form id="options-form" action="{{ request.get_full_path }}" method="post" class="pull-right">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="OPTIONS" />
|
||||
<button class="btn btn-info js-tooltip" {% if 'OPTIONS' in allowed_methods %} title="Do an OPTIONS request on the {{ name }} resource"{% else %} disabled{% endif %}>OPTIONS</button>
|
||||
{% if not 'OPTIONS' in allowed_methods %}
|
||||
<div class="js-tooltip disabled-tooltip-shield" title="OPTIONS request not allowed for resource {{ name }}"></div>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<div class="content-main">
|
||||
<div class="page-header"><h1>{{ name }}</h1></div>
|
||||
<p class="resource-description">{{ description }}</p>
|
||||
|
||||
<div class="request-info">
|
||||
<pre class="prettyprint"><b>{{ request.method }}</b> {{ request.get_full_path }}</pre>
|
||||
<div>
|
||||
<div class="response-info">
|
||||
<pre class="prettyprint"><div class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %}
|
||||
{% for key, val in response.items %}<b>{{ key }}:</b> <span class="lit">{{ val|urlize_quoted_links }}</span>
|
||||
{% endfor %}
|
||||
</div>{{ content|urlize_quoted_links }}</pre>{% endautoescape %}
|
||||
</div>
|
||||
|
||||
{% if response.status_code != 403 %}
|
||||
|
||||
{% if 'POST' in allowed_methods %}
|
||||
<form action="{{ request.get_full_path }}" method="POST" {% if post_form.is_multipart %}enctype="multipart/form-data"{% endif %} class="form-horizontal">
|
||||
<fieldset>
|
||||
<h2>POST: {{ name }}</h2>
|
||||
{% csrf_token %}
|
||||
{{ post_form.non_field_errors }}
|
||||
{% for field in post_form %}
|
||||
<div class="control-group {% if field.errors %}error{% endif %}">
|
||||
{{ field.label_tag|add_class:"control-label" }}
|
||||
<div class="controls">
|
||||
{{ field }}
|
||||
<span class="help-inline">{{ field.help_text }}</span>
|
||||
{{ field.errors|add_class:"help-block" }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-primary" title="Do a POST request on the {{ name }} resource">POST</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if 'PUT' in allowed_methods and api_settings.FORM_METHOD_OVERRIDE %}
|
||||
<form action="{{ request.get_full_path }}" method="POST" {% if put_form.is_multipart %}enctype="multipart/form-data"{% endif %} class="form-horizontal">
|
||||
<fieldset>
|
||||
<h2>PUT: {{ name }}</h2>
|
||||
<input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="PUT" />
|
||||
{% csrf_token %}
|
||||
{{ put_form.non_field_errors }}
|
||||
{% for field in put_form %}
|
||||
<div class="control-group {% if field.errors %}error{% endif %}">
|
||||
{{ field.label_tag|add_class:"control-label" }}
|
||||
<div class="controls">
|
||||
{{ field }}
|
||||
<span class='help-inline'>{{ field.help_text }}</span>
|
||||
{{ field.errors|add_class:"help-block" }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-primary js-tooltip" title="Do a PUT request on the {{ name }} resource">PUT</button>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if 'DELETE' in allowed_methods and api_settings.FORM_METHOD_OVERRIDE %}
|
||||
<form action="{{ request.get_full_path }}" method="POST" class="form-horizontal">
|
||||
<fieldset>
|
||||
<h2>DELETE: {{ name }}</h2>
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="DELETE" />
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-danger js-tooltip" title="Do a DELETE request on the {{ name }} resource">DELETE</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<!-- END content-main -->
|
||||
|
||||
</div>
|
||||
<!-- END Content -->
|
||||
|
||||
<div id="footer">
|
||||
{% block footer %}
|
||||
<a class="powered-by" href='http://django-rest-framework.org'>Django REST framework</a> <span class="version">{{ version }}</span>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
<script src="{% get_static_prefix %}djangorestframework/js/jquery-1.8.1-min.js"></script>
|
||||
<script src="{% get_static_prefix %}djangorestframework/js/bootstrap.min.js"></script>
|
||||
<script src="{% get_static_prefix %}djangorestframework/js/prettify-min.js"></script>
|
||||
<script>
|
||||
prettyPrint();
|
||||
|
||||
$('.js-tooltip').tooltip({
|
||||
delay: 1000
|
||||
});
|
||||
</script>
|
||||
|
||||
{% block extrabody %}{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,45 +0,0 @@
|
|||
{% load url from future %}
|
||||
{% load static %}
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href='{% get_static_prefix %}djangorestframework/css/style.css'/>
|
||||
</head>
|
||||
|
||||
<body class="login">
|
||||
|
||||
<div id="container">
|
||||
|
||||
<div id="header">
|
||||
<div id="branding">
|
||||
<h1 id="site-name">Django REST framework</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="content" class="colM">
|
||||
<div id="content-main">
|
||||
<form method="post" action="{% url 'djangorestframework:login' %}" id="login-form">
|
||||
{% csrf_token %}
|
||||
<div class="form-row">
|
||||
<label for="id_username">Username:</label> {{ form.username }}
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="id_password">Password:</label> {{ form.password }}
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label><input type="submit" value="Log in">
|
||||
</div>
|
||||
</form>
|
||||
<script type="text/javascript">
|
||||
document.getElementById('id_username').focus()
|
||||
</script>
|
||||
</div>
|
||||
<br class="clear">
|
||||
</div>
|
||||
|
||||
<div id="footer"></div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,40 +0,0 @@
|
|||
"""
|
||||
From http://stackoverflow.com/questions/4124220/django-adding-css-classes-when-rendering-form-fields-in-a-template
|
||||
|
||||
The add_class filter allows for inserting classes into template variables that
|
||||
contain HTML tags, useful for modifying forms without needing to change the
|
||||
Form objects.
|
||||
|
||||
To use:
|
||||
|
||||
{{ field.label_tag|add_class:"control-label" }}
|
||||
|
||||
will insert the class `controls-label` into the label tag generated by a form.
|
||||
|
||||
In the case of Django REST Framework, the filter is used to add Bootstrap-specific
|
||||
classes to the forms, while still allowing non-Bootstrap customization of the
|
||||
browsable API.
|
||||
"""
|
||||
|
||||
import re
|
||||
from django.utils.safestring import mark_safe
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
class_re = re.compile(r'(?<=class=["\'])(.*)(?=["\'])')
|
||||
|
||||
@register.filter
|
||||
def add_class(value, css_class):
|
||||
string = unicode(value)
|
||||
match = class_re.search(string)
|
||||
if match:
|
||||
m = re.search(r'^%s$|^%s\s|\s%s\s|\s%s$' % (css_class, css_class,
|
||||
css_class, css_class),
|
||||
match.group(1))
|
||||
print match.group(1)
|
||||
if not m:
|
||||
return mark_safe(class_re.sub(match.group(1) + " " + css_class,
|
||||
string))
|
||||
else:
|
||||
return mark_safe(string.replace('>', ' class="%s">' % css_class, 1))
|
||||
return value
|
|
@ -1,20 +0,0 @@
|
|||
from django.http import QueryDict
|
||||
from django.template import Library
|
||||
from urlparse import urlparse, urlunparse
|
||||
register = Library()
|
||||
|
||||
|
||||
def replace_query_param(url, key, val):
|
||||
(scheme, netloc, path, params, query, fragment) = urlparse(url)
|
||||
query_dict = QueryDict(query).copy()
|
||||
query_dict[key] = val
|
||||
query = query_dict.urlencode()
|
||||
return urlunparse((scheme, netloc, path, params, query, fragment))
|
||||
|
||||
|
||||
def add_query_param(url, param):
|
||||
key, val = param.split('=')
|
||||
return replace_query_param(url, key, val)
|
||||
|
||||
|
||||
register.filter('add_query_param', add_query_param)
|
|
@ -1,32 +0,0 @@
|
|||
"""
|
||||
Tags to optionally include the login and logout links, depending on if the
|
||||
login and logout views are in the urlconf.
|
||||
"""
|
||||
from django import template
|
||||
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def optional_login(context):
|
||||
try:
|
||||
login_url = reverse('djangorestframework:login')
|
||||
except NoReverseMatch:
|
||||
return ''
|
||||
|
||||
request = context['request']
|
||||
snippet = "<a href='%s?next=%s'>Log in</a>" % (login_url, request.path)
|
||||
return snippet
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def optional_logout(context):
|
||||
try:
|
||||
logout_url = reverse('djangorestframework:logout')
|
||||
except NoReverseMatch:
|
||||
return ''
|
||||
|
||||
request = context['request']
|
||||
snippet = "<a href='%s?next=%s'>Log out</a>" % (logout_url, request.path)
|
||||
return snippet
|
|
@ -1,102 +0,0 @@
|
|||
"""
|
||||
Adds the custom filter 'urlize_quoted_links'
|
||||
|
||||
This is identical to the built-in filter 'urlize' with the exception that
|
||||
single and double quotes are permitted as leading or trailing punctuation.
|
||||
|
||||
Almost all of this code is copied verbatim from django.utils.html
|
||||
LEADING_PUNCTUATION and TRAILING_PUNCTUATION have been modified
|
||||
"""
|
||||
|
||||
import re
|
||||
import string
|
||||
|
||||
from django.utils.safestring import SafeData, mark_safe
|
||||
from django.utils.encoding import force_unicode
|
||||
from django.utils.html import escape
|
||||
from django import template
|
||||
|
||||
# Configuration for urlize() function.
|
||||
LEADING_PUNCTUATION = ['(', '<', '<', '"', "'"]
|
||||
TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '>', '"', "'"]
|
||||
|
||||
# List of possible strings used for bullets in bulleted lists.
|
||||
DOTS = ['·', '*', '\xe2\x80\xa2', '•', '•', '•']
|
||||
|
||||
unencoded_ampersands_re = re.compile(r'&(?!(\w+|#\d+);)')
|
||||
word_split_re = re.compile(r'(\s+)')
|
||||
punctuation_re = re.compile('^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % \
|
||||
('|'.join([re.escape(x) for x in LEADING_PUNCTUATION]),
|
||||
'|'.join([re.escape(x) for x in TRAILING_PUNCTUATION])))
|
||||
simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
|
||||
link_target_attribute_re = re.compile(r'(<a [^>]*?)target=[^\s>]+')
|
||||
html_gunk_re = re.compile(r'(?:<br clear="all">|<i><\/i>|<b><\/b>|<em><\/em>|<strong><\/strong>|<\/?smallcaps>|<\/?uppercase>)', re.IGNORECASE)
|
||||
hard_coded_bullets_re = re.compile(r'((?:<p>(?:%s).*?[a-zA-Z].*?</p>\s*)+)' % '|'.join([re.escape(x) for x in DOTS]), re.DOTALL)
|
||||
trailing_empty_content_re = re.compile(r'(?:<p>(?: |\s|<br \/>)*?</p>\s*)+\Z')
|
||||
|
||||
|
||||
def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=True):
|
||||
"""
|
||||
Converts any URLs in text into clickable links.
|
||||
|
||||
Works on http://, https://, www. links and links ending in .org, .net or
|
||||
.com. Links can have trailing punctuation (periods, commas, close-parens)
|
||||
and leading punctuation (opening parens) and it'll still do the right
|
||||
thing.
|
||||
|
||||
If trim_url_limit is not None, the URLs in link text longer than this limit
|
||||
will truncated to trim_url_limit-3 characters and appended with an elipsis.
|
||||
|
||||
If nofollow is True, the URLs in link text will get a rel="nofollow"
|
||||
attribute.
|
||||
|
||||
If autoescape is True, the link text and URLs will get autoescaped.
|
||||
"""
|
||||
trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
|
||||
safe_input = isinstance(text, SafeData)
|
||||
words = word_split_re.split(force_unicode(text))
|
||||
nofollow_attr = nofollow and ' rel="nofollow"' or ''
|
||||
for i, word in enumerate(words):
|
||||
match = None
|
||||
if '.' in word or '@' in word or ':' in word:
|
||||
match = punctuation_re.match(word)
|
||||
if match:
|
||||
lead, middle, trail = match.groups()
|
||||
# Make URL we want to point to.
|
||||
url = None
|
||||
if middle.startswith('http://') or middle.startswith('https://'):
|
||||
url = middle
|
||||
elif middle.startswith('www.') or ('@' not in middle and \
|
||||
middle and middle[0] in string.ascii_letters + string.digits and \
|
||||
(middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))):
|
||||
url = 'http://%s' % middle
|
||||
elif '@' in middle and not ':' in middle and simple_email_re.match(middle):
|
||||
url = 'mailto:%s' % middle
|
||||
nofollow_attr = ''
|
||||
# Make link.
|
||||
if url:
|
||||
trimmed = trim_url(middle)
|
||||
if autoescape and not safe_input:
|
||||
lead, trail = escape(lead), escape(trail)
|
||||
url, trimmed = escape(url), escape(trimmed)
|
||||
middle = '<a href="%s"%s>%s</a>' % (url, nofollow_attr, trimmed)
|
||||
words[i] = mark_safe('%s%s%s' % (lead, middle, trail))
|
||||
else:
|
||||
if safe_input:
|
||||
words[i] = mark_safe(word)
|
||||
elif autoescape:
|
||||
words[i] = escape(word)
|
||||
elif safe_input:
|
||||
words[i] = mark_safe(word)
|
||||
elif autoescape:
|
||||
words[i] = escape(word)
|
||||
return u''.join(words)
|
||||
|
||||
|
||||
#urlize_quoted_links.needs_autoescape = True
|
||||
urlize_quoted_links.is_safe = True
|
||||
|
||||
# Register urlize_quoted_links as a custom filter
|
||||
# http://docs.djangoproject.com/en/dev/howto/custom-template-tags/
|
||||
register = template.Library()
|
||||
register.filter(urlize_quoted_links)
|
|
@ -1,12 +0,0 @@
|
|||
"""Force import of all modules in this package in order to get the standard test runner to pick up the tests. Yowzers."""
|
||||
import os
|
||||
|
||||
modules = [filename.rsplit('.', 1)[0]
|
||||
for filename in os.listdir(os.path.dirname(__file__))
|
||||
if filename.endswith('.py') and not filename.startswith('_')]
|
||||
__test__ = dict()
|
||||
|
||||
for module in modules:
|
||||
exec("from djangorestframework.tests.%s import __doc__ as module_doc" % module)
|
||||
exec("from djangorestframework.tests.%s import *" % module)
|
||||
__test__[module] = module_doc or ""
|
|
@ -1,153 +0,0 @@
|
|||
from django.conf.urls.defaults import patterns
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from django.utils import simplejson as json
|
||||
from django.http import HttpResponse
|
||||
|
||||
from djangorestframework.views import APIView
|
||||
from djangorestframework import permissions
|
||||
|
||||
from djangorestframework.authtoken.models import Token
|
||||
from djangorestframework.authentication import TokenAuthentication
|
||||
|
||||
import base64
|
||||
|
||||
|
||||
class MockView(APIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
|
||||
def post(self, request):
|
||||
return HttpResponse({'a': 1, 'b': 2, 'c': 3})
|
||||
|
||||
def put(self, request):
|
||||
return HttpResponse({'a': 1, 'b': 2, 'c': 3})
|
||||
|
||||
MockView.authentication_classes += (TokenAuthentication,)
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^$', MockView.as_view()),
|
||||
)
|
||||
|
||||
|
||||
class BasicAuthTests(TestCase):
|
||||
"""Basic authentication"""
|
||||
urls = 'djangorestframework.tests.authentication'
|
||||
|
||||
def setUp(self):
|
||||
self.csrf_client = Client(enforce_csrf_checks=True)
|
||||
self.username = 'john'
|
||||
self.email = 'lennon@thebeatles.com'
|
||||
self.password = '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"""
|
||||
auth = 'Basic %s' % base64.encodestring('%s:%s' % (self.username, self.password)).strip()
|
||||
response = self.csrf_client.post('/', {'example': 'example'}, HTTP_AUTHORIZATION=auth)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_post_json_passing_basic_auth(self):
|
||||
"""Ensure POSTing form over basic auth with correct credentials passes and does not require CSRF"""
|
||||
auth = 'Basic %s' % base64.encodestring('%s:%s' % (self.username, self.password)).strip()
|
||||
response = self.csrf_client.post('/', json.dumps({'example': 'example'}), 'application/json', HTTP_AUTHORIZATION=auth)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_post_form_failing_basic_auth(self):
|
||||
"""Ensure POSTing form over basic auth without correct credentials fails"""
|
||||
response = self.csrf_client.post('/', {'example': 'example'})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_post_json_failing_basic_auth(self):
|
||||
"""Ensure POSTing json over basic auth without correct credentials fails"""
|
||||
response = self.csrf_client.post('/', json.dumps({'example': 'example'}), 'application/json')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
|
||||
class SessionAuthTests(TestCase):
|
||||
"""User session authentication"""
|
||||
urls = 'djangorestframework.tests.authentication'
|
||||
|
||||
def setUp(self):
|
||||
self.csrf_client = Client(enforce_csrf_checks=True)
|
||||
self.non_csrf_client = Client(enforce_csrf_checks=False)
|
||||
self.username = 'john'
|
||||
self.email = 'lennon@thebeatles.com'
|
||||
self.password = 'password'
|
||||
self.user = User.objects.create_user(self.username, self.email, self.password)
|
||||
|
||||
def tearDown(self):
|
||||
self.csrf_client.logout()
|
||||
|
||||
def test_post_form_session_auth_failing_csrf(self):
|
||||
"""
|
||||
Ensure POSTing form over session authentication without CSRF token fails.
|
||||
"""
|
||||
self.csrf_client.login(username=self.username, password=self.password)
|
||||
response = self.csrf_client.post('/', {'example': 'example'})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_post_form_session_auth_passing(self):
|
||||
"""
|
||||
Ensure POSTing form over session authentication with logged in user and CSRF token passes.
|
||||
"""
|
||||
self.non_csrf_client.login(username=self.username, password=self.password)
|
||||
response = self.non_csrf_client.post('/', {'example': 'example'})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_put_form_session_auth_passing(self):
|
||||
"""
|
||||
Ensure PUTting form over session authentication with logged in user and CSRF token passes.
|
||||
"""
|
||||
self.non_csrf_client.login(username=self.username, password=self.password)
|
||||
response = self.non_csrf_client.put('/', {'example': 'example'})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_post_form_session_auth_failing(self):
|
||||
"""
|
||||
Ensure POSTing form over session authentication without logged in user fails.
|
||||
"""
|
||||
response = self.csrf_client.post('/', {'example': 'example'})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
|
||||
class TokenAuthTests(TestCase):
|
||||
"""Token authentication"""
|
||||
urls = 'djangorestframework.tests.authentication'
|
||||
|
||||
def setUp(self):
|
||||
self.csrf_client = Client(enforce_csrf_checks=True)
|
||||
self.username = 'john'
|
||||
self.email = 'lennon@thebeatles.com'
|
||||
self.password = 'password'
|
||||
self.user = User.objects.create_user(self.username, self.email, self.password)
|
||||
|
||||
self.key = 'abcd1234'
|
||||
self.token = Token.objects.create(key=self.key, user=self.user)
|
||||
|
||||
def test_post_form_passing_token_auth(self):
|
||||
"""Ensure POSTing json over token auth with correct credentials passes and does not require CSRF"""
|
||||
auth = "Token " + self.key
|
||||
response = self.csrf_client.post('/', {'example': 'example'}, HTTP_AUTHORIZATION=auth)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_post_json_passing_token_auth(self):
|
||||
"""Ensure POSTing form over token auth with correct credentials passes and does not require CSRF"""
|
||||
auth = "Token " + self.key
|
||||
response = self.csrf_client.post('/', json.dumps({'example': 'example'}), 'application/json', HTTP_AUTHORIZATION=auth)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_post_form_failing_token_auth(self):
|
||||
"""Ensure POSTing form over token auth without correct credentials fails"""
|
||||
response = self.csrf_client.post('/', {'example': 'example'})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_post_json_failing_token_auth(self):
|
||||
"""Ensure POSTing json over token auth without correct credentials fails"""
|
||||
response = self.csrf_client.post('/', json.dumps({'example': 'example'}), 'application/json')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_token_has_auto_assigned_key_if_none_provided(self):
|
||||
"""Ensure creating a token with no key will auto-assign a key"""
|
||||
token = Token.objects.create(user=self.user)
|
||||
self.assertTrue(bool(token.key))
|
|
@ -1,72 +0,0 @@
|
|||
from django.conf.urls.defaults import patterns, url
|
||||
from django.test import TestCase
|
||||
from djangorestframework.utils.breadcrumbs import get_breadcrumbs
|
||||
from djangorestframework.views import APIView
|
||||
|
||||
|
||||
class Root(APIView):
|
||||
pass
|
||||
|
||||
|
||||
class ResourceRoot(APIView):
|
||||
pass
|
||||
|
||||
|
||||
class ResourceInstance(APIView):
|
||||
pass
|
||||
|
||||
|
||||
class NestedResourceRoot(APIView):
|
||||
pass
|
||||
|
||||
|
||||
class NestedResourceInstance(APIView):
|
||||
pass
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', Root.as_view()),
|
||||
url(r'^resource/$', ResourceRoot.as_view()),
|
||||
url(r'^resource/(?P<key>[0-9]+)$', ResourceInstance.as_view()),
|
||||
url(r'^resource/(?P<key>[0-9]+)/$', NestedResourceRoot.as_view()),
|
||||
url(r'^resource/(?P<key>[0-9]+)/(?P<other>[A-Za-z]+)$', NestedResourceInstance.as_view()),
|
||||
)
|
||||
|
||||
|
||||
class BreadcrumbTests(TestCase):
|
||||
"""Tests the breadcrumb functionality used by the HTML renderer."""
|
||||
|
||||
urls = 'djangorestframework.tests.breadcrumbs'
|
||||
|
||||
def test_root_breadcrumbs(self):
|
||||
url = '/'
|
||||
self.assertEqual(get_breadcrumbs(url), [('Root', '/')])
|
||||
|
||||
def test_resource_root_breadcrumbs(self):
|
||||
url = '/resource/'
|
||||
self.assertEqual(get_breadcrumbs(url), [('Root', '/'),
|
||||
('Resource Root', '/resource/')])
|
||||
|
||||
def test_resource_instance_breadcrumbs(self):
|
||||
url = '/resource/123'
|
||||
self.assertEqual(get_breadcrumbs(url), [('Root', '/'),
|
||||
('Resource Root', '/resource/'),
|
||||
('Resource Instance', '/resource/123')])
|
||||
|
||||
def test_nested_resource_breadcrumbs(self):
|
||||
url = '/resource/123/'
|
||||
self.assertEqual(get_breadcrumbs(url), [('Root', '/'),
|
||||
('Resource Root', '/resource/'),
|
||||
('Resource Instance', '/resource/123'),
|
||||
('Nested Resource Root', '/resource/123/')])
|
||||
|
||||
def test_nested_resource_instance_breadcrumbs(self):
|
||||
url = '/resource/123/abc'
|
||||
self.assertEqual(get_breadcrumbs(url), [('Root', '/'),
|
||||
('Resource Root', '/resource/'),
|
||||
('Resource Instance', '/resource/123'),
|
||||
('Nested Resource Root', '/resource/123/'),
|
||||
('Nested Resource Instance', '/resource/123/abc')])
|
||||
|
||||
def test_broken_url_breadcrumbs_handled_gracefully(self):
|
||||
url = '/foobar'
|
||||
self.assertEqual(get_breadcrumbs(url), [('Root', '/')])
|
|
@ -1,113 +0,0 @@
|
|||
from django.test import TestCase
|
||||
from djangorestframework.views import APIView
|
||||
from djangorestframework.compat import apply_markdown
|
||||
|
||||
# We check that docstrings get nicely un-indented.
|
||||
DESCRIPTION = """an example docstring
|
||||
====================
|
||||
|
||||
* list
|
||||
* list
|
||||
|
||||
another header
|
||||
--------------
|
||||
|
||||
code block
|
||||
|
||||
indented
|
||||
|
||||
# hash style header #"""
|
||||
|
||||
# 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 = """<h2>an example docstring</h2>
|
||||
<ul>
|
||||
<li>list</li>
|
||||
<li>list</li>
|
||||
</ul>
|
||||
<h3>another header</h3>
|
||||
<pre><code>code block
|
||||
</code></pre>
|
||||
<p>indented</p>
|
||||
<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):
|
||||
def test_resource_name_uses_classname_by_default(self):
|
||||
"""Ensure Resource names are based on the classname by default."""
|
||||
class MockView(APIView):
|
||||
pass
|
||||
self.assertEquals(MockView().get_name(), 'Mock')
|
||||
|
||||
def test_resource_name_can_be_set_explicitly(self):
|
||||
"""Ensure Resource names can be set using the 'get_name' method."""
|
||||
example = 'Some Other Name'
|
||||
class MockView(APIView):
|
||||
def get_name(self):
|
||||
return example
|
||||
self.assertEquals(MockView().get_name(), example)
|
||||
|
||||
def test_resource_description_uses_docstring_by_default(self):
|
||||
"""Ensure Resource names are based on the docstring by default."""
|
||||
class MockView(APIView):
|
||||
"""an example docstring
|
||||
====================
|
||||
|
||||
* list
|
||||
* list
|
||||
|
||||
another header
|
||||
--------------
|
||||
|
||||
code block
|
||||
|
||||
indented
|
||||
|
||||
# hash style header #"""
|
||||
|
||||
self.assertEquals(MockView().get_description(), DESCRIPTION)
|
||||
|
||||
def test_resource_description_can_be_set_explicitly(self):
|
||||
"""Ensure Resource descriptions can be set using the 'get_description' method."""
|
||||
example = 'Some other description'
|
||||
|
||||
class MockView(APIView):
|
||||
"""docstring"""
|
||||
def get_description(self):
|
||||
return example
|
||||
self.assertEquals(MockView().get_description(), 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 'get_description' method."""
|
||||
example = 'Some other description'
|
||||
|
||||
class MockView(APIView):
|
||||
def get_description(self):
|
||||
return example
|
||||
self.assertEquals(MockView().get_description(), example)
|
||||
|
||||
def test_resource_description_can_be_empty(self):
|
||||
"""Ensure that if a resource has no doctring or 'description' class attribute, then it's description is the empty string."""
|
||||
class MockView(APIView):
|
||||
pass
|
||||
self.assertEquals(MockView().get_description(), '')
|
||||
|
||||
def test_markdown(self):
|
||||
"""Ensure markdown to HTML works as expected"""
|
||||
if apply_markdown:
|
||||
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)
|
|
@ -1,34 +0,0 @@
|
|||
# from django.test import TestCase
|
||||
# from django import forms
|
||||
|
||||
# from djangorestframework.compat import RequestFactory
|
||||
# from djangorestframework.views import View
|
||||
# from djangorestframework.response import Response
|
||||
|
||||
# import StringIO
|
||||
|
||||
|
||||
# class UploadFilesTests(TestCase):
|
||||
# """Check uploading of files"""
|
||||
# def setUp(self):
|
||||
# self.factory = RequestFactory()
|
||||
|
||||
# def test_upload_file(self):
|
||||
|
||||
# class FileForm(forms.Form):
|
||||
# file = forms.FileField()
|
||||
|
||||
# class MockView(View):
|
||||
# permissions = ()
|
||||
# form = FileForm
|
||||
|
||||
# def post(self, request, *args, **kwargs):
|
||||
# return Response({'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})
|
||||
# view = MockView.as_view()
|
||||
# response = view(request)
|
||||
# self.assertEquals(response.raw_content, {"FILE_CONTENT": "stuff", "FILE_NAME": "stuff.txt"})
|
|
@ -1,285 +0,0 @@
|
|||
# """Tests for the mixin module"""
|
||||
# from django.test import TestCase
|
||||
# from djangorestframework import status
|
||||
# from djangorestframework.compat import RequestFactory
|
||||
# from django.contrib.auth.models import Group, User
|
||||
# from djangorestframework.mixins import CreateModelMixin, PaginatorMixin, ReadModelMixin
|
||||
# from djangorestframework.resources import ModelResource
|
||||
# from djangorestframework.response import Response, ImmediateResponse
|
||||
# from djangorestframework.tests.models import CustomUser
|
||||
# from djangorestframework.tests.testcases import TestModelsTestCase
|
||||
# from djangorestframework.views import View
|
||||
|
||||
|
||||
# class TestModelRead(TestModelsTestCase):
|
||||
# """Tests on ReadModelMixin"""
|
||||
|
||||
# def setUp(self):
|
||||
# super(TestModelRead, self).setUp()
|
||||
# self.req = RequestFactory()
|
||||
|
||||
# def test_read(self):
|
||||
# Group.objects.create(name='other group')
|
||||
# group = Group.objects.create(name='my group')
|
||||
|
||||
# class GroupResource(ModelResource):
|
||||
# model = Group
|
||||
|
||||
# request = self.req.get('/groups')
|
||||
# mixin = ReadModelMixin()
|
||||
# mixin.resource = GroupResource
|
||||
|
||||
# response = mixin.get(request, id=group.id)
|
||||
# self.assertEquals(group.name, response.raw_content.name)
|
||||
|
||||
# def test_read_404(self):
|
||||
# class GroupResource(ModelResource):
|
||||
# model = Group
|
||||
|
||||
# request = self.req.get('/groups')
|
||||
# mixin = ReadModelMixin()
|
||||
# mixin.resource = GroupResource
|
||||
|
||||
# self.assertRaises(ImmediateResponse, mixin.get, request, id=12345)
|
||||
|
||||
|
||||
# class TestModelCreation(TestModelsTestCase):
|
||||
# """Tests on CreateModelMixin"""
|
||||
|
||||
# def setUp(self):
|
||||
# super(TestModelsTestCase, self).setUp()
|
||||
# self.req = RequestFactory()
|
||||
|
||||
# def test_creation(self):
|
||||
# self.assertEquals(0, Group.objects.count())
|
||||
|
||||
# class GroupResource(ModelResource):
|
||||
# model = Group
|
||||
|
||||
# form_data = {'name': 'foo'}
|
||||
# request = self.req.post('/groups', data=form_data)
|
||||
# mixin = CreateModelMixin()
|
||||
# mixin.resource = GroupResource
|
||||
# mixin.CONTENT = form_data
|
||||
|
||||
# response = mixin.post(request)
|
||||
# self.assertEquals(1, Group.objects.count())
|
||||
# self.assertEquals('foo', response.raw_content.name)
|
||||
|
||||
# def test_creation_with_m2m_relation(self):
|
||||
# class UserResource(ModelResource):
|
||||
# model = User
|
||||
|
||||
# def url(self, instance):
|
||||
# return "/users/%i" % instance.id
|
||||
|
||||
# group = Group(name='foo')
|
||||
# group.save()
|
||||
|
||||
# form_data = {
|
||||
# 'username': 'bar',
|
||||
# 'password': 'baz',
|
||||
# 'groups': [group.id]
|
||||
# }
|
||||
# request = self.req.post('/groups', data=form_data)
|
||||
# cleaned_data = dict(form_data)
|
||||
# cleaned_data['groups'] = [group]
|
||||
# mixin = CreateModelMixin()
|
||||
# mixin.resource = UserResource
|
||||
# mixin.CONTENT = cleaned_data
|
||||
|
||||
# response = mixin.post(request)
|
||||
# self.assertEquals(1, User.objects.count())
|
||||
# self.assertEquals(1, response.raw_content.groups.count())
|
||||
# self.assertEquals('foo', response.raw_content.groups.all()[0].name)
|
||||
|
||||
# def test_creation_with_m2m_relation_through(self):
|
||||
# """
|
||||
# Tests creation where the m2m relation uses a through table
|
||||
# """
|
||||
# class UserResource(ModelResource):
|
||||
# model = CustomUser
|
||||
|
||||
# def url(self, instance):
|
||||
# return "/customusers/%i" % instance.id
|
||||
|
||||
# form_data = {'username': 'bar0', 'groups': []}
|
||||
# request = self.req.post('/groups', data=form_data)
|
||||
# cleaned_data = dict(form_data)
|
||||
# cleaned_data['groups'] = []
|
||||
# mixin = CreateModelMixin()
|
||||
# mixin.resource = UserResource
|
||||
# mixin.CONTENT = cleaned_data
|
||||
|
||||
# response = mixin.post(request)
|
||||
# self.assertEquals(1, CustomUser.objects.count())
|
||||
# self.assertEquals(0, response.raw_content.groups.count())
|
||||
|
||||
# group = Group(name='foo1')
|
||||
# group.save()
|
||||
|
||||
# form_data = {'username': 'bar1', 'groups': [group.id]}
|
||||
# request = self.req.post('/groups', data=form_data)
|
||||
# cleaned_data = dict(form_data)
|
||||
# cleaned_data['groups'] = [group]
|
||||
# mixin = CreateModelMixin()
|
||||
# mixin.resource = UserResource
|
||||
# mixin.CONTENT = cleaned_data
|
||||
|
||||
# response = mixin.post(request)
|
||||
# self.assertEquals(2, CustomUser.objects.count())
|
||||
# self.assertEquals(1, response.raw_content.groups.count())
|
||||
# self.assertEquals('foo1', response.raw_content.groups.all()[0].name)
|
||||
|
||||
# group2 = Group(name='foo2')
|
||||
# group2.save()
|
||||
|
||||
# form_data = {'username': 'bar2', 'groups': [group.id, group2.id]}
|
||||
# request = self.req.post('/groups', data=form_data)
|
||||
# cleaned_data = dict(form_data)
|
||||
# cleaned_data['groups'] = [group, group2]
|
||||
# mixin = CreateModelMixin()
|
||||
# mixin.resource = UserResource
|
||||
# mixin.CONTENT = cleaned_data
|
||||
|
||||
# response = mixin.post(request)
|
||||
# self.assertEquals(3, CustomUser.objects.count())
|
||||
# self.assertEquals(2, response.raw_content.groups.count())
|
||||
# self.assertEquals('foo1', response.raw_content.groups.all()[0].name)
|
||||
# self.assertEquals('foo2', response.raw_content.groups.all()[1].name)
|
||||
|
||||
|
||||
# class MockPaginatorView(PaginatorMixin, View):
|
||||
# total = 60
|
||||
|
||||
# def get(self, request):
|
||||
# return Response(range(0, self.total))
|
||||
|
||||
# def post(self, request):
|
||||
# return Response({'status': 'OK'}, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
# class TestPagination(TestCase):
|
||||
# def setUp(self):
|
||||
# self.req = RequestFactory()
|
||||
|
||||
# def test_default_limit(self):
|
||||
# """ Tests if pagination works without overwriting the limit """
|
||||
# request = self.req.get('/paginator')
|
||||
# response = MockPaginatorView.as_view()(request)
|
||||
# content = response.raw_content
|
||||
|
||||
# self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
# self.assertEqual(MockPaginatorView.total, content['total'])
|
||||
# self.assertEqual(MockPaginatorView.limit, content['per_page'])
|
||||
|
||||
# self.assertEqual(range(0, MockPaginatorView.limit), content['results'])
|
||||
|
||||
# def test_overwriting_limit(self):
|
||||
# """ Tests if the limit can be overwritten """
|
||||
# limit = 10
|
||||
|
||||
# request = self.req.get('/paginator')
|
||||
# response = MockPaginatorView.as_view(limit=limit)(request)
|
||||
# content = response.raw_content
|
||||
|
||||
# self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
# self.assertEqual(content['per_page'], limit)
|
||||
|
||||
# self.assertEqual(range(0, limit), content['results'])
|
||||
|
||||
# def test_limit_param(self):
|
||||
# """ Tests if the client can set the limit """
|
||||
# from math import ceil
|
||||
|
||||
# limit = 5
|
||||
# num_pages = int(ceil(MockPaginatorView.total / float(limit)))
|
||||
|
||||
# request = self.req.get('/paginator/?limit=%d' % limit)
|
||||
# response = MockPaginatorView.as_view()(request)
|
||||
# content = response.raw_content
|
||||
|
||||
# self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
# self.assertEqual(MockPaginatorView.total, content['total'])
|
||||
# self.assertEqual(limit, content['per_page'])
|
||||
# self.assertEqual(num_pages, content['pages'])
|
||||
|
||||
# def test_exceeding_limit(self):
|
||||
# """ Makes sure the client cannot exceed the default limit """
|
||||
# from math import ceil
|
||||
|
||||
# limit = MockPaginatorView.limit + 10
|
||||
# num_pages = int(ceil(MockPaginatorView.total / float(limit)))
|
||||
|
||||
# request = self.req.get('/paginator/?limit=%d' % limit)
|
||||
# response = MockPaginatorView.as_view()(request)
|
||||
# content = response.raw_content
|
||||
|
||||
# self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
# self.assertEqual(MockPaginatorView.total, content['total'])
|
||||
# self.assertNotEqual(limit, content['per_page'])
|
||||
# self.assertNotEqual(num_pages, content['pages'])
|
||||
# self.assertEqual(MockPaginatorView.limit, content['per_page'])
|
||||
|
||||
# def test_only_works_for_get(self):
|
||||
# """ Pagination should only work for GET requests """
|
||||
# request = self.req.post('/paginator', data={'content': 'spam'})
|
||||
# response = MockPaginatorView.as_view()(request)
|
||||
# content = response.raw_content
|
||||
|
||||
# self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
# self.assertEqual(None, content.get('per_page'))
|
||||
# self.assertEqual('OK', content['status'])
|
||||
|
||||
# def test_non_int_page(self):
|
||||
# """ Tests that it can handle invalid values """
|
||||
# request = self.req.get('/paginator/?page=spam')
|
||||
# response = MockPaginatorView.as_view()(request)
|
||||
|
||||
# self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# def test_page_range(self):
|
||||
# """ Tests that the page range is handle correctly """
|
||||
# request = self.req.get('/paginator/?page=0')
|
||||
# response = MockPaginatorView.as_view()(request)
|
||||
# content = response.raw_content
|
||||
# self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# request = self.req.get('/paginator/')
|
||||
# response = MockPaginatorView.as_view()(request)
|
||||
# content = response.raw_content
|
||||
# self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
# self.assertEqual(range(0, MockPaginatorView.limit), content['results'])
|
||||
|
||||
# num_pages = content['pages']
|
||||
|
||||
# request = self.req.get('/paginator/?page=%d' % num_pages)
|
||||
# response = MockPaginatorView.as_view()(request)
|
||||
# content = response.raw_content
|
||||
# self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
# self.assertEqual(range(MockPaginatorView.limit*(num_pages-1), MockPaginatorView.total), content['results'])
|
||||
|
||||
# request = self.req.get('/paginator/?page=%d' % (num_pages + 1,))
|
||||
# response = MockPaginatorView.as_view()(request)
|
||||
# content = response.raw_content
|
||||
# self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# def test_existing_query_parameters_are_preserved(self):
|
||||
# """ Tests that existing query parameters are preserved when
|
||||
# generating next/previous page links """
|
||||
# request = self.req.get('/paginator/?foo=bar&another=something')
|
||||
# response = MockPaginatorView.as_view()(request)
|
||||
# content = response.raw_content
|
||||
# self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
# self.assertTrue('foo=bar' in content['next'])
|
||||
# self.assertTrue('another=something' in content['next'])
|
||||
# self.assertTrue('page=2' in content['next'])
|
||||
|
||||
# def test_duplicate_parameters_are_not_created(self):
|
||||
# """ Regression: ensure duplicate "page" parameters are not added to
|
||||
# paginated URLs. So page 1 should contain ?page=2, not ?page=1&page=2 """
|
||||
# request = self.req.get('/paginator/?page=1')
|
||||
# response = MockPaginatorView.as_view()(request)
|
||||
# content = response.raw_content
|
||||
# self.assertTrue('page=2' in content['next'])
|
||||
# self.assertFalse('page=1' in content['next'])
|
|
@ -1,28 +0,0 @@
|
|||
from django.db import models
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
class CustomUser(models.Model):
|
||||
"""
|
||||
A custom user model, which uses a 'through' table for the foreign key
|
||||
"""
|
||||
username = models.CharField(max_length=255, unique=True)
|
||||
groups = models.ManyToManyField(
|
||||
to=Group, blank=True, null=True, through='UserGroupMap'
|
||||
)
|
||||
|
||||
@models.permalink
|
||||
def get_absolute_url(self):
|
||||
return ('custom_user', (), {
|
||||
'pk': self.id
|
||||
})
|
||||
|
||||
|
||||
class UserGroupMap(models.Model):
|
||||
user = models.ForeignKey(to=CustomUser)
|
||||
group = models.ForeignKey(to=Group)
|
||||
|
||||
@models.permalink
|
||||
def get_absolute_url(self):
|
||||
return ('user_group_map', (), {
|
||||
'pk': self.id
|
||||
})
|
|
@ -1,90 +0,0 @@
|
|||
# from django.conf.urls.defaults import patterns, url
|
||||
# from django.forms import ModelForm
|
||||
# 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
|
||||
|
||||
|
||||
# class UserForm(ModelForm):
|
||||
# class Meta:
|
||||
# model = User
|
||||
# exclude = ('last_login', 'date_joined')
|
||||
|
||||
|
||||
# class UserResource(ModelResource):
|
||||
# model = User
|
||||
# form = UserForm
|
||||
|
||||
|
||||
# class CustomUserResource(ModelResource):
|
||||
# model = CustomUser
|
||||
|
||||
# urlpatterns = patterns('',
|
||||
# url(r'^users/$', ListOrCreateModelView.as_view(resource=UserResource), name='users'),
|
||||
# url(r'^users/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=UserResource)),
|
||||
# url(r'^customusers/$', ListOrCreateModelView.as_view(resource=CustomUserResource), name='customusers'),
|
||||
# url(r'^customusers/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=CustomUserResource)),
|
||||
# url(r'^groups/$', ListOrCreateModelView.as_view(resource=GroupResource), name='groups'),
|
||||
# url(r'^groups/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=GroupResource)),
|
||||
# )
|
||||
|
||||
|
||||
# class ModelViewTests(TestModelsTestCase):
|
||||
# """Test the model views djangorestframework provides"""
|
||||
# urls = 'djangorestframework.tests.modelviews'
|
||||
|
||||
# def test_creation(self):
|
||||
# """Ensure that a model object can be created"""
|
||||
# self.assertEqual(0, Group.objects.count())
|
||||
|
||||
# response = self.client.post('/groups/', {'name': 'foo'})
|
||||
|
||||
# self.assertEqual(response.status_code, 201)
|
||||
# self.assertEqual(1, Group.objects.count())
|
||||
# self.assertEqual('foo', Group.objects.all()[0].name)
|
||||
|
||||
# def test_creation_with_m2m_relation(self):
|
||||
# """Ensure that a model object with a m2m relation can be created"""
|
||||
# group = Group(name='foo')
|
||||
# group.save()
|
||||
# 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
|
||||
# relation uses a through table
|
||||
# """
|
||||
# group = Group(name='foo')
|
||||
# group.save()
|
||||
# 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)
|
|
@ -1,211 +0,0 @@
|
|||
import time
|
||||
|
||||
from django.conf.urls.defaults import patterns, url, include
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from djangorestframework.views import APIView
|
||||
|
||||
# Since oauth2 / django-oauth-plus are optional dependancies, we don't want to
|
||||
# always run these tests.
|
||||
|
||||
# Unfortunatly we can't skip tests easily until 2.7, se we'll just do this for now.
|
||||
try:
|
||||
import oauth2 as oauth
|
||||
from oauth_provider.decorators import oauth_required
|
||||
from oauth_provider.models import Resource, Consumer, Token
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
else:
|
||||
# Alrighty, we're good to go here.
|
||||
class ClientView(APIView):
|
||||
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'^restframework/', include('djangorestframework.urls', namespace='djangorestframework')),
|
||||
)
|
||||
|
||||
class OAuthTests(TestCase):
|
||||
"""
|
||||
OAuth authentication:
|
||||
* the user would like to access his API data from a third-party website
|
||||
* the third-party website proposes a link to get that API data
|
||||
* the user is redirected to the API and must log in if not authenticated
|
||||
* the API displays a webpage to confirm that the user trusts the third-party website
|
||||
* if confirmed, the user is redirected to the third-party website through the callback view
|
||||
* 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,
|
||||
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
|
||||
require the user to be logged in.
|
||||
"""
|
||||
response = self.client.get('/')
|
||||
self.assertEqual(response.content, 'Invalid request parameters.')
|
||||
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
|
||||
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.
|
||||
"""
|
||||
return {
|
||||
'oauth_consumer_key': self.CONSUMER_KEY,
|
||||
'oauth_signature_method': 'PLAINTEXT',
|
||||
'oauth_signature': '%s&' % self.CONSUMER_SECRET,
|
||||
'oauth_timestamp': str(int(time.time())),
|
||||
'oauth_nonce': 'requestnonce',
|
||||
'oauth_version': '1.0',
|
||||
'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/",
|
||||
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/",
|
||||
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}
|
||||
response = self.client.get("/oauth/authorize/", parameters)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.failIf(not response.content.startswith('Fake authorize view for api.example.com with params: oauth_token='))
|
||||
self.assertEqual(token.is_approved, 0)
|
||||
parameters['authorize_access'] = 1 # fake authorization by the user
|
||||
response = self.client.post("/oauth/authorize/", parameters)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.failIf(not response['Location'].startswith('http://api.example.com/request_token_ready?oauth_verifier='))
|
||||
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.
|
||||
"""
|
||||
return {
|
||||
'oauth_consumer_key': self.CONSUMER_KEY,
|
||||
'oauth_token': token.key,
|
||||
'oauth_signature_method': 'PLAINTEXT',
|
||||
'oauth_signature': '%s&%s' % (self.CONSUMER_SECRET, token.secret),
|
||||
'oauth_timestamp': str(int(time.time())),
|
||||
'oauth_nonce': 'accessnonce',
|
||||
'oauth_version': '1.0',
|
||||
'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/",
|
||||
self._create_request_token_parameters())
|
||||
token = list(Token.objects.all())[-1]
|
||||
self.client.login(username=self.username, password=self.password)
|
||||
parameters = {'oauth_token': token.key,}
|
||||
response = self.client.get("/oauth/authorize/", parameters)
|
||||
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)
|
||||
self.failIf(not response.content.startswith('oauth_token_secret='))
|
||||
access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1]
|
||||
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.
|
||||
"""
|
||||
parameters = {
|
||||
'oauth_consumer_key': self.CONSUMER_KEY,
|
||||
'oauth_token': access_token.key,
|
||||
'oauth_signature_method': 'HMAC-SHA1',
|
||||
'oauth_timestamp': str(int(time.time())),
|
||||
'oauth_nonce': 'accessresourcenonce',
|
||||
'oauth_version': '1.0',
|
||||
}
|
||||
oauth_request = oauth.Request.from_token_and_callback(access_token,
|
||||
http_url='http://testserver/', parameters=parameters)
|
||||
signature_method = oauth.SignatureMethod_HMAC_SHA1()
|
||||
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/",
|
||||
self._create_request_token_parameters())
|
||||
token = list(Token.objects.all())[-1]
|
||||
self.client.login(username=self.username, password=self.password)
|
||||
parameters = {'oauth_token': token.key,}
|
||||
response = self.client.get("/oauth/authorize/", parameters)
|
||||
parameters['authorize_access'] = 1 # fake authorization by the user
|
||||
response = self.client.post("/oauth/authorize/", parameters)
|
||||
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)
|
||||
self.assertEqual(response.content, '{"resource": "Protected!"}')
|
|
@ -1,11 +0,0 @@
|
|||
"""Tests for the djangorestframework package setup."""
|
||||
from django.test import TestCase
|
||||
import djangorestframework
|
||||
|
||||
class TestVersion(TestCase):
|
||||
"""Simple sanity test to check the VERSION exists"""
|
||||
|
||||
def test_version(self):
|
||||
"""Ensure the VERSION exists."""
|
||||
djangorestframework.VERSION
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
# """
|
||||
# ..
|
||||
# >>> from djangorestframework.parsers import FormParser
|
||||
# >>> from djangorestframework.compat import RequestFactory
|
||||
# >>> from djangorestframework.views import View
|
||||
# >>> from StringIO import StringIO
|
||||
# >>> from urllib import urlencode
|
||||
# >>> 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 ::
|
||||
#
|
||||
# <select multiple="multiple" name="key2"></select>
|
||||
#
|
||||
# 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 ::
|
||||
#
|
||||
# <select multiple="multiple" name="key2"><option value="_empty"></select>
|
||||
#
|
||||
# :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
|
||||
# from tempfile import TemporaryFile
|
||||
# from django.test import TestCase
|
||||
# from djangorestframework.compat import RequestFactory
|
||||
# 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.
|
||||
# files is a sequence of (name, filename, value) elements for data to be uploaded as files
|
||||
# Return (content_type, body)."""
|
||||
# BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
|
||||
# CRLF = '\r\n'
|
||||
# L = []
|
||||
# for (key, value) in fields:
|
||||
# L.append('--' + BOUNDARY)
|
||||
# L.append('Content-Disposition: form-data; name="%s"' % key)
|
||||
# L.append('')
|
||||
# L.append(value)
|
||||
# for (key, filename, value) in files:
|
||||
# L.append('--' + BOUNDARY)
|
||||
# L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
|
||||
# L.append('Content-Type: %s' % get_content_type(filename))
|
||||
# L.append('')
|
||||
# L.append(value)
|
||||
# L.append('--' + BOUNDARY + '--')
|
||||
# L.append('')
|
||||
# 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()
|
||||
# self.content_type, self.body = encode_multipart_formdata([('key1', 'val1'), ('key1', 'val2')],
|
||||
# [('file1', 'pic.jpg', 'blablabla'), ('file1', 't.txt', 'blobloblo')])
|
||||
#
|
||||
# def test_multipartparser(self):
|
||||
# """Ensure that MultiPartParser can parse multipart/form-data that contains a mix of several files and parameters."""
|
||||
# post_req = RequestFactory().post('/', self.body, content_type=self.content_type)
|
||||
# view = View()
|
||||
# view.request = post_req
|
||||
# (data, files) = MultiPartParser(view).parse(StringIO(self.body))
|
||||
# self.assertEqual(data['key1'], 'val1')
|
||||
# self.assertEqual(files['file1'].read(), 'blablabla')
|
||||
|
||||
from StringIO import StringIO
|
||||
from django import forms
|
||||
from django.test import TestCase
|
||||
from djangorestframework.parsers import FormParser
|
||||
from djangorestframework.parsers import XMLParser
|
||||
import datetime
|
||||
|
||||
|
||||
class Form(forms.Form):
|
||||
field1 = forms.CharField(max_length=3)
|
||||
field2 = forms.CharField()
|
||||
|
||||
|
||||
class TestFormParser(TestCase):
|
||||
def setUp(self):
|
||||
self.string = "field1=abc&field2=defghijk"
|
||||
|
||||
def test_parse(self):
|
||||
""" Make sure the `QueryDict` works OK """
|
||||
parser = FormParser()
|
||||
|
||||
stream = StringIO(self.string)
|
||||
data = parser.parse(stream)
|
||||
|
||||
self.assertEqual(Form(data).is_valid(), True)
|
||||
|
||||
|
||||
class TestXMLParser(TestCase):
|
||||
def setUp(self):
|
||||
self._input = StringIO(
|
||||
'<?xml version="1.0" encoding="utf-8"?>'
|
||||
'<root>'
|
||||
'<field_a>121.0</field_a>'
|
||||
'<field_b>dasd</field_b>'
|
||||
'<field_c></field_c>'
|
||||
'<field_d>2011-12-25 12:45:00</field_d>'
|
||||
'</root>'
|
||||
)
|
||||
self._data = {
|
||||
'field_a': 121,
|
||||
'field_b': 'dasd',
|
||||
'field_c': None,
|
||||
'field_d': datetime.datetime(2011, 12, 25, 12, 45, 00)
|
||||
}
|
||||
self._complex_data_input = StringIO(
|
||||
'<?xml version="1.0" encoding="utf-8"?>'
|
||||
'<root>'
|
||||
'<creation_date>2011-12-25 12:45:00</creation_date>'
|
||||
'<sub_data_list>'
|
||||
'<list-item><sub_id>1</sub_id><sub_name>first</sub_name></list-item>'
|
||||
'<list-item><sub_id>2</sub_id><sub_name>second</sub_name></list-item>'
|
||||
'</sub_data_list>'
|
||||
'<name>name</name>'
|
||||
'</root>'
|
||||
)
|
||||
self._complex_data = {
|
||||
"creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00),
|
||||
"name": "name",
|
||||
"sub_data_list": [
|
||||
{
|
||||
"sub_id": 1,
|
||||
"sub_name": "first"
|
||||
},
|
||||
{
|
||||
"sub_id": 2,
|
||||
"sub_name": "second"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def test_parse(self):
|
||||
parser = XMLParser()
|
||||
data = parser.parse(self._input)
|
||||
self.assertEqual(data, self._data)
|
||||
|
||||
def test_complex_data_parse(self):
|
||||
parser = XMLParser()
|
||||
data = parser.parse(self._complex_data_input)
|
||||
self.assertEqual(data, self._complex_data)
|
|
@ -1,375 +0,0 @@
|
|||
import re
|
||||
|
||||
from django.conf.urls.defaults import patterns, url, include
|
||||
from django.test import TestCase
|
||||
|
||||
from djangorestframework import status
|
||||
from djangorestframework.response import Response
|
||||
from djangorestframework.views import APIView
|
||||
from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
|
||||
XMLRenderer, JSONPRenderer, DocumentingHTMLRenderer
|
||||
from djangorestframework.parsers import YAMLParser, XMLParser
|
||||
|
||||
from StringIO import StringIO
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
DUMMYSTATUS = status.HTTP_200_OK
|
||||
DUMMYCONTENT = 'dummycontent'
|
||||
|
||||
RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x
|
||||
RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x
|
||||
|
||||
|
||||
expected_results = [
|
||||
((elem for elem in [1, 2, 3]), JSONRenderer, '[1, 2, 3]') # Generator
|
||||
]
|
||||
|
||||
|
||||
class BasicRendererTests(TestCase):
|
||||
def test_expected_results(self):
|
||||
for value, renderer_cls, expected in expected_results:
|
||||
output = renderer_cls().render(value)
|
||||
self.assertEquals(output, expected)
|
||||
|
||||
|
||||
class RendererA(BaseRenderer):
|
||||
media_type = 'mock/renderera'
|
||||
format = "formata"
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
return RENDERER_A_SERIALIZER(obj)
|
||||
|
||||
|
||||
class RendererB(BaseRenderer):
|
||||
media_type = 'mock/rendererb'
|
||||
format = "formatb"
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
return RENDERER_B_SERIALIZER(obj)
|
||||
|
||||
|
||||
class MockView(APIView):
|
||||
renderer_classes = (RendererA, RendererB)
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
response = Response(DUMMYCONTENT, status=DUMMYSTATUS)
|
||||
return response
|
||||
|
||||
|
||||
class MockGETView(APIView):
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
return Response({'foo': ['bar', 'baz']})
|
||||
|
||||
|
||||
class HTMLView(APIView):
|
||||
renderer_classes = (DocumentingHTMLRenderer, )
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
return Response('text')
|
||||
|
||||
|
||||
class HTMLView1(APIView):
|
||||
renderer_classes = (DocumentingHTMLRenderer, JSONRenderer)
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
return Response('text')
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
||||
url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
||||
url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderer_classes=[JSONRenderer, JSONPRenderer])),
|
||||
url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderer_classes=[JSONPRenderer])),
|
||||
url(r'^html$', HTMLView.as_view()),
|
||||
url(r'^html1$', HTMLView1.as_view()),
|
||||
url(r'^api', include('djangorestframework.urls', namespace='djangorestframework'))
|
||||
)
|
||||
|
||||
|
||||
class RendererEndToEndTests(TestCase):
|
||||
"""
|
||||
End-to-end testing of renderers using an RendererMixin on a generic view.
|
||||
"""
|
||||
|
||||
urls = 'djangorestframework.tests.renderers'
|
||||
|
||||
def test_default_renderer_serializes_content(self):
|
||||
"""If the Accept header is not set the default renderer should serialize the response."""
|
||||
resp = self.client.get('/')
|
||||
self.assertEquals(resp['Content-Type'], RendererA.media_type)
|
||||
self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
|
||||
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
||||
|
||||
def test_head_method_serializes_no_content(self):
|
||||
"""No response must be included in HEAD requests."""
|
||||
resp = self.client.head('/')
|
||||
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
||||
self.assertEquals(resp['Content-Type'], RendererA.media_type)
|
||||
self.assertEquals(resp.content, '')
|
||||
|
||||
def test_default_renderer_serializes_content_on_accept_any(self):
|
||||
"""If the Accept header is set to */* the default renderer should serialize the response."""
|
||||
resp = self.client.get('/', HTTP_ACCEPT='*/*')
|
||||
self.assertEquals(resp['Content-Type'], RendererA.media_type)
|
||||
self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
|
||||
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
||||
|
||||
def test_specified_renderer_serializes_content_default_case(self):
|
||||
"""If the Accept header is set the specified renderer should serialize the response.
|
||||
(In this case we check that works for the default renderer)"""
|
||||
resp = self.client.get('/', HTTP_ACCEPT=RendererA.media_type)
|
||||
self.assertEquals(resp['Content-Type'], RendererA.media_type)
|
||||
self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
|
||||
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
||||
|
||||
def test_specified_renderer_serializes_content_non_default_case(self):
|
||||
"""If the Accept header is set the specified renderer should serialize the response.
|
||||
(In this case we check that works for a non-default renderer)"""
|
||||
resp = self.client.get('/', HTTP_ACCEPT=RendererB.media_type)
|
||||
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)
|
||||
self.assertEquals(resp['Content-Type'], RendererB.media_type)
|
||||
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
|
||||
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
||||
|
||||
def test_unsatisfiable_accept_header_on_request_returns_406_status(self):
|
||||
"""If the Accept header is unsatisfiable we should return a 406 Not Acceptable response."""
|
||||
resp = self.client.get('/', HTTP_ACCEPT='foo/bar')
|
||||
self.assertEquals(resp.status_code, status.HTTP_406_NOT_ACCEPTABLE)
|
||||
|
||||
def test_specified_renderer_serializes_content_on_format_query(self):
|
||||
"""If a 'format' query is specified, the renderer with the matching
|
||||
format attribute should serialize the response."""
|
||||
resp = self.client.get('/?format=%s' % RendererB.format)
|
||||
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_format_kwargs(self):
|
||||
"""If a 'format' keyword arg is specified, the renderer with the matching
|
||||
format attribute should serialize the response."""
|
||||
resp = self.client.get('/something.formatb')
|
||||
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_is_used_on_format_query_with_matching_accept(self):
|
||||
"""If both a 'format' query and a matching Accept header specified,
|
||||
the renderer with the matching format attribute should serialize the response."""
|
||||
resp = self.client.get('/?format=%s' % RendererB.format,
|
||||
HTTP_ACCEPT=RendererB.media_type)
|
||||
self.assertEquals(resp['Content-Type'], RendererB.media_type)
|
||||
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
|
||||
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
||||
|
||||
|
||||
_flat_repr = '{"foo": ["bar", "baz"]}'
|
||||
_indented_repr = '{\n "foo": [\n "bar",\n "baz"\n ]\n}'
|
||||
|
||||
|
||||
def strip_trailing_whitespace(content):
|
||||
"""
|
||||
Seems to be some inconsistencies re. trailing whitespace with
|
||||
different versions of the json lib.
|
||||
"""
|
||||
return re.sub(' +\n', '\n', content)
|
||||
|
||||
|
||||
class JSONRendererTests(TestCase):
|
||||
"""
|
||||
Tests specific to the JSON Renderer
|
||||
"""
|
||||
|
||||
def test_without_content_type_args(self):
|
||||
"""
|
||||
Test basic JSON rendering.
|
||||
"""
|
||||
obj = {'foo': ['bar', 'baz']}
|
||||
renderer = JSONRenderer(None)
|
||||
content = renderer.render(obj, 'application/json')
|
||||
# Fix failing test case which depends on version of JSON library.
|
||||
self.assertEquals(content, _flat_repr)
|
||||
|
||||
def test_with_content_type_args(self):
|
||||
"""
|
||||
Test JSON rendering with additional content type arguments supplied.
|
||||
"""
|
||||
obj = {'foo': ['bar', 'baz']}
|
||||
renderer = JSONRenderer(None)
|
||||
content = renderer.render(obj, 'application/json; indent=2')
|
||||
self.assertEquals(strip_trailing_whitespace(content), _indented_repr)
|
||||
|
||||
|
||||
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/javascript')
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
self.assertEquals(resp['Content-Type'], 'application/javascript')
|
||||
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/javascript')
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
self.assertEquals(resp['Content-Type'], 'application/javascript')
|
||||
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/javascript')
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
self.assertEquals(resp['Content-Type'], 'application/javascript')
|
||||
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']}
|
||||
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']}
|
||||
|
||||
renderer = YAMLRenderer(None)
|
||||
parser = YAMLParser()
|
||||
|
||||
content = renderer.render(obj, 'application/yaml')
|
||||
data = parser.parse(StringIO(content))
|
||||
self.assertEquals(obj, data)
|
||||
|
||||
|
||||
class XMLRendererTestCase(TestCase):
|
||||
"""
|
||||
Tests specific to the XML Renderer
|
||||
"""
|
||||
|
||||
_complex_data = {
|
||||
"creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00),
|
||||
"name": "name",
|
||||
"sub_data_list": [
|
||||
{
|
||||
"sub_id": 1,
|
||||
"sub_name": "first"
|
||||
},
|
||||
{
|
||||
"sub_id": 2,
|
||||
"sub_name": "second"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def test_render_string(self):
|
||||
"""
|
||||
Test XML rendering.
|
||||
"""
|
||||
renderer = XMLRenderer(None)
|
||||
content = renderer.render({'field': 'astring'}, 'application/xml')
|
||||
self.assertXMLContains(content, '<field>astring</field>')
|
||||
|
||||
def test_render_integer(self):
|
||||
"""
|
||||
Test XML rendering.
|
||||
"""
|
||||
renderer = XMLRenderer(None)
|
||||
content = renderer.render({'field': 111}, 'application/xml')
|
||||
self.assertXMLContains(content, '<field>111</field>')
|
||||
|
||||
def test_render_datetime(self):
|
||||
"""
|
||||
Test XML rendering.
|
||||
"""
|
||||
renderer = XMLRenderer(None)
|
||||
content = renderer.render({
|
||||
'field': datetime.datetime(2011, 12, 25, 12, 45, 00)
|
||||
}, 'application/xml')
|
||||
self.assertXMLContains(content, '<field>2011-12-25 12:45:00</field>')
|
||||
|
||||
def test_render_float(self):
|
||||
"""
|
||||
Test XML rendering.
|
||||
"""
|
||||
renderer = XMLRenderer(None)
|
||||
content = renderer.render({'field': 123.4}, 'application/xml')
|
||||
self.assertXMLContains(content, '<field>123.4</field>')
|
||||
|
||||
def test_render_decimal(self):
|
||||
"""
|
||||
Test XML rendering.
|
||||
"""
|
||||
renderer = XMLRenderer(None)
|
||||
content = renderer.render({'field': Decimal('111.2')}, 'application/xml')
|
||||
self.assertXMLContains(content, '<field>111.2</field>')
|
||||
|
||||
def test_render_none(self):
|
||||
"""
|
||||
Test XML rendering.
|
||||
"""
|
||||
renderer = XMLRenderer(None)
|
||||
content = renderer.render({'field': None}, 'application/xml')
|
||||
self.assertXMLContains(content, '<field></field>')
|
||||
|
||||
def test_render_complex_data(self):
|
||||
"""
|
||||
Test XML rendering.
|
||||
"""
|
||||
renderer = XMLRenderer(None)
|
||||
content = renderer.render(self._complex_data, 'application/xml')
|
||||
self.assertXMLContains(content, '<sub_name>first</sub_name>')
|
||||
self.assertXMLContains(content, '<sub_name>second</sub_name>')
|
||||
|
||||
def test_render_and_parse_complex_data(self):
|
||||
"""
|
||||
Test XML rendering.
|
||||
"""
|
||||
renderer = XMLRenderer(None)
|
||||
content = StringIO(renderer.render(self._complex_data, 'application/xml'))
|
||||
|
||||
parser = XMLParser()
|
||||
complex_data_out = parser.parse(content)
|
||||
error_msg = "complex data differs!IN:\n %s \n\n OUT:\n %s" % (repr(self._complex_data), repr(complex_data_out))
|
||||
self.assertEqual(self._complex_data, complex_data_out, error_msg)
|
||||
|
||||
def assertXMLContains(self, xml, string):
|
||||
self.assertTrue(xml.startswith('<?xml version="1.0" encoding="utf-8"?>\n<root>'))
|
||||
self.assertTrue(xml.endswith('</root>'))
|
||||
self.assertTrue(string in xml, '%r not in %r' % (string, xml))
|
|
@ -1,252 +0,0 @@
|
|||
"""
|
||||
Tests for content parsing, and form-overloaded content parsing.
|
||||
"""
|
||||
from django.conf.urls.defaults import patterns
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase, Client
|
||||
|
||||
from djangorestframework import status
|
||||
from djangorestframework.authentication import SessionAuthentication
|
||||
from djangorestframework.compat import RequestFactory
|
||||
from djangorestframework.parsers import (
|
||||
FormParser,
|
||||
MultiPartParser,
|
||||
PlainTextParser,
|
||||
)
|
||||
from djangorestframework.request import Request
|
||||
from djangorestframework.response import Response
|
||||
from djangorestframework.views import APIView
|
||||
|
||||
|
||||
factory = RequestFactory()
|
||||
|
||||
|
||||
class TestMethodOverloading(TestCase):
|
||||
def test_method(self):
|
||||
"""
|
||||
Request methods should be same as underlying request.
|
||||
"""
|
||||
request = Request(factory.get('/'))
|
||||
self.assertEqual(request.method, 'GET')
|
||||
request = Request(factory.post('/'))
|
||||
self.assertEqual(request.method, 'POST')
|
||||
|
||||
def test_overloaded_method(self):
|
||||
"""
|
||||
POST requests can be overloaded to another method by setting a
|
||||
reserved form field
|
||||
"""
|
||||
request = Request(factory.post('/', {Request._METHOD_PARAM: 'DELETE'}))
|
||||
self.assertEqual(request.method, 'DELETE')
|
||||
|
||||
|
||||
class TestContentParsing(TestCase):
|
||||
def test_standard_behaviour_determines_no_content_GET(self):
|
||||
"""
|
||||
Ensure request.DATA returns None for GET request with no content.
|
||||
"""
|
||||
request = Request(factory.get('/'))
|
||||
self.assertEqual(request.DATA, None)
|
||||
|
||||
def test_standard_behaviour_determines_no_content_HEAD(self):
|
||||
"""
|
||||
Ensure request.DATA returns None for HEAD request.
|
||||
"""
|
||||
request = Request(factory.head('/'))
|
||||
self.assertEqual(request.DATA, None)
|
||||
|
||||
def test_standard_behaviour_determines_form_content_POST(self):
|
||||
"""
|
||||
Ensure request.DATA returns content for POST request with form content.
|
||||
"""
|
||||
data = {'qwerty': 'uiop'}
|
||||
request = Request(factory.post('/', data))
|
||||
request.parser_classes = (FormParser, MultiPartParser)
|
||||
self.assertEqual(request.DATA.items(), data.items())
|
||||
|
||||
def test_standard_behaviour_determines_non_form_content_POST(self):
|
||||
"""
|
||||
Ensure request.DATA returns content for POST request with
|
||||
non-form content.
|
||||
"""
|
||||
content = 'qwerty'
|
||||
content_type = 'text/plain'
|
||||
request = Request(factory.post('/', content, content_type=content_type))
|
||||
request.parser_classes = (PlainTextParser,)
|
||||
self.assertEqual(request.DATA, content)
|
||||
|
||||
def test_standard_behaviour_determines_form_content_PUT(self):
|
||||
"""
|
||||
Ensure request.DATA returns content for PUT request with form content.
|
||||
"""
|
||||
data = {'qwerty': 'uiop'}
|
||||
|
||||
from django import VERSION
|
||||
|
||||
if VERSION >= (1, 5):
|
||||
from django.test.client import MULTIPART_CONTENT, BOUNDARY, encode_multipart
|
||||
request = Request(factory.put('/', encode_multipart(BOUNDARY, data),
|
||||
content_type=MULTIPART_CONTENT))
|
||||
else:
|
||||
request = Request(factory.put('/', data))
|
||||
|
||||
request.parser_classes = (FormParser, MultiPartParser)
|
||||
self.assertEqual(request.DATA.items(), data.items())
|
||||
|
||||
def test_standard_behaviour_determines_non_form_content_PUT(self):
|
||||
"""
|
||||
Ensure request.DATA returns content for PUT request with
|
||||
non-form content.
|
||||
"""
|
||||
content = 'qwerty'
|
||||
content_type = 'text/plain'
|
||||
request = Request(factory.put('/', content, content_type=content_type))
|
||||
request.parser_classes = (PlainTextParser, )
|
||||
self.assertEqual(request.DATA, content)
|
||||
|
||||
def test_overloaded_behaviour_allows_content_tunnelling(self):
|
||||
"""
|
||||
Ensure request.DATA returns content for overloaded POST request.
|
||||
"""
|
||||
content = 'qwerty'
|
||||
content_type = 'text/plain'
|
||||
data = {
|
||||
Request._CONTENT_PARAM: content,
|
||||
Request._CONTENTTYPE_PARAM: content_type
|
||||
}
|
||||
request = Request(factory.post('/', data))
|
||||
request.parser_classes = (PlainTextParser, )
|
||||
self.assertEqual(request.DATA, content)
|
||||
|
||||
# def test_accessing_post_after_data_form(self):
|
||||
# """
|
||||
# Ensures request.POST can be accessed after request.DATA in
|
||||
# form request.
|
||||
# """
|
||||
# data = {'qwerty': 'uiop'}
|
||||
# request = factory.post('/', data=data)
|
||||
# self.assertEqual(request.DATA.items(), data.items())
|
||||
# self.assertEqual(request.POST.items(), data.items())
|
||||
|
||||
# def test_accessing_post_after_data_for_json(self):
|
||||
# """
|
||||
# Ensures request.POST can be accessed after request.DATA in
|
||||
# json request.
|
||||
# """
|
||||
# data = {'qwerty': 'uiop'}
|
||||
# content = json.dumps(data)
|
||||
# content_type = 'application/json'
|
||||
# parsers = (JSONParser, )
|
||||
|
||||
# request = factory.post('/', content, content_type=content_type,
|
||||
# parsers=parsers)
|
||||
# self.assertEqual(request.DATA.items(), data.items())
|
||||
# self.assertEqual(request.POST.items(), [])
|
||||
|
||||
# def test_accessing_post_after_data_for_overloaded_json(self):
|
||||
# """
|
||||
# Ensures request.POST can be accessed after request.DATA in overloaded
|
||||
# json request.
|
||||
# """
|
||||
# data = {'qwerty': 'uiop'}
|
||||
# content = json.dumps(data)
|
||||
# content_type = 'application/json'
|
||||
# parsers = (JSONParser, )
|
||||
# form_data = {Request._CONTENT_PARAM: content,
|
||||
# Request._CONTENTTYPE_PARAM: content_type}
|
||||
|
||||
# request = factory.post('/', form_data, parsers=parsers)
|
||||
# self.assertEqual(request.DATA.items(), data.items())
|
||||
# self.assertEqual(request.POST.items(), form_data.items())
|
||||
|
||||
# def test_accessing_data_after_post_form(self):
|
||||
# """
|
||||
# Ensures request.DATA can be accessed after request.POST in
|
||||
# form request.
|
||||
# """
|
||||
# data = {'qwerty': 'uiop'}
|
||||
# parsers = (FormParser, MultiPartParser)
|
||||
# request = factory.post('/', data, parsers=parsers)
|
||||
|
||||
# self.assertEqual(request.POST.items(), data.items())
|
||||
# self.assertEqual(request.DATA.items(), data.items())
|
||||
|
||||
# def test_accessing_data_after_post_for_json(self):
|
||||
# """
|
||||
# Ensures request.DATA can be accessed after request.POST in
|
||||
# json request.
|
||||
# """
|
||||
# data = {'qwerty': 'uiop'}
|
||||
# content = json.dumps(data)
|
||||
# content_type = 'application/json'
|
||||
# parsers = (JSONParser, )
|
||||
# request = factory.post('/', content, content_type=content_type,
|
||||
# parsers=parsers)
|
||||
# self.assertEqual(request.POST.items(), [])
|
||||
# self.assertEqual(request.DATA.items(), data.items())
|
||||
|
||||
# def test_accessing_data_after_post_for_overloaded_json(self):
|
||||
# """
|
||||
# Ensures request.DATA can be accessed after request.POST in overloaded
|
||||
# json request
|
||||
# """
|
||||
# data = {'qwerty': 'uiop'}
|
||||
# content = json.dumps(data)
|
||||
# content_type = 'application/json'
|
||||
# parsers = (JSONParser, )
|
||||
# form_data = {Request._CONTENT_PARAM: content,
|
||||
# Request._CONTENTTYPE_PARAM: content_type}
|
||||
|
||||
# request = factory.post('/', form_data, parsers=parsers)
|
||||
# self.assertEqual(request.POST.items(), form_data.items())
|
||||
# self.assertEqual(request.DATA.items(), data.items())
|
||||
|
||||
|
||||
class MockView(APIView):
|
||||
authentication_classes = (SessionAuthentication,)
|
||||
|
||||
def post(self, request):
|
||||
if request.POST.get('example') is not None:
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
return Response(status=status.INTERNAL_SERVER_ERROR)
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^$', MockView.as_view()),
|
||||
)
|
||||
|
||||
|
||||
class TestContentParsingWithAuthentication(TestCase):
|
||||
urls = 'djangorestframework.tests.request'
|
||||
|
||||
def setUp(self):
|
||||
self.csrf_client = Client(enforce_csrf_checks=True)
|
||||
self.username = 'john'
|
||||
self.email = 'lennon@thebeatles.com'
|
||||
self.password = 'password'
|
||||
self.user = User.objects.create_user(self.username, self.email, self.password)
|
||||
|
||||
def test_user_logged_in_authentication_has_POST_when_not_logged_in(self):
|
||||
"""
|
||||
Ensures request.POST exists after SessionAuthentication when user
|
||||
doesn't log in.
|
||||
"""
|
||||
content = {'example': 'example'}
|
||||
|
||||
response = self.client.post('/', content)
|
||||
self.assertEqual(status.HTTP_200_OK, response.status_code)
|
||||
|
||||
response = self.csrf_client.post('/', content)
|
||||
self.assertEqual(status.HTTP_200_OK, response.status_code)
|
||||
|
||||
# def test_user_logged_in_authentication_has_post_when_logged_in(self):
|
||||
# """Ensures request.POST exists after UserLoggedInAuthentication when user does log in"""
|
||||
# self.client.login(username='john', password='password')
|
||||
# self.csrf_client.login(username='john', password='password')
|
||||
# content = {'example': 'example'}
|
||||
|
||||
# response = self.client.post('/', content)
|
||||
# self.assertEqual(status.OK, response.status_code, "POST data is malformed")
|
||||
|
||||
# response = self.csrf_client.post('/', content)
|
||||
# self.assertEqual(status.OK, response.status_code, "POST data is malformed")
|
|
@ -1,177 +0,0 @@
|
|||
import unittest
|
||||
|
||||
from django.conf.urls.defaults import patterns, url, include
|
||||
from django.test import TestCase
|
||||
|
||||
from djangorestframework.response import Response
|
||||
from djangorestframework.views import APIView
|
||||
from djangorestframework import status
|
||||
from djangorestframework.renderers import (
|
||||
BaseRenderer,
|
||||
JSONRenderer,
|
||||
DocumentingHTMLRenderer
|
||||
)
|
||||
|
||||
|
||||
class MockPickleRenderer(BaseRenderer):
|
||||
media_type = 'application/pickle'
|
||||
|
||||
|
||||
class MockJsonRenderer(BaseRenderer):
|
||||
media_type = 'application/json'
|
||||
|
||||
|
||||
DUMMYSTATUS = status.HTTP_200_OK
|
||||
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"
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
return RENDERER_A_SERIALIZER(obj)
|
||||
|
||||
|
||||
class RendererB(BaseRenderer):
|
||||
media_type = 'mock/rendererb'
|
||||
format = "formatb"
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
return RENDERER_B_SERIALIZER(obj)
|
||||
|
||||
|
||||
class MockView(APIView):
|
||||
renderer_classes = (RendererA, RendererB)
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
return Response(DUMMYCONTENT, status=DUMMYSTATUS)
|
||||
|
||||
|
||||
class HTMLView(APIView):
|
||||
renderer_classes = (DocumentingHTMLRenderer, )
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
return Response('text')
|
||||
|
||||
|
||||
class HTMLView1(APIView):
|
||||
renderer_classes = (DocumentingHTMLRenderer, JSONRenderer)
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
return Response('text')
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
||||
url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
||||
url(r'^html$', HTMLView.as_view()),
|
||||
url(r'^html1$', HTMLView1.as_view()),
|
||||
url(r'^restframework', include('djangorestframework.urls', namespace='djangorestframework'))
|
||||
)
|
||||
|
||||
|
||||
# TODO: Clean tests bellow - remove duplicates with above, better unit testing, ...
|
||||
class RendererIntegrationTests(TestCase):
|
||||
"""
|
||||
End-to-end testing of renderers using an ResponseMixin on a generic view.
|
||||
"""
|
||||
|
||||
urls = 'djangorestframework.tests.response'
|
||||
|
||||
def test_default_renderer_serializes_content(self):
|
||||
"""If the Accept header is not set the default renderer should serialize the response."""
|
||||
resp = self.client.get('/')
|
||||
self.assertEquals(resp['Content-Type'], RendererA.media_type)
|
||||
self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
|
||||
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
||||
|
||||
def test_head_method_serializes_no_content(self):
|
||||
"""No response must be included in HEAD requests."""
|
||||
resp = self.client.head('/')
|
||||
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
||||
self.assertEquals(resp['Content-Type'], RendererA.media_type)
|
||||
self.assertEquals(resp.content, '')
|
||||
|
||||
def test_default_renderer_serializes_content_on_accept_any(self):
|
||||
"""If the Accept header is set to */* the default renderer should serialize the response."""
|
||||
resp = self.client.get('/', HTTP_ACCEPT='*/*')
|
||||
self.assertEquals(resp['Content-Type'], RendererA.media_type)
|
||||
self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
|
||||
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
||||
|
||||
def test_specified_renderer_serializes_content_default_case(self):
|
||||
"""If the Accept header is set the specified renderer should serialize the response.
|
||||
(In this case we check that works for the default renderer)"""
|
||||
resp = self.client.get('/', HTTP_ACCEPT=RendererA.media_type)
|
||||
self.assertEquals(resp['Content-Type'], RendererA.media_type)
|
||||
self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
|
||||
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
||||
|
||||
def test_specified_renderer_serializes_content_non_default_case(self):
|
||||
"""If the Accept header is set the specified renderer should serialize the response.
|
||||
(In this case we check that works for a non-default renderer)"""
|
||||
resp = self.client.get('/', HTTP_ACCEPT=RendererB.media_type)
|
||||
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)
|
||||
self.assertEquals(resp['Content-Type'], RendererB.media_type)
|
||||
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
|
||||
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
||||
|
||||
@unittest.skip('can\'t pass because view is a simple Django view and response is an ImmediateResponse')
|
||||
def test_unsatisfiable_accept_header_on_request_returns_406_status(self):
|
||||
"""If the Accept header is unsatisfiable we should return a 406 Not Acceptable response."""
|
||||
resp = self.client.get('/', HTTP_ACCEPT='foo/bar')
|
||||
self.assertEquals(resp.status_code, status.HTTP_406_NOT_ACCEPTABLE)
|
||||
|
||||
def test_specified_renderer_serializes_content_on_format_query(self):
|
||||
"""If a 'format' query is specified, the renderer with the matching
|
||||
format attribute should serialize the response."""
|
||||
resp = self.client.get('/?format=%s' % RendererB.format)
|
||||
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_format_kwargs(self):
|
||||
"""If a 'format' keyword arg is specified, the renderer with the matching
|
||||
format attribute should serialize the response."""
|
||||
resp = self.client.get('/something.formatb')
|
||||
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_is_used_on_format_query_with_matching_accept(self):
|
||||
"""If both a 'format' query and a matching Accept header specified,
|
||||
the renderer with the matching format attribute should serialize the response."""
|
||||
resp = self.client.get('/?format=%s' % RendererB.format,
|
||||
HTTP_ACCEPT=RendererB.media_type)
|
||||
self.assertEquals(resp['Content-Type'], RendererB.media_type)
|
||||
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
|
||||
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
||||
|
||||
|
||||
class Issue122Tests(TestCase):
|
||||
"""
|
||||
Tests that covers #122.
|
||||
"""
|
||||
urls = 'djangorestframework.tests.response'
|
||||
|
||||
def test_only_html_renderer(self):
|
||||
"""
|
||||
Test if no infinite recursion occurs.
|
||||
"""
|
||||
self.client.get('/html')
|
||||
|
||||
def test_html_renderer_is_first(self):
|
||||
"""
|
||||
Test if no infinite recursion occurs.
|
||||
"""
|
||||
self.client.get('/html1')
|
|
@ -1,35 +0,0 @@
|
|||
from django.conf.urls.defaults import patterns, url
|
||||
from django.test import TestCase
|
||||
from django.utils import simplejson as json
|
||||
|
||||
from djangorestframework.renderers import JSONRenderer
|
||||
from djangorestframework.reverse import reverse
|
||||
from djangorestframework.views import APIView
|
||||
from djangorestframework.response import Response
|
||||
|
||||
|
||||
class MyView(APIView):
|
||||
"""
|
||||
Mock resource which simply returns a URL, so that we can ensure
|
||||
that reversed URLs are fully qualified.
|
||||
"""
|
||||
renderers = (JSONRenderer, )
|
||||
|
||||
def get(self, request):
|
||||
return Response(reverse('myview', request=request))
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^myview$', MyView.as_view(), name='myview'),
|
||||
)
|
||||
|
||||
|
||||
class ReverseTests(TestCase):
|
||||
"""
|
||||
Tests for fully qualifed URLs when using `reverse`.
|
||||
"""
|
||||
urls = 'djangorestframework.tests.reverse'
|
||||
|
||||
def test_reversed_urls_are_fully_qualified(self):
|
||||
response = self.client.get('/myview')
|
||||
self.assertEqual(json.loads(response.content), 'http://testserver/myview')
|
|
@ -1,117 +0,0 @@
|
|||
import datetime
|
||||
from django.test import TestCase
|
||||
from djangorestframework import serializers
|
||||
|
||||
|
||||
class Comment(object):
|
||||
def __init__(self, email, content, created):
|
||||
self.email = email
|
||||
self.content = content
|
||||
self.created = created or datetime.datetime.now()
|
||||
|
||||
def __eq__(self, other):
|
||||
return all([getattr(self, attr) == getattr(other, attr)
|
||||
for attr in ('email', 'content', 'created')])
|
||||
|
||||
|
||||
class CommentSerializer(serializers.Serializer):
|
||||
email = serializers.EmailField()
|
||||
content = serializers.CharField(max_length=1000)
|
||||
created = serializers.DateTimeField()
|
||||
|
||||
def restore_object(self, data, instance=None):
|
||||
if instance is None:
|
||||
return Comment(**data)
|
||||
for key, val in data.items():
|
||||
setattr(instance, key, val)
|
||||
return instance
|
||||
|
||||
|
||||
class BasicTests(TestCase):
|
||||
def setUp(self):
|
||||
self.comment = Comment(
|
||||
'tom@example.com',
|
||||
'Happy new year!',
|
||||
datetime.datetime(2012, 1, 1)
|
||||
)
|
||||
self.data = {
|
||||
'email': 'tom@example.com',
|
||||
'content': 'Happy new year!',
|
||||
'created': datetime.datetime(2012, 1, 1)
|
||||
}
|
||||
|
||||
def test_empty(self):
|
||||
serializer = CommentSerializer()
|
||||
expected = {
|
||||
'email': '',
|
||||
'content': '',
|
||||
'created': None
|
||||
}
|
||||
self.assertEquals(serializer.data, expected)
|
||||
|
||||
def test_serialization(self):
|
||||
serializer = CommentSerializer(instance=self.comment)
|
||||
expected = self.data
|
||||
self.assertEquals(serializer.data, expected)
|
||||
|
||||
def test_deserialization_for_create(self):
|
||||
serializer = CommentSerializer(self.data)
|
||||
expected = self.comment
|
||||
self.assertEquals(serializer.is_valid(), True)
|
||||
self.assertEquals(serializer.object, expected)
|
||||
self.assertFalse(serializer.object is expected)
|
||||
|
||||
def test_deserialization_for_update(self):
|
||||
serializer = CommentSerializer(self.data, instance=self.comment)
|
||||
expected = self.comment
|
||||
self.assertEquals(serializer.is_valid(), True)
|
||||
self.assertEquals(serializer.object, expected)
|
||||
self.assertTrue(serializer.object is expected)
|
||||
|
||||
|
||||
class ValidationTests(TestCase):
|
||||
def setUp(self):
|
||||
self.comment = Comment(
|
||||
'tom@example.com',
|
||||
'Happy new year!',
|
||||
datetime.datetime(2012, 1, 1)
|
||||
)
|
||||
self.data = {
|
||||
'email': 'tom@example.com',
|
||||
'content': 'x' * 1001,
|
||||
'created': datetime.datetime(2012, 1, 1)
|
||||
}
|
||||
|
||||
def test_deserialization_for_create(self):
|
||||
serializer = CommentSerializer(self.data)
|
||||
self.assertEquals(serializer.is_valid(), False)
|
||||
self.assertEquals(serializer.errors, {'content': [u'Ensure this value has at most 1000 characters (it has 1001).']})
|
||||
|
||||
def test_deserialization_for_update(self):
|
||||
serializer = CommentSerializer(self.data, instance=self.comment)
|
||||
self.assertEquals(serializer.is_valid(), False)
|
||||
self.assertEquals(serializer.errors, {'content': [u'Ensure this value has at most 1000 characters (it has 1001).']})
|
||||
|
||||
|
||||
class MetadataTests(TestCase):
|
||||
# def setUp(self):
|
||||
# self.comment = Comment(
|
||||
# 'tomchristie',
|
||||
# 'Happy new year!',
|
||||
# datetime.datetime(2012, 1, 1)
|
||||
# )
|
||||
# self.data = {
|
||||
# 'email': 'tomchristie',
|
||||
# 'content': 'Happy new year!',
|
||||
# 'created': datetime.datetime(2012, 1, 1)
|
||||
# }
|
||||
|
||||
def test_empty(self):
|
||||
serializer = CommentSerializer()
|
||||
expected = {
|
||||
'email': serializers.CharField,
|
||||
'content': serializers.CharField,
|
||||
'created': serializers.DateTimeField
|
||||
}
|
||||
for field_name, field in expected.items():
|
||||
self.assertTrue(isinstance(serializer.data.fields[field_name], field))
|
|
@ -1,12 +0,0 @@
|
|||
"""Tests for the status module"""
|
||||
from django.test import TestCase
|
||||
from djangorestframework import status
|
||||
|
||||
|
||||
class TestStatus(TestCase):
|
||||
"""Simple sanity test to check the status module"""
|
||||
|
||||
def test_status(self):
|
||||
"""Ensure the status module is present and correct."""
|
||||
self.assertEquals(200, status.HTTP_200_OK)
|
||||
self.assertEquals(404, status.HTTP_404_NOT_FOUND)
|
|
@ -1,63 +0,0 @@
|
|||
# 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)
|
|
@ -1,144 +0,0 @@
|
|||
"""
|
||||
Tests for the throttling implementations in the permissions module.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.cache import cache
|
||||
|
||||
from djangorestframework.compat import RequestFactory
|
||||
from djangorestframework.views import APIView
|
||||
from djangorestframework.throttling import UserRateThrottle
|
||||
from djangorestframework.response import Response
|
||||
|
||||
|
||||
class User3SecRateThrottle(UserRateThrottle):
|
||||
rate = '3/sec'
|
||||
scope = 'seconds'
|
||||
|
||||
|
||||
class User3MinRateThrottle(UserRateThrottle):
|
||||
rate = '3/min'
|
||||
scope = 'minutes'
|
||||
|
||||
|
||||
class MockView(APIView):
|
||||
throttle_classes = (User3SecRateThrottle,)
|
||||
|
||||
def get(self, request):
|
||||
return Response('foo')
|
||||
|
||||
|
||||
class MockView_MinuteThrottling(APIView):
|
||||
throttle_classes = (User3MinRateThrottle,)
|
||||
|
||||
def get(self, request):
|
||||
return Response('foo')
|
||||
|
||||
|
||||
class ThrottlingTests(TestCase):
|
||||
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
|
||||
"""
|
||||
request = self.factory.get('/')
|
||||
for dummy in range(4):
|
||||
response = MockView.as_view()(request)
|
||||
self.assertEqual(429, response.status_code)
|
||||
|
||||
def set_throttle_timer(self, view, value):
|
||||
"""
|
||||
Explicitly set the timer, overriding time.time()
|
||||
"""
|
||||
view.throttle_classes[0].timer = lambda self: value
|
||||
|
||||
def test_request_throttling_expires(self):
|
||||
"""
|
||||
Ensure request rate is limited for a limited duration only
|
||||
"""
|
||||
self.set_throttle_timer(MockView, 0)
|
||||
|
||||
request = self.factory.get('/')
|
||||
for dummy in range(4):
|
||||
response = MockView.as_view()(request)
|
||||
self.assertEqual(429, response.status_code)
|
||||
|
||||
# Advance the timer by one second
|
||||
self.set_throttle_timer(MockView, 1)
|
||||
|
||||
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')
|
||||
for dummy in range(3):
|
||||
view.as_view()(request)
|
||||
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
|
||||
PerUserThrottles
|
||||
"""
|
||||
self.ensure_is_throttled(MockView, 200)
|
||||
|
||||
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
|
||||
set properly.
|
||||
"""
|
||||
request = self.factory.get('/')
|
||||
for timer, expect in expected_headers:
|
||||
self.set_throttle_timer(view, timer)
|
||||
response = view.as_view()(request)
|
||||
if expect is not None:
|
||||
self.assertEquals(response['X-Throttle-Wait-Seconds'], expect)
|
||||
else:
|
||||
self.assertFalse('X-Throttle-Wait-Seconds' in response.headers)
|
||||
|
||||
def test_seconds_fields(self):
|
||||
"""
|
||||
Ensure for second based throttles.
|
||||
"""
|
||||
self.ensure_response_header_contains_proper_throttle_field(MockView,
|
||||
((0, None),
|
||||
(0, None),
|
||||
(0, None),
|
||||
(0, '1')
|
||||
))
|
||||
|
||||
def test_minutes_fields(self):
|
||||
"""
|
||||
Ensure for minute based throttles.
|
||||
"""
|
||||
self.ensure_response_header_contains_proper_throttle_field(MockView_MinuteThrottling,
|
||||
((0, None),
|
||||
(0, None),
|
||||
(0, None),
|
||||
(0, '60')
|
||||
))
|
||||
|
||||
def test_next_rate_remains_constant_if_followed(self):
|
||||
"""
|
||||
If a client follows the recommended next request rate,
|
||||
the throttling rate should stay constant.
|
||||
"""
|
||||
self.ensure_response_header_contains_proper_throttle_field(MockView_MinuteThrottling,
|
||||
((0, None),
|
||||
(20, None),
|
||||
(40, None),
|
||||
(60, None),
|
||||
(80, None)
|
||||
))
|
|
@ -1,329 +0,0 @@
|
|||
# from django import forms
|
||||
# from django.db import models
|
||||
# from django.test import TestCase
|
||||
# from djangorestframework.response import ImmediateResponse
|
||||
# from djangorestframework.views import View
|
||||
|
||||
|
||||
# class TestDisabledValidations(TestCase):
|
||||
# """Tests on FormValidator with validation disabled by setting form to None"""
|
||||
|
||||
# def test_disabled_form_validator_returns_content_unchanged(self):
|
||||
# """If the view's form attribute is None then FormValidator(view).validate_request(content, None)
|
||||
# should just return the content unmodified."""
|
||||
# class DisabledFormResource(FormResource):
|
||||
# form = None
|
||||
|
||||
# class MockView(View):
|
||||
# resource = DisabledFormResource
|
||||
|
||||
# view = MockView()
|
||||
# content = {'qwerty': 'uiop'}
|
||||
# self.assertEqual(FormResource(view).validate_request(content, None), content)
|
||||
|
||||
# def test_disabled_form_validator_get_bound_form_returns_none(self):
|
||||
# """If the view's form attribute is None on then
|
||||
# FormValidator(view).get_bound_form(content) should just return None."""
|
||||
# class DisabledFormResource(FormResource):
|
||||
# form = None
|
||||
|
||||
# class MockView(View):
|
||||
# resource = DisabledFormResource
|
||||
|
||||
# view = MockView()
|
||||
# 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."""
|
||||
|
||||
# class DisabledModelFormView(View):
|
||||
# resource = ModelResource
|
||||
|
||||
# view = DisabledModelFormView()
|
||||
# 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)
|
||||
|
||||
|
||||
# class TestNonFieldErrors(TestCase):
|
||||
# """Tests against form validation errors caused by non-field errors. (eg as might be caused by some custom form validation)"""
|
||||
|
||||
# def test_validate_failed_due_to_non_field_error_returns_appropriate_message(self):
|
||||
# """If validation fails with a non-field error, ensure the response a non-field error"""
|
||||
# class MockForm(forms.Form):
|
||||
# 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
|
||||
|
||||
# class MockResource(FormResource):
|
||||
# form = MockForm
|
||||
|
||||
# class MockView(View):
|
||||
# pass
|
||||
|
||||
# view = MockView()
|
||||
# content = {'field1': 'example1', 'field2': 'example2'}
|
||||
# try:
|
||||
# MockResource(view).validate_request(content, None)
|
||||
# except ImmediateResponse, exc:
|
||||
# response = exc.response
|
||||
# self.assertEqual(response.raw_content, {'errors': [MockForm.ERROR_TEXT]})
|
||||
# else:
|
||||
# self.fail('ImmediateResponse was not raised')
|
||||
|
||||
|
||||
# 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):
|
||||
# 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.MockFormView = MockFormView
|
||||
# self.MockModelFormView = MockModelFormView
|
||||
|
||||
# def validation_returns_content_unchanged_if_already_valid_and_clean(self, validator):
|
||||
# """If the content is already valid and clean then validate(content) should just return the content unmodified."""
|
||||
# content = {'qwerty': 'uiop'}
|
||||
# self.assertEqual(validator.validate_request(content, None), content)
|
||||
|
||||
# def validation_failure_raises_response_exception(self, validator):
|
||||
# """If form validation fails a ResourceException 400 (Bad Request) should be raised."""
|
||||
# content = {}
|
||||
# self.assertRaises(ImmediateResponse, validator.validate_request, content, None)
|
||||
|
||||
# def validation_does_not_allow_extra_fields_by_default(self, validator):
|
||||
# """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'}
|
||||
# self.assertRaises(ImmediateResponse, 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'}
|
||||
# validator._validate(content, None, allowed_extra_fields=('extra',))
|
||||
|
||||
# def validation_allows_unknown_fields_if_explicitly_allowed(self, validator):
|
||||
# """If we set ``unknown_form_fields`` on the form resource, then don't
|
||||
# raise errors on unexpected request data"""
|
||||
# content = {'qwerty': 'uiop', 'extra': 'extra'}
|
||||
# validator.allow_unknown_form_fields = True
|
||||
# self.assertEqual({'qwerty': u'uiop'},
|
||||
# validator.validate_request(content, None),
|
||||
# "Resource didn't accept unknown fields.")
|
||||
# validator.allow_unknown_form_fields = False
|
||||
|
||||
# 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'}
|
||||
# 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 = {}
|
||||
# try:
|
||||
# validator.validate_request(content, None)
|
||||
# except ImmediateResponse, exc:
|
||||
# response = exc.response
|
||||
# self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
|
||||
# else:
|
||||
# self.fail('ResourceException was not raised')
|
||||
|
||||
# def validation_failed_due_to_field_error_returns_appropriate_message(self, validator):
|
||||
# """If validation fails due to a field error, ensure the response contains a single field error"""
|
||||
# content = {'qwerty': ''}
|
||||
# try:
|
||||
# validator.validate_request(content, None)
|
||||
# except ImmediateResponse, exc:
|
||||
# response = exc.response
|
||||
# self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
|
||||
# else:
|
||||
# self.fail('ResourceException was not raised')
|
||||
|
||||
# def validation_failed_due_to_invalid_field_returns_appropriate_message(self, validator):
|
||||
# """If validation fails due to an invalid field, ensure the response contains a single field error"""
|
||||
# content = {'qwerty': 'uiop', 'extra': 'extra'}
|
||||
# try:
|
||||
# validator.validate_request(content, None)
|
||||
# except ImmediateResponse, exc:
|
||||
# response = exc.response
|
||||
# self.assertEqual(response.raw_content, {'field_errors': {'extra': ['This field does not exist.']}})
|
||||
# else:
|
||||
# self.fail('ResourceException was not raised')
|
||||
|
||||
# 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 ImmediateResponse, exc:
|
||||
# response = exc.response
|
||||
# self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.'],
|
||||
# 'extra': ['This field does not exist.']}})
|
||||
# else:
|
||||
# self.fail('ResourceException was not raised')
|
||||
|
||||
# # Tests on FormResource
|
||||
|
||||
# def test_form_validation_returns_content_unchanged_if_already_valid_and_clean(self):
|
||||
# validator = self.MockFormResource(self.MockFormView())
|
||||
# self.validation_returns_content_unchanged_if_already_valid_and_clean(validator)
|
||||
|
||||
# def test_form_validation_failure_raises_response_exception(self):
|
||||
# validator = self.MockFormResource(self.MockFormView())
|
||||
# self.validation_failure_raises_response_exception(validator)
|
||||
|
||||
# def test_validation_does_not_allow_extra_fields_by_default(self):
|
||||
# validator = self.MockFormResource(self.MockFormView())
|
||||
# self.validation_does_not_allow_extra_fields_by_default(validator)
|
||||
|
||||
# def test_validation_allows_extra_fields_if_explicitly_set(self):
|
||||
# validator = self.MockFormResource(self.MockFormView())
|
||||
# self.validation_allows_extra_fields_if_explicitly_set(validator)
|
||||
|
||||
# def test_validation_allows_unknown_fields_if_explicitly_allowed(self):
|
||||
# validator = self.MockFormResource(self.MockFormView())
|
||||
# self.validation_allows_unknown_fields_if_explicitly_allowed(validator)
|
||||
|
||||
# def test_validation_does_not_require_extra_fields_if_explicitly_set(self):
|
||||
# validator = self.MockFormResource(self.MockFormView())
|
||||
# self.validation_does_not_require_extra_fields_if_explicitly_set(validator)
|
||||
|
||||
# def test_validation_failed_due_to_no_content_returns_appropriate_message(self):
|
||||
# validator = self.MockFormResource(self.MockFormView())
|
||||
# self.validation_failed_due_to_no_content_returns_appropriate_message(validator)
|
||||
|
||||
# def test_validation_failed_due_to_field_error_returns_appropriate_message(self):
|
||||
# validator = self.MockFormResource(self.MockFormView())
|
||||
# self.validation_failed_due_to_field_error_returns_appropriate_message(validator)
|
||||
|
||||
# def test_validation_failed_due_to_invalid_field_returns_appropriate_message(self):
|
||||
# validator = self.MockFormResource(self.MockFormView())
|
||||
# self.validation_failed_due_to_invalid_field_returns_appropriate_message(validator)
|
||||
|
||||
# def test_validation_failed_due_to_multiple_errors_returns_appropriate_message(self):
|
||||
# validator = self.MockFormResource(self.MockFormView())
|
||||
# self.validation_failed_due_to_multiple_errors_returns_appropriate_message(validator)
|
||||
|
||||
# # Same tests on ModelResource
|
||||
|
||||
# def test_modelform_validation_returns_content_unchanged_if_already_valid_and_clean(self):
|
||||
# validator = self.MockModelResource(self.MockModelFormView())
|
||||
# self.validation_returns_content_unchanged_if_already_valid_and_clean(validator)
|
||||
|
||||
# def test_modelform_validation_failure_raises_response_exception(self):
|
||||
# validator = self.MockModelResource(self.MockModelFormView())
|
||||
# self.validation_failure_raises_response_exception(validator)
|
||||
|
||||
# def test_modelform_validation_does_not_allow_extra_fields_by_default(self):
|
||||
# validator = self.MockModelResource(self.MockModelFormView())
|
||||
# self.validation_does_not_allow_extra_fields_by_default(validator)
|
||||
|
||||
# def test_modelform_validation_allows_extra_fields_if_explicitly_set(self):
|
||||
# validator = self.MockModelResource(self.MockModelFormView())
|
||||
# self.validation_allows_extra_fields_if_explicitly_set(validator)
|
||||
|
||||
# def test_modelform_validation_does_not_require_extra_fields_if_explicitly_set(self):
|
||||
# validator = self.MockModelResource(self.MockModelFormView())
|
||||
# self.validation_does_not_require_extra_fields_if_explicitly_set(validator)
|
||||
|
||||
# def test_modelform_validation_failed_due_to_no_content_returns_appropriate_message(self):
|
||||
# validator = self.MockModelResource(self.MockModelFormView())
|
||||
# self.validation_failed_due_to_no_content_returns_appropriate_message(validator)
|
||||
|
||||
# def test_modelform_validation_failed_due_to_field_error_returns_appropriate_message(self):
|
||||
# validator = self.MockModelResource(self.MockModelFormView())
|
||||
# self.validation_failed_due_to_field_error_returns_appropriate_message(validator)
|
||||
|
||||
# def test_modelform_validation_failed_due_to_invalid_field_returns_appropriate_message(self):
|
||||
# validator = self.MockModelResource(self.MockModelFormView())
|
||||
# self.validation_failed_due_to_invalid_field_returns_appropriate_message(validator)
|
||||
|
||||
# def test_modelform_validation_failed_due_to_multiple_errors_returns_appropriate_message(self):
|
||||
# validator = self.MockModelResource(self.MockModelFormView())
|
||||
# self.validation_failed_due_to_multiple_errors_returns_appropriate_message(validator)
|
||||
|
||||
|
||||
# class TestModelFormValidator(TestCase):
|
||||
# """Tests specific to ModelFormValidatorMixin"""
|
||||
|
||||
# def setUp(self):
|
||||
# """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)
|
||||
|
||||
# def test_property_fields_are_allowed_on_model_forms(self):
|
||||
# """Validation on ModelForms may include property fields that exist on the Model to be included in the input."""
|
||||
# content = {'qwerty': 'example', 'uiop': 'example', 'readonly': 'read only'}
|
||||
# self.assertEqual(self.validator.validate_request(content, None), content)
|
||||
|
||||
# def test_property_fields_are_not_required_on_model_forms(self):
|
||||
# """Validation on ModelForms does not require property fields that exist on the Model to be included in the input."""
|
||||
# content = {'qwerty': 'example', 'uiop': 'example'}
|
||||
# self.assertEqual(self.validator.validate_request(content, None), content)
|
||||
|
||||
# def test_extra_fields_not_allowed_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 = {'qwerty': 'example', 'uiop': 'example', 'readonly': 'read only', 'extra': 'extra'}
|
||||
# self.assertRaises(ImmediateResponse, 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'}
|
||||
# self.assertRaises(ImmediateResponse, 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))
|
|
@ -1,128 +0,0 @@
|
|||
# from django.core.urlresolvers import reverse
|
||||
# from django.conf.urls.defaults import patterns, url, include
|
||||
# from django.http import HttpResponse
|
||||
# from django.test import TestCase
|
||||
# from django.utils import simplejson as json
|
||||
|
||||
# from djangorestframework.views import View
|
||||
|
||||
|
||||
# class MockView(View):
|
||||
# """This is a basic mock view"""
|
||||
# pass
|
||||
|
||||
|
||||
# class MockViewFinal(View):
|
||||
# """View with final() override"""
|
||||
|
||||
# def final(self, request, response, *args, **kwargs):
|
||||
# return HttpResponse('{"test": "passed"}', content_type="application/json")
|
||||
|
||||
|
||||
# # class ResourceMockView(View):
|
||||
# # """This is a resource-based mock view"""
|
||||
|
||||
# # class MockForm(forms.Form):
|
||||
# # 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')
|
||||
|
||||
# urlpatterns = patterns('',
|
||||
# url(r'^mock/$', MockView.as_view()),
|
||||
# url(r'^mock/final/$', MockViewFinal.as_view()),
|
||||
# # url(r'^resourcemock/$', ResourceMockView.as_view()),
|
||||
# # url(r'^model/$', ListOrCreateModelView.as_view(resource=MockResource)),
|
||||
# # url(r'^model/(?P<pk>[^/]+)/$', InstanceModelView.as_view(resource=MockResource)),
|
||||
# url(r'^restframework/', include('djangorestframework.urls', namespace='djangorestframework')),
|
||||
# )
|
||||
|
||||
|
||||
# class BaseViewTests(TestCase):
|
||||
# """Test the base view class of djangorestframework"""
|
||||
# urls = 'djangorestframework.tests.views'
|
||||
|
||||
# def test_view_call_final(self):
|
||||
# response = self.client.options('/mock/final/')
|
||||
# self.assertEqual(response['Content-Type'].split(';')[0], "application/json")
|
||||
# data = json.loads(response.content)
|
||||
# self.assertEqual(data['test'], 'passed')
|
||||
|
||||
# def test_options_method_simple_view(self):
|
||||
# response = self.client.options('/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',
|
||||
# })
|
||||
|
||||
# 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',
|
||||
# })
|
||||
|
||||
# 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',
|
||||
# })
|
||||
|
||||
# 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)
|
||||
# data = json.loads(response.content)
|
||||
# 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)
|
||||
|
||||
|
||||
# class ExtraViewsTests(TestCase):
|
||||
# """Test the extra views djangorestframework provides"""
|
||||
# urls = 'djangorestframework.tests.views'
|
||||
|
||||
# def test_login_view(self):
|
||||
# """Ensure the login view exists"""
|
||||
# response = self.client.get(reverse('djangorestframework:login'))
|
||||
# self.assertEqual(response.status_code, 200)
|
||||
# self.assertEqual(response['Content-Type'].split(';')[0], 'text/html')
|
||||
|
||||
# def test_logout_view(self):
|
||||
# """Ensure the logout view exists"""
|
||||
# response = self.client.get(reverse('djangorestframework:logout'))
|
||||
# self.assertEqual(response.status_code, 200)
|
||||
# self.assertEqual(response['Content-Type'].split(';')[0], 'text/html')
|
|
@ -1,217 +0,0 @@
|
|||
from django.core.cache import cache
|
||||
from djangorestframework.settings import api_settings
|
||||
import time
|
||||
|
||||
|
||||
class BaseThrottle(object):
|
||||
"""
|
||||
Rate throttling of requests.
|
||||
"""
|
||||
|
||||
def __init__(self, view=None):
|
||||
"""
|
||||
All throttles hold a reference to the instantiating view.
|
||||
"""
|
||||
self.view = view
|
||||
|
||||
def allow_request(self, request):
|
||||
"""
|
||||
Return `True` if the request should be allowed, `False` otherwise.
|
||||
"""
|
||||
raise NotImplementedError('.allow_request() must be overridden')
|
||||
|
||||
def wait(self):
|
||||
"""
|
||||
Optionally, return a recommeded number of seconds to wait before
|
||||
the next request.
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
class SimpleRateThottle(BaseThrottle):
|
||||
"""
|
||||
A simple cache implementation, that only requires `.get_cache_key()`
|
||||
to be overridden.
|
||||
|
||||
The rate (requests / seconds) is set by a :attr:`throttle` attribute
|
||||
on the :class:`.View` class. The attribute is a string of the form 'number of
|
||||
requests/period'.
|
||||
|
||||
Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day')
|
||||
|
||||
Previous request information used for throttling is stored in the cache.
|
||||
"""
|
||||
|
||||
timer = time.time
|
||||
settings = api_settings
|
||||
cache_format = 'throtte_%(scope)s_%(ident)s'
|
||||
scope = None
|
||||
|
||||
def __init__(self, view):
|
||||
super(SimpleRateThottle, self).__init__(view)
|
||||
rate = self.get_rate_description()
|
||||
self.num_requests, self.duration = self.parse_rate_description(rate)
|
||||
|
||||
def get_cache_key(self, request):
|
||||
"""
|
||||
Should return a unique cache-key which can be used for throttling.
|
||||
Must be overridden.
|
||||
|
||||
May return `None` if the request should not be throttled.
|
||||
"""
|
||||
raise NotImplementedError('.get_cache_key() must be overridden')
|
||||
|
||||
def get_rate_description(self):
|
||||
"""
|
||||
Determine the string representation of the allowed request rate.
|
||||
"""
|
||||
try:
|
||||
return self.rate
|
||||
except AttributeError:
|
||||
return self.settings.DEFAULT_THROTTLE_RATES.get(self.scope)
|
||||
|
||||
def parse_rate_description(self, rate):
|
||||
"""
|
||||
Given the request rate string, return a two tuple of:
|
||||
<allowed number of requests>, <period of time in seconds>
|
||||
"""
|
||||
assert rate, "No throttle rate set for '%s'" % self.__class__.__name__
|
||||
num, period = rate.split('/')
|
||||
num_requests = int(num)
|
||||
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
|
||||
return (num_requests, duration)
|
||||
|
||||
def allow_request(self, request):
|
||||
"""
|
||||
Implement the check to see if the request should be throttled.
|
||||
|
||||
On success calls `throttle_success`.
|
||||
On failure calls `throttle_failure`.
|
||||
"""
|
||||
self.key = self.get_cache_key(request)
|
||||
self.history = cache.get(self.key, [])
|
||||
self.now = self.timer()
|
||||
|
||||
# Drop any requests from the history which have now passed the
|
||||
# throttle duration
|
||||
while self.history and self.history[-1] <= self.now - self.duration:
|
||||
self.history.pop()
|
||||
if len(self.history) >= self.num_requests:
|
||||
return self.throttle_failure()
|
||||
return self.throttle_success()
|
||||
|
||||
def throttle_success(self):
|
||||
"""
|
||||
Inserts the current request's timestamp along with the key
|
||||
into the cache.
|
||||
"""
|
||||
self.history.insert(0, self.now)
|
||||
cache.set(self.key, self.history, self.duration)
|
||||
return True
|
||||
|
||||
def throttle_failure(self):
|
||||
"""
|
||||
Called when a request to the API has failed due to throttling.
|
||||
"""
|
||||
return False
|
||||
|
||||
def wait(self):
|
||||
"""
|
||||
Returns the recommended next request time in seconds.
|
||||
"""
|
||||
if self.history:
|
||||
remaining_duration = self.duration - (self.now - self.history[-1])
|
||||
else:
|
||||
remaining_duration = self.duration
|
||||
|
||||
available_requests = self.num_requests - len(self.history) + 1
|
||||
|
||||
return remaining_duration / float(available_requests)
|
||||
|
||||
|
||||
class AnonRateThrottle(SimpleRateThottle):
|
||||
"""
|
||||
Limits the rate of API calls that may be made by a anonymous users.
|
||||
|
||||
The IP address of the request will be used as the unqiue cache key.
|
||||
"""
|
||||
scope = 'anon'
|
||||
|
||||
def get_cache_key(self, request):
|
||||
if request.user.is_authenticated():
|
||||
return None # Only throttle unauthenticated requests.
|
||||
|
||||
ident = request.META.get('REMOTE_ADDR', None)
|
||||
|
||||
return self.cache_format % {
|
||||
'scope': self.scope,
|
||||
'ident': ident
|
||||
}
|
||||
|
||||
|
||||
class UserRateThrottle(SimpleRateThottle):
|
||||
"""
|
||||
Limits the rate of API calls that may be made by a given user.
|
||||
|
||||
The user id will be used as a unique cache key if the user is
|
||||
authenticated. For anonymous requests, the IP address of the request will
|
||||
be used.
|
||||
"""
|
||||
scope = 'user'
|
||||
|
||||
def get_cache_key(self, request):
|
||||
if request.user.is_authenticated():
|
||||
ident = request.user.id
|
||||
else:
|
||||
ident = request.META.get('REMOTE_ADDR', None)
|
||||
|
||||
return self.cache_format % {
|
||||
'scope': self.scope,
|
||||
'ident': ident
|
||||
}
|
||||
|
||||
|
||||
class ScopedRateThrottle(SimpleRateThottle):
|
||||
"""
|
||||
Limits the rate of API calls by different amounts for various parts of
|
||||
the API. Any view that has the `throttle_scope` property set will be
|
||||
throttled. The unique cache key will be generated by concatenating the
|
||||
user id of the request, and the scope of the view being accessed.
|
||||
"""
|
||||
|
||||
scope_attr = 'throttle_scope'
|
||||
|
||||
def __init__(self, view):
|
||||
"""
|
||||
Scope is determined from the view being accessed.
|
||||
"""
|
||||
self.scope = getattr(self.view, self.scope_attr, None)
|
||||
super(ScopedRateThrottle, self).__init__(view)
|
||||
|
||||
def parse_rate_description(self, rate):
|
||||
"""
|
||||
Subclassed so that we don't fail if `view.throttle_scope` is not set.
|
||||
"""
|
||||
if not rate:
|
||||
return (None, None)
|
||||
return super(ScopedRateThrottle, self).parse_rate_description(rate)
|
||||
|
||||
def get_cache_key(self, request):
|
||||
"""
|
||||
If `view.throttle_scope` is not set, don't apply this throttle.
|
||||
|
||||
Otherwise generate the unique cache key by concatenating the user id
|
||||
with the '.throttle_scope` property of the view.
|
||||
"""
|
||||
if not self.scope:
|
||||
return None # Only throttle views if `.throttle_scope` is set.
|
||||
|
||||
if request.user.is_authenticated():
|
||||
ident = request.user.id
|
||||
else:
|
||||
ident = request.META.get('REMOTE_ADDR', None)
|
||||
|
||||
return self.cache_format % {
|
||||
'scope': self.scope,
|
||||
'ident': ident
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
from django.conf.urls.defaults import url
|
||||
from djangorestframework.settings import api_settings
|
||||
|
||||
|
||||
def format_suffix_patterns(urlpatterns, suffix_required=False, suffix_kwarg=None):
|
||||
"""
|
||||
Supplement existing urlpatterns with corrosponding patterns that also
|
||||
include a '.format' suffix. Retains urlpattern ordering.
|
||||
"""
|
||||
suffix_kwarg = suffix_kwarg or api_settings.FORMAT_SUFFIX_KWARG
|
||||
suffix_pattern = '.(?P<%s>[a-z]+)$' % suffix_kwarg
|
||||
|
||||
ret = []
|
||||
for urlpattern in urlpatterns:
|
||||
# Form our complementing '.format' urlpattern
|
||||
regex = urlpattern.regex.pattern.rstrip('$') + suffix_pattern
|
||||
view = urlpattern._callback or urlpattern._callback_str
|
||||
kwargs = urlpattern.default_args
|
||||
name = urlpattern.name
|
||||
# Add in both the existing and the new urlpattern
|
||||
if not suffix_required:
|
||||
ret.append(urlpattern)
|
||||
ret.append(url(regex, view, kwargs, name))
|
||||
return ret
|
|
@ -1,23 +0,0 @@
|
|||
"""
|
||||
Login and logout views for the browseable API.
|
||||
|
||||
Add these to your root URLconf if you're using the browseable API and
|
||||
your API requires authentication.
|
||||
|
||||
The urls must be namespaced as 'djangorestframework', and you should make sure
|
||||
your authentication settings include `SessionAuthentication`.
|
||||
|
||||
urlpatterns = patterns('',
|
||||
...
|
||||
url(r'^auth', include('djangorestframework.urls', namespace='djangorestframework'))
|
||||
)
|
||||
"""
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
|
||||
template_name = {'template_name': 'djangorestframework/login.html'}
|
||||
|
||||
urlpatterns = patterns('django.contrib.auth.views',
|
||||
url(r'^login/$', 'login', template_name, name='login'),
|
||||
url(r'^logout/$', 'logout', template_name, name='logout'),
|
||||
)
|
|
@ -1,101 +0,0 @@
|
|||
from django.utils.encoding import smart_unicode
|
||||
from django.utils.xmlutils import SimplerXMLGenerator
|
||||
from djangorestframework.compat import StringIO
|
||||
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
# From xml2dict
|
||||
class XML2Dict(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _parse_node(self, node):
|
||||
node_tree = {}
|
||||
# Save attrs and text, hope there will not be a child with same name
|
||||
if node.text:
|
||||
node_tree = node.text
|
||||
for (k, v) in node.attrib.items():
|
||||
k, v = self._namespace_split(k, v)
|
||||
node_tree[k] = v
|
||||
#Save childrens
|
||||
for child in node.getchildren():
|
||||
tag, tree = self._namespace_split(child.tag, self._parse_node(child))
|
||||
if tag not in node_tree: # the first time, so store it in dict
|
||||
node_tree[tag] = tree
|
||||
continue
|
||||
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
|
||||
|
||||
return node_tree
|
||||
|
||||
def _namespace_split(self, tag, value):
|
||||
"""
|
||||
Split the tag '{http://cs.sfsu.edu/csc867/myscheduler}patients'
|
||||
ns = http://cs.sfsu.edu/csc867/myscheduler
|
||||
name = patients
|
||||
"""
|
||||
result = re.compile("\{(.*)\}(.*)").search(tag)
|
||||
if result:
|
||||
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())
|
||||
|
||||
def fromstring(self, s):
|
||||
"""parse a string"""
|
||||
t = ET.fromstring(s)
|
||||
unused_root_tag, root_tree = self._namespace_split(t.tag, self._parse_node(t))
|
||||
return root_tree
|
||||
|
||||
|
||||
def xml2dict(input):
|
||||
return XML2Dict().fromstring(input)
|
||||
|
||||
|
||||
# Piston:
|
||||
class XMLRenderer():
|
||||
def _to_xml(self, xml, data):
|
||||
if isinstance(data, (list, tuple)):
|
||||
for item in data:
|
||||
xml.startElement("list-item", {})
|
||||
self._to_xml(xml, item)
|
||||
xml.endElement("list-item")
|
||||
|
||||
elif isinstance(data, dict):
|
||||
for key, value in data.iteritems():
|
||||
xml.startElement(key, {})
|
||||
self._to_xml(xml, value)
|
||||
xml.endElement(key)
|
||||
|
||||
elif data is None:
|
||||
# Don't output any value
|
||||
pass
|
||||
|
||||
else:
|
||||
xml.characters(smart_unicode(data))
|
||||
|
||||
def dict2xml(self, data):
|
||||
stream = StringIO.StringIO()
|
||||
|
||||
xml = SimplerXMLGenerator(stream, "utf-8")
|
||||
xml.startDocument()
|
||||
xml.startElement("root", {})
|
||||
|
||||
self._to_xml(xml, data)
|
||||
|
||||
xml.endElement("root")
|
||||
xml.endDocument()
|
||||
return stream.getvalue()
|
||||
|
||||
|
||||
def dict2xml(input):
|
||||
return XMLRenderer().dict2xml(input)
|
|
@ -1,34 +0,0 @@
|
|||
from django.core.urlresolvers import resolve, get_script_prefix
|
||||
|
||||
|
||||
def get_breadcrumbs(url):
|
||||
"""Given a url returns a list of breadcrumbs, which are each a tuple of (name, url)."""
|
||||
|
||||
from djangorestframework.views import APIView
|
||||
|
||||
def breadcrumbs_recursive(url, breadcrumbs_list, prefix):
|
||||
"""Add tuples of (name, url) to the breadcrumbs list, progressively chomping off parts of the url."""
|
||||
|
||||
try:
|
||||
(view, unused_args, unused_kwargs) = resolve(url)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
# Check if this is a REST framework view, and if so add it to the breadcrumbs
|
||||
if isinstance(getattr(view, 'cls_instance', None), APIView):
|
||||
breadcrumbs_list.insert(0, (view.cls_instance.get_name(), prefix + 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, prefix)
|
||||
|
||||
# Drop trailing non-slash off the end and continue to try to resolve more breadcrumbs
|
||||
return breadcrumbs_recursive(url[:url.rfind('/') + 1], breadcrumbs_list, prefix)
|
||||
|
||||
prefix = get_script_prefix().rstrip('/')
|
||||
url = url[len(prefix):]
|
||||
return breadcrumbs_recursive(url, [], prefix)
|
|
@ -1,38 +0,0 @@
|
|||
"""
|
||||
Helper classes for parsers.
|
||||
"""
|
||||
import datetime
|
||||
import decimal
|
||||
from django.utils import simplejson as json
|
||||
from djangorestframework.compat import timezone
|
||||
|
||||
|
||||
class JSONEncoder(json.JSONEncoder):
|
||||
"""
|
||||
JSONEncoder subclass that knows how to encode date/time,
|
||||
decimal types, and generators.
|
||||
"""
|
||||
def default(self, o):
|
||||
# For Date Time string spec, see ECMA 262
|
||||
# http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
|
||||
if isinstance(o, datetime.datetime):
|
||||
r = o.isoformat()
|
||||
if o.microsecond:
|
||||
r = r[:23] + r[26:]
|
||||
if r.endswith('+00:00'):
|
||||
r = r[:-6] + 'Z'
|
||||
return r
|
||||
elif isinstance(o, datetime.date):
|
||||
return o.isoformat()
|
||||
elif isinstance(o, datetime.time):
|
||||
if timezone and timezone.is_aware(o):
|
||||
raise ValueError("JSON can't represent timezone-aware times.")
|
||||
r = o.isoformat()
|
||||
if o.microsecond:
|
||||
r = r[:12]
|
||||
return r
|
||||
elif isinstance(o, decimal.Decimal):
|
||||
return str(o)
|
||||
elif hasattr(o, '__iter__'):
|
||||
return [i for i in o]
|
||||
return super(JSONEncoder, self).default(o)
|
|
@ -1,113 +0,0 @@
|
|||
"""
|
||||
Handling of media types, as found in HTTP Content-Type and Accept headers.
|
||||
|
||||
See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
||||
"""
|
||||
|
||||
from django.http.multipartparser import parse_header
|
||||
|
||||
|
||||
def media_type_matches(lhs, rhs):
|
||||
"""
|
||||
Returns ``True`` if the media type in the first argument <= the
|
||||
media type in the second argument. The media types are strings
|
||||
as described by the HTTP spec.
|
||||
|
||||
Valid media type strings include:
|
||||
|
||||
'application/json; indent=4'
|
||||
'application/json'
|
||||
'text/*'
|
||||
'*/*'
|
||||
"""
|
||||
lhs = _MediaType(lhs)
|
||||
rhs = _MediaType(rhs)
|
||||
return lhs.match(rhs)
|
||||
|
||||
|
||||
def is_form_media_type(media_type):
|
||||
"""
|
||||
Return True if the media type is a valid form media type as defined by the HTML4 spec.
|
||||
(NB. HTML5 also adds text/plain to the list of valid form media types, but we don't support this here)
|
||||
"""
|
||||
media_type = _MediaType(media_type)
|
||||
return media_type.full_type == 'application/x-www-form-urlencoded' or \
|
||||
media_type.full_type == 'multipart/form-data'
|
||||
|
||||
|
||||
def add_media_type_param(media_type, key, val):
|
||||
"""
|
||||
Add a key, value parameter to a media type string, and return the new media type string.
|
||||
"""
|
||||
media_type = _MediaType(media_type)
|
||||
media_type.params[key] = val
|
||||
return str(media_type)
|
||||
|
||||
|
||||
def get_media_type_params(media_type):
|
||||
"""
|
||||
Return a dictionary of the parameters on the given media type.
|
||||
"""
|
||||
return _MediaType(media_type).params
|
||||
|
||||
|
||||
def order_by_precedence(media_type_lst):
|
||||
"""
|
||||
Returns a list of sets of media type strings, ordered by precedence.
|
||||
Precedence is determined by how specific a media type is:
|
||||
|
||||
3. 'type/subtype; param=val'
|
||||
2. 'type/subtype'
|
||||
1. 'type/*'
|
||||
0. '*/*'
|
||||
"""
|
||||
ret = [set(), set(), set(), set()]
|
||||
for media_type in media_type_lst:
|
||||
precedence = _MediaType(media_type).precedence
|
||||
ret[3 - precedence].add(media_type)
|
||||
return [media_types for media_types in ret if media_types]
|
||||
|
||||
|
||||
class _MediaType(object):
|
||||
def __init__(self, media_type_str):
|
||||
if media_type_str is None:
|
||||
media_type_str = ''
|
||||
self.orig = media_type_str
|
||||
self.full_type, self.params = parse_header(media_type_str)
|
||||
self.main_type, sep, self.sub_type = self.full_type.partition('/')
|
||||
|
||||
def match(self, other):
|
||||
"""Return true if this MediaType satisfies the given MediaType."""
|
||||
for key in self.params.keys():
|
||||
if key != 'q' and other.params.get(key, None) != self.params.get(key, None):
|
||||
return False
|
||||
|
||||
if self.sub_type != '*' and other.sub_type != '*' and other.sub_type != self.sub_type:
|
||||
return False
|
||||
|
||||
if self.main_type != '*' and other.main_type != '*' and other.main_type != self.main_type:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def precedence(self):
|
||||
"""
|
||||
Return a precedence level from 0-3 for the media type given how specific it is.
|
||||
"""
|
||||
if self.main_type == '*':
|
||||
return 0
|
||||
elif self.sub_type == '*':
|
||||
return 1
|
||||
elif not self.params or self.params.keys() == ['q']:
|
||||
return 2
|
||||
return 3
|
||||
|
||||
def __str__(self):
|
||||
return unicode(self).encode('utf-8')
|
||||
|
||||
def __unicode__(self):
|
||||
ret = "%s/%s" % (self.main_type, self.sub_type)
|
||||
for key, val in self.params.items():
|
||||
ret += "; %s=%s" % (key, val)
|
||||
return ret
|
|
@ -1,304 +0,0 @@
|
|||
"""
|
||||
The :mod:`views` module provides the Views you will most probably
|
||||
be subclassing in your implementation.
|
||||
|
||||
By setting or modifying class attributes on your view, you change it's predefined behaviour.
|
||||
"""
|
||||
|
||||
import re
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import Http404
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from djangorestframework.compat import View as _View, apply_markdown
|
||||
from djangorestframework.response import Response
|
||||
from djangorestframework.request import Request
|
||||
from djangorestframework.settings import api_settings
|
||||
from djangorestframework import status, exceptions
|
||||
|
||||
|
||||
def _remove_trailing_string(content, trailing):
|
||||
"""
|
||||
Strip trailing component `trailing` from `content` if it exists.
|
||||
Used when generating names from view classes.
|
||||
"""
|
||||
if content.endswith(trailing) and content != trailing:
|
||||
return content[:-len(trailing)]
|
||||
return content
|
||||
|
||||
|
||||
def _remove_leading_indent(content):
|
||||
"""
|
||||
Remove leading indent from a block of text.
|
||||
Used when generating descriptions from docstrings.
|
||||
"""
|
||||
whitespace_counts = [len(line) - len(line.lstrip(' '))
|
||||
for line in content.splitlines()[1:] if line.lstrip()]
|
||||
|
||||
# unindent the content if needed
|
||||
if whitespace_counts:
|
||||
whitespace_pattern = '^' + (' ' * min(whitespace_counts))
|
||||
return re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content)
|
||||
return content
|
||||
|
||||
|
||||
def _camelcase_to_spaces(content):
|
||||
"""
|
||||
Translate 'CamelCaseNames' to 'Camel Case Names'.
|
||||
Used when generating names from view classes.
|
||||
"""
|
||||
camelcase_boundry = '(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))'
|
||||
return re.sub(camelcase_boundry, ' \\1', content).strip()
|
||||
|
||||
|
||||
class APIView(_View):
|
||||
settings = api_settings
|
||||
|
||||
renderer_classes = api_settings.DEFAULT_RENDERERS
|
||||
parser_classes = api_settings.DEFAULT_PARSERS
|
||||
authentication_classes = api_settings.DEFAULT_AUTHENTICATION
|
||||
throttle_classes = api_settings.DEFAULT_THROTTLES
|
||||
permission_classes = api_settings.DEFAULT_PERMISSIONS
|
||||
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION
|
||||
|
||||
@classmethod
|
||||
def as_view(cls, **initkwargs):
|
||||
"""
|
||||
Override the default :meth:`as_view` to store an instance of the view
|
||||
as an attribute on the callable function. This allows us to discover
|
||||
information about the view when we do URL reverse lookups.
|
||||
"""
|
||||
view = super(APIView, cls).as_view(**initkwargs)
|
||||
view.cls_instance = cls(**initkwargs)
|
||||
return view
|
||||
|
||||
@property
|
||||
def allowed_methods(self):
|
||||
"""
|
||||
Return the list of allowed HTTP methods, uppercased.
|
||||
"""
|
||||
return [method.upper() for method in self.http_method_names
|
||||
if hasattr(self, method)]
|
||||
|
||||
@property
|
||||
def default_response_headers(self):
|
||||
return {
|
||||
'Allow': ', '.join(self.allowed_methods),
|
||||
'Vary': 'Authenticate, Accept'
|
||||
}
|
||||
|
||||
def get_name(self):
|
||||
"""
|
||||
Return the resource or view class name for use as this view's name.
|
||||
Override to customize.
|
||||
"""
|
||||
name = self.__class__.__name__
|
||||
name = _remove_trailing_string(name, 'View')
|
||||
return _camelcase_to_spaces(name)
|
||||
|
||||
def get_description(self, html=False):
|
||||
"""
|
||||
Return the resource or view docstring for use as this view's description.
|
||||
Override to customize.
|
||||
"""
|
||||
description = self.__doc__ or ''
|
||||
description = _remove_leading_indent(description)
|
||||
if html:
|
||||
return self.markup_description(description)
|
||||
return description
|
||||
|
||||
def markup_description(self, description):
|
||||
"""
|
||||
Apply HTML markup to the description of this view.
|
||||
"""
|
||||
if apply_markdown:
|
||||
description = apply_markdown(description)
|
||||
else:
|
||||
description = escape(description).replace('\n', '<br />')
|
||||
return mark_safe(description)
|
||||
|
||||
def http_method_not_allowed(self, request, *args, **kwargs):
|
||||
"""
|
||||
Called if `request.method` does not corrospond to a handler method.
|
||||
"""
|
||||
raise exceptions.MethodNotAllowed(request.method)
|
||||
|
||||
def permission_denied(self, request):
|
||||
"""
|
||||
If request is not permitted, determine what kind of exception to raise.
|
||||
"""
|
||||
raise exceptions.PermissionDenied()
|
||||
|
||||
def throttled(self, request, wait):
|
||||
"""
|
||||
If request is throttled, determine what kind of exception to raise.
|
||||
"""
|
||||
raise exceptions.Throttled(wait)
|
||||
|
||||
@property
|
||||
def _parsed_media_types(self):
|
||||
"""
|
||||
Return a list of all the media types that this view can parse.
|
||||
"""
|
||||
return [parser.media_type for parser in self.parser_classes]
|
||||
|
||||
@property
|
||||
def _default_parser(self):
|
||||
"""
|
||||
Return the view's default parser class.
|
||||
"""
|
||||
return self.parser_classes[0]
|
||||
|
||||
@property
|
||||
def _rendered_media_types(self):
|
||||
"""
|
||||
Return an list of all the media types that this response can render.
|
||||
"""
|
||||
return [renderer.media_type for renderer in self.renderer_classes]
|
||||
|
||||
@property
|
||||
def _rendered_formats(self):
|
||||
"""
|
||||
Return a list of all the formats that this response can render.
|
||||
"""
|
||||
return [renderer.format for renderer in self.renderer_classes]
|
||||
|
||||
@property
|
||||
def _default_renderer(self):
|
||||
"""
|
||||
Return the response's default renderer class.
|
||||
"""
|
||||
return self.renderer_classes[0]
|
||||
|
||||
def get_format_suffix(self, **kwargs):
|
||||
"""
|
||||
Determine if the request includes a '.json' style format suffix
|
||||
"""
|
||||
if self.settings.FORMAT_SUFFIX_KWARG:
|
||||
return kwargs.get(self.settings.FORMAT_SUFFIX_KWARG)
|
||||
|
||||
def get_renderers(self, format=None):
|
||||
"""
|
||||
Instantiates and returns the list of renderers that this view can use.
|
||||
"""
|
||||
return [renderer(self) for renderer in self.renderer_classes]
|
||||
|
||||
def get_permissions(self):
|
||||
"""
|
||||
Instantiates and returns the list of permissions that this view requires.
|
||||
"""
|
||||
return [permission(self) for permission in self.permission_classes]
|
||||
|
||||
def get_throttles(self):
|
||||
"""
|
||||
Instantiates and returns the list of thottles that this view uses.
|
||||
"""
|
||||
return [throttle(self) for throttle in self.throttle_classes]
|
||||
|
||||
def content_negotiation(self, request, force=False):
|
||||
"""
|
||||
Determine which renderer and media type to use render the response.
|
||||
"""
|
||||
renderers = self.get_renderers()
|
||||
conneg = self.content_negotiation_class()
|
||||
return conneg.negotiate(request, renderers, self.format, force)
|
||||
|
||||
def check_permissions(self, request, obj=None):
|
||||
"""
|
||||
Check if request should be permitted.
|
||||
"""
|
||||
for permission in self.get_permissions():
|
||||
if not permission.has_permission(request, obj):
|
||||
self.permission_denied(request)
|
||||
|
||||
def check_throttles(self, request):
|
||||
"""
|
||||
Check if request should be throttled.
|
||||
"""
|
||||
for throttle in self.get_throttles():
|
||||
if not throttle.allow_request(request):
|
||||
self.throttled(request, throttle.wait())
|
||||
|
||||
def initialize_request(self, request, *args, **kargs):
|
||||
"""
|
||||
Returns the initial request object.
|
||||
"""
|
||||
return Request(request, parser_classes=self.parser_classes,
|
||||
authentication_classes=self.authentication_classes)
|
||||
|
||||
def initial(self, request, *args, **kwargs):
|
||||
"""
|
||||
Runs anything that needs to occur prior to calling the method handlers.
|
||||
"""
|
||||
self.format = self.get_format_suffix(**kwargs)
|
||||
self.check_permissions(request)
|
||||
self.check_throttles(request)
|
||||
self.renderer, self.media_type = self.content_negotiation(request)
|
||||
|
||||
def finalize_response(self, request, response, *args, **kwargs):
|
||||
"""
|
||||
Returns the final response object.
|
||||
"""
|
||||
if isinstance(response, Response):
|
||||
if not getattr(self, 'renderer', None):
|
||||
self.renderer, self.media_type = self.content_negotiation(request, force=True)
|
||||
response.renderer = self.renderer
|
||||
response.media_type = self.media_type
|
||||
|
||||
for key, value in self.headers.items():
|
||||
response[key] = value
|
||||
|
||||
return response
|
||||
|
||||
def handle_exception(self, exc):
|
||||
"""
|
||||
Handle any exception that occurs, by returning an appropriate response,
|
||||
or re-raising the error.
|
||||
"""
|
||||
if isinstance(exc, exceptions.Throttled):
|
||||
# Throttle wait header
|
||||
self.headers['X-Throttle-Wait-Seconds'] = '%d' % exc.wait
|
||||
|
||||
if isinstance(exc, exceptions.APIException):
|
||||
return Response({'detail': exc.detail}, status=exc.status_code)
|
||||
elif isinstance(exc, Http404):
|
||||
return Response({'detail': 'Not found'},
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
elif isinstance(exc, PermissionDenied):
|
||||
return Response({'detail': 'Permission denied'},
|
||||
status=status.HTTP_403_FORBIDDEN)
|
||||
raise
|
||||
|
||||
# Note: session based authentication is explicitly CSRF validated,
|
||||
# all other authentication is CSRF exempt.
|
||||
@csrf_exempt
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
"""
|
||||
`.dispatch()` is pretty much the same as Django's regular dispatch,
|
||||
but with extra hooks for startup, finalize, and exception handling.
|
||||
"""
|
||||
request = self.initialize_request(request, *args, **kwargs)
|
||||
self.request = request
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.headers = self.default_response_headers
|
||||
|
||||
try:
|
||||
self.initial(request, *args, **kwargs)
|
||||
|
||||
# Get the appropriate handler method
|
||||
if request.method.lower() in self.http_method_names:
|
||||
handler = getattr(self, request.method.lower(),
|
||||
self.http_method_not_allowed)
|
||||
else:
|
||||
handler = self.http_method_not_allowed
|
||||
|
||||
response = handler(request, *args, **kwargs)
|
||||
|
||||
except Exception as exc:
|
||||
response = self.handle_exception(exc)
|
||||
|
||||
self.response = self.finalize_response(request, response, *args, **kwargs)
|
||||
return self.response
|
|
@ -1,2 +1 @@
|
|||
Django>=1.3
|
||||
URLObject>=2.0.0
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
|
||||
This CSS file contains some tweaks specific to the included Bootstrap theme.
|
||||
It's separate from `style.css` so that it can be easily overridden by replacing
|
||||
a single block in the template.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
.form-actions {
|
||||
background: transparent;
|
||||
border-top-color: transparent;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.navbar-inverse .brand a {
|
||||
color: #999;
|
||||
}
|
||||
.navbar-inverse .brand:hover a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
.com { color: #93a1a1; }
|
||||
.lit { color: #195f91; }
|
||||
.pun, .opn, .clo { color: #93a1a1; }
|
||||
.fun { color: #dc322f; }
|
||||
.str, .atv { color: #D14; }
|
||||
.kwd, .prettyprint .tag { color: #1e347b; }
|
||||
.typ, .atn, .dec, .var { color: teal; }
|
||||
.pln { color: #48484c; }
|
||||
|
||||
.prettyprint {
|
||||
padding: 8px;
|
||||
background-color: #f7f7f9;
|
||||
border: 1px solid #e1e1e8;
|
||||
}
|
||||
.prettyprint.linenums {
|
||||
-webkit-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
|
||||
-moz-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
|
||||
box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
|
||||
}
|
||||
|
||||
/* Specify class=linenums on a pre to get line numbering */
|
||||
ol.linenums {
|
||||
margin: 0 0 0 33px; /* IE indents via margin-left */
|
||||
}
|
||||
ol.linenums li {
|
||||
padding-left: 12px;
|
||||
color: #bebec5;
|
||||
line-height: 20px;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
body {
|
||||
padding-top: 0;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
/* The navbar is fixed at >= 980px wide, so add padding to the body to prevent
|
||||
content running up underneath it. */
|
||||
@media (min-width:980px) {
|
||||
body {
|
||||
padding-top: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h2, h3 {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.resource-description, .response-info {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
#footer {
|
||||
border-top: 1px solid #eee;
|
||||
margin-top: 2em;
|
||||
padding-top: 1em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.version:before {
|
||||
content: "v";
|
||||
opacity: 0.6;
|
||||
padding-right: 0.25em;
|
||||
}
|
||||
|
||||
.format-option {
|
||||
font-family: Menlo, Consolas, "Andale Mono", "Lucida Console", monospace;
|
||||
}
|
||||
|
||||
#options-form {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* To allow tooltips to work on disabled elements */
|
||||
.disabled-tooltip-shield {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#options-form {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.errorlist {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
word-wrap: normal;
|
||||
white-space: pre;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0px;
|
||||
}
|
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 12 KiB |
|
@ -1,28 +0,0 @@
|
|||
var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
|
||||
(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
|
||||
[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
|
||||
f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
|
||||
(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
|
||||
{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
|
||||
t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
|
||||
"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
|
||||
l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
|
||||
q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
|
||||
q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
|
||||
"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
|
||||
a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
|
||||
for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
|
||||
m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
|
||||
a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
|
||||
j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
|
||||
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
|
||||
H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
|
||||
J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
|
||||
I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
|
||||
["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
|
||||
/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
|
||||
["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
|
||||
hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
|
||||
!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
|
||||
250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
|
||||
PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();
|
0
rest_framework/static/rest_framework/img/glyphicons-halflings-white.png
Executable file → Normal file
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
0
rest_framework/static/rest_framework/img/glyphicons-halflings.png
Executable file → Normal file
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |