mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-01-25 00:34:21 +03:00
merged with trunk
This commit is contained in:
commit
c04cb5145c
1
AUTHORS
1
AUTHORS
|
@ -29,6 +29,7 @@ Benoit C <dzen>
|
||||||
Chris Pickett <bunchesofdonald>
|
Chris Pickett <bunchesofdonald>
|
||||||
Ben Timby <btimby>
|
Ben Timby <btimby>
|
||||||
Michele Lazzeri <michelelazzeri-nextage>
|
Michele Lazzeri <michelelazzeri-nextage>
|
||||||
|
Camille Harang <mammique>
|
||||||
|
|
||||||
THANKS TO:
|
THANKS TO:
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,13 @@ Release Notes
|
||||||
development
|
development
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
* Saner template variable autoescaping.
|
* Added DjangoModelPermissions class to support `django.contrib.auth` style permissions.
|
||||||
* Use `staticfiles` for css files.
|
* Use `staticfiles` for css files.
|
||||||
- Easier to override. Won't conflict with customised admin styles (eg grappelli)
|
- Easier to override. Won't conflict with customised admin styles (eg grappelli)
|
||||||
* Drop implied 'pk' filter if last arg in urlconf is unnamed.
|
* Drop implied 'pk' filter if last arg in urlconf is unnamed.
|
||||||
- Too magical. Explict is better than implicit.
|
- Too magical. Explict is better than implicit.
|
||||||
|
* Saner template variable autoescaping.
|
||||||
|
* Tider setup.py
|
||||||
* Bugfixes:
|
* Bugfixes:
|
||||||
- Bug with PerUserThrottling when user contains unicode chars.
|
- Bug with PerUserThrottling when user contains unicode chars.
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
recursive-include djangorestframework/static *.ico *.txt
|
recursive-include djangorestframework/static *.ico *.txt *.css
|
||||||
recursive-include djangorestframework/templates *.txt *.html
|
recursive-include djangorestframework/templates *.txt *.html
|
||||||
recursive-include examples .keep *.py *.txt
|
recursive-include examples .keep *.py *.txt
|
||||||
recursive-include docs *.py *.rst *.html *.txt
|
recursive-include docs *.py *.rst *.html *.txt
|
||||||
include AUTHORS LICENSE requirements.txt tox.ini
|
include AUTHORS LICENSE CHANGELOG.rst requirements.txt tox.ini
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
Django REST framework
|
Django REST framework
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Django REST framework makes it easy to build well-connected, self-describing RESTful Web APIs.
|
**Django REST framework makes it easy to build well-connected, self-describing RESTful Web APIs.**
|
||||||
|
|
||||||
|
**Author:** Tom Christie. `Follow me on Twitter <https://twitter.com/_tomchristie>`_.
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
|
||||||
|
|
|
@ -499,7 +499,7 @@ class PaginatorMixin(object):
|
||||||
Constructs a url used for getting the next/previous urls
|
Constructs a url used for getting the next/previous urls
|
||||||
"""
|
"""
|
||||||
url = URLObject.parse(self.request.get_full_path())
|
url = URLObject.parse(self.request.get_full_path())
|
||||||
url = url.add_query_param('page', page_number)
|
url = url.set_query_param('page', page_number)
|
||||||
|
|
||||||
limit = self.get_limit()
|
limit = self.get_limit()
|
||||||
if limit != self.limit:
|
if limit != self.limit:
|
||||||
|
|
|
@ -20,6 +20,8 @@ __all__ = (
|
||||||
'PerResourceThrottling'
|
'PerResourceThrottling'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
|
||||||
|
|
||||||
|
|
||||||
_403_FORBIDDEN_RESPONSE = ImmediateResponse(
|
_403_FORBIDDEN_RESPONSE = ImmediateResponse(
|
||||||
{'detail': 'You do not have permission to access this resource. ' +
|
{'detail': 'You do not have permission to access this resource. ' +
|
||||||
|
@ -84,8 +86,54 @@ class IsUserOrIsAnonReadOnly(BasePermission):
|
||||||
|
|
||||||
def check_permission(self, user):
|
def check_permission(self, user):
|
||||||
if (not user.is_authenticated() and
|
if (not user.is_authenticated() and
|
||||||
self.view.method != 'GET' and
|
self.view.method not in SAFE_METHODS):
|
||||||
self.view.method != 'HEAD'):
|
raise _403_FORBIDDEN_RESPONSE
|
||||||
|
|
||||||
|
|
||||||
|
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 'read' 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
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
return [perm % kwargs for perm in self.perms_map[method]]
|
||||||
|
except KeyError:
|
||||||
|
ErrorResponse(status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||||
|
|
||||||
|
def check_permission(self, user):
|
||||||
|
method = self.view.method
|
||||||
|
model_cls = self.view.resource.model
|
||||||
|
perms = self.get_required_permissions(method, model_cls)
|
||||||
|
|
||||||
|
if not user.is_authenticated or not user.has_perms(perms):
|
||||||
raise _403_FORBIDDEN_RESPONSE
|
raise _403_FORBIDDEN_RESPONSE
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -162,7 +162,7 @@ class Response(SimpleTemplateResponse):
|
||||||
return self.renderers[0]
|
return self.renderers[0]
|
||||||
|
|
||||||
|
|
||||||
class ImmediateResponse(Response, BaseException):
|
class ImmediateResponse(Response, Exception):
|
||||||
"""
|
"""
|
||||||
A subclass of :class:`Response` used to abort the current request handling.
|
A subclass of :class:`Response` used to abort the current request handling.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1129,6 +1129,58 @@ fieldset.monospace textarea {
|
||||||
float: right;
|
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 */
|
/* Overrides specific to REST framework */
|
||||||
|
|
||||||
#site-name a {
|
#site-name a {
|
||||||
|
@ -1147,6 +1199,11 @@ fieldset.monospace textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom styles */
|
/* Custom styles */
|
||||||
|
|
||||||
.version {
|
.version {
|
||||||
font-size: 8px;
|
font-size: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
border-bottom: 0.25em !important;
|
||||||
|
}
|
||||||
|
|
|
@ -1,28 +1,21 @@
|
||||||
|
{% load static %}
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
{% if ADMIN_MEDIA_PREFIX %}
|
<link rel="stylesheet" type="text/css" href='{% get_static_prefix %}css/djangorestframework.css'/>
|
||||||
<link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/base.css'/>
|
|
||||||
<link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/forms.css'/>
|
|
||||||
<link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/login.css' />
|
|
||||||
{% else %}
|
|
||||||
<link rel="stylesheet" type="text/css" href='{{STATIC_URL}}admin/css/base.css'/>
|
|
||||||
<link rel="stylesheet" type="text/css" href='{{STATIC_URL}}admin/css/forms.css'/>
|
|
||||||
<link rel="stylesheet" type="text/css" href='{{STATIC_URL}}admin/css/login.css' />
|
|
||||||
{% endif %}
|
|
||||||
<style>
|
|
||||||
.form-row {border-bottom: 0.25em !important}</style>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="login">
|
<body class="login">
|
||||||
|
|
||||||
<div id="container">
|
<div id="container">
|
||||||
|
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<div id="branding">
|
<div id="branding">
|
||||||
<h1 id="site-name">Django REST framework</h1>
|
<h1 id="site-name">Django REST framework</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="content" class="colM">
|
<div id="content" class="colM">
|
||||||
|
|
||||||
<div id="content-main">
|
<div id="content-main">
|
||||||
<form method="post" action="{% url djangorestframework.utils.staticviews.api_login %}" id="login-form">
|
<form method="post" action="{% url djangorestframework.utils.staticviews.api_login %}" id="login-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
@ -37,13 +30,10 @@
|
||||||
<label> </label><input type="submit" value="Log in">
|
<label> </label><input type="submit" value="Log in">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
document.getElementById('id_username').focus()
|
document.getElementById('id_username').focus()
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<br class="clear">
|
<br class="clear">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -275,3 +275,12 @@ class TestPagination(TestCase):
|
||||||
self.assertTrue('foo=bar' in content['next'])
|
self.assertTrue('foo=bar' in content['next'])
|
||||||
self.assertTrue('another=something' in content['next'])
|
self.assertTrue('another=something' in content['next'])
|
||||||
self.assertTrue('page=2' 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 = json.loads(response.content)
|
||||||
|
self.assertTrue('page=2' in content['next'])
|
||||||
|
self.assertFalse('page=1' in content['next'])
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# Settings for djangorestframework examples project
|
# Settings for djangorestframework examples project
|
||||||
|
import django
|
||||||
import os
|
import os
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
@ -84,19 +85,17 @@ TEMPLATE_DIRS = (
|
||||||
# Don't forget to use absolute paths, not relative paths.
|
# Don't forget to use absolute paths, not relative paths.
|
||||||
)
|
)
|
||||||
|
|
||||||
# for loading initial data
|
if django.VERSION < (1, 3):
|
||||||
##SERIALIZATION_MODULES = {
|
staticfiles = 'staticfiles'
|
||||||
# 'yml': "django.core.serializers.pyyaml"
|
else:
|
||||||
|
staticfiles = 'django.contrib.staticfiles'
|
||||||
#}
|
|
||||||
|
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.sites',
|
'django.contrib.sites',
|
||||||
'django.contrib.staticfiles',
|
staticfiles,
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
|
|
||||||
'djangorestframework',
|
'djangorestframework',
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
from django.conf.urls.defaults import patterns, include, url
|
from django.conf.urls.defaults import patterns, include
|
||||||
from django.conf import settings
|
|
||||||
from sandbox.views import Sandbox
|
from sandbox.views import Sandbox
|
||||||
|
try:
|
||||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||||
|
except ImportError: # Django <= 1.2
|
||||||
|
from staticfiles.urls import staticfiles_urlpatterns
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
(r'^$', Sandbox.as_view()),
|
(r'^$', Sandbox.as_view()),
|
||||||
|
|
63
setup.py
Normal file → Executable file
63
setup.py
Normal file → Executable file
|
@ -1,30 +1,67 @@
|
||||||
#!/usr/bin/env/python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
import os, re
|
|
||||||
|
|
||||||
path = os.path.join(os.path.dirname(__file__), 'djangorestframework', '__init__.py')
|
def get_version(package):
|
||||||
init_py = open(path).read()
|
"""
|
||||||
VERSION = re.match("__version__ = '([^']+)'", init_py).group(1)
|
Return package version as listed in `__version__` in `init.py`.
|
||||||
|
"""
|
||||||
|
init_py = open(os.path.join(package, '__init__.py')).read()
|
||||||
|
return re.match("__version__ = ['\"]([^'\"]+)['\"]", init_py).group(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_packages(package):
|
||||||
|
"""
|
||||||
|
Return root package and all sub-packages.
|
||||||
|
"""
|
||||||
|
return [dirpath
|
||||||
|
for dirpath, dirnames, filenames in os.walk(package)
|
||||||
|
if os.path.exists(os.path.join(dirpath, '__init__.py'))]
|
||||||
|
|
||||||
|
|
||||||
|
def get_package_data(package):
|
||||||
|
"""
|
||||||
|
Return all files under the root package, that are not in a
|
||||||
|
package themselves.
|
||||||
|
"""
|
||||||
|
walk = [(dirpath.replace(package + os.sep, '', 1), filenames)
|
||||||
|
for dirpath, dirnames, filenames in os.walk(package)
|
||||||
|
if not os.path.exists(os.path.join(dirpath, '__init__.py'))]
|
||||||
|
|
||||||
|
filepaths = []
|
||||||
|
for base, filenames in walk:
|
||||||
|
filepaths.extend([os.path.join(base, filename)
|
||||||
|
for filename in filenames])
|
||||||
|
return {package: filepaths}
|
||||||
|
|
||||||
|
|
||||||
|
version = get_version('djangorestframework')
|
||||||
|
|
||||||
|
|
||||||
|
if sys.argv[-1] == 'publish':
|
||||||
|
os.system("python setup.py sdist upload")
|
||||||
|
print "You probably want to also tag the version now:"
|
||||||
|
print " git tag -a %s -m 'version %s'" % (version, version)
|
||||||
|
print " git push --tags"
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='djangorestframework',
|
name='djangorestframework',
|
||||||
version = VERSION,
|
version=version,
|
||||||
url='http://django-rest-framework.org',
|
url='http://django-rest-framework.org',
|
||||||
download_url='http://pypi.python.org/pypi/djangorestframework/',
|
download_url='http://pypi.python.org/pypi/djangorestframework/',
|
||||||
license='BSD',
|
license='BSD',
|
||||||
description='A lightweight REST framework for Django.',
|
description='A lightweight REST framework for Django.',
|
||||||
author='Tom Christie',
|
author='Tom Christie',
|
||||||
author_email='tom@tomchristie.com',
|
author_email='tom@tomchristie.com',
|
||||||
packages = ['djangorestframework',
|
packages=get_packages('djangorestframework'),
|
||||||
'djangorestframework.templatetags',
|
package_data=get_package_data('djangorestframework'),
|
||||||
'djangorestframework.tests',
|
|
||||||
'djangorestframework.runtests',
|
|
||||||
'djangorestframework.utils'],
|
|
||||||
package_dir={'djangorestframework': 'djangorestframework'},
|
|
||||||
package_data = {'djangorestframework': ['templates/*', 'static/*']},
|
|
||||||
test_suite='djangorestframework.runtests.runcoverage.main',
|
test_suite='djangorestframework.runtests.runcoverage.main',
|
||||||
install_requires=['URLObject>=0.6.0'],
|
install_requires=['URLObject>=0.6.0'],
|
||||||
classifiers=[
|
classifiers=[
|
||||||
|
|
Loading…
Reference in New Issue
Block a user