From cb6f62b2afdb00d04a251dafa1a8d7a6bf620737 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Fri, 27 Jan 2012 20:45:53 +0100 Subject: [PATCH 01/35] fixes #146 rest.ep.io now runs tip. --- examples/requirements-epio.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/requirements-epio.txt b/examples/requirements-epio.txt index 76ab5bd23..b49626763 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 From 7af16dc762090a1184fd98fac9f4df9b733a18be Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Sat, 28 Jan 2012 12:47:16 +0100 Subject: [PATCH 02/35] Thanks @michelelazzzeri-nextage, if you give your full name, it will be listed here explicitly. --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 8d33f8501..106dd14c9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -28,6 +28,7 @@ Sebastian Żurek Benoit C Chris Pickett Ben Timby + THANKS TO: From 76a8c11cf1aeed605d1da895a44fbd8630f8a30a Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Sat, 28 Jan 2012 13:37:47 +0100 Subject: [PATCH 03/35] Preparing release notes for taggging and uploading to pypi. --- RELEASES | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/RELEASES b/RELEASES index 85d26c711..d41ea1859 100644 --- a/RELEASES +++ b/RELEASES @@ -1,3 +1,15 @@ +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 + +0.3.1 + +* [not documented] + 0.3.0 * JSONP Support From a9e0159481a1b52e396bb0edee5f03563226e6da Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Sat, 28 Jan 2012 13:38:29 +0100 Subject: [PATCH 04/35] prepare for tagging --- djangorestframework/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangorestframework/__init__.py b/djangorestframework/__init__.py index 55f92cc4b..a5ba90e23 100644 --- a/djangorestframework/__init__.py +++ b/djangorestframework/__init__.py @@ -1,3 +1,3 @@ -__version__ = '0.3.2-dev' +__version__ = '0.3.2' VERSION = __version__ # synonym From cc226e7e67b13a25c59a727581f4fd780f5d553a Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Sat, 28 Jan 2012 14:06:34 +0100 Subject: [PATCH 05/35] moving forward --- djangorestframework/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangorestframework/__init__.py b/djangorestframework/__init__.py index a5ba90e23..55f92cc4b 100644 --- a/djangorestframework/__init__.py +++ b/djangorestframework/__init__.py @@ -1,3 +1,3 @@ -__version__ = '0.3.2' +__version__ = '0.3.2-dev' VERSION = __version__ # synonym From bbfa404e4679f4229e44fd7e641e62fdd2e7bdd5 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Sat, 28 Jan 2012 15:27:06 +0100 Subject: [PATCH 06/35] Fix silly error. This makes more sense. --- djangorestframework/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangorestframework/__init__.py b/djangorestframework/__init__.py index 55f92cc4b..0aaa29152 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 From 22ee89f0f3fa295a265547336f837261bb919f34 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 28 Jan 2012 14:38:06 +0000 Subject: [PATCH 07/35] Tidy up auto-escaping. --- djangorestframework/templates/renderer.html | 2 +- djangorestframework/templates/renderer.txt | 4 ++-- djangorestframework/views.py | 7 +++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/djangorestframework/templates/renderer.html b/djangorestframework/templates/renderer.html index ff761023f..5faa8b3e1 100644 --- a/djangorestframework/templates/renderer.html +++ b/djangorestframework/templates/renderer.html @@ -50,7 +50,7 @@

{{ name }}

-

{% autoescape off %}{{ description }}{% endautoescape %}

+

{{ description }}

{{ response.status }} {{ response.status_text }}{% autoescape off %}
 {% for key, val in response.headers.items %}{{ key }}: {{ val|urlize_quoted_links }}
