aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AUTHORS2
-rw-r--r--CHANGELOG.rst (renamed from RELEASES)41
-rw-r--r--MANIFEST.in4
-rw-r--r--README.rst7
-rw-r--r--djangorestframework/__init__.py2
-rw-r--r--djangorestframework/mixins.py66
-rw-r--r--djangorestframework/permissions.py2
-rw-r--r--djangorestframework/response.py2
-rw-r--r--djangorestframework/runtests/settings.py8
-rw-r--r--djangorestframework/static/css/djangorestframework.css1209
-rw-r--r--djangorestframework/templates/api_login.html90
-rw-r--r--djangorestframework/templates/renderer.html31
-rw-r--r--djangorestframework/templates/renderer.txt4
-rw-r--r--djangorestframework/tests/mixins.py13
-rw-r--r--djangorestframework/views.py7
-rw-r--r--docs/index.rst2
-rw-r--r--examples/requirements-epio.txt2
-rw-r--r--examples/settings.py24
-rw-r--r--examples/urls.py9
-rwxr-xr-x[-rw-r--r--]setup.py81
-rw-r--r--tox.ini6
21 files changed, 1444 insertions, 168 deletions
diff --git a/AUTHORS b/AUTHORS
index 8d33f850..7fae4578 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -28,6 +28,8 @@ Sebastian Żurek <sebzur>
Benoit C <dzen>
Chris Pickett <bunchesofdonald>
Ben Timby <btimby>
+Michele Lazzeri <michelelazzeri-nextage>
+Camille Harang <mammique>
THANKS TO:
diff --git a/RELEASES b/CHANGELOG.rst
index 85d26c71..d80eb93f 100644
--- a/RELEASES
+++ b/CHANGELOG.rst
@@ -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
+-----
* JSONP Support
* Bugfixes, including support for latest markdown release
0.2.4
+-----
* Fix broken IsAdminUser permission.
* OPTIONS support.
@@ -11,20 +46,24 @@
* Drop mentions of Blog, BitBucket.
0.2.3
+-----
* Fix some throttling bugs.
* ``X-Throttle`` header on throttling.
* Support for nesting resources on related models.
0.2.2
+-----
* Throttling support complete.
0.2.1
+-----
* Couple of simple bugfixes over 0.2.0
0.2.0
+-----
* 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.
@@ -49,9 +88,11 @@
You can reuse these mixin classes individually without using the ``View`` class.
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.
0.1.0
+-----
* Initial release.
diff --git a/MANIFEST.in b/MANIFEST.in
index fc9ce976..5c6a1c57 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -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 examples .keep *.py *.txt
recursive-include docs *.py *.rst *.html *.txt
-include AUTHORS LICENSE requirements.txt tox.ini
+include AUTHORS LICENSE CHANGELOG.rst requirements.txt tox.ini
diff --git a/README.rst b/README.rst
index 9cec01cf..23a8075e 100644
--- a/README.rst
+++ b/README.rst
@@ -1,7 +1,12 @@
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:
diff --git a/djangorestframework/__init__.py b/djangorestframework/__init__.py
index 55f92cc4..0aaa2915 100644
--- a/djangorestframework/__init__.py
+++ b/djangorestframework/__init__.py
@@ -1,3 +1,3 @@
-__version__ = '0.3.2-dev'
+__version__ = '0.3.3-dev'
VERSION = __version__ # synonym
diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py
index 70492d84..cb0b913a 100644
--- a/djangorestframework/mixins.py
+++ b/djangorestframework/mixins.py
@@ -6,7 +6,6 @@ classes that can be added to a `View`.
from django.contrib.auth.models import AnonymousUser
from django.core.paginator import Paginator
from django.db.models.fields.related import ForeignKey
-from django.db.models.query import Q
from django.http import HttpResponse
from urlobject import URLObject
@@ -486,44 +485,25 @@ class ModelMixin(object):
queryset = None
- def build_query(self, *args, **kwargs):
- """ Returns django.db.models.Q object to be used for the objects retrival.
-
- Arguments:
- - 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.
+ def get_query_kwargs(self, *args, **kwargs):
+ """
+ Return a dict of kwargs that will be used to build the
+ model instance retrieval or to filter querysets.
"""
- tmp = dict(kwargs)
+ kwargs = dict(kwargs)
# If the URLconf includes a .(?P<format>\w+) pattern to match against
# a .json, .xml suffix, then drop the 'format' kwarg before
# constructing the query.
- if BaseRenderer._FORMAT_QUERY_PARAM in tmp:
- del tmp[BaseRenderer._FORMAT_QUERY_PARAM]
+ if BaseRenderer._FORMAT_QUERY_PARAM in kwargs:
+ del kwargs[BaseRenderer._FORMAT_QUERY_PARAM]
- if args:
- # 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)
+ return 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:
- model: model class (django.db.models.Model subclass) to work with
@@ -548,12 +528,11 @@ class ModelMixin(object):
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 model.objects.get(self.build_query(*args, **kwargs))
+ return self.get_queryset().get(**kwargs)
def get_queryset(self):
"""
@@ -575,21 +554,15 @@ class ReadModelMixin(ModelMixin):
"""
def get(self, request, *args, **kwargs):
model = self.resource.model
+ query_kwargs = self.get_query_kwargs(request, *args, **kwargs)
try:
- self.model_instance = self.get_object(*args, **kwargs)
+ self.model_instance = self.get_instance(**query_kwargs)
except model.DoesNotExist:
raise ErrorResponse(status.HTTP_404_NOT_FOUND)
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):
"""
@@ -637,11 +610,12 @@ class UpdateModelMixin(ModelMixin):
"""
def put(self, request, *args, **kwargs):
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
# correctly at the moment - will end up with a new url
try:
- self.model_instance = self.get_object(*args, **kwargs)
+ self.model_instance = self.get_instance(**query_kwargs)
for (key, val) in self.CONTENT.items():
setattr(self.model_instance, key, val)
@@ -657,9 +631,10 @@ class DeleteModelMixin(ModelMixin):
"""
def delete(self, request, *args, **kwargs):
model = self.resource.model
+ query_kwargs = self.get_query_kwargs(request, *args, **kwargs)
try:
- instance = self.get_object(*args, **kwargs)
+ instance = self.get_instance(**query_kwargs)
except model.DoesNotExist:
raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {})
@@ -675,8 +650,9 @@ class ListModelMixin(ModelMixin):
def get(self, request, *args, **kwargs):
queryset = self.get_queryset()
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:
queryset = queryset.order_by(*ordering)
@@ -710,7 +686,7 @@ class PaginatorMixin(object):
Constructs a url used for getting the next/previous urls
"""
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()
if limit != self.limit:
diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py
index 945023ce..dfe55ce9 100644
--- a/djangorestframework/permissions.py
+++ b/djangorestframework/permissions.py
@@ -188,7 +188,7 @@ class PerUserThrottling(BaseThrottle):
def get_cache_key(self):
if self.auth.is_authenticated():
- ident = str(self.auth)
+ ident = self.auth.id
else:
ident = self.view.request.META.get('REMOTE_ADDR', None)
return 'throttle_user_%s' % ident
diff --git a/djangorestframework/response.py b/djangorestframework/response.py
index 96345cee..13f0477e 100644
--- a/djangorestframework/response.py
+++ b/djangorestframework/response.py
@@ -34,7 +34,7 @@ class Response(object):
return STATUS_CODE_TEXT.get(self.status, '')
-class ErrorResponse(BaseException):
+class ErrorResponse(Exception):
"""
An exception representing an Response that should be returned immediately.
Any content should be serialized as-is, without being filtered.
diff --git a/djangorestframework/runtests/settings.py b/djangorestframework/runtests/settings.py
index 07855fa7..f54a554b 100644
--- a/djangorestframework/runtests/settings.py
+++ b/djangorestframework/runtests/settings.py
@@ -97,6 +97,14 @@ INSTALLED_APPS = (
'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.
try:
import oauth_provider
diff --git a/djangorestframework/static/css/djangorestframework.css b/djangorestframework/static/css/djangorestframework.css
new file mode 100644
index 00000000..1e75b8e8
--- /dev/null
+++ b/djangorestframework/static/css/djangorestframework.css
@@ -0,0 +1,1209 @@
+/********************** 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(../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(../img/admin/arrow-up.gif) right .4em no-repeat;
+}
+
+table thead th.descending a {
+ background: url(../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(../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(../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(../img/admin/nav-bg-reverse.gif);
+ background-position: top;
+}
+
+.button[disabled], input[type=submit][disabled], input[type=button][disabled] {
+ background-image: url(../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(../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(../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(../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(../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(../img/admin/icon_success.gif) 5px .3em no-repeat;
+}
+
+ul.messagelist li.warning{
+ background-image: url(../img/admin/icon_alert.gif);
+}
+
+ul.messagelist li.error{
+ background-image: url(../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(../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(../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(../img/admin/icon_error.gif) 5px .3em no-repeat;
+}
+
+.description {
+ font-size: 12px;
+ padding: 5px 0 0 12px;
+}
+
+/* BREADCRUMBS */
+
+div.breadcrumbs {
+ background: white url(../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(../img/admin/icon_addlink.gif) 0 .2em no-repeat;
+}
+
+.changelink {
+ padding-left: 12px;
+ background: url(../img/admin/icon_changelink.gif) 0 .2em no-repeat;
+}
+
+.deletelink {
+ padding-left: 12px;
+ background: url(../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(../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(../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(../img/admin/tool-right.gif) 100% 0 no-repeat;
+}
+
+.object-tools a:hover, .object-tools li:hover a {
+ background: #5b80b2 url(../img/admin/tool-right_over.gif) 100% 0 no-repeat;
+}
+
+.object-tools a.viewsitelink, .object-tools a.golink {
+ background: #999 url(../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(../img/admin/tooltag-arrowright_over.gif) top right no-repeat;
+}
+
+.object-tools a.addlink {
+ background: #999 url(../img/admin/tooltag-add.gif) top right no-repeat;
+ padding-right: 28px;
+}
+
+.object-tools a.addlink:hover {
+ background: #5b80b2 url(../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(../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(../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(../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(../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(../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(../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(../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/templates/api_login.html b/djangorestframework/templates/api_login.html
index 750f898b..016a4e10 100644
--- a/djangorestframework/templates/api_login.html
+++ b/djangorestframework/templates/api_login.html
@@ -1,54 +1,44 @@
+{% load static %}
<html>
- <head>
- {% 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'/>
- <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>
- <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.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>&nbsp;</label><input type="submit" value="Log in">
- </div>
-</form>
-<script type="text/javascript">
-document.getElementById('id_username').focus()
-</script>
-</div>
+ <head>
+ <link rel="stylesheet" type="text/css" href='{% get_static_prefix %}css/djangorestframework.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.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>&nbsp;</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>
-
- <br class="clear">
- </div>
-
- <div id="footer"></div>
-
-</div>
-</body>
+ </div>
+ </body>
</html>
diff --git a/djangorestframework/templates/renderer.html b/djangorestframework/templates/renderer.html
index ff761023..e396a58f 100644
--- a/djangorestframework/templates/renderer.html
+++ b/djangorestframework/templates/renderer.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"
"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">
- <head>
- <style>
- /* Override some of the Django admin styling */
- #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;}
- </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>
+ <head>
+ <link rel="stylesheet" type="text/css" href='{% get_static_prefix %}css/djangorestframework.css'/>
+ <title>Django REST framework - {{ name }}</title>
+ </head>
<body>
<div id="container">
@@ -34,7 +23,7 @@
<div class="breadcrumbs">
{% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
- <a href="{{breadcrumb_url}}">{{breadcrumb_name}}</a> {% if not forloop.last %}&rsaquo;{% endif %}
+ <a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a> {% if not forloop.last %}&rsaquo;{% endif %}
{% endfor %}
</div>
@@ -50,7 +39,7 @@
<div class='content-main'>
<h1>{{ name }}</h1>
- <p>{% autoescape off %}{{ description }}{% endautoescape %}</p>
+ <p>{{ description }}</p>
<div class='module'>
<pre><b>{{ response.status }} {{ response.status_text }}</b>{% autoescape off %}
{% for key, val in response.headers.items %}<b>{{ key }}:</b> {{ val|urlize_quoted_links }}
diff --git a/djangorestframework/templates/renderer.txt b/djangorestframework/templates/renderer.txt
index 5be8c117..b584952c 100644
--- a/djangorestframework/templates/renderer.txt
+++ b/djangorestframework/templates/renderer.txt
@@ -1,8 +1,8 @@
-{{ name }}
+{% autoescape off %}{{ name }}
{{ 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 }}
{% endfor %}
{{ content }}{% endautoescape %}
diff --git a/djangorestframework/tests/mixins.py b/djangorestframework/tests/mixins.py
index 88b13dd5..8268fdca 100644
--- a/djangorestframework/tests/mixins.py
+++ b/djangorestframework/tests/mixins.py
@@ -30,7 +30,7 @@ class TestModelRead(TestModelsTestCase):
mixin = ReadModelMixin()
mixin.resource = GroupResource
- response = mixin.get(request, group.id)
+ response = mixin.get(request, id=group.id)
self.assertEquals(group.name, response.name)
def test_read_404(self):
@@ -41,7 +41,7 @@ class TestModelRead(TestModelsTestCase):
mixin = ReadModelMixin()
mixin.resource = GroupResource
- self.assertRaises(ErrorResponse, mixin.get, request, 12345)
+ self.assertRaises(ErrorResponse, mixin.get, request, id=12345)
class TestModelCreation(TestModelsTestCase):
@@ -280,3 +280,12 @@ class TestPagination(TestCase):
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 = json.loads(response.content)
+ self.assertTrue('page=2' in content['next'])
+ self.assertFalse('page=1' in content['next'])
diff --git a/djangorestframework/views.py b/djangorestframework/views.py
index 88d81d25..32d2437c 100644
--- a/djangorestframework/views.py
+++ b/djangorestframework/views.py
@@ -36,6 +36,7 @@ def _remove_trailing_string(content, trailing):
return content[:-len(trailing)]
return content
+
def _remove_leading_indent(content):
"""
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 content
+
def _camelcase_to_spaces(content):
"""
Translate 'CamelCaseNames' to 'Camel Case Names'.
@@ -161,9 +163,10 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
def markup_description(self, description):
if apply_markdown:
- return apply_markdown(description)
+ description = apply_markdown(description)
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):
"""
diff --git a/docs/index.rst b/docs/index.rst
index 0221ea05..ecc1f118 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -105,6 +105,8 @@ The following example exposes your `MyModel` model through an api. It will provi
contents
+.. include:: ../CHANGELOG.rst
+
Indices and tables
------------------
diff --git a/examples/requirements-epio.txt b/examples/requirements-epio.txt
index 76ab5bd2..b4962676 100644
--- a/examples/requirements-epio.txt
+++ b/examples/requirements-epio.txt
@@ -1,3 +1,3 @@
Pygments==1.4
Markdown==2.0.3
-djangorestframework
+git+git://github.com/tomchristie/django-rest-framework.git
diff --git a/examples/settings.py b/examples/settings.py
index e12b7f3f..5ff9fd0f 100644
--- a/examples/settings.py
+++ b/examples/settings.py
@@ -1,4 +1,5 @@
# Settings for djangorestframework examples project
+import django
import os
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).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
# 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.
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.
)
-# for loading initial data
-##SERIALIZATION_MODULES = {
- # 'yml': "django.core.serializers.pyyaml"
-
-#}
-
+if django.VERSION < (1, 3):
+ staticfiles = 'staticfiles'
+else:
+ staticfiles = 'django.contrib.staticfiles'
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
+ staticfiles,
'django.contrib.messages',
'djangorestframework',
diff --git a/examples/urls.py b/examples/urls.py
index 08d97a14..33297b55 100644
--- a/examples/urls.py
+++ b/examples/urls.py
@@ -1,6 +1,10 @@
-from django.conf.urls.defaults import patterns, include, url
-from django.conf import settings
+from django.conf.urls.defaults import patterns, include
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('',
(r'^$', Sandbox.as_view()),
@@ -15,3 +19,4 @@ urlpatterns = patterns('',
(r'^', include('djangorestframework.urls')),
)
+urlpatterns += staticfiles_urlpatterns()
diff --git a/setup.py b/setup.py
index 690a7e0f..5cd2a28f 100644..100755
--- a/setup.py
+++ b/setup.py
@@ -1,33 +1,70 @@
-#!/usr/bin/env/python
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
from setuptools import setup
+import re
+import os
+import sys
-import os, re
-path = os.path.join(os.path.dirname(__file__), 'djangorestframework', '__init__.py')
-init_py = open(path).read()
-VERSION = re.match("__version__ = '([^']+)'", init_py).group(1)
+def get_version(package):
+ """
+ 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(
- name = 'djangorestframework',
- version = VERSION,
- url = 'http://django-rest-framework.org',
- download_url = 'http://pypi.python.org/pypi/djangorestframework/',
- license = 'BSD',
- description = 'A lightweight REST framework for Django.',
- author = 'Tom Christie',
- author_email = 'tom@tomchristie.com',
- packages = ['djangorestframework',
- 'djangorestframework.templatetags',
- 'djangorestframework.tests',
- 'djangorestframework.runtests',
- 'djangorestframework.utils'],
- package_dir={'djangorestframework': 'djangorestframework'},
- package_data = {'djangorestframework': ['templates/*', 'static/*']},
- test_suite = 'djangorestframework.runtests.runcoverage.main',
+ name='djangorestframework',
+ version=version,
+ url='http://django-rest-framework.org',
+ download_url='http://pypi.python.org/pypi/djangorestframework/',
+ license='BSD',
+ description='A lightweight REST framework for Django.',
+ author='Tom Christie',
+ author_email='tom@tomchristie.com',
+ packages=get_packages('djangorestframework'),
+ package_data=get_package_data('djangorestframework'),
+ test_suite='djangorestframework.runtests.runcoverage.main',
install_requires=['URLObject>=0.6.0'],
- classifiers = [
+ classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
'Framework :: Django',
diff --git a/tox.ini b/tox.ini
index 2e6ab204..8c3af256 100644
--- a/tox.ini
+++ b/tox.ini
@@ -32,6 +32,7 @@ commands=
basepython=python2.5
deps=
django==1.2.4
+ django-staticfiles>=1.1.2
coverage==3.4
URLObject>=0.6.0
unittest-xml-reporting==1.2
@@ -43,6 +44,7 @@ deps=
basepython=python2.6
deps=
django==1.2.4
+ django-staticfiles>=1.1.2
coverage==3.4
URLObject>=0.6.0
unittest-xml-reporting==1.2
@@ -54,6 +56,7 @@ deps=
basepython=python2.7
deps=
django==1.2.4
+ django-staticfiles>=1.1.2
coverage==3.4
URLObject>=0.6.0
unittest-xml-reporting==1.2
@@ -135,6 +138,7 @@ commands=
python examples/runtests.py
deps=
django==1.2.4
+ django-staticfiles>=1.1.2
coverage==3.4
URLObject>=0.6.0
wsgiref==0.1.2
@@ -150,6 +154,7 @@ commands=
python examples/runtests.py
deps=
django==1.2.4
+ django-staticfiles>=1.1.2
coverage==3.4
URLObject>=0.6.0
wsgiref==0.1.2
@@ -165,6 +170,7 @@ commands=
python examples/runtests.py
deps=
django==1.2.4
+ django-staticfiles>=1.1.2
coverage==3.4
URLObject>=0.6.0
wsgiref==0.1.2