mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-03 13:14:30 +03:00
Merge remote branch 'tomchristie/master'
This commit is contained in:
commit
add5f32e8a
2
AUTHORS
2
AUTHORS
|
@ -28,6 +28,8 @@ Sebastian Żurek <sebzur>
|
||||||
Benoit C <dzen>
|
Benoit C <dzen>
|
||||||
Chris Pickett <bunchesofdonald>
|
Chris Pickett <bunchesofdonald>
|
||||||
Ben Timby <btimby>
|
Ben Timby <btimby>
|
||||||
|
Michele Lazzeri <michelelazzeri-nextage>
|
||||||
|
Camille Harang <mammique>
|
||||||
|
|
||||||
THANKS TO:
|
THANKS TO:
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,44 @@
|
||||||
|
Release Notes
|
||||||
|
=============
|
||||||
|
|
||||||
|
development
|
||||||
|
-----------
|
||||||
|
|
||||||
|
* Saner template variable autoescaping.
|
||||||
|
* Use `staticfiles` for css files.
|
||||||
|
- Easier to override. Won't conflict with customised admin styles (eg grappelli)
|
||||||
|
* Drop implied 'pk' filter if last arg in urlconf is unnamed.
|
||||||
|
- Too magical. Explict is better than implicit.
|
||||||
|
* Tider setup.py
|
||||||
|
* Bugfixes:
|
||||||
|
- Bug with PerUserThrottling when user contains unicode chars.
|
||||||
|
|
||||||
|
0.3.2
|
||||||
|
-----
|
||||||
|
|
||||||
|
* Bugfixes:
|
||||||
|
* Fix 403 for POST and PUT from the UI with UserLoggedInAuthentication (#115)
|
||||||
|
* serialize_model method in serializer.py may cause wrong value (#73)
|
||||||
|
* Fix Error when clicking OPTIONS button (#146)
|
||||||
|
* And many other fixes
|
||||||
|
* Remove short status codes
|
||||||
|
- Zen of Python: "There should be one-- and preferably only one --obvious way to do it."
|
||||||
|
* get_name, get_description become methods on the view - makes them overridable.
|
||||||
|
* Improved model mixin API - Hooks for build_query, get_instance_data, get_model, get_queryset, get_ordering
|
||||||
|
|
||||||
|
0.3.1
|
||||||
|
-----
|
||||||
|
|
||||||
|
* [not documented]
|
||||||
|
|
||||||
0.3.0
|
0.3.0
|
||||||
|
-----
|
||||||
|
|
||||||
* JSONP Support
|
* JSONP Support
|
||||||
* Bugfixes, including support for latest markdown release
|
* Bugfixes, including support for latest markdown release
|
||||||
|
|
||||||
0.2.4
|
0.2.4
|
||||||
|
-----
|
||||||
|
|
||||||
* Fix broken IsAdminUser permission.
|
* Fix broken IsAdminUser permission.
|
||||||
* OPTIONS support.
|
* OPTIONS support.
|
||||||
|
@ -11,20 +46,24 @@
|
||||||
* Drop mentions of Blog, BitBucket.
|
* Drop mentions of Blog, BitBucket.
|
||||||
|
|
||||||
0.2.3
|
0.2.3
|
||||||
|
-----
|
||||||
|
|
||||||
* Fix some throttling bugs.
|
* Fix some throttling bugs.
|
||||||
* ``X-Throttle`` header on throttling.
|
* ``X-Throttle`` header on throttling.
|
||||||
* Support for nesting resources on related models.
|
* Support for nesting resources on related models.
|
||||||
|
|
||||||
0.2.2
|
0.2.2
|
||||||
|
-----
|
||||||
|
|
||||||
* Throttling support complete.
|
* Throttling support complete.
|
||||||
|
|
||||||
0.2.1
|
0.2.1
|
||||||
|
-----
|
||||||
|
|
||||||
* Couple of simple bugfixes over 0.2.0
|
* Couple of simple bugfixes over 0.2.0
|
||||||
|
|
||||||
0.2.0
|
0.2.0
|
||||||
|
-----
|
||||||
|
|
||||||
* Big refactoring changes since 0.1.0, ask on the discussion group if anything isn't clear.
|
* Big refactoring changes since 0.1.0, ask on the discussion group if anything isn't clear.
|
||||||
The public API has been massively cleaned up. Expect it to be fairly stable from here on in.
|
The public API has been massively cleaned up. Expect it to be fairly stable from here on in.
|
||||||
|
@ -49,9 +88,11 @@
|
||||||
You can reuse these mixin classes individually without using the ``View`` class.
|
You can reuse these mixin classes individually without using the ``View`` class.
|
||||||
|
|
||||||
0.1.1
|
0.1.1
|
||||||
|
-----
|
||||||
|
|
||||||
* Final build before pulling in all the refactoring changes for 0.2, in case anyone needs to hang on to 0.1.
|
* Final build before pulling in all the refactoring changes for 0.2, in case anyone needs to hang on to 0.1.
|
||||||
|
|
||||||
0.1.0
|
0.1.0
|
||||||
|
-----
|
||||||
|
|
||||||
* Initial release.
|
* Initial release.
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
__version__ = '0.3.2-dev'
|
__version__ = '0.3.3-dev'
|
||||||
|
|
||||||
VERSION = __version__ # synonym
|
VERSION = __version__ # synonym
|
||||||
|
|
|
@ -6,7 +6,6 @@ classes that can be added to a `View`.
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.db.models.fields.related import ForeignKey
|
from django.db.models.fields.related import ForeignKey
|
||||||
from django.db.models.query import Q
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from urlobject import URLObject
|
from urlobject import URLObject
|
||||||
|
|
||||||
|
@ -486,44 +485,25 @@ class ModelMixin(object):
|
||||||
|
|
||||||
queryset = None
|
queryset = None
|
||||||
|
|
||||||
def build_query(self, *args, **kwargs):
|
def get_query_kwargs(self, *args, **kwargs):
|
||||||
""" Returns django.db.models.Q object to be used for the objects retrival.
|
"""
|
||||||
|
Return a dict of kwargs that will be used to build the
|
||||||
Arguments:
|
model instance retrieval or to filter querysets.
|
||||||
- args: unnamed URL arguments
|
|
||||||
- kwargs: named URL arguments
|
|
||||||
|
|
||||||
If a URL passes any arguments to the view being the QueryMixin subclass
|
|
||||||
build_query manages the arguments and provides the Q object that will be
|
|
||||||
used for the objects retrival with filter/get queryset methods.
|
|
||||||
|
|
||||||
Technically, neither args nor kwargs have to be provided, however the default
|
|
||||||
behaviour is to map all kwargs as the query constructors so that if this
|
|
||||||
method is not overriden only kwargs keys being model fields are valid.
|
|
||||||
|
|
||||||
If positional args are provided, the last one argument is understood
|
|
||||||
as the primary key. However this usage should be considered
|
|
||||||
deperecated, and will be removed in a future version.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tmp = dict(kwargs)
|
kwargs = dict(kwargs)
|
||||||
|
|
||||||
# If the URLconf includes a .(?P<format>\w+) pattern to match against
|
# If the URLconf includes a .(?P<format>\w+) pattern to match against
|
||||||
# a .json, .xml suffix, then drop the 'format' kwarg before
|
# a .json, .xml suffix, then drop the 'format' kwarg before
|
||||||
# constructing the query.
|
# constructing the query.
|
||||||
if BaseRenderer._FORMAT_QUERY_PARAM in tmp:
|
if BaseRenderer._FORMAT_QUERY_PARAM in kwargs:
|
||||||
del tmp[BaseRenderer._FORMAT_QUERY_PARAM]
|
del kwargs[BaseRenderer._FORMAT_QUERY_PARAM]
|
||||||
|
|
||||||
if args:
|
return kwargs
|
||||||
# If we have any no kwargs then assume the last arg represents the
|
|
||||||
# primrary key. Otherwise assume the kwargs uniquely identify the
|
|
||||||
# model.
|
|
||||||
tmp.update({'pk': args[-1]})
|
|
||||||
return Q(**tmp)
|
|
||||||
|
|
||||||
def get_instance_data(self, model, content, **kwargs):
|
def get_instance_data(self, model, content, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns the dict with the data for model instance creation/update query.
|
Returns the dict with the data for model instance creation/update.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- model: model class (django.db.models.Model subclass) to work with
|
- model: model class (django.db.models.Model subclass) to work with
|
||||||
|
@ -548,12 +528,11 @@ class ModelMixin(object):
|
||||||
|
|
||||||
return all_kw_args
|
return all_kw_args
|
||||||
|
|
||||||
def get_object(self, *args, **kwargs):
|
def get_instance(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Get the instance object for read/update/delete requests.
|
Get a model instance for read/update/delete requests.
|
||||||
"""
|
"""
|
||||||
model = self.resource.model
|
return self.get_queryset().get(**kwargs)
|
||||||
return model.objects.get(self.build_query(*args, **kwargs))
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""
|
"""
|
||||||
|
@ -575,21 +554,15 @@ class ReadModelMixin(ModelMixin):
|
||||||
"""
|
"""
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
model = self.resource.model
|
model = self.resource.model
|
||||||
|
query_kwargs = self.get_query_kwargs(request, *args, **kwargs)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.model_instance = self.get_object(*args, **kwargs)
|
self.model_instance = self.get_instance(**query_kwargs)
|
||||||
except model.DoesNotExist:
|
except model.DoesNotExist:
|
||||||
raise ErrorResponse(status.HTTP_404_NOT_FOUND)
|
raise ErrorResponse(status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
return self.model_instance
|
return self.model_instance
|
||||||
|
|
||||||
def build_query(self, *args, **kwargs):
|
|
||||||
# Build query is overriden to filter the kwargs priori
|
|
||||||
# to use them as build_query argument
|
|
||||||
filtered_keywords = kwargs.copy()
|
|
||||||
|
|
||||||
return super(ReadModelMixin, self).build_query(*args, **filtered_keywords)
|
|
||||||
|
|
||||||
|
|
||||||
class CreateModelMixin(ModelMixin):
|
class CreateModelMixin(ModelMixin):
|
||||||
"""
|
"""
|
||||||
|
@ -637,11 +610,12 @@ class UpdateModelMixin(ModelMixin):
|
||||||
"""
|
"""
|
||||||
def put(self, request, *args, **kwargs):
|
def put(self, request, *args, **kwargs):
|
||||||
model = self.resource.model
|
model = self.resource.model
|
||||||
|
query_kwargs = self.get_query_kwargs(request, *args, **kwargs)
|
||||||
|
|
||||||
# TODO: update on the url of a non-existing resource url doesn't work
|
# TODO: update on the url of a non-existing resource url doesn't work
|
||||||
# correctly at the moment - will end up with a new url
|
# correctly at the moment - will end up with a new url
|
||||||
try:
|
try:
|
||||||
self.model_instance = self.get_object(*args, **kwargs)
|
self.model_instance = self.get_instance(**query_kwargs)
|
||||||
|
|
||||||
for (key, val) in self.CONTENT.items():
|
for (key, val) in self.CONTENT.items():
|
||||||
setattr(self.model_instance, key, val)
|
setattr(self.model_instance, key, val)
|
||||||
|
@ -657,9 +631,10 @@ class DeleteModelMixin(ModelMixin):
|
||||||
"""
|
"""
|
||||||
def delete(self, request, *args, **kwargs):
|
def delete(self, request, *args, **kwargs):
|
||||||
model = self.resource.model
|
model = self.resource.model
|
||||||
|
query_kwargs = self.get_query_kwargs(request, *args, **kwargs)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
instance = self.get_object(*args, **kwargs)
|
instance = self.get_instance(**query_kwargs)
|
||||||
except model.DoesNotExist:
|
except model.DoesNotExist:
|
||||||
raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {})
|
raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {})
|
||||||
|
|
||||||
|
@ -675,8 +650,9 @@ class ListModelMixin(ModelMixin):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
queryset = self.get_queryset()
|
queryset = self.get_queryset()
|
||||||
ordering = self.get_ordering()
|
ordering = self.get_ordering()
|
||||||
|
query_kwargs = self.get_query_kwargs(request, *args, **kwargs)
|
||||||
|
|
||||||
queryset = queryset.filter(self.build_query(**kwargs))
|
queryset = queryset.filter(**query_kwargs)
|
||||||
if ordering:
|
if ordering:
|
||||||
queryset = queryset.order_by(*ordering)
|
queryset = queryset.order_by(*ordering)
|
||||||
|
|
||||||
|
@ -710,7 +686,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:
|
||||||
|
|
|
@ -188,7 +188,7 @@ class PerUserThrottling(BaseThrottle):
|
||||||
|
|
||||||
def get_cache_key(self):
|
def get_cache_key(self):
|
||||||
if self.auth.is_authenticated():
|
if self.auth.is_authenticated():
|
||||||
ident = str(self.auth)
|
ident = self.auth.id
|
||||||
else:
|
else:
|
||||||
ident = self.view.request.META.get('REMOTE_ADDR', None)
|
ident = self.view.request.META.get('REMOTE_ADDR', None)
|
||||||
return 'throttle_user_%s' % ident
|
return 'throttle_user_%s' % ident
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Response(object):
|
||||||
return STATUS_CODE_TEXT.get(self.status, '')
|
return STATUS_CODE_TEXT.get(self.status, '')
|
||||||
|
|
||||||
|
|
||||||
class ErrorResponse(BaseException):
|
class ErrorResponse(Exception):
|
||||||
"""
|
"""
|
||||||
An exception representing an Response that should be returned immediately.
|
An exception representing an Response that should be returned immediately.
|
||||||
Any content should be serialized as-is, without being filtered.
|
Any content should be serialized as-is, without being filtered.
|
||||||
|
|
|
@ -97,6 +97,14 @@ INSTALLED_APPS = (
|
||||||
'djangorestframework',
|
'djangorestframework',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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.
|
# OAuth support is optional, so we only test oauth if it's installed.
|
||||||
try:
|
try:
|
||||||
import oauth_provider
|
import oauth_provider
|
||||||
|
|
1209
djangorestframework/static/css/djangorestframework.css
Normal file
1209
djangorestframework/static/css/djangorestframework.css
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -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>
|
||||||
|
|
|
@ -1,25 +1,14 @@
|
||||||
{% load urlize_quoted_links %}{% load add_query_param %}<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
{% load urlize_quoted_links %}
|
||||||
|
{% load add_query_param %}
|
||||||
|
{% load static %}
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
<head>
|
<head>
|
||||||
<style>
|
<link rel="stylesheet" type="text/css" href='{% get_static_prefix %}css/djangorestframework.css'/>
|
||||||
/* Override some of the Django admin styling */
|
<title>Django REST framework - {{ name }}</title>
|
||||||
#site-name a {color: #F4F379 !important;}
|
</head>
|
||||||
.errorlist {display: inline !important}
|
|
||||||
.errorlist li {display: inline !important; background: white !important; color: black !important; border: 0 !important;}
|
|
||||||
/* Custom styles */
|
|
||||||
.version{font-size:8px;}
|
|
||||||
</style>
|
|
||||||
{% if ADMIN_MEDIA_PREFIX %}
|
|
||||||
<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'/>
|
|
||||||
{% 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'/>
|
|
||||||
{% endif %}
|
|
||||||
<title>Django REST framework - {{ name }}</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
<body>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
|
|
||||||
|
@ -34,7 +23,7 @@
|
||||||
|
|
||||||
<div class="breadcrumbs">
|
<div class="breadcrumbs">
|
||||||
{% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
|
{% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
|
||||||
<a href="{{breadcrumb_url}}">{{breadcrumb_name}}</a> {% if not forloop.last %}›{% endif %}
|
<a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a> {% if not forloop.last %}›{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -50,7 +39,7 @@
|
||||||
|
|
||||||
<div class='content-main'>
|
<div class='content-main'>
|
||||||
<h1>{{ name }}</h1>
|
<h1>{{ name }}</h1>
|
||||||
<p>{% autoescape off %}{{ description }}{% endautoescape %}</p>
|
<p>{{ description }}</p>
|
||||||
<div class='module'>
|
<div class='module'>
|
||||||
<pre><b>{{ response.status }} {{ response.status_text }}</b>{% autoescape off %}
|
<pre><b>{{ response.status }} {{ response.status_text }}</b>{% autoescape off %}
|
||||||
{% for key, val in response.headers.items %}<b>{{ key }}:</b> {{ val|urlize_quoted_links }}
|
{% for key, val in response.headers.items %}<b>{{ key }}:</b> {{ val|urlize_quoted_links }}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{{ name }}
|
{% autoescape off %}{{ name }}
|
||||||
|
|
||||||
{{ description }}
|
{{ description }}
|
||||||
|
|
||||||
{% autoescape off %}HTTP/1.0 {{ response.status }} {{ response.status_text }}
|
HTTP/1.0 {{ response.status }} {{ response.status_text }}
|
||||||
{% for key, val in response.headers.items %}{{ key }}: {{ val }}
|
{% for key, val in response.headers.items %}{{ key }}: {{ val }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{{ content }}{% endautoescape %}
|
{{ content }}{% endautoescape %}
|
||||||
|
|
|
@ -30,7 +30,7 @@ class TestModelRead(TestModelsTestCase):
|
||||||
mixin = ReadModelMixin()
|
mixin = ReadModelMixin()
|
||||||
mixin.resource = GroupResource
|
mixin.resource = GroupResource
|
||||||
|
|
||||||
response = mixin.get(request, group.id)
|
response = mixin.get(request, id=group.id)
|
||||||
self.assertEquals(group.name, response.name)
|
self.assertEquals(group.name, response.name)
|
||||||
|
|
||||||
def test_read_404(self):
|
def test_read_404(self):
|
||||||
|
@ -41,7 +41,7 @@ class TestModelRead(TestModelsTestCase):
|
||||||
mixin = ReadModelMixin()
|
mixin = ReadModelMixin()
|
||||||
mixin.resource = GroupResource
|
mixin.resource = GroupResource
|
||||||
|
|
||||||
self.assertRaises(ErrorResponse, mixin.get, request, 12345)
|
self.assertRaises(ErrorResponse, mixin.get, request, id=12345)
|
||||||
|
|
||||||
|
|
||||||
class TestModelCreation(TestModelsTestCase):
|
class TestModelCreation(TestModelsTestCase):
|
||||||
|
@ -280,3 +280,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'])
|
||||||
|
|
|
@ -36,6 +36,7 @@ def _remove_trailing_string(content, trailing):
|
||||||
return content[:-len(trailing)]
|
return content[:-len(trailing)]
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
def _remove_leading_indent(content):
|
def _remove_leading_indent(content):
|
||||||
"""
|
"""
|
||||||
Remove leading indent from a block of text.
|
Remove leading indent from a block of text.
|
||||||
|
@ -50,6 +51,7 @@ def _remove_leading_indent(content):
|
||||||
return re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content)
|
return re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content)
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
def _camelcase_to_spaces(content):
|
def _camelcase_to_spaces(content):
|
||||||
"""
|
"""
|
||||||
Translate 'CamelCaseNames' to 'Camel Case Names'.
|
Translate 'CamelCaseNames' to 'Camel Case Names'.
|
||||||
|
@ -161,9 +163,10 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
||||||
|
|
||||||
def markup_description(self, description):
|
def markup_description(self, description):
|
||||||
if apply_markdown:
|
if apply_markdown:
|
||||||
return apply_markdown(description)
|
description = apply_markdown(description)
|
||||||
else:
|
else:
|
||||||
return mark_safe(escape(description).replace('\n', '<br />'))
|
description = escape(description).replace('\n', '<br />')
|
||||||
|
return mark_safe(description)
|
||||||
|
|
||||||
def http_method_not_allowed(self, request, *args, **kwargs):
|
def http_method_not_allowed(self, request, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -105,6 +105,8 @@ The following example exposes your `MyModel` model through an api. It will provi
|
||||||
|
|
||||||
contents
|
contents
|
||||||
|
|
||||||
|
.. include:: ../CHANGELOG.rst
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
Pygments==1.4
|
Pygments==1.4
|
||||||
Markdown==2.0.3
|
Markdown==2.0.3
|
||||||
djangorestframework
|
git+git://github.com/tomchristie/django-rest-framework.git
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# Settings for djangorestframework examples project
|
# Settings for djangorestframework examples project
|
||||||
|
import django
|
||||||
import os
|
import os
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
@ -53,16 +54,10 @@ MEDIA_ROOT = os.path.join(os.getenv('EPIO_DATA_DIRECTORY', '.'), 'media')
|
||||||
# trailing slash if there is a path component (optional in other cases).
|
# trailing slash if there is a path component (optional in other cases).
|
||||||
# Examples: "http://media.lawrence.com", "http://example.com/media/"
|
# Examples: "http://media.lawrence.com", "http://example.com/media/"
|
||||||
# NOTE: None of the djangorestframework examples serve media content via MEDIA_URL.
|
# NOTE: None of the djangorestframework examples serve media content via MEDIA_URL.
|
||||||
MEDIA_URL = ''
|
MEDIA_URL = '/uploads/'
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
|
|
||||||
# trailing slash.
|
|
||||||
# Examples: "http://foo.com/media/", "/media/".
|
|
||||||
# NOTE: djangorestframework does not require the admin app to be installed,
|
|
||||||
# but it does require the admin media be served. Django's test server will do
|
|
||||||
# this for you automatically, but in production you'll want to make sure you
|
|
||||||
# serve the admin media from somewhere.
|
|
||||||
ADMIN_MEDIA_PREFIX = '/static/admin'
|
|
||||||
|
|
||||||
# Make this unique, and don't share it with anybody.
|
# Make this unique, and don't share it with anybody.
|
||||||
SECRET_KEY = 't&9mru2_k$t8e2-9uq-wu2a1)9v*us&j3i#lsqkt(lbx*vh1cu'
|
SECRET_KEY = 't&9mru2_k$t8e2-9uq-wu2a1)9v*us&j3i#lsqkt(lbx*vh1cu'
|
||||||
|
@ -90,18 +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',
|
||||||
|
staticfiles,
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
|
|
||||||
'djangorestframework',
|
'djangorestframework',
|
||||||
|
|
|
@ -1,6 +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
|
||||||
|
except ImportError: # Django <= 1.2
|
||||||
|
from staticfiles.urls import staticfiles_urlpatterns
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
(r'^$', Sandbox.as_view()),
|
(r'^$', Sandbox.as_view()),
|
||||||
|
@ -15,3 +19,4 @@ urlpatterns = patterns('',
|
||||||
(r'^', include('djangorestframework.urls')),
|
(r'^', include('djangorestframework.urls')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
urlpatterns += staticfiles_urlpatterns()
|
||||||
|
|
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',
|
||||||
|
|
6
tox.ini
6
tox.ini
|
@ -32,6 +32,7 @@ commands=
|
||||||
basepython=python2.5
|
basepython=python2.5
|
||||||
deps=
|
deps=
|
||||||
django==1.2.4
|
django==1.2.4
|
||||||
|
django-staticfiles>=1.1.2
|
||||||
coverage==3.4
|
coverage==3.4
|
||||||
URLObject>=0.6.0
|
URLObject>=0.6.0
|
||||||
unittest-xml-reporting==1.2
|
unittest-xml-reporting==1.2
|
||||||
|
@ -43,6 +44,7 @@ deps=
|
||||||
basepython=python2.6
|
basepython=python2.6
|
||||||
deps=
|
deps=
|
||||||
django==1.2.4
|
django==1.2.4
|
||||||
|
django-staticfiles>=1.1.2
|
||||||
coverage==3.4
|
coverage==3.4
|
||||||
URLObject>=0.6.0
|
URLObject>=0.6.0
|
||||||
unittest-xml-reporting==1.2
|
unittest-xml-reporting==1.2
|
||||||
|
@ -54,6 +56,7 @@ deps=
|
||||||
basepython=python2.7
|
basepython=python2.7
|
||||||
deps=
|
deps=
|
||||||
django==1.2.4
|
django==1.2.4
|
||||||
|
django-staticfiles>=1.1.2
|
||||||
coverage==3.4
|
coverage==3.4
|
||||||
URLObject>=0.6.0
|
URLObject>=0.6.0
|
||||||
unittest-xml-reporting==1.2
|
unittest-xml-reporting==1.2
|
||||||
|
@ -135,6 +138,7 @@ commands=
|
||||||
python examples/runtests.py
|
python examples/runtests.py
|
||||||
deps=
|
deps=
|
||||||
django==1.2.4
|
django==1.2.4
|
||||||
|
django-staticfiles>=1.1.2
|
||||||
coverage==3.4
|
coverage==3.4
|
||||||
URLObject>=0.6.0
|
URLObject>=0.6.0
|
||||||
wsgiref==0.1.2
|
wsgiref==0.1.2
|
||||||
|
@ -150,6 +154,7 @@ commands=
|
||||||
python examples/runtests.py
|
python examples/runtests.py
|
||||||
deps=
|
deps=
|
||||||
django==1.2.4
|
django==1.2.4
|
||||||
|
django-staticfiles>=1.1.2
|
||||||
coverage==3.4
|
coverage==3.4
|
||||||
URLObject>=0.6.0
|
URLObject>=0.6.0
|
||||||
wsgiref==0.1.2
|
wsgiref==0.1.2
|
||||||
|
@ -165,6 +170,7 @@ commands=
|
||||||
python examples/runtests.py
|
python examples/runtests.py
|
||||||
deps=
|
deps=
|
||||||
django==1.2.4
|
django==1.2.4
|
||||||
|
django-staticfiles>=1.1.2
|
||||||
coverage==3.4
|
coverage==3.4
|
||||||
URLObject>=0.6.0
|
URLObject>=0.6.0
|
||||||
wsgiref==0.1.2
|
wsgiref==0.1.2
|
||||||
|
|
Loading…
Reference in New Issue
Block a user