diff options
| author | Alen Mujezinovic | 2012-02-09 11:13:42 +0000 |
|---|---|---|
| committer | Alen Mujezinovic | 2012-02-09 11:13:42 +0000 |
| commit | add5f32e8a9ba530591c23b8123f36468595fd88 (patch) | |
| tree | 7d4794e487614c9928d5f31420dfb423350cbff6 /djangorestframework | |
| parent | dd680d7a0ae997f2e562db383f23b56624e7ea98 (diff) | |
| parent | c5691cca0e5b61b6cf866f5b8085f950e4637f5a (diff) | |
| download | django-rest-framework-add5f32e8a9ba530591c23b8123f36468595fd88.tar.bz2 | |
Merge remote branch 'tomchristie/master'
Diffstat (limited to 'djangorestframework')
| -rw-r--r-- | djangorestframework/__init__.py | 2 | ||||
| -rw-r--r-- | djangorestframework/mixins.py | 66 | ||||
| -rw-r--r-- | djangorestframework/permissions.py | 2 | ||||
| -rw-r--r-- | djangorestframework/response.py | 2 | ||||
| -rw-r--r-- | djangorestframework/runtests/settings.py | 8 | ||||
| -rw-r--r-- | djangorestframework/static/css/djangorestframework.css | 1209 | ||||
| -rw-r--r-- | djangorestframework/templates/api_login.html | 90 | ||||
| -rw-r--r-- | djangorestframework/templates/renderer.html | 31 | ||||
| -rw-r--r-- | djangorestframework/templates/renderer.txt | 4 | ||||
| -rw-r--r-- | djangorestframework/tests/mixins.py | 13 | ||||
| -rw-r--r-- | djangorestframework/views.py | 7 |
11 files changed, 1309 insertions, 125 deletions
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> </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> </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 %}›{% endif %} + <a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a> {% if not forloop.last %}›{% 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): """ |
