mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-10-31 07:57:55 +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,54 +1,44 @@ | ||||||
|  | {% load static %} | ||||||
| <html> | <html> | ||||||
|   <head> | 
 | ||||||
|     {% if ADMIN_MEDIA_PREFIX %} |     <head> | ||||||
|       <link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/base.css'/> |         <link rel="stylesheet" type="text/css" href='{% get_static_prefix %}css/djangorestframework.css'/> | ||||||
|       <link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/forms.css'/> |     </head> | ||||||
|       <link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/login.css' /> | 
 | ||||||
|     {% else %} |     <body class="login"> | ||||||
|       <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'/> |         <div id="container"> | ||||||
|       <link rel="stylesheet" type="text/css" href='{{STATIC_URL}}admin/css/login.css' /> | 
 | ||||||
|     {% endif %} |             <div id="header"> | ||||||
|      <style> |                 <div id="branding"> | ||||||
|      .form-row {border-bottom: 0.25em !important}</style> |                     <h1 id="site-name">Django REST framework</h1> | ||||||
|   </head> |                 </div> | ||||||
|   <body class="login"> |             </div> | ||||||
| <div id="container"> | 
 | ||||||
|   <div id="header"> |             <div id="content" class="colM"> | ||||||
|         <div id="branding"> |                 <div id="content-main"> | ||||||
|           <h1 id="site-name">Django REST framework</h1> |                     <form method="post" action="{% url djangorestframework.utils.staticviews.api_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> |         </div> | ||||||
|     </div> |     </body> | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| <div id="content" class="colM"> |  | ||||||
| 
 |  | ||||||
| <div id="content-main"> |  | ||||||
| <form method="post" action="{% url djangorestframework.utils.staticviews.api_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> | </html> | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| from django.contrib.staticfiles.urls import staticfiles_urlpatterns | try: | ||||||
|  |     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()), | ||||||
|  |  | ||||||
							
								
								
									
										81
									
								
								setup.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										81
									
								
								setup.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							|  | @ -1,33 +1,70 @@ | ||||||
| #!/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', |     test_suite='djangorestframework.runtests.runcoverage.main', | ||||||
|                 'djangorestframework.runtests', |  | ||||||
|                 'djangorestframework.utils'], |  | ||||||
|     package_dir={'djangorestframework': 'djangorestframework'}, |  | ||||||
|     package_data = {'djangorestframework': ['templates/*', 'static/*']}, |  | ||||||
|     test_suite = 'djangorestframework.runtests.runcoverage.main', |  | ||||||
|     install_requires=['URLObject>=0.6.0'], |     install_requires=['URLObject>=0.6.0'], | ||||||
|     classifiers = [ |     classifiers=[ | ||||||
|         'Development Status :: 4 - Beta', |         'Development Status :: 4 - Beta', | ||||||
|         'Environment :: Web Environment', |         'Environment :: Web Environment', | ||||||
|         'Framework :: Django', |         'Framework :: Django', | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user