diff options
Diffstat (limited to 'djangorestframework')
68 files changed, 0 insertions, 9059 deletions
diff --git a/djangorestframework/__init__.py b/djangorestframework/__init__.py deleted file mode 100644 index 557f5943..00000000 --- a/djangorestframework/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -__version__ = '2.0.0' - -VERSION = __version__ # synonym diff --git a/djangorestframework/authentication.py b/djangorestframework/authentication.py deleted file mode 100644 index a6634392..00000000 --- a/djangorestframework/authentication.py +++ /dev/null @@ -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 diff --git a/djangorestframework/authtoken/__init__.py b/djangorestframework/authtoken/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/djangorestframework/authtoken/__init__.py +++ /dev/null diff --git a/djangorestframework/authtoken/migrations/0001_initial.py b/djangorestframework/authtoken/migrations/0001_initial.py deleted file mode 100644 index a91006b0..00000000 --- a/djangorestframework/authtoken/migrations/0001_initial.py +++ /dev/null @@ -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']
\ No newline at end of file diff --git a/djangorestframework/authtoken/migrations/__init__.py b/djangorestframework/authtoken/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/djangorestframework/authtoken/migrations/__init__.py +++ /dev/null diff --git a/djangorestframework/authtoken/models.py b/djangorestframework/authtoken/models.py deleted file mode 100644 index fd47e6c7..00000000 --- a/djangorestframework/authtoken/models.py +++ /dev/null @@ -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() diff --git a/djangorestframework/authtoken/views.py b/djangorestframework/authtoken/views.py deleted file mode 100644 index e69de29b..00000000 --- a/djangorestframework/authtoken/views.py +++ /dev/null diff --git a/djangorestframework/compat.py b/djangorestframework/compat.py deleted file mode 100644 index f21dc4ef..00000000 --- a/djangorestframework/compat.py +++ /dev/null @@ -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 diff --git a/djangorestframework/decorators.py b/djangorestframework/decorators.py deleted file mode 100644 index ac976bec..00000000 --- a/djangorestframework/decorators.py +++ /dev/null @@ -1,94 +0,0 @@ -from functools import wraps -from django.utils.decorators import available_attrs -from djangorestframework.views import APIView - - -class LazyViewCreator(object): - - """ - This class is responsible for dynamically creating an APIView subclass that - will wrap a function-based view. Instances of this class are created - by the function-based view decorators (below), and each decorator is - responsible for setting attributes on the instance that will eventually be - copied onto the final class-based view. The CBV gets created lazily the first - time it's needed, and then cached for future use. - - This is done so that the ordering of stacked decorators is irrelevant. - """ - - def __init__(self, wrapped_view): - - self.wrapped_view = wrapped_view - - # Each item in this dictionary will be copied onto the final - # class-based view that gets created when this object is called - self.final_view_attrs = { - 'http_method_names': APIView.http_method_names, - 'renderer_classes': APIView.renderer_classes, - 'parser_classes': APIView.parser_classes, - 'authentication_classes': APIView.authentication_classes, - 'throttle_classes': APIView.throttle_classes, - 'permission_classes': APIView.permission_classes, - } - self._cached_view = None - - def handler(self, *args, **kwargs): - return self.wrapped_view(*args, **kwargs) - - @property - def view(self): - """ - Accessor for the dynamically created class-based view. This will - be created if necessary and cached for next time. - """ - - if self._cached_view is None: - - class WrappedAPIView(APIView): - pass - - for attr, value in self.final_view_attrs.items(): - setattr(WrappedAPIView, attr, value) - - # Attach the wrapped view function for each of the - # allowed HTTP methods - for method in WrappedAPIView.http_method_names: - setattr(WrappedAPIView, method.lower(), self.handler) - - self._cached_view = WrappedAPIView.as_view() - - return self._cached_view - - def __call__(self, *args, **kwargs): - """ - This is the actual code that gets run per-request - """ - return self.view(*args, **kwargs) - - @staticmethod - def maybe_create(func_or_instance): - """ - If the argument is already an instance of LazyViewCreator, - just return it. Otherwise, create a new one. - """ - if isinstance(func_or_instance, LazyViewCreator): - return func_or_instance - return LazyViewCreator(func_or_instance) - - -def _create_attribute_setting_decorator(attribute, filter=lambda item: item): - def decorator(value): - def inner(func): - wrapper = LazyViewCreator.maybe_create(func) - wrapper.final_view_attrs[attribute] = filter(value) - return wrapper - return inner - return decorator - - -api_view = _create_attribute_setting_decorator('http_method_names', filter=lambda methods: [method.lower() for method in methods]) -renderer_classes = _create_attribute_setting_decorator('renderer_classes') -parser_classes = _create_attribute_setting_decorator('parser_classes') -authentication_classes = _create_attribute_setting_decorator('authentication_classes') -throttle_classes = _create_attribute_setting_decorator('throttle_classes') -permission_classes = _create_attribute_setting_decorator('permission_classes') diff --git a/djangorestframework/exceptions.py b/djangorestframework/exceptions.py deleted file mode 100644 index 3f5b23f6..00000000 --- a/djangorestframework/exceptions.py +++ /dev/null @@ -1,62 +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 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 UnsupportedMediaType(APIException): - status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE - default_detail = "Unsupported media type '%s' in request." - - def __init__(self, media_type, detail=None): - self.detail = (detail or self.default_detail) % media_type - - -class Throttled(APIException): - status_code = status.HTTP_429_TOO_MANY_REQUESTS - default_detail = "Request was throttled." - extra_detail = "Expected available in %d second%s." - - def __init__(self, wait=None, detail=None): - import math - self.wait = wait and math.ceil(wait) or None - if wait is not None: - format = detail or self.default_detail + self.extra_detail - self.detail = format % (self.wait, self.wait != 1 and 's' or '') - else: - self.detail = detail or self.default_detail diff --git a/djangorestframework/fields.py b/djangorestframework/fields.py deleted file mode 100644 index 13b0e37d..00000000 --- a/djangorestframework/fields.py +++ /dev/null @@ -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) diff --git a/djangorestframework/generics.py b/djangorestframework/generics.py deleted file mode 100644 index 5815b73a..00000000 --- a/djangorestframework/generics.py +++ /dev/null @@ -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) diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py deleted file mode 100644 index 1f06dd34..00000000 --- a/djangorestframework/mixins.py +++ /dev/null @@ -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.error_data, 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) diff --git a/djangorestframework/models.py b/djangorestframework/models.py deleted file mode 100644 index 5b53a526..00000000 --- a/djangorestframework/models.py +++ /dev/null @@ -1 +0,0 @@ -# Just to keep things like ./manage.py test happy diff --git a/djangorestframework/parsers.py b/djangorestframework/parsers.py deleted file mode 100644 index fb08c5a0..00000000 --- a/djangorestframework/parsers.py +++ /dev/null @@ -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 diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py deleted file mode 100644 index 3a669822..00000000 --- a/djangorestframework/permissions.py +++ /dev/null @@ -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 diff --git a/djangorestframework/renderers.py b/djangorestframework/renderers.py deleted file mode 100644 index 26e8cba1..00000000 --- a/djangorestframework/renderers.py +++ /dev/null @@ -1,408 +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_response(self, accept): - """ - Returns :const:`True` if this renderer is able to deal with the given - *accept* media type. - - The default implementation for this function is to check the *accept* - 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. - """ - # TODO: format overriding must go out of here - format = None - if self.view is not None: - format = self.view.kwargs.get(self._FORMAT_QUERY_PARAM, None) - if format is None and self.view is not None: - format = self.view.request.GET.get(self._FORMAT_QUERY_PARAM, None) - - if format is not None: - return format == self.format - return media_type_matches(self.media_type, accept) - - 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 diff --git a/djangorestframework/request.py b/djangorestframework/request.py deleted file mode 100644 index 450d2ac7..00000000 --- a/djangorestframework/request.py +++ /dev/null @@ -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) diff --git a/djangorestframework/response.py b/djangorestframework/response.py deleted file mode 100644 index e1366bdb..00000000 --- a/djangorestframework/response.py +++ /dev/null @@ -1,173 +0,0 @@ -""" -The :mod:`response` module provides :class:`Response` and :class:`ImmediateResponse` classes. - -`Response` is a subclass of `HttpResponse`, and can be similarly instantiated and returned -from any view. It is a bit smarter than Django's `HttpResponse`, for it renders automatically -its content to a serial format by using a list of :mod:`renderers`. - -To determine the content type to which it must render, default behaviour is to use standard -HTTP Accept header content negotiation. But `Response` also supports overriding the content type -by specifying an ``_accept=`` parameter in the URL. Also, `Response` will ignore `Accept` headers -from Internet Explorer user agents and use a sensible browser `Accept` header instead. -""" - - -import re -from django.template.response import SimpleTemplateResponse -from django.core.handlers.wsgi import STATUS_CODE_TEXT -from djangorestframework.settings import api_settings -from djangorestframework.utils.mediatypes import order_by_precedence -from djangorestframework import status - - -MSIE_USER_AGENT_REGEX = re.compile(r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )') - - -class NotAcceptable(Exception): - pass - - -class Response(SimpleTemplateResponse): - """ - An HttpResponse that may include content that hasn't yet been serialized. - - Kwargs: - - content(object). The raw content, not yet serialized. - This must be native Python data that renderers can handle. - (e.g.: `dict`, `str`, ...) - - renderer_classes(list/tuple). The renderers to use for rendering the response content. - """ - - _ACCEPT_QUERY_PARAM = api_settings.URL_ACCEPT_OVERRIDE - _IGNORE_IE_ACCEPT_HEADER = True - - def __init__(self, content=None, status=None, headers=None, view=None, - request=None, renderer_classes=None, format=None): - # First argument taken by `SimpleTemplateResponse.__init__` is template_name, - # which we don't need - super(Response, self).__init__(None, status=status) - - self.raw_content = content - self.has_content_body = content is not None - self.headers = headers and headers[:] or [] - self.view = view - self.request = request - self.renderer_classes = renderer_classes - self.format = format - - def get_renderers(self): - """ - Instantiates and returns the list of renderers the response will use. - """ - if self.renderer_classes is None: - renderer_classes = api_settings.DEFAULT_RENDERERS - else: - renderer_classes = self.renderer_classes - - if self.format: - return [cls(self.view) for cls in renderer_classes - if cls.format == self.format] - return [cls(self.view) for cls in renderer_classes] - - @property - def rendered_content(self): - """ - The final rendered content. Accessing this attribute triggers the - complete rendering cycle: selecting suitable renderer, setting - response's actual content type, rendering data. - """ - renderer, media_type = self._determine_renderer() - - # Set the media type of the response - self['Content-Type'] = renderer.media_type - - # Render the response content - if self.has_content_body: - return renderer.render(self.raw_content, media_type) - return renderer.render() - - def render(self): - try: - return super(Response, self).render() - except NotAcceptable: - response = self._get_406_response() - return response.render() - - @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, '') - - def _determine_accept_list(self): - """ - Returns a list of accepted media types. This list is determined from : - - 1. overload with `_ACCEPT_QUERY_PARAM` - 2. `Accept` header of the request - - If those are useless, a default value is returned instead. - """ - request = self.request - - if (self._ACCEPT_QUERY_PARAM and - request.GET.get(self._ACCEPT_QUERY_PARAM, None)): - # Use _accept parameter override - return [request.GET.get(self._ACCEPT_QUERY_PARAM)] - elif (self._IGNORE_IE_ACCEPT_HEADER and - 'HTTP_USER_AGENT' in request.META and - MSIE_USER_AGENT_REGEX.match(request.META['HTTP_USER_AGENT']) and - request.META.get('HTTP_X_REQUESTED_WITH', '') != 'XMLHttpRequest'): - # Ignore MSIE's broken accept behavior except for AJAX requests - # and do something sensible instead - return ['text/html', '*/*'] - elif 'HTTP_ACCEPT' in request.META: - # Use standard HTTP Accept negotiation - return [token.strip() for token in request.META['HTTP_ACCEPT'].split(',')] - else: - # No accept header specified - return ['*/*'] - - def _determine_renderer(self): - """ - Determines the appropriate renderer for the output, given the list of - accepted media types, and the :attr:`renderer_classes` set on this class. - - Returns a 2-tuple of `(renderer, media_type)` - - See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html - """ - - renderers = self.get_renderers() - accepts = self._determine_accept_list() - - # Not acceptable response - Ignore accept header. - if self.status_code == 406: - return (renderers[0], renderers[0].media_type) - - # 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_response(media_type): - return renderer, media_type - - # No acceptable renderers were found - raise NotAcceptable - - def _get_406_response(self): - renderer = self.renderer_classes[0] - return Response( - { - 'detail': 'Could not satisfy the client\'s Accept header', - 'available_types': [renderer.media_type - for renderer in self.renderer_classes] - }, - status=status.HTTP_406_NOT_ACCEPTABLE, - view=self.view, request=self.request, renderer_classes=[renderer]) diff --git a/djangorestframework/reverse.py b/djangorestframework/reverse.py deleted file mode 100644 index ba663f98..00000000 --- a/djangorestframework/reverse.py +++ /dev/null @@ -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) diff --git a/djangorestframework/runtests/__init__.py b/djangorestframework/runtests/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/djangorestframework/runtests/__init__.py +++ /dev/null diff --git a/djangorestframework/runtests/runcoverage.py b/djangorestframework/runtests/runcoverage.py deleted file mode 100755 index 4fa7cb8d..00000000 --- a/djangorestframework/runtests/runcoverage.py +++ /dev/null @@ -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() diff --git a/djangorestframework/runtests/runtests.py b/djangorestframework/runtests/runtests.py deleted file mode 100755 index 1628aff7..00000000 --- a/djangorestframework/runtests/runtests.py +++ /dev/null @@ -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() diff --git a/djangorestframework/runtests/settings.py b/djangorestframework/runtests/settings.py deleted file mode 100644 index da2ae5b8..00000000 --- a/djangorestframework/runtests/settings.py +++ /dev/null @@ -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' diff --git a/djangorestframework/runtests/urls.py b/djangorestframework/runtests/urls.py deleted file mode 100644 index 4b7da787..00000000 --- a/djangorestframework/runtests/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Blank URLConf just to keep runtests.py happy. -""" -from django.conf.urls.defaults import * - -urlpatterns = patterns('', -) diff --git a/djangorestframework/serializers.py b/djangorestframework/serializers.py deleted file mode 100644 index 46980ee6..00000000 --- a/djangorestframework/serializers.py +++ /dev/null @@ -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) diff --git a/djangorestframework/settings.py b/djangorestframework/settings.py deleted file mode 100644 index e5181f4b..00000000 --- a/djangorestframework/settings.py +++ /dev/null @@ -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': (), - - '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', - - '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', - '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 setting not 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 Exception, e: - import traceback - tb = traceback.format_exc() - import pdb; pdb.set_trace() - 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() diff --git a/djangorestframework/static/djangorestframework/css/style.css b/djangorestframework/static/djangorestframework/css/style.css deleted file mode 100644 index d47aa16a..00000000 --- a/djangorestframework/static/djangorestframework/css/style.css +++ /dev/null @@ -1,1209 +0,0 @@ -/********************** admin 'base.css' ************************/ - -body { - margin: 0; - padding: 0; - font-size: 12px; - font-family: "Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif; - color: #333; - background: #fff; -} - -/* LINKS */ - -a:link, a:visited { - color: #5b80b2; - text-decoration: none; -} - -a:hover { - color: #036; -} - -a img { - border: none; -} - -a.section:link, a.section:visited { - color: white; - text-decoration: none; -} - -/* GLOBAL DEFAULTS */ - -p, ol, ul, dl { - margin: .2em 0 .8em 0; -} - -p { - padding: 0; - line-height: 140%; -} - -h1,h2,h3,h4,h5 { - font-weight: bold; -} - -h1 { - font-size: 18px; - color: #666; - padding: 0 6px 0 0; - margin: 0 0 .2em 0; -} - -h2 { - font-size: 16px; - margin: 1em 0 .5em 0; -} - -h2.subhead { - font-weight: normal; - margin-top: 0; -} - -h3 { - font-size: 14px; - margin: .8em 0 .3em 0; - color: #666; - font-weight: bold; -} - -h4 { - font-size: 12px; - margin: 1em 0 .8em 0; - padding-bottom: 3px; -} - -h5 { - font-size: 10px; - margin: 1.5em 0 .5em 0; - color: #666; - text-transform: uppercase; - letter-spacing: 1px; -} - -ul li { - list-style-type: square; - padding: 1px 0; -} - -ul.plainlist { - margin-left: 0 !important; -} - -ul.plainlist li { - list-style-type: none; -} - -li ul { - margin-bottom: 0; -} - -li, dt, dd { - font-size: 11px; - line-height: 14px; -} - -dt { - font-weight: bold; - margin-top: 4px; -} - -dd { - margin-left: 0; -} - -form { - margin: 0; - padding: 0; -} - -fieldset { - margin: 0; - padding: 0; -} - -blockquote { - font-size: 11px; - color: #777; - margin-left: 2px; - padding-left: 10px; - border-left: 5px solid #ddd; -} - -code, pre { - font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; - background: inherit; - color: #666; - font-size: 11px; -} - -pre.literal-block { - margin: 10px; - background: #eee; - padding: 6px 8px; -} - -code strong { - color: #930; -} - -hr { - clear: both; - color: #eee; - background-color: #eee; - height: 1px; - border: none; - margin: 0; - padding: 0; - font-size: 1px; - line-height: 1px; -} - -/* TEXT STYLES & MODIFIERS */ - -.small { - font-size: 11px; -} - -.tiny { - font-size: 10px; -} - -p.tiny { - margin-top: -2px; -} - -.mini { - font-size: 9px; -} - -p.mini { - margin-top: -3px; -} - -.help, p.help { - font-size: 10px !important; - color: #999; -} - -p img, h1 img, h2 img, h3 img, h4 img, td img { - vertical-align: middle; -} - -.quiet, a.quiet:link, a.quiet:visited { - color: #999 !important; - font-weight: normal !important; -} - -.quiet strong { - font-weight: bold !important; -} - -.float-right { - float: right; -} - -.float-left { - float: left; -} - -.clear { - clear: both; -} - -.align-left { - text-align: left; -} - -.align-right { - text-align: right; -} - -.example { - margin: 10px 0; - padding: 5px 10px; - background: #efefef; -} - -.nowrap { - white-space: nowrap; -} - -/* TABLES */ - -table { - border-collapse: collapse; - border-color: #ccc; -} - -td, th { - font-size: 11px; - line-height: 13px; - border-bottom: 1px solid #eee; - vertical-align: top; - padding: 5px; - font-family: "Lucida Grande", Verdana, Arial, sans-serif; -} - -th { - text-align: left; - font-size: 12px; - font-weight: bold; -} - -thead th, -tfoot td { - color: #666; - padding: 2px 5px; - font-size: 11px; - background: #e1e1e1 url(../../admin/img/admin/nav-bg.gif) top left repeat-x; - border-left: 1px solid #ddd; - border-bottom: 1px solid #ddd; -} - -tfoot td { - border-bottom: none; - border-top: 1px solid #ddd; -} - -thead th:first-child, -tfoot td:first-child { - border-left: none !important; -} - -thead th.optional { - font-weight: normal !important; -} - -fieldset table { - border-right: 1px solid #eee; -} - -tr.row-label td { - font-size: 9px; - padding-top: 2px; - padding-bottom: 0; - border-bottom: none; - color: #666; - margin-top: -1px; -} - -tr.alt { - background: #f6f6f6; -} - -.row1 { - background: #EDF3FE; -} - -.row2 { - background: white; -} - -/* SORTABLE TABLES */ - -thead th a:link, thead th a:visited { - color: #666; - display: block; -} - -table thead th.sorted { - background-position: bottom left !important; -} - -table thead th.sorted a { - padding-right: 13px; -} - -table thead th.ascending a { - background: url(../../admin/img/admin/arrow-up.gif) right .4em no-repeat; -} - -table thead th.descending a { - background: url(../../admin/img/admin/arrow-down.gif) right .4em no-repeat; -} - -/* ORDERABLE TABLES */ - -table.orderable tbody tr td:hover { - cursor: move; -} - -table.orderable tbody tr td:first-child { - padding-left: 14px; - background-image: url(../../admin/img/admin/nav-bg-grabber.gif); - background-repeat: repeat-y; -} - -table.orderable-initalized .order-cell, body>tr>td.order-cell { - display: none; -} - -/* FORM DEFAULTS */ - -input, textarea, select, .form-row p { - margin: 2px 0; - padding: 2px 3px; - vertical-align: middle; - font-family: "Lucida Grande", Verdana, Arial, sans-serif; - font-weight: normal; - font-size: 11px; -} - -textarea { - vertical-align: top !important; -} - -input[type=text], input[type=password], textarea, select, .vTextField { - border: 1px solid #ccc; -} - -/* FORM BUTTONS */ - -.button, input[type=submit], input[type=button], .submit-row input { - background: white url(../../admin/img/admin/nav-bg.gif) bottom repeat-x; - padding: 3px 5px; - color: black; - border: 1px solid #bbb; - border-color: #ddd #aaa #aaa #ddd; -} - -.button:active, input[type=submit]:active, input[type=button]:active { - background-image: url(../../admin/img/admin/nav-bg-reverse.gif); - background-position: top; -} - -.button[disabled], input[type=submit][disabled], input[type=button][disabled] { - background-image: url(../../admin/img/admin/nav-bg.gif); - background-position: bottom; - opacity: 0.4; -} - -.button.default, input[type=submit].default, .submit-row input.default { - border: 2px solid #5b80b2; - background: #7CA0C7 url(../../admin/img/admin/default-bg.gif) bottom repeat-x; - font-weight: bold; - color: white; - float: right; -} - -.button.default:active, input[type=submit].default:active { - background-image: url(../../admin/img/admin/default-bg-reverse.gif); - background-position: top; -} - -.button[disabled].default, input[type=submit][disabled].default, input[type=button][disabled].default { - background-image: url(../../admin/img/admin/default-bg.gif); - background-position: bottom; - opacity: 0.4; -} - - -/* MODULES */ - -.module { - border: 1px solid #ccc; - margin-bottom: 5px; - background: white; -} - -.module p, .module ul, .module h3, .module h4, .module dl, .module pre { - padding-left: 10px; - padding-right: 10px; -} - -.module blockquote { - margin-left: 12px; -} - -.module ul, .module ol { - margin-left: 1.5em; -} - -.module h3 { - margin-top: .6em; -} - -.module h2, .module caption, .inline-group h2 { - margin: 0; - padding: 2px 5px 3px 5px; - font-size: 11px; - text-align: left; - font-weight: bold; - background: #7CA0C7 url(../../admin/img/admin/default-bg.gif) top left repeat-x; - color: white; -} - -.module table { - border-collapse: collapse; -} - -/* MESSAGES & ERRORS */ - -ul.messagelist { - padding: 0 0 5px 0; - margin: 0; -} - -ul.messagelist li { - font-size: 12px; - display: block; - padding: 4px 5px 4px 25px; - margin: 0 0 3px 0; - border-bottom: 1px solid #ddd; - color: #666; - background: #ffc url(../../admin/img/admin/icon_success.gif) 5px .3em no-repeat; -} - -ul.messagelist li.warning{ - background-image: url(../../admin/img/admin/icon_alert.gif); -} - -ul.messagelist li.error{ - background-image: url(../../admin/img/admin/icon_error.gif); -} - -.errornote { - font-size: 12px !important; - display: block; - padding: 4px 5px 4px 25px; - margin: 0 0 3px 0; - border: 1px solid red; - color: red; - background: #ffc url(../../admin/img/admin/icon_error.gif) 5px .3em no-repeat; -} - -ul.errorlist { - margin: 0 !important; - padding: 0 !important; -} - -.errorlist li { - font-size: 12px !important; - display: block; - padding: 4px 5px 4px 25px; - margin: 0 0 3px 0; - border: 1px solid red; - color: white; - background: red url(../../admin/img/admin/icon_alert.gif) 5px .3em no-repeat; -} - -.errorlist li a { - color: white; - text-decoration: underline; -} - -td ul.errorlist { - margin: 0 !important; - padding: 0 !important; -} - -td ul.errorlist li { - margin: 0 !important; -} - -.errors { - background: #ffc; -} - -.errors input, .errors select, .errors textarea { - border: 1px solid red; -} - -div.system-message { - background: #ffc; - margin: 10px; - padding: 6px 8px; - font-size: .8em; -} - -div.system-message p.system-message-title { - padding: 4px 5px 4px 25px; - margin: 0; - color: red; - background: #ffc url(../../admin/img/admin/icon_error.gif) 5px .3em no-repeat; -} - -.description { - font-size: 12px; - padding: 5px 0 0 12px; -} - -/* BREADCRUMBS */ - -div.breadcrumbs { - background: white url(../../admin/img/admin/nav-bg-reverse.gif) 0 -10px repeat-x; - padding: 2px 8px 3px 8px; - font-size: 11px; - color: #999; - border-top: 1px solid white; - border-bottom: 1px solid #ccc; - text-align: left; -} - -/* ACTION ICONS */ - -.addlink { - padding-left: 12px; - background: url(../../admin/img/admin/icon_addlink.gif) 0 .2em no-repeat; -} - -.changelink { - padding-left: 12px; - background: url(../../admin/img/admin/icon_changelink.gif) 0 .2em no-repeat; -} - -.deletelink { - padding-left: 12px; - background: url(../../admin/img/admin/icon_deletelink.gif) 0 .25em no-repeat; -} - -a.deletelink:link, a.deletelink:visited { - color: #CC3434; -} - -a.deletelink:hover { - color: #993333; -} - -/* OBJECT TOOLS */ - -.object-tools { - font-size: 10px; - font-weight: bold; - font-family: Arial,Helvetica,sans-serif; - padding-left: 0; - float: right; - position: relative; - margin-top: -2.4em; - margin-bottom: -2em; -} - -.form-row .object-tools { - margin-top: 5px; - margin-bottom: 5px; - float: none; - height: 2em; - padding-left: 3.5em; -} - -.object-tools li { - display: block; - float: left; - background: url(../../admin/img/admin/tool-left.gif) 0 0 no-repeat; - padding: 0 0 0 8px; - margin-left: 2px; - height: 16px; -} - -.object-tools li:hover { - background: url(../../admin/img/admin/tool-left_over.gif) 0 0 no-repeat; -} - -.object-tools a:link, .object-tools a:visited { - display: block; - float: left; - color: white; - padding: .1em 14px .1em 8px; - height: 14px; - background: #999 url(../../admin/img/admin/tool-right.gif) 100% 0 no-repeat; -} - -.object-tools a:hover, .object-tools li:hover a { - background: #5b80b2 url(../../admin/img/admin/tool-right_over.gif) 100% 0 no-repeat; -} - -.object-tools a.viewsitelink, .object-tools a.golink { - background: #999 url(../../admin/img/admin/tooltag-arrowright.gif) top right no-repeat; - padding-right: 28px; -} - -.object-tools a.viewsitelink:hover, .object-tools a.golink:hover { - background: #5b80b2 url(../../admin/img/admin/tooltag-arrowright_over.gif) top right no-repeat; -} - -.object-tools a.addlink { - background: #999 url(../../admin/img/admin/tooltag-add.gif) top right no-repeat; - padding-right: 28px; -} - -.object-tools a.addlink:hover { - background: #5b80b2 url(../../admin/img/admin/tooltag-add_over.gif) top right no-repeat; -} - -/* OBJECT HISTORY */ - -table#change-history { - width: 100%; -} - -table#change-history tbody th { - width: 16em; -} - -/* PAGE STRUCTURE */ - -#container { - position: relative; - width: 100%; - min-width: 760px; - padding: 0; -} - -#content { - margin: 10px 15px; -} - -#header { - width: 100%; -} - -#content-main { - float: left; - width: 100%; -} - -#content-related { - float: right; - width: 18em; - position: relative; - margin-right: -19em; -} - -#footer { - clear: both; - padding: 10px; -} - -/* COLUMN TYPES */ - -.colMS { - margin-right: 20em !important; -} - -.colSM { - margin-left: 20em !important; -} - -.colSM #content-related { - float: left; - margin-right: 0; - margin-left: -19em; -} - -.colSM #content-main { - float: right; -} - -.popup .colM { - width: 95%; -} - -.subcol { - float: left; - width: 46%; - margin-right: 15px; -} - -.dashboard #content { - width: 500px; -} - -/* HEADER */ - -#header { - background: #417690; - color: #ffc; - overflow: hidden; -} - -#header a:link, #header a:visited { - color: white; -} - -#header a:hover { - text-decoration: underline; -} - -#branding h1 { - padding: 0 10px; - font-size: 18px; - margin: 8px 0; - font-weight: normal; - color: #f4f379; -} - -#branding h2 { - padding: 0 10px; - font-size: 14px; - margin: -8px 0 8px 0; - font-weight: normal; - color: #ffc; -} - -#user-tools { - position: absolute; - top: 0; - right: 0; - padding: 1.2em 10px; - font-size: 11px; - text-align: right; -} - -/* SIDEBAR */ - -#content-related h3 { - font-size: 12px; - color: #666; - margin-bottom: 3px; -} - -#content-related h4 { - font-size: 11px; -} - -#content-related .module h2 { - background: #eee url(../../admin/img/admin/nav-bg.gif) bottom left repeat-x; - color: #666; -} - -/********************** admin 'forms.css' ************************/ - -/* FORM ROWS */ - -.form-row { - overflow: hidden; - padding: 8px 12px; - font-size: 11px; - border-bottom: 1px solid #eee; -} - -.form-row img, .form-row input { - vertical-align: middle; -} - -form .form-row p { - padding-left: 0; - font-size: 11px; -} - -/* FORM LABELS */ - -form h4 { - margin: 0 !important; - padding: 0 !important; - border: none !important; -} - -label { - font-weight: normal !important; - color: #666; - font-size: 12px; -} - -.required label, label.required { - font-weight: bold !important; - color: #333 !important; -} - -/* RADIO BUTTONS */ - -form ul.radiolist li { - list-style-type: none; -} - -form ul.radiolist label { - float: none; - display: inline; -} - -form ul.inline { - margin-left: 0; - padding: 0; -} - -form ul.inline li { - float: left; - padding-right: 7px; -} - -/* ALIGNED FIELDSETS */ - -.aligned label { - display: block; - padding: 3px 10px 0 0; - float: left; - width: 8em; -} - -.aligned ul label { - display: inline; - float: none; - width: auto; -} - -.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField { - width: 350px; -} - -form .aligned p, form .aligned ul { - margin-left: 7em; - padding-left: 30px; -} - -form .aligned table p { - margin-left: 0; - padding-left: 0; -} - -form .aligned p.help { - padding-left: 38px; -} - -.aligned .vCheckboxLabel { - float: none !important; - display: inline; - padding-left: 4px; -} - -.colM .aligned .vLargeTextField, .colM .aligned .vXMLLargeTextField { - width: 610px; -} - -.checkbox-row p.help { - margin-left: 0; - padding-left: 0 !important; -} - -fieldset .field-box { - float: left; - margin-right: 20px; -} - -/* WIDE FIELDSETS */ - -.wide label { - width: 15em !important; -} - -form .wide p { - margin-left: 15em; -} - -form .wide p.help { - padding-left: 38px; -} - -.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField { - width: 450px; -} - -/* COLLAPSED FIELDSETS */ - -fieldset.collapsed * { - display: none; -} - -fieldset.collapsed h2, fieldset.collapsed { - display: block !important; -} - -fieldset.collapsed h2 { - background-image: url(../../admin/img/admin/nav-bg.gif); - background-position: bottom left; - color: #999; -} - -fieldset.collapsed .collapse-toggle { - background: transparent; - display: inline !important; -} - -/* MONOSPACE TEXTAREAS */ - -fieldset.monospace textarea { - font-family: "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace; -} - -/* SUBMIT ROW */ - -.submit-row { - padding: 5px 7px; - text-align: right; - background: white url(../../admin/img/admin/nav-bg.gif) 0 100% repeat-x; - border: 1px solid #ccc; - margin: 5px 0; - overflow: hidden; -} - -.submit-row input { - margin: 0 0 0 5px; -} - -.submit-row p { - margin: 0.3em; -} - -.submit-row p.deletelink-box { - float: left; -} - -.submit-row .deletelink { - background: url(../../admin/img/admin/icon_deletelink.gif) 0 50% no-repeat; - padding-left: 14px; -} - -/* CUSTOM FORM FIELDS */ - -.vSelectMultipleField { - vertical-align: top !important; -} - -.vCheckboxField { - border: none; -} - -.vDateField, .vTimeField { - margin-right: 2px; -} - -.vURLField { - width: 30em; -} - -.vLargeTextField, .vXMLLargeTextField { - width: 48em; -} - -.flatpages-flatpage #id_content { - height: 40.2em; -} - -.module table .vPositiveSmallIntegerField { - width: 2.2em; -} - -.vTextField { - width: 20em; -} - -.vIntegerField { - width: 5em; -} - -.vForeignKeyRawIdAdminField { - width: 5em; -} - -/* INLINES */ - -.inline-group { - padding: 0; - border: 1px solid #ccc; - margin: 10px 0; -} - -.inline-group .aligned label { - width: 8em; -} - -.inline-related { - position: relative; -} - -.inline-related h3 { - margin: 0; - color: #666; - padding: 3px 5px; - font-size: 11px; - background: #e1e1e1 url(../../admin/img/admin/nav-bg.gif) top left repeat-x; - border-bottom: 1px solid #ddd; -} - -.inline-related h3 span.delete { - float: right; -} - -.inline-related h3 span.delete label { - margin-left: 2px; - font-size: 11px; -} - -.inline-related fieldset { - margin: 0; - background: #fff; - border: none; -} - -.inline-related fieldset.module h3 { - margin: 0; - padding: 2px 5px 3px 5px; - font-size: 11px; - text-align: left; - font-weight: bold; - background: #bcd; - color: #fff; -} - -.inline-group .tabular fieldset.module { - border: none; - border-bottom: 1px solid #ddd; -} - -.inline-related.tabular fieldset.module table { - width: 100%; -} - -.last-related fieldset { - border: none; -} - -.inline-group .tabular tr.has_original td { - padding-top: 2em; -} - -.inline-group .tabular tr td.original { - padding: 2px 0 0 0; - width: 0; - _position: relative; -} - -.inline-group .tabular th.original { - width: 0px; - padding: 0; -} - -.inline-group .tabular td.original p { - position: absolute; - left: 0; - height: 1.1em; - padding: 2px 7px; - overflow: hidden; - font-size: 9px; - font-weight: bold; - color: #666; - _width: 700px; -} - -.inline-group ul.tools { - padding: 0; - margin: 0; - list-style: none; -} - -.inline-group ul.tools li { - display: inline; - padding: 0 5px; -} - -.inline-group div.add-row, -.inline-group .tabular tr.add-row td { - color: #666; - padding: 3px 5px; - border-bottom: 1px solid #ddd; - background: #e1e1e1 url(../../admin/img/admin/nav-bg.gif) top left repeat-x; -} - -.inline-group .tabular tr.add-row td { - padding: 4px 5px 3px; - border-bottom: none; -} - -.inline-group ul.tools a.add, -.inline-group div.add-row a, -.inline-group .tabular tr.add-row td a { - background: url(../../admin/img/admin/icon_addlink.gif) 0 50% no-repeat; - padding-left: 14px; - font-size: 11px; - outline: 0; /* Remove dotted border around link */ -} - -.empty-form { - display: none; -} - -/* IE7 specific bug fixes */ - -.submit-row input { - float: right; -} - -body.login { - background: #eee; -} - -.login #container { - background: white; - border: 1px solid #ccc; - width: 28em; - min-width: 300px; - margin-left: auto; - margin-right: auto; - margin-top: 100px; -} - -.login #content-main { - width: 100%; -} - -.login form { - margin-top: 1em; -} - -.login .form-row { - padding: 4px 0; - float: left; - width: 100%; -} - -.login .form-row label { - float: left; - width: 9em; - padding-right: 0.5em; - line-height: 2em; - text-align: right; - font-size: 1em; - color: #333; -} - -.login .form-row #id_username, .login .form-row #id_password { - width: 14em; -} - -.login span.help { - font-size: 10px; - display: block; -} - -.login .submit-row { - clear: both; - padding: 1em 0 0 9.4em; -} - -/* Overrides specific to REST framework */ - -#site-name a { - color: #F4F379 !important; -} - -.errorlist { - display: inline !important; -} - -.errorlist li { - display: inline !important; - background: white !important; - color: black !important; - border: 0 !important; -} - -/* Custom styles */ - -.version { - font-size: 8px; -} - -.form-row { - border-bottom: 0.25em !important; -} diff --git a/djangorestframework/status.py b/djangorestframework/status.py deleted file mode 100644 index f3a5e481..00000000 --- a/djangorestframework/status.py +++ /dev/null @@ -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 diff --git a/djangorestframework/templates/djangorestframework/api.html b/djangorestframework/templates/djangorestframework/api.html deleted file mode 100644 index fd9bcc98..00000000 --- a/djangorestframework/templates/djangorestframework/api.html +++ /dev/null @@ -1,3 +0,0 @@ -{% extends "djangorestframework/base.html" %} - -{# Override this template in your own templates directory to customize #}
\ No newline at end of file diff --git a/djangorestframework/templates/djangorestframework/api.txt b/djangorestframework/templates/djangorestframework/api.txt deleted file mode 100644 index b2f071d6..00000000 --- a/djangorestframework/templates/djangorestframework/api.txt +++ /dev/null @@ -1,8 +0,0 @@ -{% autoescape off %}{{ name }} - -{{ description }} - -HTTP {{ response.status }} {{ response.status_text }} -{% for key, val in response.headers.items %}{{ key }}: {{ val }} -{% endfor %} -{{ content }}{% endautoescape %} diff --git a/djangorestframework/templates/djangorestframework/base.html b/djangorestframework/templates/djangorestframework/base.html deleted file mode 100644 index 97e81bbe..00000000 --- a/djangorestframework/templates/djangorestframework/base.html +++ /dev/null @@ -1,151 +0,0 @@ -{% load url from future %} -{% load urlize_quoted_links %} -{% load add_query_param %} -{% load optional_login %} -{% load static %} -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml"> - <head> - <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 %}"> - <div id="container"> - - <div id="header"> - <div id="branding"> - <h1 id="site-name">{% block branding %}<a href='http://django-rest-framework.org'>Django REST framework</a> <span class="version"> v {{ version }}</span>{% endblock %}</h1> - </div> - <div id="user-tools"> - {% block userlinks %} - {% if user.is_active %} - Welcome, {{ user }}. - {% optional_login %} - {% else %} - {% optional_logout %} - {% endif %} - {% endblock %} - </div> - {% block nav-global %}{% endblock %} - </div> - - <div class="breadcrumbs"> - {% block breadcrumbs %} - {% for breadcrumb_name, breadcrumb_url in breadcrumblist %} - <a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a> {% if not forloop.last %}›{% endif %} - {% endfor %} - {% endblock %} - </div> - - <!-- Content --> - <div id="content" class="{% block coltype %}colM{% endblock %}"> - - {% if 'OPTIONS' in allowed_methods and api_settings.FORM_METHOD_OVERRIDE %} - <form action="{{ request.get_full_path }}" method="post"> - {% csrf_token %} - <input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="OPTIONS" /> - <input type="submit" value="OPTIONS" class="default" /> - </form> - {% endif %} - - <div class='content-main'> - <h1>{{ name }}</h1> - <p>{{ description }}</p> - <div class='module'> - <pre><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %} -{% for key, val in response.items %}<b>{{ key }}:</b> {{ val|urlize_quoted_links }} -{% endfor %} -{{ content|urlize_quoted_links }}</pre>{% endautoescape %}</div> - - {% if 'GET' in allowed_methods %} - <form> - <fieldset class='module aligned'> - <h2>GET {{ name }}</h2> - <div class='submit-row' style='margin: 0; border: 0'> - <a href='{{ request.get_full_path }}' rel="nofollow" style='float: left'>GET</a> - {% for format in available_formats %} - {% with FORMAT_PARAM|add:"="|add:format as param %} - [<a href='{{ request.get_full_path|add_query_param:param }}' rel="nofollow">{{ format }}</a>] - {% endwith %} - {% endfor %} - </div> - </fieldset> - </form> - {% endif %} - - - {# Only display the POST/PUT/DELETE forms if method tunneling via POST forms is enabled and the user has permissions on this view. #} - {% 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 %}> - <fieldset class='module aligned'> - <h2>POST {{ name }}</h2> - {% csrf_token %} - {{ post_form.non_field_errors }} - {% for field in post_form %} - <div class='form-row'> - {{ field.label_tag }} - {{ field }} - <span class='help'>{{ field.help_text }}</span> - {{ field.errors }} - </div> - {% endfor %} - <div class='submit-row' style='margin: 0; border: 0'> - <input type="submit" value="POST" class="default" /> - </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 %}> - <fieldset class='module aligned'> - <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='form-row'> - {{ field.label_tag }} - {{ field }} - <span class='help'>{{ field.help_text }}</span> - {{ field.errors }} - </div> - {% endfor %} - <div class='submit-row' style='margin: 0; border: 0'> - <input type="submit" value="PUT" class="default" /> - </div> - </fieldset> - </form> - {% endif %} - - {% if 'DELETE' in allowed_methods and api_settings.FORM_METHOD_OVERRIDE %} - <form action="{{ request.get_full_path }}" method="POST"> - <fieldset class='module aligned'> - <h2>DELETE {{ name }}</h2> - {% csrf_token %} - <input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="DELETE" /> - <div class='submit-row' style='margin: 0; border: 0'> - <input type="submit" value="DELETE" class="default" /> - </div> - </fieldset> - </form> - {% endif %} - - {% endif %} - </div> - <!-- END content-main --> - - </div> - <!-- END Content --> - - {% block footer %}<div id="footer"></div>{% endblock %} - </div> - </body> -</html> diff --git a/djangorestframework/templates/djangorestframework/login.html b/djangorestframework/templates/djangorestframework/login.html deleted file mode 100644 index fd82561f..00000000 --- a/djangorestframework/templates/djangorestframework/login.html +++ /dev/null @@ -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> diff --git a/djangorestframework/templatetags/__init__.py b/djangorestframework/templatetags/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/djangorestframework/templatetags/__init__.py +++ /dev/null diff --git a/djangorestframework/templatetags/add_query_param.py b/djangorestframework/templatetags/add_query_param.py deleted file mode 100644 index 4cf0133b..00000000 --- a/djangorestframework/templatetags/add_query_param.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.template import Library -from urlobject import URLObject -register = Library() - - -def add_query_param(url, param): - return unicode(URLObject(url).with_query(param)) - - -register.filter('add_query_param', add_query_param) diff --git a/djangorestframework/templatetags/optional_login.py b/djangorestframework/templatetags/optional_login.py deleted file mode 100644 index c448c142..00000000 --- a/djangorestframework/templatetags/optional_login.py +++ /dev/null @@ -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 diff --git a/djangorestframework/templatetags/urlize_quoted_links.py b/djangorestframework/templatetags/urlize_quoted_links.py deleted file mode 100644 index e8852fad..00000000 --- a/djangorestframework/templatetags/urlize_quoted_links.py +++ /dev/null @@ -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) diff --git a/djangorestframework/tests/__init__.py b/djangorestframework/tests/__init__.py deleted file mode 100644 index 641b0277..00000000 --- a/djangorestframework/tests/__init__.py +++ /dev/null @@ -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 "" diff --git a/djangorestframework/tests/accept.py b/djangorestframework/tests/accept.py deleted file mode 100644 index 7258f461..00000000 --- a/djangorestframework/tests/accept.py +++ /dev/null @@ -1,83 +0,0 @@ -from django.conf.urls.defaults import patterns, url, include -from django.test import TestCase - -from djangorestframework.compat import RequestFactory -from djangorestframework.views import APIView -from djangorestframework.response import Response - - -# See: http://www.useragentstring.com/ -MSIE_9_USER_AGENT = 'Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))' -MSIE_8_USER_AGENT = 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; Media Center PC 4.0; SLCC1; .NET CLR 3.0.04320)' -MSIE_7_USER_AGENT = 'Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)' -FIREFOX_4_0_USER_AGENT = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/4.0 (.NET CLR 3.5.30729)' -CHROME_11_0_USER_AGENT = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.655.0 Safari/534.17' -SAFARI_5_0_USER_AGENT = 'Mozilla/5.0 (X11; U; Linux x86_64; en-ca) AppleWebKit/531.2+ (KHTML, like Gecko) Version/5.0 Safari/531.2+' -OPERA_11_0_MSIE_USER_AGENT = 'Mozilla/4.0 (compatible; MSIE 8.0; X11; Linux x86_64; pl) Opera 11.00' -OPERA_11_0_OPERA_USER_AGENT = 'Opera/9.80 (X11; Linux x86_64; U; pl) Presto/2.7.62 Version/11.00' - - -urlpatterns = patterns('', - url(r'^api', include('djangorestframework.urls', namespace='djangorestframework')) -) - - -class UserAgentMungingTest(TestCase): - """ - We need to fake up the accept headers when we deal with MSIE. Blergh. - http://www.gethifi.com/blog/browser-rest-http-accept-headers - """ - - urls = 'djangorestframework.tests.accept' - - def setUp(self): - - class MockView(APIView): - permissions = () - response_class = Response - - def get(self, request): - return self.response_class({'a': 1, 'b': 2, 'c': 3}) - - self.req = RequestFactory() - self.MockView = MockView - self.view = MockView.as_view() - - def test_munge_msie_accept_header(self): - """Send MSIE user agent strings and ensure that we get an HTML response, - even if we set a */* accept header.""" - for user_agent in (MSIE_9_USER_AGENT, - MSIE_8_USER_AGENT, - MSIE_7_USER_AGENT): - req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent) - resp = self.view(req) - resp.render() - self.assertEqual(resp['Content-Type'], 'text/html') - - def test_dont_rewrite_msie_accept_header(self): - """Turn off _IGNORE_IE_ACCEPT_HEADER, send MSIE user agent strings and ensure - that we get a JSON response if we set a */* accept header.""" - class IgnoreIEAcceptResponse(Response): - _IGNORE_IE_ACCEPT_HEADER = False - view = self.MockView.as_view(response_class=IgnoreIEAcceptResponse) - - for user_agent in (MSIE_9_USER_AGENT, - MSIE_8_USER_AGENT, - MSIE_7_USER_AGENT): - req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent) - resp = view(req) - resp.render() - self.assertEqual(resp['Content-Type'], 'application/json') - - def test_dont_munge_nice_browsers_accept_header(self): - """Send Non-MSIE user agent strings and ensure that we get a JSON response, - if we set a */* Accept header. (Other browsers will correctly set the Accept header)""" - for user_agent in (FIREFOX_4_0_USER_AGENT, - CHROME_11_0_USER_AGENT, - SAFARI_5_0_USER_AGENT, - OPERA_11_0_MSIE_USER_AGENT, - OPERA_11_0_OPERA_USER_AGENT): - req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent) - resp = self.view(req) - resp.render() - self.assertEqual(resp['Content-Type'], 'application/json') diff --git a/djangorestframework/tests/authentication.py b/djangorestframework/tests/authentication.py deleted file mode 100644 index ddbee4b6..00000000 --- a/djangorestframework/tests/authentication.py +++ /dev/null @@ -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)) diff --git a/djangorestframework/tests/breadcrumbs.py b/djangorestframework/tests/breadcrumbs.py deleted file mode 100644 index ae84106c..00000000 --- a/djangorestframework/tests/breadcrumbs.py +++ /dev/null @@ -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', '/')]) diff --git a/djangorestframework/tests/description.py b/djangorestframework/tests/description.py deleted file mode 100644 index b88451eb..00000000 --- a/djangorestframework/tests/description.py +++ /dev/null @@ -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) diff --git a/djangorestframework/tests/files.py b/djangorestframework/tests/files.py deleted file mode 100644 index 90a613b9..00000000 --- a/djangorestframework/tests/files.py +++ /dev/null @@ -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"}) diff --git a/djangorestframework/tests/methods.py b/djangorestframework/tests/methods.py deleted file mode 100644 index e69de29b..00000000 --- a/djangorestframework/tests/methods.py +++ /dev/null diff --git a/djangorestframework/tests/mixins.py b/djangorestframework/tests/mixins.py deleted file mode 100644 index 05ce655d..00000000 --- a/djangorestframework/tests/mixins.py +++ /dev/null @@ -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']) diff --git a/djangorestframework/tests/models.py b/djangorestframework/tests/models.py deleted file mode 100644 index 4cae68b6..00000000 --- a/djangorestframework/tests/models.py +++ /dev/null @@ -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 - }) diff --git a/djangorestframework/tests/modelviews.py b/djangorestframework/tests/modelviews.py deleted file mode 100644 index 73cb0b2b..00000000 --- a/djangorestframework/tests/modelviews.py +++ /dev/null @@ -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) diff --git a/djangorestframework/tests/oauthentication.py b/djangorestframework/tests/oauthentication.py deleted file mode 100644 index 2621317d..00000000 --- a/djangorestframework/tests/oauthentication.py +++ /dev/null @@ -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!"}') diff --git a/djangorestframework/tests/package.py b/djangorestframework/tests/package.py deleted file mode 100644 index d18128f7..00000000 --- a/djangorestframework/tests/package.py +++ /dev/null @@ -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 - diff --git a/djangorestframework/tests/parsers.py b/djangorestframework/tests/parsers.py deleted file mode 100644 index c9b6afd0..00000000 --- a/djangorestframework/tests/parsers.py +++ /dev/null @@ -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) diff --git a/djangorestframework/tests/renderers.py b/djangorestframework/tests/renderers.py deleted file mode 100644 index 718c903f..00000000 --- a/djangorestframework/tests/renderers.py +++ /dev/null @@ -1,384 +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) - - def test_conflicting_format_query_and_accept_ignores_accept(self): - """If a 'format' query is specified that does not match the Accept - header, we should only honor the 'format' query string.""" - resp = self.client.get('/?format=%s' % RendererB.format, - HTTP_ACCEPT='dummy') - 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)) diff --git a/djangorestframework/tests/request.py b/djangorestframework/tests/request.py deleted file mode 100644 index 51e3660c..00000000 --- a/djangorestframework/tests/request.py +++ /dev/null @@ -1,266 +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.utils 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_GET_method(self): - """ - GET requests identified. - """ - request = factory.get('/') - self.assertEqual(request.method, 'GET') - - def test_POST_method(self): - """ - POST requests identified. - """ - request = factory.post('/') - self.assertEqual(request.method, 'POST') - - def test_HEAD_method(self): - """ - HEAD requests identified. - """ - request = factory.head('/') - self.assertEqual(request.method, 'HEAD') - - def test_overloaded_method(self): - """ - POST requests can be overloaded to another method by setting a - reserved form field - """ - 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 = 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 = 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'} - parsers = (FormParser, MultiPartParser) - request = factory.post('/', data, parser=parsers) - 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' - parsers = (PlainTextParser,) - request = factory.post('/', content, content_type=content_type, - parsers=parsers) - 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'} - parsers = (FormParser, MultiPartParser) - - from django import VERSION - - if VERSION >= (1, 5): - from django.test.client import MULTIPART_CONTENT, BOUNDARY, encode_multipart - request = factory.put('/', encode_multipart(BOUNDARY, data), parsers=parsers, - content_type=MULTIPART_CONTENT) - else: - request = factory.put('/', data, parsers=parsers) - - 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' - parsers = (PlainTextParser, ) - request = factory.put('/', content, content_type=content_type, - parsers=parsers) - 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 - } - parsers = (PlainTextParser, ) - request = factory.post('/', data, parsers=parsers) - 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") diff --git a/djangorestframework/tests/response.py b/djangorestframework/tests/response.py deleted file mode 100644 index 0483d826..00000000 --- a/djangorestframework/tests/response.py +++ /dev/null @@ -1,309 +0,0 @@ -import json -import unittest - -from django.conf.urls.defaults import patterns, url, include -from django.test import TestCase - -from djangorestframework.response import Response, NotAcceptable -from djangorestframework.views import APIView -from djangorestframework.compat import RequestFactory -from djangorestframework import status -from djangorestframework.renderers import ( - BaseRenderer, - JSONRenderer, - DocumentingHTMLRenderer, - DEFAULT_RENDERERS -) - - -class MockPickleRenderer(BaseRenderer): - media_type = 'application/pickle' - - -class MockJsonRenderer(BaseRenderer): - media_type = 'application/json' - - -class TestResponseDetermineRenderer(TestCase): - - def get_response(self, url='', accept_list=[], renderer_classes=[]): - kwargs = {} - if accept_list is not None: - kwargs['HTTP_ACCEPT'] = ','.join(accept_list) - request = RequestFactory().get(url, **kwargs) - return Response(request=request, renderer_classes=renderer_classes) - - def test_determine_accept_list_accept_header(self): - """ - Test that determine_accept_list takes the Accept header. - """ - accept_list = ['application/pickle', 'application/json'] - response = self.get_response(accept_list=accept_list) - self.assertEqual(response._determine_accept_list(), accept_list) - - def test_determine_accept_list_default(self): - """ - Test that determine_accept_list takes the default renderer if Accept is not specified. - """ - response = self.get_response(accept_list=None) - self.assertEqual(response._determine_accept_list(), ['*/*']) - - def test_determine_accept_list_overriden_header(self): - """ - Test Accept header overriding. - """ - accept_list = ['application/pickle', 'application/json'] - response = self.get_response(url='?_accept=application/x-www-form-urlencoded', - accept_list=accept_list) - self.assertEqual(response._determine_accept_list(), ['application/x-www-form-urlencoded']) - - def test_determine_renderer(self): - """ - Test that right renderer is chosen, in the order of Accept list. - """ - accept_list = ['application/pickle', 'application/json'] - renderer_classes = (MockPickleRenderer, MockJsonRenderer) - response = self.get_response(accept_list=accept_list, renderer_classes=renderer_classes) - renderer, media_type = response._determine_renderer() - self.assertEqual(media_type, 'application/pickle') - self.assertTrue(isinstance(renderer, MockPickleRenderer)) - - renderer_classes = (MockJsonRenderer, ) - response = self.get_response(accept_list=accept_list, renderer_classes=renderer_classes) - renderer, media_type = response._determine_renderer() - self.assertEqual(media_type, 'application/json') - self.assertTrue(isinstance(renderer, MockJsonRenderer)) - - def test_determine_renderer_default(self): - """ - Test determine renderer when Accept was not specified. - """ - renderer_classes = (MockPickleRenderer, ) - response = self.get_response(accept_list=None, renderer_classes=renderer_classes) - renderer, media_type = response._determine_renderer() - self.assertEqual(media_type, '*/*') - self.assertTrue(isinstance(renderer, MockPickleRenderer)) - - def test_determine_renderer_no_renderer(self): - """ - Test determine renderer when no renderer can satisfy the Accept list. - """ - accept_list = ['application/json'] - renderer_classes = (MockPickleRenderer, ) - response = self.get_response(accept_list=accept_list, renderer_classes=renderer_classes) - self.assertRaises(NotAcceptable, response._determine_renderer) - - -class TestResponseRenderContent(TestCase): - def get_response(self, url='', accept_list=[], content=None, renderer_classes=None): - request = RequestFactory().get(url, HTTP_ACCEPT=','.join(accept_list)) - return Response(request=request, content=content, renderer_classes=renderer_classes or DEFAULT_RENDERERS) - - def test_render(self): - """ - Test rendering simple data to json. - """ - content = {'a': 1, 'b': [1, 2, 3]} - content_type = 'application/json' - response = self.get_response(accept_list=[content_type], content=content) - response = response.render() - self.assertEqual(json.loads(response.content), content) - self.assertEqual(response['Content-Type'], content_type) - - def test_render_no_renderer(self): - """ - Test rendering response when no renderer can satisfy accept. - """ - content = 'bla' - content_type = 'weirdcontenttype' - response = self.get_response(accept_list=[content_type], content=content) - response = response.render() - self.assertEqual(response.status_code, 406) - self.assertIsNotNone(response.content) - - # def test_render_renderer_raises_ImmediateResponse(self): - # """ - # Test rendering response when renderer raises ImmediateResponse - # """ - # class PickyJSONRenderer(BaseRenderer): - # """ - # A renderer that doesn't make much sense, just to try - # out raising an ImmediateResponse - # """ - # media_type = 'application/json' - - # def render(self, obj=None, media_type=None): - # raise ImmediateResponse({'error': '!!!'}, status=400) - - # response = self.get_response( - # accept_list=['application/json'], - # renderers=[PickyJSONRenderer, JSONRenderer] - # ) - # response = response.render() - # self.assertEqual(response.status_code, 400) - # self.assertEqual(response.content, json.dumps({'error': '!!!'})) - - -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) - - def test_conflicting_format_query_and_accept_ignores_accept(self): - """If a 'format' query is specified that does not match the Accept - header, we should only honor the 'format' query string.""" - resp = self.client.get('/?format=%s' % RendererB.format, - HTTP_ACCEPT='dummy') - 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') diff --git a/djangorestframework/tests/reverse.py b/djangorestframework/tests/reverse.py deleted file mode 100644 index b4791135..00000000 --- a/djangorestframework/tests/reverse.py +++ /dev/null @@ -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') diff --git a/djangorestframework/tests/serializer.py b/djangorestframework/tests/serializer.py deleted file mode 100644 index 93ae81ee..00000000 --- a/djangorestframework/tests/serializer.py +++ /dev/null @@ -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)) diff --git a/djangorestframework/tests/status.py b/djangorestframework/tests/status.py deleted file mode 100644 index 53ecae11..00000000 --- a/djangorestframework/tests/status.py +++ /dev/null @@ -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) diff --git a/djangorestframework/tests/testcases.py b/djangorestframework/tests/testcases.py deleted file mode 100644 index 51b4afbc..00000000 --- a/djangorestframework/tests/testcases.py +++ /dev/null @@ -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) diff --git a/djangorestframework/tests/throttling.py b/djangorestframework/tests/throttling.py deleted file mode 100644 index 3033614f..00000000 --- a/djangorestframework/tests/throttling.py +++ /dev/null @@ -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) - )) diff --git a/djangorestframework/tests/validators.py b/djangorestframework/tests/validators.py deleted file mode 100644 index 80ad2b17..00000000 --- a/djangorestframework/tests/validators.py +++ /dev/null @@ -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)) diff --git a/djangorestframework/tests/views.py b/djangorestframework/tests/views.py deleted file mode 100644 index d4e4098a..00000000 --- a/djangorestframework/tests/views.py +++ /dev/null @@ -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') diff --git a/djangorestframework/throttling.py b/djangorestframework/throttling.py deleted file mode 100644 index 6249bd42..00000000 --- a/djangorestframework/throttling.py +++ /dev/null @@ -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 - } diff --git a/djangorestframework/urlpatterns.py b/djangorestframework/urlpatterns.py deleted file mode 100644 index 6cef721e..00000000 --- a/djangorestframework/urlpatterns.py +++ /dev/null @@ -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 diff --git a/djangorestframework/urls.py b/djangorestframework/urls.py deleted file mode 100644 index e446c396..00000000 --- a/djangorestframework/urls.py +++ /dev/null @@ -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'), -) diff --git a/djangorestframework/utils/__init__.py b/djangorestframework/utils/__init__.py deleted file mode 100644 index bb5bb6d7..00000000 --- a/djangorestframework/utils/__init__.py +++ /dev/null @@ -1,139 +0,0 @@ -from django.utils.encoding import smart_unicode -from django.utils.xmlutils import SimplerXMLGenerator - -from djangorestframework.compat import StringIO -from djangorestframework.compat import RequestFactory as DjangoRequestFactory -from djangorestframework.request import Request - -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) - - -class RequestFactory(DjangoRequestFactory): - """ - Replicate RequestFactory, but return Request, not HttpRequest. - """ - def get(self, *args, **kwargs): - parsers = kwargs.pop('parsers', None) - request = super(RequestFactory, self).get(*args, **kwargs) - return Request(request, parsers) - - def post(self, *args, **kwargs): - parsers = kwargs.pop('parsers', None) - request = super(RequestFactory, self).post(*args, **kwargs) - return Request(request, parsers) - - def put(self, *args, **kwargs): - parsers = kwargs.pop('parsers', None) - request = super(RequestFactory, self).put(*args, **kwargs) - return Request(request, parsers) - - def delete(self, *args, **kwargs): - parsers = kwargs.pop('parsers', None) - request = super(RequestFactory, self).delete(*args, **kwargs) - return Request(request, parsers) - - def head(self, *args, **kwargs): - parsers = kwargs.pop('parsers', None) - request = super(RequestFactory, self).head(*args, **kwargs) - return Request(request, parsers) - - def options(self, *args, **kwargs): - parsers = kwargs.pop('parsers', None) - request = super(RequestFactory, self).options(*args, **kwargs) - return Request(request, parsers) diff --git a/djangorestframework/utils/breadcrumbs.py b/djangorestframework/utils/breadcrumbs.py deleted file mode 100644 index 84dd3dfa..00000000 --- a/djangorestframework/utils/breadcrumbs.py +++ /dev/null @@ -1,32 +0,0 @@ -from django.core.urlresolvers import resolve - - -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): - """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(), url)) - - if url == '': - # All done - return breadcrumbs_list - - elif url.endswith('/'): - # Drop trailing slash off the end and continue to try to resolve more breadcrumbs - return breadcrumbs_recursive(url.rstrip('/'), breadcrumbs_list) - - # Drop trailing non-slash off the end and continue to try to resolve more breadcrumbs - return breadcrumbs_recursive(url[:url.rfind('/') + 1], breadcrumbs_list) - - return breadcrumbs_recursive(url, []) diff --git a/djangorestframework/utils/encoders.py b/djangorestframework/utils/encoders.py deleted file mode 100644 index 74876017..00000000 --- a/djangorestframework/utils/encoders.py +++ /dev/null @@ -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) diff --git a/djangorestframework/utils/mediatypes.py b/djangorestframework/utils/mediatypes.py deleted file mode 100644 index 5eba7fb2..00000000 --- a/djangorestframework/utils/mediatypes.py +++ /dev/null @@ -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 diff --git a/djangorestframework/views.py b/djangorestframework/views.py deleted file mode 100644 index a309386b..00000000 --- a/djangorestframework/views.py +++ /dev/null @@ -1,282 +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): - 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 - - @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_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 requires. - """ - return [throttle(self) for throttle in self.throttle_classes] - - 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 finalize_response(self, request, response, *args, **kwargs): - """ - Returns the final response object. - """ - if isinstance(response, Response): - response.view = self - response.request = request - response.renderer_classes = self.renderer_classes - if api_settings.FORMAT_SUFFIX_KWARG: - response.format = kwargs.get(api_settings.FORMAT_SUFFIX_KWARG, None) - - for key, value in self.headers.items(): - response[key] = value - - return response - - def initial(self, request, *args, **kwargs): - """ - Runs anything that needs to occur prior to calling the method handlers. - """ - self.check_permissions(request) - self.check_throttles(request) - - 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): - 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): - """ - `APIView.dispatch()` is pretty much the same as Django's regular - `View.dispatch()`, except that it includes hooks to: - - * Initialize the request object. - * Finalize the response object. - * Handle exceptions that occur in the handler method. - * An initial hook for code such as permission checking that should - occur prior to running the method handlers. - """ - 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 |