diff --git a/djangorestframework/templates/renderer.txt b/djangorestframework/templates/renderer.txt
index 5be8c1175..b584952cb 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/views.py b/djangorestframework/views.py
index 88d81d25b..32d2437c1 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', '
')) + description = escape(description).replace('\n', '
') + return mark_safe(description) def http_method_not_allowed(self, request, *args, **kwargs): """ From f34ed6d1f3d8c1c4c5059beba944aa386e9a15ab Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 28 Jan 2012 17:45:13 +0000 Subject: [PATCH 08/35] 0.3.2 Release notes --- RELEASES | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/RELEASES b/RELEASES index d41ea1859..facdfee2b 100644 --- a/RELEASES +++ b/RELEASES @@ -1,3 +1,7 @@ +0.3.3 (dev) + +* Saner template varible autoescaping + 0.3.2 * Bugfixes: @@ -5,6 +9,10 @@ * 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 From 765ec0b76ea22c857190ee6a25e9e6615e5b0c5e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 28 Jan 2012 18:53:55 +0000 Subject: [PATCH 09/35] Use `staticfiles` for serving css. Fixes #116. --- .../static/css/djangorestframework.css | 1152 +++++++++++++++++ djangorestframework/templates/renderer.html | 29 +- examples/settings.py | 13 +- examples/urls.py | 2 + 4 files changed, 1167 insertions(+), 29 deletions(-) create mode 100644 djangorestframework/static/css/djangorestframework.css diff --git a/djangorestframework/static/css/djangorestframework.css b/djangorestframework/static/css/djangorestframework.css new file mode 100644 index 000000000..8fc4bace5 --- /dev/null +++ b/djangorestframework/static/css/djangorestframework.css @@ -0,0 +1,1152 @@ +/********************** 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; +} + +/* 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; +} diff --git a/djangorestframework/templates/renderer.html b/djangorestframework/templates/renderer.html index 5faa8b3e1..e396a58f5 100644 --- a/djangorestframework/templates/renderer.html +++ b/djangorestframework/templates/renderer.html @@ -1,25 +1,14 @@ -{% load urlize_quoted_links %}{% load add_query_param %} + +{% load urlize_quoted_links %} +{% load add_query_param %} +{% load static %} - - - {% if ADMIN_MEDIA_PREFIX %} - - - {% else %} - - - {% endif %} - Django REST framework - {{ name }} - + + + Django REST framework - {{ name }} +
@@ -34,7 +23,7 @@ diff --git a/examples/settings.py b/examples/settings.py index e12b7f3fe..a8846b1b3 100644 --- a/examples/settings.py +++ b/examples/settings.py @@ -53,16 +53,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' @@ -102,6 +96,7 @@ INSTALLED_APPS = ( 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', + 'django.contrib.staticfiles', 'django.contrib.messages', 'djangorestframework', diff --git a/examples/urls.py b/examples/urls.py index 08d97a14a..bd4087fdd 100644 --- a/examples/urls.py +++ b/examples/urls.py @@ -1,6 +1,7 @@ from django.conf.urls.defaults import patterns, include, url from django.conf import settings from sandbox.views import Sandbox +from django.contrib.staticfiles.urls import staticfiles_urlpatterns urlpatterns = patterns('', (r'^$', Sandbox.as_view()), @@ -15,3 +16,4 @@ urlpatterns = patterns('', (r'^', include('djangorestframework.urls')), ) +urlpatterns += staticfiles_urlpatterns() From c0674e36d498b7b727e3d5a260b5cdf4104b4d26 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 28 Jan 2012 19:06:40 +0000 Subject: [PATCH 10/35] Drop implicit 'pk' on last arg in urlconf. (Too magical). --- djangorestframework/mixins.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index 7f0870f85..2ea5e8404 100644 --- a/djangorestframework/mixins.py +++ b/djangorestframework/mixins.py @@ -507,11 +507,6 @@ class ModelMixin(object): if BaseRenderer._FORMAT_QUERY_PARAM in tmp: del tmp[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) def get_instance_data(self, model, content, **kwargs): From f115be29e0c65adc474ed71aa94112aa7d5511c0 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 28 Jan 2012 19:10:07 +0000 Subject: [PATCH 11/35] Latest changelog. --- RELEASES | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/RELEASES b/RELEASES index facdfee2b..8e67e7269 100644 --- a/RELEASES +++ b/RELEASES @@ -1,6 +1,10 @@ -0.3.3 (dev) +development -* Saner template varible autoescaping +* Saner template varible 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. 0.3.2 From 289e421543830caa153a8ceb4f9685edced84fad Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Sun, 29 Jan 2012 13:46:04 +0100 Subject: [PATCH 12/35] Add Michele's full name. --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 106dd14c9..2466de043 100644 --- a/AUTHORS +++ b/AUTHORS @@ -28,7 +28,7 @@ Sebastian Żurek Benoit C Chris Pickett Ben Timby - +Michele Lazzeri THANKS TO: From c7a805603405e21897b1788408beb5165685a2ec Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sun, 29 Jan 2012 13:19:56 +0000 Subject: [PATCH 13/35] Use named args in mixin tests. --- djangorestframework/tests/mixins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/djangorestframework/tests/mixins.py b/djangorestframework/tests/mixins.py index 88b13dd5f..a7512efc7 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): From 278b3576f311ad49a95b8efb997a194e5211df57 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 30 Jan 2012 14:13:57 +0000 Subject: [PATCH 14/35] Fixes #148. Thanks @dvinegla. --- djangorestframework/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py index 945023cec..d832c5feb 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 = unicode(self.auth) else: ident = self.view.request.META.get('REMOTE_ADDR', None) return 'throttle_user_%s' % ident From 9746bb34cccd3158c0b13c0559342d9f88c07fdb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 30 Jan 2012 14:14:48 +0000 Subject: [PATCH 15/35] Update RELEASES --- RELEASES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASES b/RELEASES index 8e67e7269..5dd0ce779 100644 --- a/RELEASES +++ b/RELEASES @@ -5,6 +5,8 @@ development - 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. +* Bugfixes: + - Bug with PerUserThrottling when user contains unicode chars. 0.3.2 From 50c359c551728b8cdbb963a9f233c635065effb0 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 30 Jan 2012 15:54:38 +0000 Subject: [PATCH 16/35] Refs #148 --- djangorestframework/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py index d832c5feb..dfe55ce94 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 = unicode(self.auth) + ident = self.auth.id else: ident = self.view.request.META.get('REMOTE_ADDR', None) return 'throttle_user_%s' % ident From 243d80ccb0a6df6ce727356f1ffbd5a55098dfb9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 30 Jan 2012 16:05:13 +0000 Subject: [PATCH 17/35] Django 1.2 needs django-staticfiles --- tox.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tox.ini b/tox.ini index 2e6ab204c..7601cd02d 100644 --- a/tox.ini +++ b/tox.ini @@ -34,6 +34,7 @@ deps= django==1.2.4 coverage==3.4 URLObject>=0.6.0 + django-staticfiles>=1.1.2 unittest-xml-reporting==1.2 Pyyaml==3.10 # Optional packages: @@ -45,6 +46,7 @@ deps= django==1.2.4 coverage==3.4 URLObject>=0.6.0 + django-staticfiles>=1.1.2 unittest-xml-reporting==1.2 Pyyaml==3.10 # Optional packages: @@ -56,6 +58,7 @@ deps= django==1.2.4 coverage==3.4 URLObject>=0.6.0 + django-staticfiles>=1.1.2 unittest-xml-reporting==1.2 Pyyaml==3.10 # Optional packages: From 39d4a39441485bb5783fb0cc400a38a8feab14f6 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Mon, 30 Jan 2012 18:29:54 +0100 Subject: [PATCH 18/35] renamed to CHANGELOG.rst and included release notes in index.rst --- RELEASES => CHANGELOG.rst | 14 ++++++++++++++ docs/index.rst | 2 ++ 2 files changed, 16 insertions(+) rename RELEASES => CHANGELOG.rst (96%) diff --git a/RELEASES b/CHANGELOG.rst similarity index 96% rename from RELEASES rename to CHANGELOG.rst index 5dd0ce779..152f22671 100644 --- a/RELEASES +++ b/CHANGELOG.rst @@ -1,4 +1,8 @@ +Release Notes +============= + development +----------- * Saner template varible autoescaping. * Use `staticfiles` for css files. @@ -9,6 +13,7 @@ development - Bug with PerUserThrottling when user contains unicode chars. 0.3.2 +----- * Bugfixes: * Fix 403 for POST and PUT from the UI with UserLoggedInAuthentication (#115) @@ -21,15 +26,18 @@ development * 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. @@ -37,20 +45,24 @@ development * 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. @@ -75,9 +87,11 @@ development 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/docs/index.rst b/docs/index.rst index 0221ea052..ecc1f1182 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 ------------------ From 497d6df5e578b3ee750c65de7265b87fce2b0c7c Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Mon, 30 Jan 2012 21:24:20 +0100 Subject: [PATCH 19/35] Fix typo. --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 152f22671..9da9df2e1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,7 +4,7 @@ Release Notes development ----------- -* Saner template varible autoescaping. +* 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. From b2fcfffb3bdaed89d39ee563c58dc0ede5e857ac Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 31 Jan 2012 09:05:52 +0000 Subject: [PATCH 20/35] django-staticfiles for Django 1.2 compatability --- djangorestframework/runtests/settings.py | 6 ++++++ tox.ini | 9 ++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/djangorestframework/runtests/settings.py b/djangorestframework/runtests/settings.py index 07855fa7a..25f771114 100644 --- a/djangorestframework/runtests/settings.py +++ b/djangorestframework/runtests/settings.py @@ -97,6 +97,12 @@ INSTALLED_APPS = ( 'djangorestframework', ) +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/tox.ini b/tox.ini index 7601cd02d..8c3af2567 100644 --- a/tox.ini +++ b/tox.ini @@ -32,9 +32,9 @@ commands= basepython=python2.5 deps= django==1.2.4 + django-staticfiles>=1.1.2 coverage==3.4 URLObject>=0.6.0 - django-staticfiles>=1.1.2 unittest-xml-reporting==1.2 Pyyaml==3.10 # Optional packages: @@ -44,9 +44,9 @@ deps= basepython=python2.6 deps= django==1.2.4 + django-staticfiles>=1.1.2 coverage==3.4 URLObject>=0.6.0 - django-staticfiles>=1.1.2 unittest-xml-reporting==1.2 Pyyaml==3.10 # Optional packages: @@ -56,9 +56,9 @@ deps= basepython=python2.7 deps= django==1.2.4 + django-staticfiles>=1.1.2 coverage==3.4 URLObject>=0.6.0 - django-staticfiles>=1.1.2 unittest-xml-reporting==1.2 Pyyaml==3.10 # Optional packages: @@ -138,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 @@ -153,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 @@ -168,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 From 7886fa2b16fe9a82b1b83b7e2ddc361870b21d48 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 1 Feb 2012 20:24:45 +0000 Subject: [PATCH 21/35] Update test settings for 1.2 --- djangorestframework/runtests/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/djangorestframework/runtests/settings.py b/djangorestframework/runtests/settings.py index 25f771114..f54a554b3 100644 --- a/djangorestframework/runtests/settings.py +++ b/djangorestframework/runtests/settings.py @@ -97,6 +97,8 @@ INSTALLED_APPS = ( 'djangorestframework', ) +STATIC_URL = '/static/' + import django if django.VERSION < (1, 3): From 894f63259880252ed5317ce485eb13c4429b65c1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 1 Feb 2012 20:48:32 +0000 Subject: [PATCH 22/35] Remove use of Q objects. --- djangorestframework/mixins.py | 59 ++++++++++++----------------------- 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index 2ea5e8404..c7b32d2d2 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 @@ -479,39 +478,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\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] - 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 @@ -536,12 +521,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): """ @@ -563,21 +547,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): """ @@ -625,11 +603,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) @@ -645,9 +624,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, {}) @@ -663,8 +643,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) From da8187d2c293e095969c00751c21338d53c7f3f2 Mon Sep 17 00:00:00 2001 From: Camille Harang Date: Wed, 1 Feb 2012 23:56:54 +0100 Subject: [PATCH 23/35] * -> ** --- djangorestframework/mixins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index c7b32d2d2..f4a9c998a 100644 --- a/djangorestframework/mixins.py +++ b/djangorestframework/mixins.py @@ -608,7 +608,7 @@ class UpdateModelMixin(ModelMixin): # 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_instance(*query_kwargs) + self.model_instance = self.get_instance(**query_kwargs) for (key, val) in self.CONTENT.items(): setattr(self.model_instance, key, val) From bd25e99f155b68e31239bf1c9e3d4e70d53bbddd Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 2 Feb 2012 09:05:28 +0000 Subject: [PATCH 24/35] Python docs say inherit from Exception, not BaseException. --- djangorestframework/response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangorestframework/response.py b/djangorestframework/response.py index 96345cee2..13f0477ed 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. From 87ef85587dd58afa80ef2955c819c974edaa2cfc Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 2 Feb 2012 16:31:40 +0000 Subject: [PATCH 25/35] Update AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 2466de043..7fae45784 100644 --- a/AUTHORS +++ b/AUTHORS @@ -29,6 +29,7 @@ Benoit C Chris Pickett Ben Timby Michele Lazzeri +Camille Harang THANKS TO: From 15fc26f50b94d41d1024a3f40fe21af5f2d07bfb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 7 Feb 2012 08:58:15 +0000 Subject: [PATCH 26/35] Fix up packaging and staticfiles changes. Fixes #155. Fixes #153. Fixes #150. --- CHANGELOG.rst | 1 + MANIFEST.in | 4 +- .../static/css/djangorestframework.css | 57 ++++++++++++ djangorestframework/templates/api_login.html | 92 +++++++++---------- setup.py | 83 ++++++++++++----- 5 files changed, 161 insertions(+), 76 deletions(-) mode change 100644 => 100755 setup.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9da9df2e1..d80eb93f4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,7 @@ development - 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. diff --git a/MANIFEST.in b/MANIFEST.in index fc9ce9769..5c6a1c578 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/djangorestframework/static/css/djangorestframework.css b/djangorestframework/static/css/djangorestframework.css index 8fc4bace5..1e75b8e81 100644 --- a/djangorestframework/static/css/djangorestframework.css +++ b/djangorestframework/static/css/djangorestframework.css @@ -1129,6 +1129,58 @@ fieldset.monospace textarea { 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 { @@ -1147,6 +1199,11 @@ fieldset.monospace textarea { } /* 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 750f898bb..016a4e109 100644 --- a/djangorestframework/templates/api_login.html +++ b/djangorestframework/templates/api_login.html @@ -1,54 +1,44 @@ +{% load static %} - - {% if ADMIN_MEDIA_PREFIX %} - - - - {% else %} - - - - {% endif %} - - - -
- - + diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 690a7e0fd..919806297 --- 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 +from distutils.core 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', From ee2c8606af597820f146df164775937d7e62c811 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 7 Feb 2012 09:29:48 +0000 Subject: [PATCH 27/35] Tweaks. --- README.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9cec01cf2..520f7fc6a 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,11 @@ 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 `. + +Overview +======== Features: From 25b6718111b8f0ef30cdd40cdca125591972ae5f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 7 Feb 2012 09:30:08 +0000 Subject: [PATCH 28/35] Tweak --- README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 520f7fc6a..0d5608c19 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,8 @@ Django REST framework ===================== **Django REST framework makes it easy to build well-connected, self-describing RESTful Web APIs.** -**Author:** Tom Christie. `Follow me on twitter `. + +**Author:** Tom Christie. `Follow me on twitter `_. Overview ======== From 15edc88e70279ef46920da0ff5a9d289c69465f1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 7 Feb 2012 09:31:08 +0000 Subject: [PATCH 29/35] Tweak --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 0d5608c19..23a8075e8 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ Django REST framework **Django REST framework makes it easy to build well-connected, self-describing RESTful Web APIs.** -**Author:** Tom Christie. `Follow me on twitter `_. +**Author:** Tom Christie. `Follow me on Twitter `_. Overview ======== From 304c99598346c7168e951bcd9f144b9adb537914 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 7 Feb 2012 09:47:15 +0000 Subject: [PATCH 30/35] Fix distutils errors --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 919806297..8bc44c0e5 100755 --- a/setup.py +++ b/setup.py @@ -62,8 +62,7 @@ setup( 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'], + requires=['URLObject>=0.6.0'], classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', From 8c1e6beb85c55d6327a582fd87f60c92419a2697 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 7 Feb 2012 09:55:01 +0000 Subject: [PATCH 31/35] Distutils, you are a harsh master. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8bc44c0e5..ef2e7362c 100755 --- a/setup.py +++ b/setup.py @@ -62,7 +62,7 @@ setup( author_email='tom@tomchristie.com', packages=get_packages('djangorestframework'), package_data=get_package_data('djangorestframework'), - requires=['URLObject>=0.6.0'], + requires=['URLObject (>=0.6.0)'], classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', From 6207791af1702ec4a80d689b39ca4e4d1c9d8157 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 7 Feb 2012 10:14:34 +0000 Subject: [PATCH 32/35] Attempt to stop banging head against brick wall. --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index ef2e7362c..5cd2a28fa 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from distutils.core import setup +from setuptools import setup import re import os import sys @@ -62,7 +62,8 @@ setup( author_email='tom@tomchristie.com', packages=get_packages('djangorestframework'), package_data=get_package_data('djangorestframework'), - requires=['URLObject (>=0.6.0)'], + test_suite='djangorestframework.runtests.runcoverage.main', + install_requires=['URLObject>=0.6.0'], classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', From 0f49f9b2b5c4e36c6edbb9b135e5653bdec46ce2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 7 Feb 2012 11:01:43 +0000 Subject: [PATCH 33/35] In examples settings, use staticfiles for 1.2, django.contrib.staticfiles otherwise --- examples/settings.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/settings.py b/examples/settings.py index a8846b1b3..5ff9fd0f3 100644 --- a/examples/settings.py +++ b/examples/settings.py @@ -1,4 +1,5 @@ # Settings for djangorestframework examples project +import django import os DEBUG = True @@ -84,19 +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', - 'django.contrib.staticfiles', + staticfiles, 'django.contrib.messages', 'djangorestframework', From 76a7d35813b637bb199a0d388468f9265f8adaf2 Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Tue, 7 Feb 2012 11:08:55 +0000 Subject: [PATCH 34/35] Ensure duplicate "page" parameters are not created Previously, URLObject.add_query_param was used to generate next/previous page links in PaginatorMixin. This resulted in (for example) page 2's "next" link having the params: ?page=2&page=3 Instead, URLObject.set_query_param should be used to replace the current value of the "page" parameter. --- djangorestframework/mixins.py | 2 +- djangorestframework/tests/mixins.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index f4a9c998a..836c3a594 100644 --- a/djangorestframework/mixins.py +++ b/djangorestframework/mixins.py @@ -679,7 +679,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/tests/mixins.py b/djangorestframework/tests/mixins.py index a7512efc7..8268fdca7 100644 --- a/djangorestframework/tests/mixins.py +++ b/djangorestframework/tests/mixins.py @@ -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']) From cbac9244ac93f50210aaf73f626366804581ee9b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 7 Feb 2012 11:22:47 +0000 Subject: [PATCH 35/35] Use app for Django 1.2 --- examples/urls.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/urls.py b/examples/urls.py index bd4087fdd..33297b550 100644 --- a/examples/urls.py +++ b/examples/urls.py @@ -1,7 +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 -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('', (r'^$', Sandbox.as_view()),