diff options
| author | Adrian Holovaty <adrian@holovaty.com> | 2006-05-02 01:31:56 +0000 |
|---|---|---|
| committer | Adrian Holovaty <adrian@holovaty.com> | 2006-05-02 01:31:56 +0000 |
| commit | f69cf70ed813a8cd7e1f963a14ae39103e8d5265 (patch) | |
| tree | d3b32e84cd66573b3833ddf662af020f8ef2f7a8 /django/contrib/admin | |
| parent | d5dbeaa9be359a4c794885c2e9f1b5a7e5e51fb8 (diff) | |
MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards-incompatible. Please read http://code.djangoproject.com/wiki/RemovingTheMagic for upgrade instructions.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@2809 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/contrib/admin')
64 files changed, 1203 insertions, 1084 deletions
diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py index 31a6ba37c9..0284f13114 100644 --- a/django/contrib/admin/filterspecs.py +++ b/django/contrib/admin/filterspecs.py @@ -6,7 +6,7 @@ Each filter subclass knows how to display a filter for a field that passes a certain test -- e.g. being a DateField or ForeignKey. """ -from django.core import meta +from django.db import models import datetime class FilterSpec(object): @@ -50,13 +50,13 @@ class FilterSpec(object): class RelatedFilterSpec(FilterSpec): def __init__(self, f, request, params): super(RelatedFilterSpec, self).__init__(f, request, params) - if isinstance(f, meta.ManyToManyField): - self.lookup_title = f.rel.to.verbose_name + if isinstance(f, models.ManyToManyField): + self.lookup_title = f.rel.to._meta.verbose_name else: self.lookup_title = f.verbose_name - self.lookup_kwarg = '%s__%s__exact' % (f.name, f.rel.to.pk.name) + self.lookup_kwarg = '%s__%s__exact' % (f.name, f.rel.to._meta.pk.name) self.lookup_val = request.GET.get(self.lookup_kwarg, None) - self.lookup_choices = f.rel.to.get_model_module().get_list() + self.lookup_choices = f.rel.to._default_manager.all() def has_output(self): return len(self.lookup_choices) > 1 @@ -69,7 +69,7 @@ class RelatedFilterSpec(FilterSpec): 'query_string': cl.get_query_string({}, [self.lookup_kwarg]), 'display': _('All')} for val in self.lookup_choices: - pk_val = getattr(val, self.field.rel.to.pk.attname) + pk_val = getattr(val, self.field.rel.to._meta.pk.attname) yield {'selected': self.lookup_val == str(pk_val), 'query_string': cl.get_query_string( {self.lookup_kwarg: pk_val}), 'display': val} @@ -103,7 +103,7 @@ class DateFieldFilterSpec(FilterSpec): today = datetime.date.today() one_week_ago = today - datetime.timedelta(days=7) - today_str = isinstance(self.field, meta.DateTimeField) and today.strftime('%Y-%m-%d 23:59:59') or today.strftime('%Y-%m-%d') + today_str = isinstance(self.field, models.DateTimeField) and today.strftime('%Y-%m-%d 23:59:59') or today.strftime('%Y-%m-%d') self.links = ( (_('Any date'), {}), @@ -126,7 +126,7 @@ class DateFieldFilterSpec(FilterSpec): 'query_string': cl.get_query_string( param_dict, self.field_generic), 'display': title} -FilterSpec.register(lambda f: isinstance(f, meta.DateField), DateFieldFilterSpec) +FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec) class BooleanFieldFilterSpec(FilterSpec): def __init__(self, f, request, params): @@ -144,9 +144,9 @@ class BooleanFieldFilterSpec(FilterSpec): yield {'selected': self.lookup_val == v and not self.lookup_val2, 'query_string': cl.get_query_string( {self.lookup_kwarg: v}, [self.lookup_kwarg2]), 'display': k} - if isinstance(self.field, meta.NullBooleanField): + if isinstance(self.field, models.NullBooleanField): yield {'selected': self.lookup_val2 == 'True', 'query_string': cl.get_query_string( {self.lookup_kwarg2: 'True'}, [self.lookup_kwarg]), 'display': _('Unknown')} -FilterSpec.register(lambda f: isinstance(f, meta.BooleanField) or isinstance(f, meta.NullBooleanField), BooleanFieldFilterSpec) +FilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField), BooleanFieldFilterSpec) diff --git a/django/contrib/admin/media/css/base.css b/django/contrib/admin/media/css/base.css index 7c4437f454..88f7d9a95a 100644 --- a/django/contrib/admin/media/css/base.css +++ b/django/contrib/admin/media/css/base.css @@ -1,3 +1,14 @@ -@import url(global.css); -@import url(changelists.css); +/* + DJANGO Admin + by Wilson Miner wilson@lawrence.com +*/ + +/* Block IE 5 */ +@import "null?\"\{"; + +/* Import other styles */ +@import url('global.css'); +@import url('layout.css'); + +/* Import patch for IE 6 Windows */ /*\*/ @import "patch-iewin.css"; /**/
\ No newline at end of file diff --git a/django/contrib/admin/media/css/changelists.css b/django/contrib/admin/media/css/changelists.css index 7ff59c5e6b..2269c9fe20 100644 --- a/django/contrib/admin/media/css/changelists.css +++ b/django/contrib/admin/media/css/changelists.css @@ -1,16 +1,13 @@ -/* - DJANGO Admin Changelist Styles - by Wilson Miner wilson@lawrence.com - Copyright (c) 2005 Lawrence Journal-World -*/ +@import url('base.css'); +/* CHANGELISTS */ #changelist { position:relative; width:100%; } #changelist table { width:100%; } .change-list .filtered table { border-right:1px solid #ddd; } .change-list .filtered { min-height:400px; _height:400px; } .change-list .filtered { background:white url(../img/admin/changelist-bg.gif) top right repeat-y !important; } .change-list .filtered table, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { margin-right:160px !important; width:auto !important; } -.change-list .filtered table tbody th { padding-right:10px; } +.change-list .filtered table tbody th { padding-right:1em; } #changelist .toplinks { border-bottom:1px solid #ccc !important; } #changelist .paginator { color:#666; border-top:1px solid #eee; border-bottom:1px solid #eee; background:white url(../img/admin/nav-bg.gif) 0 180% repeat-x; overflow:hidden; } .change-list .filtered .paginator { border-right:1px solid #ddd; } @@ -42,3 +39,12 @@ .change-list ul.toplinks li { float: left; width: 9em; padding:3px 6px; font-weight: bold; list-style-type:none; } .change-list ul.toplinks .date-back a { color:#999; } .change-list ul.toplinks .date-back a:hover { color:#036; } + +/* PAGINATOR */ +.paginator { font-size:11px; padding-top:10px; padding-bottom:10px; line-height:22px; margin:0; border-top:1px solid #ddd; } +.paginator a:link, .paginator a:visited { padding:2px 6px; border:solid 1px #ccc; background:white; text-decoration:none; } +.paginator a.showall { padding:0 !important; border:none !important; } +.paginator a.showall:hover { color:#036 !important; background:transparent !important; } +.paginator .end { border-width:2px !important; margin-right:6px; } +.paginator .this-page { padding:2px 6px; font-weight:bold; font-size:13px; vertical-align:top; } +.paginator a:hover { color:white; background:#5b80b2; border-color:#036; } diff --git a/django/contrib/admin/media/css/dashboard.css b/django/contrib/admin/media/css/dashboard.css new file mode 100644 index 0000000000..d27797324b --- /dev/null +++ b/django/contrib/admin/media/css/dashboard.css @@ -0,0 +1,10 @@ +@import url('base.css'); + +/* DASHBOARD */ +.dashboard .module table th { width:100%; } +.dashboard .module table td { white-space:nowrap; } +.dashboard .module table td a { display:block; padding-right:.6em; } + +/* RECENT ACTIONS MODULE */ +.module ul.actionlist { margin-left:0; } +ul.actionlist li { list-style-type:none; }
\ No newline at end of file diff --git a/django/contrib/admin/media/css/forms.css b/django/contrib/admin/media/css/forms.css new file mode 100644 index 0000000000..b66f268fec --- /dev/null +++ b/django/contrib/admin/media/css/forms.css @@ -0,0 +1,60 @@ +@import url('base.css'); +@import url('widgets.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; } +label.inline { margin-left:20px; } +.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:0 1em 3px 0; float:left; width:8em; } +.aligned label.inline { display:inline; float:none; } +.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; } + +/* 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 { padding:3px 5px !important; 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; } +.submit-row input { margin:0 0 0 5px; } +.submit-row p { margin-top:0.3em; } +.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; }
\ No newline at end of file diff --git a/django/contrib/admin/media/css/global.css b/django/contrib/admin/media/css/global.css index 765e752d48..67e37324e5 100644 --- a/django/contrib/admin/media/css/global.css +++ b/django/contrib/admin/media/css/global.css @@ -1,19 +1,14 @@ -/* - DJANGO Admin Global Styles - by Wilson Miner wilson@lawrence.com - Copyright (c) 2005 Lawrence Journal-World -*/ - -body { margin:0; padding:0; font-family:"Lucida Grande","Bitstream Vera Sans",Verdana,Arial,sans-serif; color:#333; background:#fff; } +body { margin:0; padding:0; font-size:12px; font-family:"Lucida Grande","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; } -/* GLOBAL DEFAULTS */ -p, ol, ul, dl { margin:.2em 0 .8em 0; font-size:12px; } +/* 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; } @@ -21,6 +16,7 @@ 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; } @@ -28,150 +24,83 @@ 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; } -/* PAGE STRUCTURE */ -#container { position:relative; width:100%; min-width:760px; } -#content { margin:10px 15px; } -#header { width:100%; } -#content-main { float:left; width:100%; } -#content-related { float:right; width:220px; position:relative; margin-right:-230px; } -#footer { clear:both; padding:10px; } - -/* COLUMN TYPES */ -.colMS { margin-right:245px !important; } -.colSM { margin-left:245px !important; } -.colSM #content-related { float:left; margin-right:0; margin-left:-230px; } -.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; min-height:2.4em; overflow:hidden; } -#header a:link, #header a:visited { color:white; } -#header a:hover { text-decoration:underline; } -#branding h1 { padding:0.5em 10px 0 10px; font-size:18px; margin:0; font-weight:normal; color:#f4f379; } -#branding h2 { padding:0 10px 0.8em 10px; font-size:14px; margin: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; } +/* 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 */ +/* 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; } -thead th { font-weight:bold; 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; } -thead th:first-child { border-left:none !important; } -.superwide table th, .superwide table td, .superwide table input, .superwide table select { font-size:10px; } -.module table { border-collapse: collapse; } +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; } -#home-page table.module tr:hover { background:#EDF3FE; } 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; } -table#change-history { width:100%; } -table#change-history tbody th { width:16em; } -/* TABLE SORTING */ +/* 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-down.gif) right .4em no-repeat; } table thead th.descending a { background:url(../img/admin/arrow-up.gif) right .4em no-repeat; } -/* 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 h2, .module caption { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; background:#7CA0C7 url(../img/admin/default-bg.gif) left top repeat-x; color:white; font-weight:bold; } -.module caption { border:1px solid #ccc; border-bottom:none; } -.module h3 { margin-top:.6em; } -#content-related .module h2 { background:#eee url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; } -#content-main .verbose .actionlist { float:right; font-size:10px; width:17em; position:relative; top:-1.6em; margin:0 8px; } - -/* DASHBOARD */ -.dashboard .module table th { width:100%; } -.dashboard .module table td { white-space:nowrap; } -.dashboard .module table td a { display:block; padding-right:.6em; } +/* 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; } -/* RECENT ACTIONS MODULE */ -.module ul.actionlist { margin-left:0; } -ul.actionlist li { list-style-type:none; } - -/* FORM DEFAULTS */ -input, textarea, select { margin:2px 0; padding:2px 3px; vertical-align:middle; border:1px solid #ccc; font-family:"Lucida Grande", Verdana, Arial, sans-serif; font-weight:normal; font-size:11px; } +/* FORM DEFAULTS */ +input, textarea, select { 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=checkbox], input[type=radio] { border:none; } +input[type=text], input[type=password], textarea, select, .vTextField { border:1px solid #ccc; } /* FORM BUTTONS */ input[type=submit], input[type=button], .submit-row input { background:white url(../img/admin/nav-bg.gif) bottom repeat-x; padding:3px; color:black; } input[type=submit]:active, input[type=button]:active { background-image:url(../img/admin/nav-bg-reverse.gif); background-position:top; } 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; } input[type=submit].default:active { background-image:url(../img/admin/default-bg-reverse.gif); background-position:top; } -.submit-row { padding:5px 7px; text-align:right; background:#ffc; border:1px solid #ccc; margin:5px 0; } -.submit-row input { margin:0 0 0 5px; } -.submit-row .float-left { padding-top:.1em; } - -/* FORM ROWS */ -.form-row { clear:both; padding:8px 12px; font-size:11px; } -html>body .form-row { border-bottom:1px solid #eee; } -.form-row:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } -.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; } -label.inline { margin-left:20px; } -.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:0 1em 3px 0; float:left; text-align:left; width:8em; } -.aligned label.inline { display:inline; float:none; } -.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; } -.colM .aligned .vLargeTextField, colM .aligned .vXMLLargeTextField { width:610px; } -.checkbox-row p.help { margin-left:0; padding-left:0 !important; } - -/* 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 .collapse-toggle { display: inline !important; } -fieldset.collapse h2 a.collapse-toggle { color:#ffc; } -fieldset.collapse h2 a.collapse-toggle:hover { text-decoration:underline; } -.hidden { display:none; } - -/* MONOSPACE TEXTAREAS */ -fieldset.monospace textarea { font-family:"Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace; } +/* 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 { 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 */ +/* 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; } .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; } @@ -183,17 +112,21 @@ td ul.errorlist li { margin:0 !important; } .error input, .error select { 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; } -/* ACTION ICONS */ +/* 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 .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; margin-bottom:5px; float:right; position:relative; margin-top:-2.4em; margin-bottom:-2em; } -.form-row .object-tools { margin-top:0; margin-bottom:0; } +/* 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; } @@ -203,123 +136,6 @@ a.deletelink:hover { color:#993333; } .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; } -/* INLINE CONTROLS */ -#inline-controls { font-weight:bold; font-size:12px; } -#inline-specific-controls { margin-left:6px; padding:0 8px; border-left:6px solid #ccc; } - -/* BREADCRUMBS */ -p.breadcrumbs { font-size:11px; color:#ccc;text-align:left; } /* old breadcrumbs style */ -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; } - -/* SELECTOR (FILTER INTERFACE) */ -.selector { width:580px; float:left; } -.selector select { width:270px; height:170px; } -.selector-available, .selector-chosen { float:left; width:270px; text-align:center; margin-bottom:5px; } -.selector-available h2, .selector-chosen h2 { border:1px solid #ccc; } -.selector .selector-available h2 { background:white url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; } -.selector .selector-filter { background:white; border:1px solid #ccc; border-width:0 1px; padding:3px; color:#999; font-size:10px; margin:0; text-align:left; } -.selector .selector-chosen .selector-filter { padding:4px 5px; } -.selector .selector-available input { width:230px; } -.selector ul.selector-chooser { float:left; width:22px; height:50px; background:url(../img/admin/chooser-bg.gif) top center no-repeat; margin:13% 3px 0 3px; padding:0; } -.selector-chooser li { margin:0; padding:3px; list-style-type:none; } -.selector select { margin-bottom:5px; margin-top:0; } -.selector-add, .selector-remove { width:16px; height:16px; display:block; text-indent:-3000px; } -.selector-add { background:url(../img/admin/selector-add.gif) top center no-repeat; margin-bottom:2px; } -.selector-remove { background:url(../img/admin/selector-remove.gif) top center no-repeat; } -a.selector-chooseall, a.selector-clearall { display:block; width:6em; text-align:left; margin-left:auto; margin-right:auto; font-weight:bold; color:#666; padding:3px 0 3px 18px; } -a.selector-chooseall:hover, a.selector-clearall:hover { color:#036; } -a.selector-chooseall { width:7em; background:url(../img/admin/selector-addall.gif) left center no-repeat; } -a.selector-clearall { background:url(../img/admin/selector-removeall.gif) left center no-repeat; } - -/* Stacked selectors for long items */ -.stacked { float:left; width:500px; } -.stacked select { width:480px; height:100px; } -.stacked .selector-available, .stacked .selector-chosen { width:480px; } -.stacked .selector-available { margin-bottom:0; } -.stacked .selector-available input { width:442px; } -.stacked ul.selector-chooser { height:22px; width:50px; margin:0 0 3px 40%; background:url(../img/admin/chooser_stacked-bg.gif) top center no-repeat; } -.stacked .selector-chooser li { float:left; padding:3px 3px 3px 5px; } -.stacked .selector-chooseall, .stacked .selector-clearall { display:none; } -.stacked .selector-add { background-image:url(../img/admin/selector_stacked-add.gif); } -.stacked .selector-remove { background-image:url(../img/admin/selector_stacked-remove.gif); } - -/* DATE AND TIME */ -p.datetime { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; } -.datetime span { font-size:11px; font-weight:normal; color:#ccc; white-space:nowrap; } -.vDateField { margin-left:4px; } -table p.datetime { font-size:10px; margin-left:0; padding-left:0; } - -/* FILE UPLOADS */ -p.file-upload { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; } -.file-upload a { font-weight:normal; } -.file-upload .deletelink { margin-left:5px; } - -/* CALENDARS & CLOCKS */ -.calendarbox, .clockbox { margin:5px auto; font-size:11px; width: 16em; text-align: center; background:white; position:relative; } -.clockbox { width:9em; } -.calendar { margin:0; padding: 0; } -.calendar table { margin: 0; padding: 0; border-collapse:collapse; background:white; width:99%; } -.calendar caption, .calendarbox h2 { margin: 0; font-size:11px; text-align:center; border-top:none; } -.calendar th { font-size:10px; color:#666; padding:2px 3px; text-align:center; background:#e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x; border-bottom:1px solid #ddd; } -.calendar td { font-size:11px; text-align: center; padding: 0; border-top:1px solid #eee; border-bottom:none; } -.calendar td.selected a { background: #C9DBED; } -.calendar td.nonday { background:#efefef; } -.calendar td.today a { background:#ffc; } -.calendar td a, .timelist a { display: block; font-weight:bold; padding:4px; text-decoration: none; color:#444; } -.calendar td a:hover, .timelist a:hover { background: #5b80b2; color:white; } -.calendar td a:active, .timelist a:active { background: #036; color:white; } -.calendarnav { font-size:10px; text-align: center; color:#ccc; margin:0; padding:1px 3px; } -.calendarnav a:link, #calendarnav a:visited, #calendarnav a:hover { color: #999; } -.calendar-shortcuts { background:white; font-size:10px; line-height:11px; border-top:1px solid #eee; padding:3px 0 4px; color:#ccc; } -.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next { display:block; position:absolute; font-weight:bold; font-size:12px; background:#C9DBED url(../img/admin/default-bg.gif) bottom left repeat-x; padding:1px 4px 2px 4px; color:white; } -.calendarnav-previous:hover, .calendarnav-next:hover { background:#036; } -.calendarnav-previous { top:0; left:0; } -.calendarnav-next { top:0; right:0; } -.calendar-cancel { margin:0 !important; padding:0; font-size:10px; background:#e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x; border-top:1px solid #ddd; } -.calendar-cancel a { padding:2px; color:#999; } -ul.timelist, .timelist li { list-style-type:none; margin:0; padding:0; } -.timelist a { padding:2px; } - -/* ORDERING WIDGET */ -ul#orderthese { position:absolute; top:8em; right:0; width:240px; padding:0; margin:0; list-style-type:none; } -ul#orderthese li { list-style-type:none; display:block; padding:0; margin:6px 0; width:214px; background:#f6f6f6; white-space:nowrap; overflow:hidden; } -ul#orderthese li span { display:block; border:1px solid #e7e7e7; background:transparent url(../img/admin/nav-bg-grabber.gif) top left repeat-y; font-size:10px !important; padding:4px 6px 4px 12px; } -ul#orderthese span:hover { background-color:#efefef; } - -/* PAGINATOR */ -.paginator { font-size:11px; padding-top:10px; padding-bottom:10px; line-height:22px; margin:0; border-top:1px solid #ddd; } -.paginator a:link, .paginator a:visited { padding:2px 6px; border:solid 1px #ccc; background:white; text-decoration:none; } -.paginator a.showall { padding:0 !important; border:none !important; } -.paginator a.showall:hover { color:#036 !important; background:transparent !important; } -.paginator .end { border-width:2px !important; margin-right:6px; } -.paginator .this-page { padding:2px 6px; font-weight:bold; font-size:13px; vertical-align:top; } -.paginator a:hover { color:white; background:#5b80b2; border-color:#036; } - -/* 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; } - -/* CUSTOM FORM FIELDS */ -.vSelectMultipleField { vertical-align:top !important; } -.vCheckboxField { border:none; } -.vDateField, .vTimeField { margin-right:2px; } -.vFileUploadField { border:none; } -.vURLField { width:380px; } -.vLargeTextField, .vXMLLargeTextField { width:480px; } -.colM .vLargeTextField, .colM .vXMLLargeTextField { width:720px; } -body.core-flatfile #id_content { height: 400px; } -.module table .vPositiveSmallIntegerField { width: 22px; }
\ No newline at end of file +/* OBJECT HISTORY */ +table#change-history { width:100%; } +table#change-history tbody th { width:16em; } diff --git a/django/contrib/admin/media/css/layout.css b/django/contrib/admin/media/css/layout.css new file mode 100644 index 0000000000..19c9286b85 --- /dev/null +++ b/django/contrib/admin/media/css/layout.css @@ -0,0 +1,29 @@ +/* PAGE STRUCTURE */ +#container { position:relative; width:100%; min-width:760px; } +#content { margin:10px 15px; } +#header { width:100%; } +#content-main { float:left; width:100%; } +#content-related { float:right; width:220px; position:relative; margin-right:-230px; } +#footer { clear:both; padding:10px; } + +/* COLUMN TYPES */ +.colMS { margin-right:245px !important; } +.colSM { margin-left:245px !important; } +.colSM #content-related { float:left; margin-right:0; margin-left:-230px; } +.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; }
\ No newline at end of file diff --git a/django/contrib/admin/media/css/login.css b/django/contrib/admin/media/css/login.css new file mode 100644 index 0000000000..041135f31e --- /dev/null +++ b/django/contrib/admin/media/css/login.css @@ -0,0 +1,13 @@ +@import url('base.css'); +@import url('layout.css'); + +/* LOGIN FORM */ +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:7em; 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:16em; } +.login span.help { font-size:10px; display:block; } +.login .submit-row { clear:both; padding:1em 0 0 7.4em; }
\ No newline at end of file diff --git a/django/contrib/admin/media/css/patch-iewin.css b/django/contrib/admin/media/css/patch-iewin.css index 46531325df..b2e6a4c560 100644 --- a/django/contrib/admin/media/css/patch-iewin.css +++ b/django/contrib/admin/media/css/patch-iewin.css @@ -1,7 +1,6 @@ * html #container { position:static; } /* keep header from flowing off the page */ * html .colMS #content-related { margin-right:0; margin-left:10px; position:static; } /* put the right sidebars back on the page */ * html .colSM #content-related { margin-right:10px; margin-left:-115px; position:static; } /* put the left sidebars back on the page */ +* html .form-row { height:1%; } * html .dashboard #content { width:768px; } /* proper fixed width for dashboard in IE6 */ -* html .dashboard #content-main { width:535px; } /* proper fixed width for dashboard in IE6 */ -* html #content { width /**/: 768px; } /* fixed width for IE5 */ -* html #content-main { width /**/: 535px; } /* fixed width for IE5 */
\ No newline at end of file +* html .dashboard #content-main { width:535px; } /* proper fixed width for dashboard in IE6 */
\ No newline at end of file diff --git a/django/contrib/admin/media/css/widgets.css b/django/contrib/admin/media/css/widgets.css new file mode 100644 index 0000000000..bf526bfd66 --- /dev/null +++ b/django/contrib/admin/media/css/widgets.css @@ -0,0 +1,101 @@ +/* SELECTOR (FILTER INTERFACE) */ +.selector { width:580px; float:left; } +.selector select { width:270px; height:17.2em; } +.selector-available, .selector-chosen { float:left; width:270px; text-align:center; margin-bottom:5px; } +.selector-available h2, .selector-chosen h2 { border:1px solid #ccc; } +.selector .selector-available h2 { background:white url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; } +.selector .selector-filter { background:white; border:1px solid #ccc; border-width:0 1px; padding:3px; color:#999; font-size:10px; margin:0; text-align:left; } +.selector .selector-chosen .selector-filter { padding:4px 5px; } +.selector .selector-available input { width:230px; } +.selector ul.selector-chooser { float:left; width:22px; height:50px; background:url(../img/admin/chooser-bg.gif) top center no-repeat; margin:8em 3px 0 3px; padding:0; } +.selector-chooser li { margin:0; padding:3px; list-style-type:none; } +.selector select { margin-bottom:5px; margin-top:0; } +.selector-add, .selector-remove { width:16px; height:16px; display:block; text-indent:-3000px; } +.selector-add { background:url(../img/admin/selector-add.gif) top center no-repeat; margin-bottom:2px; } +.selector-remove { background:url(../img/admin/selector-remove.gif) top center no-repeat; } +a.selector-chooseall, a.selector-clearall { display:block; width:6em; text-align:left; margin-left:auto; margin-right:auto; font-weight:bold; color:#666; padding:3px 0 3px 18px; } +a.selector-chooseall:hover, a.selector-clearall:hover { color:#036; } +a.selector-chooseall { width:7em; background:url(../img/admin/selector-addall.gif) left center no-repeat; } +a.selector-clearall { background:url(../img/admin/selector-removeall.gif) left center no-repeat; } + +/* STACKED SELECTORS */ +.stacked { float:left; width:500px; } +.stacked select { width:480px; height:10.1em; } +.stacked .selector-available, .stacked .selector-chosen { width:480px; } +.stacked .selector-available { margin-bottom:0; } +.stacked .selector-available input { width:442px; } +.stacked ul.selector-chooser { height:22px; width:50px; margin:0 0 3px 40%; background:url(../img/admin/chooser_stacked-bg.gif) top center no-repeat; } +.stacked .selector-chooser li { float:left; padding:3px 3px 3px 5px; } +.stacked .selector-chooseall, .stacked .selector-clearall { display:none; } +.stacked .selector-add { background-image:url(../img/admin/selector_stacked-add.gif); } +.stacked .selector-remove { background-image:url(../img/admin/selector_stacked-remove.gif); } + +/* DATE AND TIME */ +p.datetime { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; } +.datetime span { font-size:11px; color:#ccc; font-weight:normal; white-space:nowrap; } +.vDateField { margin-left:4px; } +table p.datetime { font-size:10px; margin-left:0; padding-left:0; } + +/* FILE UPLOADS */ +p.file-upload { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; } +.file-upload a { font-weight:normal; } +.file-upload .deletelink { margin-left:5px; } + +/* CALENDARS & CLOCKS */ +.calendarbox, .clockbox { margin:5px auto; font-size:11px; width:16em; text-align:center; background:white; position:relative; } +.clockbox { width:9em; } +.calendar { margin:0; padding: 0; } +.calendar table { margin:0; padding:0; border-collapse:collapse; background:white; width:99%; } +.calendar caption, .calendarbox h2 { margin: 0; font-size:11px; text-align:center; border-top:none; } +.calendar th { font-size:10px; color:#666; padding:2px 3px; text-align:center; background:#e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x; border-bottom:1px solid #ddd; } +.calendar td { font-size:11px; text-align: center; padding: 0; border-top:1px solid #eee; border-bottom:none; } +.calendar td.selected a { background: #C9DBED; } +.calendar td.nonday { background:#efefef; } +.calendar td.today a { background:#ffc; } +.calendar td a, .timelist a { display: block; font-weight:bold; padding:4px; text-decoration: none; color:#444; } +.calendar td a:hover, .timelist a:hover { background: #5b80b2; color:white; } +.calendar td a:active, .timelist a:active { background: #036; color:white; } +.calendarnav { font-size:10px; text-align: center; color:#ccc; margin:0; padding:1px 3px; } +.calendarnav a:link, #calendarnav a:visited, #calendarnav a:hover { color: #999; } +.calendar-shortcuts { background:white; font-size:10px; line-height:11px; border-top:1px solid #eee; padding:3px 0 4px; color:#ccc; } +.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next { display:block; position:absolute; font-weight:bold; font-size:12px; background:#C9DBED url(../img/admin/default-bg.gif) bottom left repeat-x; padding:1px 4px 2px 4px; color:white; } +.calendarnav-previous:hover, .calendarnav-next:hover { background:#036; } +.calendarnav-previous { top:0; left:0; } +.calendarnav-next { top:0; right:0; } +.calendar-cancel { margin:0 !important; padding:0; font-size:10px; background:#e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x; border-top:1px solid #ddd; } +.calendar-cancel a { padding:2px; color:#999; } +ul.timelist, .timelist li { list-style-type:none; margin:0; padding:0; } +.timelist a { padding:2px; } + +/* INLINE ORDERER */ +ul.orderer { position:relative; padding:0 !important; margin:0 !important; list-style-type:none; } +ul.orderer li { list-style-type:none; display:block; padding:0; margin:0; border:1px solid #bbb; border-width:0 1px 1px 0; white-space:nowrap; overflow:hidden; background:#e2e2e2 url(../img/admin/nav-bg-grabber.gif) repeat-y; } +ul.orderer li:hover { cursor:move; background-color:#ddd; } +ul.orderer li a.selector { margin-left:12px; overflow:hidden; width:83%; font-size:10px !important; padding:0.6em 0; } +ul.orderer li a:link, ul.orderer li a:visited { color:#333; } +ul.orderer li .inline-deletelink { position:absolute; right:4px; margin-top:0.6em; } +ul.orderer li.selected { background-color:#f8f8f8; border-right-color:#f8f8f8; } +ul.orderer li.deleted { background:#bbb url(../img/admin/deleted-overlay.gif); } +ul.orderer li.deleted a:link, ul.orderer li.deleted a:visited { color:#888; } +ul.orderer li.deleted .inline-deletelink { background-image:url(../img/admin/inline-restore.png); } +ul.orderer li.deleted:hover, ul.orderer li.deleted a.selector:hover { cursor:default; } + +/* EDIT INLINE */ +.inline-deletelink { display:block; text-indent:-9999px; background:transparent url(../img/admin/inline-delete.png) no-repeat; width:15px; height:15px; margin:0.4em 0; border: 0px none; } +.inline-deletelink:hover { background-position:-15px 0; cursor:pointer; } +.editinline button.addlink { border: 0px none; color: #5b80b2; font-size: 100%; cursor: pointer; } +.editinline button.addlink:hover { color: #036; cursor: pointer; } +.editinline table .help { text-align:right; float:right; padding-left:2em; } +.editinline tfoot .addlink { white-space:nowrap; } +.editinline table thead th:last-child { border-left:none; } +.editinline tr.deleted { background:#ddd url(../img/admin/deleted-overlay.gif); } +.editinline tr.deleted .inline-deletelink { background-image:url(../img/admin/inline-restore.png); } +.editinline tr.deleted td:hover { cursor:default; } +.editinline tr.deleted td:first-child { background-image:none !important; } + +/* EDIT INLINE - STACKED */ +.editinline-stacked { min-width:758px; } +.editinline-stacked .inline-object { margin-left:210px; background:white; } +.editinline-stacked .inline-source { float:left; width:200px; background:#f8f8f8; } +.editinline-stacked .inline-splitter { float:left; width:9px; background:#f8f8f8 url(../img/admin/inline-splitter-bg.gif) 50% 50% no-repeat; border-right:1px solid #ccc; } +.editinline-stacked .controls { clear:both; background:#e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; padding:3px 4px; font-size:11px; border-top:1px solid #ddd; }
\ No newline at end of file diff --git a/django/contrib/admin/media/img/admin/deleted-overlay.gif b/django/contrib/admin/media/img/admin/deleted-overlay.gif Binary files differnew file mode 100644 index 0000000000..dc3828fe06 --- /dev/null +++ b/django/contrib/admin/media/img/admin/deleted-overlay.gif diff --git a/django/contrib/admin/media/img/admin/inline-delete-8bit.png b/django/contrib/admin/media/img/admin/inline-delete-8bit.png Binary files differnew file mode 100644 index 0000000000..95caf59a8d --- /dev/null +++ b/django/contrib/admin/media/img/admin/inline-delete-8bit.png diff --git a/django/contrib/admin/media/img/admin/inline-delete.png b/django/contrib/admin/media/img/admin/inline-delete.png Binary files differnew file mode 100644 index 0000000000..d59bcd2444 --- /dev/null +++ b/django/contrib/admin/media/img/admin/inline-delete.png diff --git a/django/contrib/admin/media/img/admin/inline-restore-8bit.png b/django/contrib/admin/media/img/admin/inline-restore-8bit.png Binary files differnew file mode 100644 index 0000000000..e087c8ead3 --- /dev/null +++ b/django/contrib/admin/media/img/admin/inline-restore-8bit.png diff --git a/django/contrib/admin/media/img/admin/inline-restore.png b/django/contrib/admin/media/img/admin/inline-restore.png Binary files differnew file mode 100644 index 0000000000..efdd92ac39 --- /dev/null +++ b/django/contrib/admin/media/img/admin/inline-restore.png diff --git a/django/contrib/admin/media/img/admin/inline-splitter-bg.gif b/django/contrib/admin/media/img/admin/inline-splitter-bg.gif Binary files differnew file mode 100644 index 0000000000..32ac5b3498 --- /dev/null +++ b/django/contrib/admin/media/img/admin/inline-splitter-bg.gif diff --git a/django/contrib/admin/media/js/admin/RelatedObjectLookups.js b/django/contrib/admin/media/js/admin/RelatedObjectLookups.js index 72c4e95eca..cb84790f44 100644 --- a/django/contrib/admin/media/js/admin/RelatedObjectLookups.js +++ b/django/contrib/admin/media/js/admin/RelatedObjectLookups.js @@ -39,7 +39,7 @@ function dismissAddAnotherPopup(win, newId, newRepr) { if (elem.nodeName == 'SELECT') { var o = new Option(newRepr, newId); elem.options[elem.options.length] = o; - elem.selectedIndex = elem.length - 1; + o.selected = true; } else if (elem.nodeName == 'INPUT') { elem.value = newId; } diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py new file mode 100644 index 0000000000..022d20bed9 --- /dev/null +++ b/django/contrib/admin/models.py @@ -0,0 +1,51 @@ +from django.db import models +from django.contrib.contenttypes.models import ContentType +from django.contrib.auth.models import User +from django.utils.translation import gettext_lazy as _ + +ADDITION = 1 +CHANGE = 2 +DELETION = 3 + +class LogEntryManager(models.Manager): + def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''): + e = self.model(None, None, user_id, content_type_id, object_id, object_repr[:200], action_flag, change_message) + e.save() + +class LogEntry(models.Model): + action_time = models.DateTimeField(_('action time'), auto_now=True) + user = models.ForeignKey(User) + content_type = models.ForeignKey(ContentType, blank=True, null=True) + object_id = models.TextField(_('object id'), blank=True, null=True) + object_repr = models.CharField(_('object repr'), maxlength=200) + action_flag = models.PositiveSmallIntegerField(_('action flag')) + change_message = models.TextField(_('change message'), blank=True) + objects = LogEntryManager() + class Meta: + verbose_name = _('log entry') + verbose_name_plural = _('log entries') + db_table = 'django_admin_log' + ordering = ('-action_time',) + + def __repr__(self): + return str(self.action_time) + + def is_addition(self): + return self.action_flag == ADDITION + + def is_change(self): + return self.action_flag == CHANGE + + def is_deletion(self): + return self.action_flag == DELETION + + def get_edited_object(self): + "Returns the edited object represented by this log entry" + return self.content_type.get_object_for_this_type(pk=self.object_id) + + def get_admin_url(self): + """ + Returns the admin URL to edit the object represented by this log entry. + This is relative to the Django admin index page. + """ + return "%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, self.object_id) diff --git a/django/contrib/admin/models/__init__.py b/django/contrib/admin/models/__init__.py deleted file mode 100644 index e11e2df093..0000000000 --- a/django/contrib/admin/models/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__all__ = ['admin'] diff --git a/django/contrib/admin/models/admin.py b/django/contrib/admin/models/admin.py deleted file mode 100644 index b7bcda192b..0000000000 --- a/django/contrib/admin/models/admin.py +++ /dev/null @@ -1,50 +0,0 @@ -from django.core import meta -from django.models import auth, core -from django.utils.translation import gettext_lazy as _ - -class LogEntry(meta.Model): - action_time = meta.DateTimeField(_('action time'), auto_now=True) - user = meta.ForeignKey(auth.User) - content_type = meta.ForeignKey(core.ContentType, blank=True, null=True) - object_id = meta.TextField(_('object id'), blank=True, null=True) - object_repr = meta.CharField(_('object repr'), maxlength=200) - action_flag = meta.PositiveSmallIntegerField(_('action flag')) - change_message = meta.TextField(_('change message'), blank=True) - class META: - module_name = 'log' - verbose_name = _('log entry') - verbose_name_plural = _('log entries') - db_table = 'django_admin_log' - ordering = ('-action_time',) - module_constants = { - 'ADDITION': 1, - 'CHANGE': 2, - 'DELETION': 3, - } - - def __repr__(self): - return str(self.action_time) - - def is_addition(self): - return self.action_flag == ADDITION - - def is_change(self): - return self.action_flag == CHANGE - - def is_deletion(self): - return self.action_flag == DELETION - - def get_edited_object(self): - "Returns the edited object represented by this log entry" - return self.get_content_type().get_object_for_this_type(pk=self.object_id) - - def get_admin_url(self): - """ - Returns the admin URL to edit the object represented by this log entry. - This is relative to the Django admin index page. - """ - return "%s/%s/%s/" % (self.get_content_type().get_package(), self.get_content_type().python_module_name, self.object_id) - - def _module_log_action(user_id, content_type_id, object_id, object_repr, action_flag, change_message=''): - e = LogEntry(None, None, user_id, content_type_id, object_id, object_repr[:200], action_flag, change_message) - e.save() diff --git a/django/contrib/admin/templates/admin/404.html b/django/contrib/admin/templates/admin/404.html index d791f565ba..9bf4293e76 100644 --- a/django/contrib/admin/templates/admin/404.html +++ b/django/contrib/admin/templates/admin/404.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} {% block title %}{% trans 'Page not found' %}{% endblock %} diff --git a/django/contrib/admin/templates/admin/500.html b/django/contrib/admin/templates/admin/500.html index 9d3e3de32c..b30e43170d 100644 --- a/django/contrib/admin/templates/admin/500.html +++ b/django/contrib/admin/templates/admin/500.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} {% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans "Home" %}</a> › {% trans "Server error" %}</div>{% endblock %} diff --git a/django/contrib/admin/templates/admin/base_site.html b/django/contrib/admin/templates/admin/base_site.html index b4d285b779..b867bd29bd 100644 --- a/django/contrib/admin/templates/admin/base_site.html +++ b/django/contrib/admin/templates/admin/base_site.html @@ -1,4 +1,4 @@ -{% extends "admin/base" %} +{% extends "admin/base.html" %} {% load i18n %} {% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %} diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index 02d5760509..a667087f5f 100644 --- a/django/contrib/admin/templates/admin/change_form.html +++ b/django/contrib/admin/templates/admin/change_form.html @@ -1,36 +1,39 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n admin_modify adminmedia %} {% block extrahead %}{{ block.super }} <script type="text/javascript" src="../../../jsi18n/"></script> -{% for js in bound_manipulator.javascript_imports %}{% include_admin_script js %}{% endfor %} +{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %} {% endblock %} -{% block coltype %}{{ bound_manipulator.coltype }}{% endblock %} -{% block bodyclass %}{{ app_label }}-{{ bound_manipulator.object_name.lower }} change-form{% endblock %} +{% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %} +{% block coltype %}{% if ordered_objects %}colMS{% else %}colM{% endif %}{% endblock %} +{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %} {% block userlinks %}<a href="../../../doc/">{% trans 'Documentation' %}</a> / <a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %} {% block breadcrumbs %}{% if not is_popup %} <div class="breadcrumbs"> <a href="../../../">{% trans "Home" %}</a> › - <a href="../">{{ bound_manipulator.verbose_name_plural|capfirst }}</a> › - {% if add %}{% trans "Add" %} {{ bound_manipulator.verbose_name }}{% else %}{{ bound_manipulator.original|striptags|truncatewords:"18" }}{% endif %} + <a href="../">{{ opts.verbose_name_plural|capfirst }}</a> › + {% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|striptags|truncatewords:"18" }}{% endif %} </div> {% endif %}{% endblock %} {% block content %}<div id="content-main"> {% if change %}{% if not is_popup %} <ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li> - {% if bound_manipulator.has_absolute_url %}<li><a href="/r/{{ bound_manipulator.content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%} + {% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%} </ul> {% endif %}{% endif %} -<form {{ bound_manipulator.form_enc_attrib }} action="{{ form_url }}" method="post">{% block form_top %}{% endblock %} +<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post">{% block form_top %}{% endblock %} +<div> {% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %} -{% if bound_manipulator.save_on_top %}{% submit_row bound_manipulator %}{% endif %} +{% if opts.admin.save_on_top %}{% submit_row %}{% endif %} {% if form.error_dict %} <p class="errornote"> {% blocktrans count form.error_dict.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %} </p> {% endif %} -{% for bound_field_set in bound_manipulator.bound_field_sets %} +{% for bound_field_set in bound_field_sets %} <fieldset class="module aligned {{ bound_field_set.classes }}"> {% if bound_field_set.name %}<h2>{{ bound_field_set.name }}</h2>{% endif %} + {% if bound_field_set.description %}<div class="description">{{ bound_field_set.description }}</div>{% endif %} {% for bound_field_line in bound_field_set %} {% admin_field_line bound_field_line %} {% for bound_field in bound_field_line %} @@ -41,7 +44,7 @@ {% endfor %} {% block after_field_sets %}{% endblock %} {% if change %} - {% if bound_manipulator.ordered_objects %} + {% if ordered_objects %} <fieldset class="module"><h2>{% trans "Ordering" %}</h2> <div class="form-row{% if form.order_.errors %} error{% endif %} "> {% if form.order_.errors %}{{ form.order_.html_error_list }}{% endif %} @@ -49,27 +52,17 @@ </div></fieldset> {% endif %} {% endif %} -{% for related_object in bound_manipulator.inline_related_objects %}{% edit_inline related_object %}{% endfor %} +{% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %} {% block after_related_objects %}{% endblock %} -{% submit_row bound_manipulator %} +{% submit_row %} {% if add %} - <script type="text/javascript">document.getElementById("{{ bound_manipulator.first_form_field_id }}").focus();</script> + <script type="text/javascript">document.getElementById("{{ first_form_field_id }}").focus();</script> {% endif %} -{% if bound_manipulator.auto_populated_fields %} +{% if auto_populated_fields %} <script type="text/javascript"> - {% auto_populated_field_script bound_manipulator.auto_populated_fields change %} + {% auto_populated_field_script auto_populated_fields change %} </script> {% endif %} -{% if change %} - {% if bound_manipulator.ordered_objects %} - {% if form.order_objects %}<ul id="orderthese"> - {% for object in form.order_objects %} - <li id="p{% object_pk bound_manipulator object %}"> - <span id="handlep{% object_pk bound_manipulator object %}">{{ object|truncatewords:"5" }}</span> - </li> - {% endfor %} - </ul>{% endif %} - {% endif %} -{% endif %} +</div> </form></div> {% endblock %} diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html index 17b388b699..5b54bfb8cc 100644 --- a/django/contrib/admin/templates/admin/change_list.html +++ b/django/contrib/admin/templates/admin/change_list.html @@ -1,8 +1,9 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load adminmedia admin_list i18n %} +{% block stylesheet %}{% admin_media_prefix %}css/changelists.css{% endblock %} {% block bodyclass %}change-list{% endblock %} {% block userlinks %}<a href="../../doc/">{% trans 'Documentation' %}</a> / <a href="../../password_change/">{% trans 'Change password' %}</a> / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %} -{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> › {{ cl.opts.verbose_name_plural|capfirst }} </div>{% endblock %}{% endif %} +{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> › {{ cl.opts.verbose_name_plural|capfirst }}</div>{% endblock %}{% endif %} {% block coltype %}flex{% endblock %} {% block content %} <div id="content-main"> diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html index 7fba4cebd7..f907c18a16 100644 --- a/django/contrib/admin/templates/admin/delete_confirmation.html +++ b/django/contrib/admin/templates/admin/delete_confirmation.html @@ -1,6 +1,14 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} {% block userlinks %}<a href="../../../../doc/">{% trans 'Documentation' %}</a> / <a href="../../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../../logout/">{% trans 'Log out' %}</a>{% endblock %} +{% block breadcrumbs %} +<div class="breadcrumbs"> + <a href="../../../../">{% trans "Home" %}</a> › + <a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> › + <a href="../">{{ object|striptags|truncatewords:"18" }}</a> › + {% trans 'Delete' %} +</div> +{% endblock %} {% block content %} {% if perms_lacking %} <p>{% blocktrans %}Deleting the {{ object_name }} '{{ object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}</p> @@ -13,8 +21,10 @@ <p>{% blocktrans %}Are you sure you want to delete the {{ object_name }} "{{ object }}"? All of the following related items will be deleted:{% endblocktrans %}</p> <ul>{{ deleted_objects|unordered_list }}</ul> <form action="" method="post"> + <div> <input type="hidden" name="post" value="yes" /> <input type="submit" value="{% trans "Yes, I'm sure" %}" /> + </div> </form> {% endif %} {% endblock %} diff --git a/django/contrib/admin/templates/admin/edit_inline_stacked.html b/django/contrib/admin/templates/admin/edit_inline_stacked.html index 62549ef82d..45aa0a4f58 100644 --- a/django/contrib/admin/templates/admin/edit_inline_stacked.html +++ b/django/contrib/admin/templates/admin/edit_inline_stacked.html @@ -13,4 +13,4 @@ {% endif %} {% endfor %} {% endfor %} -</fieldset>
\ No newline at end of file +</fieldset> diff --git a/django/contrib/admin/templates/admin/field_line.html b/django/contrib/admin/templates/admin/field_line.html index 10f37d5dc9..b7e2fc2ae0 100644 --- a/django/contrib/admin/templates/admin/field_line.html +++ b/django/contrib/admin/templates/admin/field_line.html @@ -9,14 +9,6 @@ {% if not bound_field.has_label_first %} {% field_label bound_field %} {% endif %} - {% if change %} - {% if bound_field.field.primary_key %} - {{ bound_field.original_value }} - {% endif %} - {% if bound_field.raw_id_admin %} - {% if bound_field.existing_display %} <strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %} - {% endif %} - {% endif %} {% if bound_field.field.help_text %}<p class="help">{{ bound_field.field.help_text }}</p>{% endif %} {% endfor %} </div> diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html index aabd4eecca..246086861b 100644 --- a/django/contrib/admin/templates/admin/index.html +++ b/django/contrib/admin/templates/admin/index.html @@ -1,6 +1,7 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} +{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/dashboard.css{% endblock %} {% block coltype %}colMS{% endblock %} {% block bodyclass %}dashboard{% endblock %} {% block breadcrumbs %}{% endblock %} @@ -13,14 +14,14 @@ {% if app_list %} {% for app in app_list %} <div class="module"> - <h2>{{ app.name }}</h2> - <table> + <table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}"> + <caption>{{ app.name }}</caption> {% for model in app.models %} <tr> {% if model.perms.change %} - <th><a href="{{ model.admin_url }}">{{ model.name }}</a></th> + <th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th> {% else %} - <th>{{ model.name }}</th> + <th scope="row">{{ model.name }}</th> {% endif %} {% if model.perms.add %} @@ -57,7 +58,7 @@ {% else %} <ul class="actionlist"> {% for entry in admin_log %} - <li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{{ entry.get_content_type.name|capfirst }}</span></li> + <li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{{ entry.content_type.name|capfirst }}</span></li> {% endfor %} </ul> {% endif %} diff --git a/django/contrib/admin/templates/admin/login.html b/django/contrib/admin/templates/admin/login.html index ea823e1020..5f338f703e 100644 --- a/django/contrib/admin/templates/admin/login.html +++ b/django/contrib/admin/templates/admin/login.html @@ -1,6 +1,9 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} +{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/login.css{% endblock %} +{% block bodyclass %}login{% endblock %} +{% block content_title %}{% endblock %} {% block breadcrumbs %}{% endblock %} {% block content %} @@ -9,20 +12,18 @@ <p class="errornote">{{ error_message }}</p> {% endif %} <div id="content-main"> -<form action="{{ app_path }}" method="post"> - -<p class="aligned"> -<label for="id_username">{% trans 'Username:' %}</label> <input type="text" name="username" id="id_username" /> -</p> -<p class="aligned"> -<label for="id_password">{% trans 'Password:' %}</label> <input type="password" name="password" id="id_password" /> -<input type="hidden" name="this_is_the_login_form" value="1" /> -<input type="hidden" name="post_data" value="{{ post_data }}" />{% comment %} <span class="help">{% trans 'Have you <a href="/password_reset/">forgotten your password</a>?' %}</span>{% endcomment %} -</p> - -<div class="aligned "> -<label> </label><input type="submit" value="{% trans 'Log in' %}" /> -</div> +<form action="{{ app_path }}" method="post" id="login-form"> + <div class="form-row"> + <label for="id_username">{% trans 'Username:' %}</label> <input type="text" name="username" id="id_username" /> + </div> + <div class="form-row"> + <label for="id_password">{% trans 'Password:' %}</label> <input type="password" name="password" id="id_password" /> + <input type="hidden" name="this_is_the_login_form" value="1" /> + <input type="hidden" name="post_data" value="{{ post_data }}" /> {% comment %}<span class="help">{% trans 'Have you <a href="/password_reset/">forgotten your password</a>?' %}</span>{% endcomment %} + </div> + <div class="submit-row"> + <label> </label><input type="submit" value="{% trans 'Log in' %}" /> + </div> </form> <script type="text/javascript"> diff --git a/django/contrib/admin/templates/admin/object_history.html b/django/contrib/admin/templates/admin/object_history.html index b182f40acb..0dbe7af743 100644 --- a/django/contrib/admin/templates/admin/object_history.html +++ b/django/contrib/admin/templates/admin/object_history.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} {% block userlinks %}<a href="../../../../doc/">{% trans 'Documentation' %}</a> / <a href="../../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../../logout/">{% trans 'Log out' %}</a>{% endblock %} {% block breadcrumbs %} @@ -15,16 +15,16 @@ <table id="change-history"> <thead> <tr> - <th>{% trans 'Date/time' %}</th> - <th>{% trans 'User' %}</th> - <th>{% trans 'Action' %}</th> + <th scope="col">{% trans 'Date/time' %}</th> + <th scope="col">{% trans 'User' %}</th> + <th scope="col">{% trans 'Action' %}</th> </tr> </thead> <tbody> {% for action in action_list %} <tr> - <th>{{ action.action_time|date:_("DATE_WITH_TIME_FULL") }}</th> - <td>{{ action.get_user.username }}{% if action.get_user.first_name %} ({{ action.get_user.first_name }} {{ action.get_user.last_name }}){% endif %}</td> + <th scope="row">{{ action.action_time|date:_("DATE_WITH_TIME_FULL") }}</th> + <td>{{ action.user.username }}{% if action.user.first_name %} ({{ action.user.first_name }} {{ action.user.last_name }}){% endif %}</td> <td>{{ action.change_message}}</td> </tr> {% endfor %} diff --git a/django/contrib/admin/templates/admin/search_form.html b/django/contrib/admin/templates/admin/search_form.html index c2d6cf59c5..24eced9ef9 100644 --- a/django/contrib/admin/templates/admin/search_form.html +++ b/django/contrib/admin/templates/admin/search_form.html @@ -3,7 +3,7 @@ {% if cl.lookup_opts.admin.search_fields %} <div id="toolbar"><form id="changelist-search" action="" method="get"> <div><!-- DIV needed for valid HTML --> -<label><img src="{% admin_media_prefix %}img/admin/icon_searchbox.png" alt="Search" /></label> +<label for="searchbar"><img src="{% admin_media_prefix %}img/admin/icon_searchbox.png" alt="Search" /></label> <input type="text" size="40" name="{{ search_var }}" value="{{ cl.query|escape }}" id="searchbar" /> <input type="submit" value="{% trans 'Go' %}" /> {% if show_result_count %} diff --git a/django/contrib/admin/templates/admin/template_validator.html b/django/contrib/admin/templates/admin/template_validator.html index f9ac09a77d..422e90261f 100644 --- a/django/contrib/admin/templates/admin/template_validator.html +++ b/django/contrib/admin/templates/admin/template_validator.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% block content %} diff --git a/django/contrib/admin/templates/admin_doc/bookmarklets.html b/django/contrib/admin/templates/admin_doc/bookmarklets.html index 708473750a..fa5942926f 100644 --- a/django/contrib/admin/templates/admin_doc/bookmarklets.html +++ b/django/contrib/admin/templates/admin_doc/bookmarklets.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% block breadcrumbs %}{% load i18n %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> › <a href="../">{% trans "Documentation" %}</a> › {% trans "Bookmarklets" %}</div>{% endblock %} {% block userlinks %}<a href="../../password_change/">{% trans 'Change password' %}</a> / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %} diff --git a/django/contrib/admin/templates/admin_doc/index.html b/django/contrib/admin/templates/admin_doc/index.html index 6665f4bb42..331774d2b3 100644 --- a/django/contrib/admin/templates/admin_doc/index.html +++ b/django/contrib/admin/templates/admin_doc/index.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> › Documentation</div>{% endblock %}
{% block userlinks %}<a href="../password_change/">{% trans 'Change password' %}</a> / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %}
diff --git a/django/contrib/admin/templates/admin_doc/missing_docutils.html b/django/contrib/admin/templates/admin_doc/missing_docutils.html index a7cf7e95d7..d0b571f957 100644 --- a/django/contrib/admin/templates/admin_doc/missing_docutils.html +++ b/django/contrib/admin/templates/admin_doc/missing_docutils.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> › Documentation</div>{% endblock %} {% block userlinks %}<a href="../password_change/">{% trans 'Change password' %}</a> / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %} diff --git a/django/contrib/admin/templates/admin_doc/model_detail.html b/django/contrib/admin/templates/admin_doc/model_detail.html index 0eb2971048..953bb4bf54 100644 --- a/django/contrib/admin/templates/admin_doc/model_detail.html +++ b/django/contrib/admin/templates/admin_doc/model_detail.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} {% block userlinks %}<a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %} {% block extrahead %} @@ -41,6 +41,6 @@ </table> </div> -<p class="small"><a href="../">‹ Back to Models Documentation</p> +<p class="small"><a href="../">‹ Back to Models Documentation</a></p> </div> {% endblock %} diff --git a/django/contrib/admin/templates/admin_doc/model_index.html b/django/contrib/admin/templates/admin_doc/model_index.html index 3183b577e1..c681da75c9 100644 --- a/django/contrib/admin/templates/admin_doc/model_index.html +++ b/django/contrib/admin/templates/admin_doc/model_index.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} {% block coltype %}colSM{% endblock %} {% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> › <a href="../">Documentation</a> › Models</div>{% endblock %} @@ -8,18 +8,19 @@ {% block content %} -<h1>Models Documentation</h1> +<h1>Model documentation</h1> + +{% regroup models by app_label as grouped_models %} <div id="content-main"> -{% regroup models|dictsort:"module" by module as grouped_models %} {% for group in grouped_models %} <div class="module"> -<h2 id='{{ group.grouper }}'>{{ group.grouper }}</h2> +<h2 id="{{ group.grouper }}">{{ group.grouper|capfirst }}</h2> <table class="xfull"> {% for model in group.list %} <tr> -<th><a href="{{ model.name }}/">{{ model.class }}</a></th> +<th><a href="{{ model.app_label }}.{{ model.object_name.lower }}/">{{ model.object_name }}</a></th> </tr> {% endfor %} </table> @@ -32,11 +33,11 @@ {% block sidebar %} <div id="content-related" class="sidebar"> <div class="module"> -<h2>Model Groups Quick List</h2> +<h2>Model groups</h2> <ul> -{% regroup models|dictsort:"module" by module as grouped_models %} +{% regroup models by app_label as grouped_models %} {% for group in grouped_models %} - <li><a href="#{{ group.grouper }}">{{ group.grouper }}</a></li> + <li><a href="#{{ group.grouper }}">{{ group.grouper|capfirst }}</a></li> {% endfor %} </ul> </div> diff --git a/django/contrib/admin/templates/admin_doc/template_detail.html b/django/contrib/admin/templates/admin_doc/template_detail.html index d2a2831098..df67f1856b 100644 --- a/django/contrib/admin/templates/admin_doc/template_detail.html +++ b/django/contrib/admin/templates/admin_doc/template_detail.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} {% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> › <a href="../../">Documentation</a> › Templates › {{ name }}</div>{% endblock %} {% block userlinks %}<a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %} diff --git a/django/contrib/admin/templates/admin_doc/template_filter_index.html b/django/contrib/admin/templates/admin_doc/template_filter_index.html index 30ddee9d64..72344c16cb 100644 --- a/django/contrib/admin/templates/admin_doc/template_filter_index.html +++ b/django/contrib/admin/templates/admin_doc/template_filter_index.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} {% block coltype %}colSM{% endblock %} {% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> › <a href="../">Documentation</a> › filters</div>{% endblock %} diff --git a/django/contrib/admin/templates/admin_doc/template_tag_index.html b/django/contrib/admin/templates/admin_doc/template_tag_index.html index 9051ae5dde..287475ab09 100644 --- a/django/contrib/admin/templates/admin_doc/template_tag_index.html +++ b/django/contrib/admin/templates/admin_doc/template_tag_index.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} {% block coltype %}colSM{% endblock %} {% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> › <a href="../">Documentation</a> › Tags</div>{% endblock %} diff --git a/django/contrib/admin/templates/admin_doc/view_detail.html b/django/contrib/admin/templates/admin_doc/view_detail.html index a7e920e0d6..ba90399358 100644 --- a/django/contrib/admin/templates/admin_doc/view_detail.html +++ b/django/contrib/admin/templates/admin_doc/view_detail.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} {% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> › <a href="../../">Documentation</a> › <a href="../">Views</a> › {{ name }}</div>{% endblock %} {% block userlinks %}<a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %} diff --git a/django/contrib/admin/templates/admin_doc/view_index.html b/django/contrib/admin/templates/admin_doc/view_index.html index a054ceb66b..caab8a2e71 100644 --- a/django/contrib/admin/templates/admin_doc/view_index.html +++ b/django/contrib/admin/templates/admin_doc/view_index.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
{% load i18n %}
{% block coltype %}colSM{% endblock %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> › <a href="../">Documentation</a> › Views</div>{% endblock %}
diff --git a/django/contrib/admin/templates/registration/logged_out.html b/django/contrib/admin/templates/registration/logged_out.html index 756f82aadc..d339ef0a49 100644 --- a/django/contrib/admin/templates/registration/logged_out.html +++ b/django/contrib/admin/templates/registration/logged_out.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a></div>{% endblock %} diff --git a/django/contrib/admin/templates/registration/password_change_done.html b/django/contrib/admin/templates/registration/password_change_done.html index f163e55a68..85e1bf216c 100644 --- a/django/contrib/admin/templates/registration/password_change_done.html +++ b/django/contrib/admin/templates/registration/password_change_done.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> › {% trans 'Password change' %}</div>{% endblock %} diff --git a/django/contrib/admin/templates/registration/password_change_form.html b/django/contrib/admin/templates/registration/password_change_form.html index 096e66ce13..036d56212c 100644 --- a/django/contrib/admin/templates/registration/password_change_form.html +++ b/django/contrib/admin/templates/registration/password_change_form.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} {% block userlinks %}<a href="../doc/">{% trans 'Documentation' %}</a> / {% trans 'Change password' %} / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %} {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> › {% trans 'Password change' %}</div>{% endblock %} diff --git a/django/contrib/admin/templates/registration/password_reset_done.html b/django/contrib/admin/templates/registration/password_reset_done.html index dff293c931..f97b5688c2 100644 --- a/django/contrib/admin/templates/registration/password_reset_done.html +++ b/django/contrib/admin/templates/registration/password_reset_done.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> › {% trans 'Password reset' %}</div>{% endblock %} diff --git a/django/contrib/admin/templates/registration/password_reset_form.html b/django/contrib/admin/templates/registration/password_reset_form.html index 1b6a2c9a17..423821ba60 100644 --- a/django/contrib/admin/templates/registration/password_reset_form.html +++ b/django/contrib/admin/templates/registration/password_reset_form.html @@ -1,4 +1,4 @@ -{% extends "admin/base_site" %} +{% extends "admin/base_site.html" %} {% load i18n %} {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> › {% trans 'Password reset' %}</div>{% endblock %} diff --git a/django/contrib/admin/templates/widget/foreign.html b/django/contrib/admin/templates/widget/foreign.html index 582d65df7f..6b43d044bd 100644 --- a/django/contrib/admin/templates/widget/foreign.html +++ b/django/contrib/admin/templates/widget/foreign.html @@ -1,12 +1,20 @@ {% load admin_modify adminmedia %} {% output_all bound_field.form_fields %} {% if bound_field.raw_id_admin %} -{% if bound_field.field.rel.limit_choices_to %} - <a href="../../../{{ bound_field.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/?{% for limit_choice in bound_field.field.rel.limit_choices_to.items %}{% if not forloop.first %}{{"&"|escape}}{% endif %}{{ limit_choice|join:"=" }}{% endfor %}" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a> -{% else %} - <a href="../../../{{ bound_field.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a> -{% endif %} + {% if bound_field.field.rel.limit_choices_to %} + <a href="{{ bound_field.related_url }}?{% for limit_choice in bound_field.field.rel.limit_choices_to.items %}{% if not forloop.first %}&{% endif %}{{ limit_choice|join:"=" }}{% endfor %}" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a> + {% else %} + <a href="{{ bound_field.related_url }}" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a> + {% endif %} {% else %} {% if bound_field.needs_add_label %} - <a href="../../../{{ bound_field.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/add/" class="add-another" id="add_{{ bound_field.element_id }}" onclick="return showAddAnotherPopup(this);"> <img src="{% admin_media_prefix %}img/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a> + <a href="{{ bound_field.related_url }}add/" class="add-another" id="add_{{ bound_field.element_id }}" onclick="return showAddAnotherPopup(this);"> <img src="{% admin_media_prefix %}img/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a> {% endif %}{% endif %} +{% if change %} + {% if bound_field.field.primary_key %} + {{ bound_field.original_value }} + {% endif %} + {% if bound_field.raw_id_admin %} + {% if bound_field.existing_display %} <strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %} + {% endif %} +{% endif %} diff --git a/django/contrib/admin/templates/widget/many_to_many.html b/django/contrib/admin/templates/widget/many_to_many.html index 151fe04f30..a93aa65f73 100644 --- a/django/contrib/admin/templates/widget/many_to_many.html +++ b/django/contrib/admin/templates/widget/many_to_many.html @@ -1 +1 @@ -{% include "widget/foreign" %} +{% include "widget/foreign.html" %} diff --git a/django/contrib/admin/templates/widget/one_to_one.html b/django/contrib/admin/templates/widget/one_to_one.html index 151fe04f30..a79a12314f 100644 --- a/django/contrib/admin/templates/widget/one_to_one.html +++ b/django/contrib/admin/templates/widget/one_to_one.html @@ -1 +1,2 @@ -{% include "widget/foreign" %} +{% if add %}{% include "widget/foreign.html" %}{% endif %} +{% if change %}{% if bound_field.existing_display %} <strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}{% endif %} diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index b87fd15e60..5a6b6a24e9 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -1,14 +1,15 @@ -from django.contrib.admin.views.main import MAX_SHOW_ALL_ALLOWED, DEFAULT_RESULTS_PER_PAGE, ALL_VAR +from django import template +from django.conf import settings +from django.contrib.admin.views.main import MAX_SHOW_ALL_ALLOWED, ALL_VAR from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR from django.contrib.admin.views.main import IS_POPUP_VAR, EMPTY_CHANGELIST_VALUE, MONTHS -from django.core import meta, template from django.core.exceptions import ObjectDoesNotExist +from django.db import models from django.utils import dateformat from django.utils.html import escape from django.utils.text import capfirst from django.utils.translation import get_date_formats -from django.conf.settings import ADMIN_MEDIA_PREFIX -from django.core.template import Library +from django.template import Library register = Library() @@ -16,11 +17,11 @@ DOT = '.' def paginator_number(cl,i): if i == DOT: - return '... ' + return '... ' elif i == cl.page_num: - return '<span class="this-page">%d</span> ' % (i+1) + return '<span class="this-page">%d</span> ' % (i+1) else: - return '<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1) + return '<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1) paginator_number = register.simple_tag(paginator_number) def pagination(cl): @@ -64,7 +65,7 @@ def pagination(cl): 'ALL_VAR': ALL_VAR, '1': 1, } -pagination = register.inclusion_tag('admin/pagination')(pagination) +pagination = register.inclusion_tag('admin/pagination.html')(pagination) def result_headers(cl): lookup_opts = cl.lookup_opts @@ -72,22 +73,22 @@ def result_headers(cl): for i, field_name in enumerate(lookup_opts.admin.list_display): try: f = lookup_opts.get_field(field_name) - except meta.FieldDoesNotExist: + except models.FieldDoesNotExist: # For non-field list_display values, check for the function # attribute "short_description". If that doesn't exist, fall - # back to the method name. And __repr__ is a special-case. - if field_name == '__repr__': + # back to the method name. And __str__ is a special-case. + if field_name == '__str__': header = lookup_opts.verbose_name else: - func = getattr(cl.mod.Klass, field_name) # Let AttributeErrors propagate. + attr = getattr(cl.model, field_name) # Let AttributeErrors propagate. try: - header = func.short_description + header = attr.short_description except AttributeError: - header = func.__name__.replace('_', ' ') + header = field_name.replace('_', ' ') # Non-field list_display values don't get ordering capability. yield {"text": header} else: - if isinstance(f.rel, meta.ManyToOneRel) and f.null: + if isinstance(f.rel, models.ManyToOneRel) and f.null: yield {"text": f.verbose_name} else: th_classes = [] @@ -108,34 +109,37 @@ def items_for_result(cl, result): row_class = '' try: f = cl.lookup_opts.get_field(field_name) - except meta.FieldDoesNotExist: - # For non-field list_display values, the value is a method - # name. Execute the method. + except models.FieldDoesNotExist: + # For non-field list_display values, the value is either a method + # or a property. try: - func = getattr(result, field_name) - result_repr = str(func()) + attr = getattr(result, field_name) + allow_tags = getattr(attr, 'allow_tags', False) + if callable(attr): + attr = attr() + result_repr = str(attr) except AttributeError, ObjectDoesNotExist: result_repr = EMPTY_CHANGELIST_VALUE else: # Strip HTML tags in the resulting text, except if the # function has an "allow_tags" attribute set to True. - if not getattr(func, 'allow_tags', False): + if not allow_tags: result_repr = escape(result_repr) else: field_val = getattr(result, f.attname) - if isinstance(f.rel, meta.ManyToOneRel): + if isinstance(f.rel, models.ManyToOneRel): if field_val is not None: - result_repr = getattr(result, 'get_%s' % f.name)() + result_repr = getattr(result, f.name) else: result_repr = EMPTY_CHANGELIST_VALUE # Dates and times are special: They're formatted in a certain way. - elif isinstance(f, meta.DateField) or isinstance(f, meta.TimeField): + elif isinstance(f, models.DateField) or isinstance(f, models.TimeField): if field_val: (date_format, datetime_format, time_format) = get_date_formats() - if isinstance(f, meta.DateTimeField): + if isinstance(f, models.DateTimeField): result_repr = capfirst(dateformat.format(field_val, datetime_format)) - elif isinstance(f, meta.TimeField): + elif isinstance(f, models.TimeField): result_repr = capfirst(dateformat.time_format(field_val, time_format)) else: result_repr = capfirst(dateformat.format(field_val, date_format)) @@ -143,15 +147,15 @@ def items_for_result(cl, result): result_repr = EMPTY_CHANGELIST_VALUE row_class = ' class="nowrap"' # Booleans are special: We use images. - elif isinstance(f, meta.BooleanField) or isinstance(f, meta.NullBooleanField): + elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField): BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'} - result_repr = '<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val) + result_repr = '<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val) # ImageFields are special: Use a thumbnail. - elif isinstance(f, meta.ImageField): + elif isinstance(f, models.ImageField): from django.parts.media.photos import get_thumbnail_url result_repr = '<img src="%s" alt="%s" title="%s" />' % (get_thumbnail_url(getattr(result, 'get_%s_url' % f.name)(), '120'), field_val, field_val) # FloatFields are special: Zero-pad the decimals. - elif isinstance(f, meta.FloatField): + elif isinstance(f, models.FloatField): if field_val is not None: result_repr = ('%%.%sf' % f.decimal_places) % field_val else: @@ -163,7 +167,7 @@ def items_for_result(cl, result): else: result_repr = escape(str(field_val)) if result_repr == '': - result_repr = ' ' + result_repr = ' ' if first: # First column is a special case first = False url = cl.url_for_result(result) @@ -181,28 +185,20 @@ def result_list(cl): return {'cl': cl, 'result_headers': list(result_headers(cl)), 'results': list(results(cl))} -result_list = register.inclusion_tag("admin/change_list_results")(result_list) +result_list = register.inclusion_tag("admin/change_list_results.html")(result_list) def date_hierarchy(cl): - lookup_opts, params, lookup_params, lookup_mod = \ - cl.lookup_opts, cl.params, cl.lookup_params, cl.lookup_mod - - if lookup_opts.admin.date_hierarchy: - field_name = lookup_opts.admin.date_hierarchy - + if cl.lookup_opts.admin.date_hierarchy: + field_name = cl.lookup_opts.admin.date_hierarchy year_field = '%s__year' % field_name month_field = '%s__month' % field_name day_field = '%s__day' % field_name field_generic = '%s__' % field_name - year_lookup = params.get(year_field) - month_lookup = params.get(month_field) - day_lookup = params.get(day_field) - - def link(d): - return cl.get_query_string(d, [field_generic]) + year_lookup = cl.params.get(year_field) + month_lookup = cl.params.get(month_field) + day_lookup = cl.params.get(day_field) - def get_dates(unit, params): - return getattr(lookup_mod, 'get_%s_list' % field_name)(unit, **params) + link = lambda d: cl.get_query_string(d, [field_generic]) if year_lookup and month_lookup and day_lookup: month_name = MONTHS[int(month_lookup)] @@ -215,9 +211,7 @@ def date_hierarchy(cl): 'choices': [{'title': "%s %s" % (month_name, day_lookup)}] } elif year_lookup and month_lookup: - date_lookup_params = lookup_params.copy() - date_lookup_params.update({year_field: year_lookup, month_field: month_lookup}) - days = get_dates('day', date_lookup_params) + days = cl.query_set.filter(**{year_field: year_lookup, month_field: month_lookup}).dates(field_name, 'day') return { 'show': True, 'back': { @@ -230,9 +224,7 @@ def date_hierarchy(cl): } for day in days] } elif year_lookup: - date_lookup_params = lookup_params.copy() - date_lookup_params.update({year_field: year_lookup}) - months = get_dates('month', date_lookup_params) + months = cl.query_set.filter(**{year_field: year_lookup}).dates(field_name, 'month') return { 'show' : True, 'back': { @@ -240,20 +232,20 @@ def date_hierarchy(cl): 'title': _('All dates') }, 'choices': [{ - 'link': link( {year_field: year_lookup, month_field: month.month}), - 'title': "%s %s" % (month.strftime('%B') , month.year) + 'link': link({year_field: year_lookup, month_field: month.month}), + 'title': "%s %s" % (month.strftime('%B'), month.year) } for month in months] } else: - years = get_dates('year', lookup_params) + years = cl.query_set.dates(field_name, 'year') return { 'show': True, 'choices': [{ 'link': link({year_field: year.year}), 'title': year.year - } for year in years ] + } for year in years] } -date_hierarchy = register.inclusion_tag('admin/date_hierarchy')(date_hierarchy) +date_hierarchy = register.inclusion_tag('admin/date_hierarchy.html')(date_hierarchy) def search_form(cl): return { @@ -261,12 +253,12 @@ def search_form(cl): 'show_result_count': cl.result_count != cl.full_result_count and not cl.opts.one_to_one_field, 'search_var': SEARCH_VAR } -search_form = register.inclusion_tag('admin/search_form')(search_form) +search_form = register.inclusion_tag('admin/search_form.html')(search_form) def filter(cl, spec): return {'title': spec.title(), 'choices' : list(spec.choices(cl))} -filter = register.inclusion_tag('admin/filter')(filter) +filter = register.inclusion_tag('admin/filter.html')(filter) def filters(cl): return {'cl': cl} -filters = register.inclusion_tag('admin/filters')(filters) +filters = register.inclusion_tag('admin/filters.html')(filters) diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py index 6135e74eb4..9f646214ce 100644 --- a/django/contrib/admin/templatetags/admin_modify.py +++ b/django/contrib/admin/templatetags/admin_modify.py @@ -1,11 +1,13 @@ -from django.core import template, template_loader, meta +from django import template +from django.contrib.admin.views.main import AdminBoundField +from django.template import loader from django.utils.html import escape from django.utils.text import capfirst from django.utils.functional import curry -from django.contrib.admin.views.main import AdminBoundField -from django.core.meta.fields import BoundField, Field -from django.core.meta import BoundRelatedObject, TABULAR, STACKED -from django.conf.settings import ADMIN_MEDIA_PREFIX +from django.db import models +from django.db.models.fields import Field +from django.db.models.related import BoundRelatedObject +from django.conf import settings import re register = template.Library() @@ -16,30 +18,28 @@ def class_name_to_underscored(name): return '_'.join([s.lower() for s in word_re.findall(name)[:-1]]) def include_admin_script(script_path): - return '<script type="text/javascript" src="%s%s"></script>' % (ADMIN_MEDIA_PREFIX, script_path) + return '<script type="text/javascript" src="%s%s"></script>' % (settings.ADMIN_MEDIA_PREFIX, script_path) include_admin_script = register.simple_tag(include_admin_script) -def submit_row(context, bound_manipulator): +def submit_row(context): + opts = context['opts'] change = context['change'] - add = context['add'] - show_delete = context['show_delete'] - has_delete_permission = context['has_delete_permission'] is_popup = context['is_popup'] return { - 'onclick_attrib': (bound_manipulator.ordered_objects and change + 'onclick_attrib': (opts.get_ordered_objects() and change and 'onclick="submitOrderForm();"' or ''), - 'show_delete_link': (not is_popup and has_delete_permission - and (change or show_delete)), - 'show_save_as_new': not is_popup and change and bound_manipulator.save_as, - 'show_save_and_add_another': not is_popup and (not bound_manipulator.save_as or add), - 'show_save_and_continue': not is_popup, + 'show_delete_link': (not is_popup and context['has_delete_permission'] + and (change or context['show_delete'])), + 'show_save_as_new': not is_popup and change and opts.admin.save_as, + 'show_save_and_add_another': not is_popup and (not opts.admin.save_as or context['add']), + 'show_save_and_continue': not is_popup and context['has_change_permission'], 'show_save': True } -submit_row = register.inclusion_tag('admin/submit_line', takes_context=True)(submit_row) +submit_row = register.inclusion_tag('admin/submit_line.html', takes_context=True)(submit_row) def field_label(bound_field): class_names = [] - if isinstance(bound_field.field, meta.BooleanField): + if isinstance(bound_field.field, models.BooleanField): class_names.append("vCheckboxLabel") colon = "" else: @@ -64,16 +64,15 @@ class FieldWidgetNode(template.Node): if not cls.nodelists.has_key(klass): try: field_class_name = klass.__name__ - template_name = "widget/%s" % \ - class_name_to_underscored(field_class_name) - nodelist = template_loader.get_template(template_name).nodelist + template_name = "widget/%s.html" % class_name_to_underscored(field_class_name) + nodelist = loader.get_template(template_name).nodelist except template.TemplateDoesNotExist: super_klass = bool(klass.__bases__) and klass.__bases__[0] or None if super_klass and super_klass != Field: nodelist = cls.get_nodelist(super_klass) else: if not cls.default: - cls.default = template_loader.get_template("widget/default").nodelist + cls.default = loader.get_template("widget/default.html").nodelist nodelist = cls.default cls.nodelists[klass] = nodelist @@ -97,21 +96,22 @@ class FieldWrapper(object): self.field = field def needs_header(self): - return not isinstance(self.field, meta.AutoField) + return not isinstance(self.field, models.AutoField) def header_class_attribute(self): return self.field.blank and ' class="optional"' or '' def use_raw_id_admin(self): - return isinstance(self.field.rel, (meta.ManyToOneRel, meta.ManyToManyRel)) \ + return isinstance(self.field.rel, (models.ManyToOneRel, models.ManyToManyRel)) \ and self.field.rel.raw_id_admin class FormFieldCollectionWrapper(object): - def __init__(self, field_mapping, fields): + def __init__(self, field_mapping, fields, index): self.field_mapping = field_mapping self.fields = fields self.bound_fields = [AdminBoundField(field, self.field_mapping, field_mapping['original']) for field in self.fields] + self.index = index class TabularBoundRelatedObject(BoundRelatedObject): def __init__(self, related_object, field_mapping, original): @@ -120,29 +120,25 @@ class TabularBoundRelatedObject(BoundRelatedObject): fields = self.relation.editable_fields() - self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping, fields) - for field_mapping in self.field_mappings] + self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping, fields, i) + for (i,field_mapping) in self.field_mappings.items() ] self.original_row_needed = max([fw.use_raw_id_admin() for fw in self.field_wrapper_list]) self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url') def template_name(self): - return "admin/edit_inline_tabular" + return "admin/edit_inline_tabular.html" class StackedBoundRelatedObject(BoundRelatedObject): def __init__(self, related_object, field_mapping, original): super(StackedBoundRelatedObject, self).__init__(related_object, field_mapping, original) fields = self.relation.editable_fields() - self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping ,fields) - for field_mapping in self.field_mappings] + self.field_mappings.fill() + self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping ,fields, i) + for (i,field_mapping) in self.field_mappings.items()] self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url') def template_name(self): - return "admin/edit_inline_stacked" - -bound_related_object_overrides = { - TABULAR: TabularBoundRelatedObject, - STACKED: StackedBoundRelatedObject, -} + return "admin/edit_inline_stacked.html" class EditInlineNode(template.Node): def __init__(self, rel_var): @@ -150,21 +146,16 @@ class EditInlineNode(template.Node): def render(self, context): relation = template.resolve_variable(self.rel_var, context) - context.push() - - klass = relation.field.rel.edit_inline - bound_related_object_class = bound_related_object_overrides.get(klass, klass) - + if relation.field.rel.edit_inline == models.TABULAR: + bound_related_object_class = TabularBoundRelatedObject + else: + bound_related_object_class = StackedBoundRelatedObject original = context.get('original', None) - bound_related_object = relation.bind(context['form'], original, bound_related_object_class) context['bound_related_object'] = bound_related_object - - t = template_loader.get_template(bound_related_object.template_name()) - + t = loader.get_template(bound_related_object.template_name()) output = t.render(context) - context.pop() return output @@ -191,30 +182,30 @@ auto_populated_field_script = register.simple_tag(auto_populated_field_script) def filter_interface_script_maybe(bound_field): f = bound_field.field - if f.rel and isinstance(f.rel, meta.ManyToManyRel) and f.rel.filter_interface: - return '<script type="text/javascript">addEvent(window, "load", function(e) {' \ + if f.rel and isinstance(f.rel, models.ManyToManyRel) and f.rel.filter_interface: + return '<script type="text/javascript">addEvent(window, "load", function(e) {' \ ' SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % ( - f.name, f.verbose_name, f.rel.filter_interface-1, ADMIN_MEDIA_PREFIX) + f.name, f.verbose_name, f.rel.filter_interface-1, settings.ADMIN_MEDIA_PREFIX) else: return '' filter_interface_script_maybe = register.simple_tag(filter_interface_script_maybe) -def do_one_arg_tag(node_factory, parser,token): - tokens = token.contents.split() - if len(tokens) != 2: - raise template.TemplateSyntaxError("%s takes 1 argument" % tokens[0]) - return node_factory(tokens[1]) - -def register_one_arg_tag(node): - tag_name = class_name_to_underscored(node.__name__) - parse_func = curry(do_one_arg_tag, node) - register.tag(tag_name, parse_func) +def field_widget(parser, token): + bits = token.contents.split() + if len(bits) != 2: + raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0] + return FieldWidgetNode(bits[1]) +field_widget = register.tag(field_widget) -register_one_arg_tag(FieldWidgetNode) -register_one_arg_tag(EditInlineNode) +def edit_inline(parser, token): + bits = token.contents.split() + if len(bits) != 2: + raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0] + return EditInlineNode(bits[1]) +edit_inline = register.tag(edit_inline) def admin_field_line(context, argument_val): - if (isinstance(argument_val, BoundField)): + if isinstance(argument_val, AdminBoundField): bound_fields = [argument_val] else: bound_fields = [bf for bf in argument_val] @@ -229,7 +220,7 @@ def admin_field_line(context, argument_val): break # Assumes BooleanFields won't be stacked next to each other! - if isinstance(bound_fields[0].field, meta.BooleanField): + if isinstance(bound_fields[0].field, models.BooleanField): class_names.append('checkbox-row') return { @@ -238,8 +229,4 @@ def admin_field_line(context, argument_val): 'bound_fields': bound_fields, 'class_names': " ".join(class_names), } -admin_field_line = register.inclusion_tag('admin/field_line', takes_context=True)(admin_field_line) - -def object_pk(bound_manip, ordered_obj): - return bound_manip.get_ordered_object_pk(ordered_obj) -object_pk = register.simple_tag(object_pk) +admin_field_line = register.inclusion_tag('admin/field_line.html', takes_context=True)(admin_field_line) diff --git a/django/contrib/admin/templatetags/adminapplist.py b/django/contrib/admin/templatetags/adminapplist.py index 7a91516ebc..c328ddf203 100644 --- a/django/contrib/admin/templatetags/adminapplist.py +++ b/django/contrib/admin/templatetags/adminapplist.py @@ -1,4 +1,5 @@ -from django.core import template +from django import template +from django.db.models import get_models register = template.Library() @@ -7,20 +8,24 @@ class AdminApplistNode(template.Node): self.varname = varname def render(self, context): - from django.core import meta + from django.db import models from django.utils.text import capfirst app_list = [] user = context['user'] - for app in meta.get_installed_model_modules(): - app_label = app.__name__[app.__name__.rindex('.')+1:] + for app in models.get_apps(): + # Determine the app_label. + app_models = get_models(app) + if not app_models: + continue + app_label = app_models[0]._meta.app_label + has_module_perms = user.has_module_perms(app_label) if has_module_perms: model_list = [] - for m in app._MODELS: + for m in app_models: if m._meta.admin: - module_name = m._meta.module_name perms = { 'add': user.has_perm("%s.%s" % (app_label, m._meta.get_add_permission())), 'change': user.has_perm("%s.%s" % (app_label, m._meta.get_change_permission())), @@ -32,7 +37,7 @@ class AdminApplistNode(template.Node): if True in perms.values(): model_list.append({ 'name': capfirst(m._meta.verbose_name_plural), - 'admin_url': '%s/%s/' % (app_label, m._meta.module_name), + 'admin_url': '%s/%s/' % (app_label, m.__name__.lower()), 'perms': perms, }) diff --git a/django/contrib/admin/templatetags/adminmedia.py b/django/contrib/admin/templatetags/adminmedia.py index cd513f6c81..266c017e3d 100644 --- a/django/contrib/admin/templatetags/adminmedia.py +++ b/django/contrib/admin/templatetags/adminmedia.py @@ -1,10 +1,11 @@ -from django.core.template import Library +from django.template import Library + register = Library() def admin_media_prefix(): try: - from django.conf.settings import ADMIN_MEDIA_PREFIX + from django.conf import settings except ImportError: return '' - return ADMIN_MEDIA_PREFIX + return settings.ADMIN_MEDIA_PREFIX admin_media_prefix = register.simple_tag(admin_media_prefix) diff --git a/django/contrib/admin/templatetags/log.py b/django/contrib/admin/templatetags/log.py index 013e07c80f..5caba2b795 100644 --- a/django/contrib/admin/templatetags/log.py +++ b/django/contrib/admin/templatetags/log.py @@ -1,5 +1,5 @@ -from django.models.admin import log -from django.core import template +from django import template +from django.contrib.admin.models import LogEntry register = template.Library() @@ -13,7 +13,7 @@ class AdminLogNode(template.Node): def render(self, context): if self.user is not None and not self.user.isdigit(): self.user = context[self.user].id - context[self.varname] = log.get_list(user__id__exact=self.user, limit=self.limit, select_related=True) + context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit] return '' class DoGetAdminLog: diff --git a/django/contrib/admin/urls.py b/django/contrib/admin/urls.py new file mode 100644 index 0000000000..dde848d766 --- /dev/null +++ b/django/contrib/admin/urls.py @@ -0,0 +1,31 @@ +from django.conf.urls.defaults import * + +urlpatterns = patterns('', + ('^$', 'django.contrib.admin.views.main.index'), + ('^r/(\d+)/(\d+)/$', 'django.views.defaults.shortcut'), + ('^jsi18n/$', 'django.views.i18n.javascript_catalog', {'packages': 'django.conf'}), + ('^logout/$', 'django.contrib.auth.views.logout'), + ('^password_change/$', 'django.contrib.auth.views.password_change'), + ('^password_change/done/$', 'django.contrib.auth.views.password_change_done'), + ('^template_validator/$', 'django.contrib.admin.views.template.template_validator'), + + # Documentation + ('^doc/$', 'django.contrib.admin.views.doc.doc_index'), + ('^doc/bookmarklets/$', 'django.contrib.admin.views.doc.bookmarklets'), + ('^doc/tags/$', 'django.contrib.admin.views.doc.template_tag_index'), + ('^doc/filters/$', 'django.contrib.admin.views.doc.template_filter_index'), + ('^doc/views/$', 'django.contrib.admin.views.doc.view_index'), + ('^doc/views/jump/$', 'django.contrib.admin.views.doc.jump_to_view'), + ('^doc/views/(?P<view>[^/]+)/$', 'django.contrib.admin.views.doc.view_detail'), + ('^doc/models/$', 'django.contrib.admin.views.doc.model_index'), + ('^doc/models/(?P<app_label>[^\.]+)\.(?P<model_name>[^/]+)/$', 'django.contrib.admin.views.doc.model_detail'), +# ('^doc/templates/$', 'django.views.admin.doc.template_index'), + ('^doc/templates/(?P<template>.*)/$', 'django.contrib.admin.views.doc.template_detail'), + + # Add/change/delete/history + ('^([^/]+)/([^/]+)/$', 'django.contrib.admin.views.main.change_list'), + ('^([^/]+)/([^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'), + ('^([^/]+)/([^/]+)/(.+)/history/$', 'django.contrib.admin.views.main.history'), + ('^([^/]+)/([^/]+)/(.+)/delete/$', 'django.contrib.admin.views.main.delete_stage'), + ('^([^/]+)/([^/]+)/(.+)/$', 'django.contrib.admin.views.main.change_stage'), +) diff --git a/django/contrib/admin/urls/__init__.py b/django/contrib/admin/urls/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 --- a/django/contrib/admin/urls/__init__.py +++ /dev/null diff --git a/django/contrib/admin/urls/admin.py b/django/contrib/admin/urls/admin.py deleted file mode 100644 index 3f4dbab419..0000000000 --- a/django/contrib/admin/urls/admin.py +++ /dev/null @@ -1,58 +0,0 @@ -from django.conf.urls.defaults import * -from django.conf.settings import INSTALLED_APPS - -urlpatterns = ( - ('^$', 'django.contrib.admin.views.main.index'), - ('^jsi18n/$', 'django.views.i18n.javascript_catalog', {'packages': 'django.conf'}), - ('^logout/$', 'django.views.auth.login.logout'), - ('^password_change/$', 'django.views.registration.passwords.password_change'), - ('^password_change/done/$', 'django.views.registration.passwords.password_change_done'), - ('^template_validator/$', 'django.contrib.admin.views.template.template_validator'), - - # Documentation - ('^doc/$', 'django.contrib.admin.views.doc.doc_index'), - ('^doc/bookmarklets/$', 'django.contrib.admin.views.doc.bookmarklets'), - ('^doc/tags/$', 'django.contrib.admin.views.doc.template_tag_index'), - ('^doc/filters/$', 'django.contrib.admin.views.doc.template_filter_index'), - ('^doc/views/$', 'django.contrib.admin.views.doc.view_index'), - ('^doc/views/jump/$', 'django.contrib.admin.views.doc.jump_to_view'), - ('^doc/views/(?P<view>[^/]+)/$', 'django.contrib.admin.views.doc.view_detail'), - ('^doc/models/$', 'django.contrib.admin.views.doc.model_index'), - ('^doc/models/(?P<model>[^/]+)/$', 'django.contrib.admin.views.doc.model_detail'), -# ('^doc/templates/$', 'django.views.admin.doc.template_index'), - ('^doc/templates/(?P<template>.*)/$', 'django.contrib.admin.views.doc.template_detail'), -) - -if 'ellington.events' in INSTALLED_APPS: - urlpatterns += ( - ("^events/usersubmittedevents/(?P<object_id>\d+)/$", 'ellington.events.views.admin.user_submitted_event_change_stage'), - ("^events/usersubmittedevents/(?P<object_id>\d+)/delete/$", 'ellington.events.views.admin.user_submitted_event_delete_stage'), - ) - -if 'ellington.news' in INSTALLED_APPS: - urlpatterns += ( - ("^stories/preview/$", 'ellington.news.views.admin.story_preview'), - ("^stories/js/inlinecontrols/$", 'ellington.news.views.admin.inlinecontrols_js'), - ("^stories/js/inlinecontrols/(?P<label>[-\w]+)/$", 'ellington.news.views.admin.inlinecontrols_js_specific'), - ) - -if 'ellington.alerts' in INSTALLED_APPS: - urlpatterns += ( - ("^alerts/send/$", 'ellington.alerts.views.admin.send_alert_form'), - ("^alerts/send/do/$", 'ellington.alerts.views.admin.send_alert_action'), - ) - -if 'ellington.media' in INSTALLED_APPS: - urlpatterns += ( - ('^media/photos/caption/(?P<photo_id>\d+)/$', 'ellington.media.views.admin.get_exif_caption'), - ) - -urlpatterns += ( - # Metasystem admin pages - ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/$', 'django.contrib.admin.views.main.change_list'), - ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'), - ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/history/$', 'django.contrib.admin.views.main.history'), - ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/delete/$', 'django.contrib.admin.views.main.delete_stage'), - ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/$', 'django.contrib.admin.views.main.change_stage'), -) -urlpatterns = patterns('', *urlpatterns) diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py index 80a8f2a773..c6c794c75e 100644 --- a/django/contrib/admin/utils.py +++ b/django/contrib/admin/utils.py @@ -82,18 +82,18 @@ ROLES = { def create_reference_role(rolename, urlbase): def _role(name, rawtext, text, lineno, inliner, options={}, content=[]): - node = docutils.nodes.reference(rawtext, text, refuri=(urlbase % (inliner.document.settings.link_base, text)), **options) + node = docutils.nodes.reference(rawtext, text, refuri=(urlbase % (inliner.document.settings.link_base, text.lower())), **options) return [node], [] docutils.parsers.rst.roles.register_canonical_role(rolename, _role) def default_reference_role(name, rawtext, text, lineno, inliner, options={}, content=[]): context = inliner.document.settings.default_reference_context - node = docutils.nodes.reference(rawtext, text, refuri=(ROLES[context] % (inliner.document.settings.link_base, text)), **options) + node = docutils.nodes.reference(rawtext, text, refuri=(ROLES[context] % (inliner.document.settings.link_base, text.lower())), **options) return [node], [] if docutils_is_available: docutils.parsers.rst.roles.register_canonical_role('cmsreference', default_reference_role) docutils.parsers.rst.roles.DEFAULT_INTERPRETED_ROLE = 'cmsreference' - for (name, urlbase) in ROLES.items(): + for name, urlbase in ROLES.items(): create_reference_role(name, urlbase) diff --git a/django/contrib/admin/views/decorators.py b/django/contrib/admin/views/decorators.py index 5ddc17fa85..d984077dfb 100644 --- a/django/contrib/admin/views/decorators.py +++ b/django/contrib/admin/views/decorators.py @@ -1,7 +1,7 @@ -from django.core.extensions import DjangoContext, render_to_response -from django.conf.settings import SECRET_KEY -from django.models.auth import users -from django.utils import httpwrappers +from django import http, template +from django.conf import settings +from django.contrib.auth.models import User, SESSION_KEY +from django.shortcuts import render_to_response from django.utils.translation import gettext_lazy import base64, datetime, md5 import cPickle as pickle @@ -19,22 +19,22 @@ def _display_login_form(request, error_message=''): post_data = _encode_post_data(request.POST) else: post_data = _encode_post_data({}) - return render_to_response('admin/login', { + return render_to_response('admin/login.html', { 'title': _('Log in'), 'app_path': request.path, 'post_data': post_data, 'error_message': error_message - }, context_instance=DjangoContext(request)) + }, context_instance=template.RequestContext(request)) def _encode_post_data(post_data): pickled = pickle.dumps(post_data) - pickled_md5 = md5.new(pickled + SECRET_KEY).hexdigest() + pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest() return base64.encodestring(pickled + pickled_md5) def _decode_post_data(encoded_data): encoded_data = base64.decodestring(encoded_data) pickled, tamper_check = encoded_data[:-32], encoded_data[-32:] - if md5.new(pickled + SECRET_KEY).hexdigest() != tamper_check: + if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check: from django.core.exceptions import SuspiciousOperation raise SuspiciousOperation, "User may have tampered with session cookie." return pickle.loads(pickled) @@ -53,7 +53,7 @@ def staff_member_required(view_func): request.POST = _decode_post_data(request.POST['post_data']) return view_func(request, *args, **kwargs) - assert hasattr(request, 'session'), "The Django admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.middleware.sessions.SessionMiddleware'." + assert hasattr(request, 'session'), "The Django admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'." # If this isn't already the login page, display it. if not request.POST.has_key(LOGIN_FORM_KEY): @@ -71,14 +71,14 @@ def staff_member_required(view_func): # Check the password. username = request.POST.get('username', '') try: - user = users.get_object(username__exact=username, is_staff__exact=True) - except users.UserDoesNotExist: + user = User.objects.get(username=username, is_staff=True) + except User.DoesNotExist: message = ERROR_MESSAGE if '@' in username: # Mistakenly entered e-mail address instead of username? Look it up. try: - user = users.get_object(email__exact=username) - except users.UserDoesNotExist: + user = User.objects.get(email=username) + except User.DoesNotExist: message = _("Usernames cannot contain the '@' character.") else: message = _("Your e-mail address is not your username. Try '%s' instead.") % user.username @@ -87,7 +87,7 @@ def staff_member_required(view_func): # The user data is correct; log in the user in and continue. else: if user.check_password(request.POST.get('password', '')): - request.session[users.SESSION_KEY] = user.id + request.session[SESSION_KEY] = user.id user.last_login = datetime.datetime.now() user.save() if request.POST.has_key('post_data'): @@ -99,7 +99,7 @@ def staff_member_required(view_func): return view_func(request, *args, **kwargs) else: request.session.delete_test_cookie() - return httpwrappers.HttpResponseRedirect(request.path) + return http.HttpResponseRedirect(request.path) else: return _display_login_form(request, ERROR_MESSAGE) diff --git a/django/contrib/admin/views/doc.py b/django/contrib/admin/views/doc.py index 9a6d3ec400..f3675b6adf 100644 --- a/django/contrib/admin/views/doc.py +++ b/django/contrib/admin/views/doc.py @@ -1,12 +1,14 @@ -from django.core import meta -from django import templatetags +from django import template, templatetags +from django.template import RequestContext from django.conf import settings from django.contrib.admin.views.decorators import staff_member_required -from django.models.core import sites -from django.core.extensions import DjangoContext, render_to_response -from django.core.exceptions import Http404, ViewDoesNotExist -from django.core import template, urlresolvers +from django.db import models +from django.shortcuts import render_to_response +from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist +from django.http import Http404, get_host +from django.core import urlresolvers from django.contrib.admin import utils +from django.contrib.sites.models import Site import inspect, os, re # Exclude methods starting with these strings from documentation @@ -15,15 +17,15 @@ MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_') def doc_index(request): if not utils.docutils_is_available: return missing_docutils_page(request) - return render_to_response('admin_doc/index', context_instance=DjangoContext(request)) + return render_to_response('admin_doc/index.html', context_instance=RequestContext(request)) doc_index = staff_member_required(doc_index) def bookmarklets(request): # Hack! This couples this view to the URL it lives at. admin_root = request.path[:-len('doc/bookmarklets/')] - return render_to_response('admin_doc/bookmarklets', { - 'admin_url': "%s://%s%s" % (os.environ.get('HTTPS') == 'on' and 'https' or 'http', request.META['HTTP_HOST'], admin_root), - }, context_instance=DjangoContext(request)) + return render_to_response('admin_doc/bookmarklets.html', { + 'admin_url': "%s://%s%s" % (os.environ.get('HTTPS') == 'on' and 'https' or 'http', get_host(request), admin_root), + }, context_instance=RequestContext(request)) bookmarklets = staff_member_required(bookmarklets) def template_tag_index(request): @@ -54,7 +56,7 @@ def template_tag_index(request): 'library': tag_library, }) - return render_to_response('admin_doc/template_tag_index', {'tags': tags}, context_instance=DjangoContext(request)) + return render_to_response('admin_doc/template_tag_index.html', {'tags': tags}, context_instance=RequestContext(request)) template_tag_index = staff_member_required(template_tag_index) def template_filter_index(request): @@ -84,16 +86,20 @@ def template_filter_index(request): 'meta': metadata, 'library': tag_library, }) - return render_to_response('admin_doc/template_filter_index', {'filters': filters}, context_instance=DjangoContext(request)) + return render_to_response('admin_doc/template_filter_index.html', {'filters': filters}, context_instance=RequestContext(request)) template_filter_index = staff_member_required(template_filter_index) def view_index(request): if not utils.docutils_is_available: return missing_docutils_page(request) + if settings.ADMIN_FOR: + settings_modules = [__import__(m, '', '', ['']) for m in settings.ADMIN_FOR] + else: + settings_modules = [settings] + views = [] - for site_settings_module in settings.ADMIN_FOR: - settings_mod = __import__(site_settings_module, '', '', ['']) + for settings_mod in settings_modules: urlconf = __import__(settings_mod.ROOT_URLCONF, '', '', ['']) view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns) for (func, regex) in view_functions: @@ -101,10 +107,10 @@ def view_index(request): 'name': func.__name__, 'module': func.__module__, 'site_id': settings_mod.SITE_ID, - 'site': sites.get_object(pk=settings_mod.SITE_ID), + 'site': Site.objects.get(pk=settings_mod.SITE_ID), 'url': simplify_regex(regex), }) - return render_to_response('admin_doc/view_index', {'views': views}, context_instance=DjangoContext(request)) + return render_to_response('admin_doc/view_index.html', {'views': views}, context_instance=RequestContext(request)) view_index = staff_member_required(view_index) def view_detail(request, view): @@ -123,51 +129,63 @@ def view_detail(request, view): body = utils.parse_rst(body, 'view', 'view:' + view) for key in metadata: metadata[key] = utils.parse_rst(metadata[key], 'model', 'view:' + view) - return render_to_response('admin_doc/view_detail', { + return render_to_response('admin_doc/view_detail.html', { 'name': view, 'summary': title, 'body': body, 'meta': metadata, - }, context_instance=DjangoContext(request)) + }, context_instance=RequestContext(request)) view_detail = staff_member_required(view_detail) def model_index(request): if not utils.docutils_is_available: return missing_docutils_page(request) - models = [] - for app in meta.get_installed_model_modules(): - for model in app._MODELS: - opts = model._meta - models.append({ - 'name': '%s.%s' % (opts.app_label, opts.module_name), - 'module': opts.app_label, - 'class': opts.module_name, - }) - return render_to_response('admin_doc/model_index', {'models': models}, context_instance=DjangoContext(request)) + m_list = [m._meta for m in models.get_models()] + return render_to_response('admin_doc/model_index.html', {'models': m_list}, context_instance=RequestContext(request)) model_index = staff_member_required(model_index) -def model_detail(request, model): +def model_detail(request, app_label, model_name): if not utils.docutils_is_available: return missing_docutils_page(request) + # Get the model class. try: - model = meta.get_app(model) - except ImportError: - raise Http404 - opts = model.Klass._meta + app_mod = models.get_app(app_label) + except ImproperlyConfigured: + raise Http404, "App %r not found" % app_label + model = None + for m in models.get_models(app_mod): + if m._meta.object_name.lower() == model_name: + model = m + break + if model is None: + raise Http404, "Model %r not found in app %r" % (model_name, app_label) - # Gather fields/field descriptions + opts = model._meta + + # Gather fields/field descriptions. fields = [] for field in opts.fields: + # ForeignKey is a special case since the field will actually be a + # descriptor that returns the other object + if isinstance(field, models.ForeignKey): + data_type = related_object_name = field.rel.to.__name__ + app_label = field.rel.to._meta.app_label + verbose = utils.parse_rst(("the related `%s.%s` object" % (app_label, data_type)), 'model', 'model:' + data_type) + else: + data_type = get_readable_field_data_type(field) + verbose = field.verbose_name fields.append({ 'name': field.name, - 'data_type': get_readable_field_data_type(field), - 'verbose': field.verbose_name, + 'data_type': data_type, + 'verbose': verbose, 'help': field.help_text, }) - for func_name, func in model.Klass.__dict__.items(): - if callable(func) and len(inspect.getargspec(func)[0]) == 0: + + # Gather model methods. + for func_name, func in model.__dict__.items(): + if (inspect.isfunction(func) and len(inspect.getargspec(func)[0]) == 1): try: for exclude in MODEL_METHODS_EXCLUDE: if func_name.startswith(exclude): @@ -182,12 +200,26 @@ def model_detail(request, model): 'data_type': get_return_data_type(func_name), 'verbose': verbose, }) - return render_to_response('admin_doc/model_detail', { - 'name': '%s.%s' % (opts.app_label, opts.module_name), - 'summary': "Fields on %s objects" % opts.verbose_name, + + # Gather related objects + for rel in opts.get_all_related_objects(): + verbose = "related `%s.%s` objects" % (rel.opts.app_label, rel.opts.object_name) + accessor = rel.get_accessor_name() + fields.append({ + 'name' : "%s.all" % accessor, + 'verbose' : utils.parse_rst("all " + verbose , 'model', 'model:' + opts.module_name), + }) + fields.append({ + 'name' : "%s.count" % accessor, + 'verbose' : utils.parse_rst("number of " + verbose , 'model', 'model:' + opts.module_name), + }) + + return render_to_response('admin_doc/model_detail.html', { + 'name': '%s.%s' % (opts.app_label, opts.object_name), + 'summary': "Fields on %s objects" % opts.object_name, 'description': model.__doc__, 'fields': fields, - }, context_instance=DjangoContext(request)) + }, context_instance=RequestContext(request)) model_detail = staff_member_required(model_detail) def template_detail(request, template): @@ -201,13 +233,13 @@ def template_detail(request, template): 'exists': os.path.exists(template_file), 'contents': lambda: os.path.exists(template_file) and open(template_file).read() or '', 'site_id': settings_mod.SITE_ID, - 'site': sites.get_object(pk=settings_mod.SITE_ID), + 'site': Site.objects.get(pk=settings_mod.SITE_ID), 'order': list(settings_mod.TEMPLATE_DIRS).index(dir), }) - return render_to_response('admin_doc/template_detail', { + return render_to_response('admin_doc/template_detail.html', { 'name': template, 'templates': templates, - }, context_instance=DjangoContext(request)) + }, context_instance=RequestContext(request)) template_detail = staff_member_required(template_detail) #################### @@ -216,7 +248,7 @@ template_detail = staff_member_required(template_detail) def missing_docutils_page(request): """Display an error message for people without docutils""" - return render_to_response('admin_doc/missing_docutils') + return render_to_response('admin_doc/missing_docutils.html') def load_all_installed_template_libraries(): # Load/register all template tag libraries from installed apps. @@ -271,9 +303,6 @@ DATA_TYPE_MAPPING = { } def get_readable_field_data_type(field): - # ForeignKey is a special case. Use the field type of the relation. - if field.get_internal_type() == 'ForeignKey': - field = field.rel.get_related_field() return DATA_TYPE_MAPPING[field.get_internal_type()] % field.__dict__ def extract_views_from_urlpatterns(urlpatterns, base=''): @@ -295,15 +324,23 @@ def extract_views_from_urlpatterns(urlpatterns, base=''): raise TypeError, "%s does not appear to be a urlpattern object" % p return views -# Clean up urlpattern regexes into something somewhat readable by Mere Humans: -# turns something like "^(?P<sport_slug>\w+)/athletes/(?P<athlete_slug>\w+)/$" -# into "<sport_slug>/athletes/<athlete_slug>/" - named_group_matcher = re.compile(r'\(\?P(<\w+>).+?\)') +non_named_group_matcher = re.compile(r'\(.*?\)') def simplify_regex(pattern): + """ + Clean up urlpattern regexes into something somewhat readable by Mere Humans: + turns something like "^(?P<sport_slug>\w+)/athletes/(?P<athlete_slug>\w+)/$" + into "<sport_slug>/athletes/<athlete_slug>/" + """ + # handle named groups first pattern = named_group_matcher.sub(lambda m: m.group(1), pattern) - pattern = pattern.replace('^', '').replace('$', '').replace('?', '').replace('//', '/') + + # handle non-named groups + pattern = non_named_group_matcher.sub("<var>", pattern) + + # clean up any outstanding regex-y characters. + pattern = pattern.replace('^', '').replace('$', '').replace('?', '').replace('//', '/').replace('\\', '') if not pattern.startswith('/'): pattern = '/' + pattern return pattern diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 3d92a7d949..9913914858 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -1,32 +1,34 @@ -# Generic admin views. -from django.contrib.admin.views.decorators import staff_member_required +from django import forms, template +from django.conf import settings from django.contrib.admin.filterspecs import FilterSpec -from django.core import formfields, meta, template -from django.core.template import loader -from django.core.meta.fields import BoundField, BoundFieldLine, BoundFieldSet -from django.core.exceptions import Http404, ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied -from django.core.extensions import DjangoContext as Context -from django.core.extensions import get_object_or_404, render_to_response +from django.contrib.admin.views.decorators import staff_member_required +from django.views.decorators.cache import never_cache +from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied from django.core.paginator import ObjectPaginator, InvalidPage -from django.conf.settings import ADMIN_MEDIA_PREFIX -try: - from django.models.admin import log -except ImportError: - raise ImproperlyConfigured, "You don't have 'django.contrib.admin' in INSTALLED_APPS." -from django.utils.html import escape -from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect -from django.utils.text import capfirst, get_text_list +from django.shortcuts import get_object_or_404, render_to_response +from django.db import models +from django.db.models.query import handle_legacy_orderlist, QuerySet +from django.http import Http404, HttpResponse, HttpResponseRedirect +from django.template import loader from django.utils import dateformat from django.utils.dates import MONTHS from django.utils.html import escape +from django.utils.text import capfirst, get_text_list import operator -# The system will display a "Show all" link only if the total result count -# is less than or equal to this setting. -MAX_SHOW_ALL_ALLOWED = 200 +from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION +if not LogEntry._meta.installed: + raise ImproperlyConfigured, "You'll need to put 'django.contrib.admin' in your INSTALLED_APPS setting before you can use the admin application." -DEFAULT_RESULTS_PER_PAGE = 100 +if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS: + raise ImproperlyConfigured, "You'll need to put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting before you can use the admin application." + +# The system will display a "Show all" link on the change list only if the +# total result count is less than or equal to this setting. +MAX_SHOW_ALL_ALLOWED = 200 +# Changelist settings ALL_VAR = 'all' ORDER_VAR = 'o' ORDER_TYPE_VAR = 'ot' @@ -34,229 +36,59 @@ PAGE_VAR = 'p' SEARCH_VAR = 'q' IS_POPUP_VAR = 'pop' -# Text to display within changelist table cells if the value is blank. +# Text to display within change-list table cells if the value is blank. EMPTY_CHANGELIST_VALUE = '(None)' -def _get_mod_opts(app_label, module_name): - "Helper function that returns a tuple of (module, opts), raising Http404 if necessary." - try: - mod = meta.get_module(app_label, module_name) - except ImportError: - raise Http404 # Invalid app or module name. Maybe it's not in INSTALLED_APPS. - opts = mod.Klass._meta - if not opts.admin: - raise Http404 # This object is valid but has no admin interface. - return mod, opts - -def index(request): - return render_to_response('admin/index', {'title': _('Site administration')}, context_instance=Context(request)) -index = staff_member_required(index) +use_raw_id_admin = lambda field: isinstance(field.rel, (models.ManyToOneRel, models.ManyToManyRel)) and field.rel.raw_id_admin class IncorrectLookupParameters(Exception): pass -class ChangeList(object): - def __init__(self, request, app_label, module_name): - self.get_modules_and_options(app_label, module_name, request) - self.get_search_parameters(request) - self.get_ordering() - self.query = request.GET.get(SEARCH_VAR, '') - self.get_lookup_params() - self.get_results(request) - self.title = (self.is_popup - and _('Select %s') % self.opts.verbose_name - or _('Select %s to change') % self.opts.verbose_name) - self.get_filters(request) - self.pk_attname = self.lookup_opts.pk.attname - - def get_filters(self, request): - self.filter_specs = [] - if self.lookup_opts.admin.list_filter and not self.opts.one_to_one_field: - filter_fields = [self.lookup_opts.get_field(field_name) \ - for field_name in self.lookup_opts.admin.list_filter] - for f in filter_fields: - spec = FilterSpec.create(f, request, self.params) - if spec and spec.has_output(): - self.filter_specs.append(spec) - self.has_filters = bool(self.filter_specs) +def quote(s): + """ + Ensure that primary key values do not confuse the admin URLs by escaping + any '/', '_' and ':' characters. Similar to urllib.quote, except that the + quoting is slightly different so that it doesn't get autoamtically + unquoted by the web browser. + """ + if type(s) != type(''): + return s + res = list(s) + for i in range(len(res)): + c = res[i] + if c in ':/_': + res[i] = '_%02X' % ord(c) + return ''.join(res) - def get_query_string(self, new_params={}, remove=[]): - p = self.params.copy() - for r in remove: - for k in p.keys(): - if k.startswith(r): - del p[k] - for k, v in new_params.items(): - if p.has_key(k) and v is None: - del p[k] - elif v is not None: - p[k] = v - return '?' + '&'.join(['%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20') - - def get_modules_and_options(self, app_label, module_name, request): - self.mod, self.opts = _get_mod_opts(app_label, module_name) - if not request.user.has_perm(app_label + '.' + self.opts.get_change_permission()): - raise PermissionDenied - - self.lookup_mod, self.lookup_opts = self.mod, self.opts - - def get_search_parameters(self, request): - # Get search parameters from the query string. - try: - self.page_num = int(request.GET.get(PAGE_VAR, 0)) - except ValueError: - self.page_num = 0 - self.show_all = request.GET.has_key(ALL_VAR) - self.is_popup = request.GET.has_key(IS_POPUP_VAR) - self.params = dict(request.GET.items()) - if self.params.has_key(PAGE_VAR): - del self.params[PAGE_VAR] - - def get_results(self, request): - lookup_mod, lookup_params, show_all, page_num = \ - self.lookup_mod, self.lookup_params, self.show_all, self.page_num - # Get the results. - try: - paginator = ObjectPaginator(lookup_mod, lookup_params, DEFAULT_RESULTS_PER_PAGE) - # Naked except! Because we don't have any other way of validating "params". - # They might be invalid if the keyword arguments are incorrect, or if the - # values are not in the correct type (which would result in a database - # error). - except: - raise IncorrectLookupParameters() - - # Get the total number of objects, with no filters applied. - real_lookup_params = lookup_params.copy() - del real_lookup_params['order_by'] - if real_lookup_params: - full_result_count = lookup_mod.get_count() - else: - full_result_count = paginator.hits - del real_lookup_params - result_count = paginator.hits - can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED - multi_page = result_count > DEFAULT_RESULTS_PER_PAGE - - # Get the list of objects to display on this page. - if (show_all and can_show_all) or not multi_page: - result_list = lookup_mod.get_list(**lookup_params) - else: +def unquote(s): + """ + Undo the effects of quote(). Based heavily on urllib.unquote(). + """ + mychr = chr + myatoi = int + list = s.split('_') + res = [list[0]] + myappend = res.append + del list[0] + for item in list: + if item[1:2]: try: - result_list = paginator.get_page(page_num) - except InvalidPage: - result_list = [] - (self.result_count, self.full_result_count, self.result_list, - self.can_show_all, self.multi_page, self.paginator) = (result_count, - full_result_count, result_list, can_show_all, multi_page, paginator ) - - def url_for_result(self, result): - return "%s/" % getattr(result, self.pk_attname) - - def get_ordering(self): - lookup_opts, params = self.lookup_opts, self.params - # For ordering, first check the "ordering" parameter in the admin options, - # then check the object's default ordering. If neither of those exist, - # order descending by ID by default. Finally, look for manually-specified - # ordering from the query string. - ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name] - - # Normalize it to new-style ordering. - ordering = meta.handle_legacy_orderlist(ordering) - - if ordering[0].startswith('-'): - order_field, order_type = ordering[0][1:], 'desc' - else: - order_field, order_type = ordering[0], 'asc' - if params.has_key(ORDER_VAR): - try: - try: - f = lookup_opts.get_field(lookup_opts.admin.list_display[int(params[ORDER_VAR])]) - except meta.FieldDoesNotExist: - pass - else: - if not isinstance(f.rel, meta.ManyToOneRel) or not f.null: - order_field = f.name - except (IndexError, ValueError): - pass # Invalid ordering specified. Just use the default. - if params.has_key(ORDER_TYPE_VAR) and params[ORDER_TYPE_VAR] in ('asc', 'desc'): - order_type = params[ORDER_TYPE_VAR] - self.order_field, self.order_type = order_field, order_type - - def get_lookup_params(self): - # Prepare the lookup parameters for the API lookup. - (params, order_field, lookup_opts, order_type, opts, query) = \ - (self.params, self.order_field, self.lookup_opts, self.order_type, self.opts, self.query) - - lookup_params = params.copy() - for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR): - if lookup_params.has_key(i): - del lookup_params[i] - # If the order-by field is a field with a relationship, order by the value - # in the related table. - lookup_order_field = order_field - try: - f = lookup_opts.get_field(order_field) - except meta.FieldDoesNotExist: - pass - else: - if isinstance(lookup_opts.get_field(order_field).rel, meta.ManyToOneRel): - f = lookup_opts.get_field(order_field) - rel_ordering = f.rel.to.ordering and f.rel.to.ordering[0] or f.rel.to.pk.column - lookup_order_field = '%s.%s' % (f.rel.to.db_table, rel_ordering) - # Use select_related if one of the list_display options is a field with a - # relationship. - if lookup_opts.admin.list_select_related: - lookup_params['select_related'] = True + myappend(mychr(myatoi(item[:2], 16)) + + item[2:]) + except ValueError: + myappend('_' + item) else: - for field_name in lookup_opts.admin.list_display: - try: - f = lookup_opts.get_field(field_name) - except meta.FieldDoesNotExist: - pass - else: - if isinstance(f.rel, meta.ManyToOneRel): - lookup_params['select_related'] = True - break - lookup_params['order_by'] = ((order_type == 'desc' and '-' or '') + lookup_order_field,) - if lookup_opts.admin.search_fields and query: - complex_queries = [] - for bit in query.split(): - or_queries = [] - for field_name in lookup_opts.admin.search_fields: - or_queries.append(meta.Q(**{'%s__icontains' % field_name: bit})) - complex_queries.append(reduce(operator.or_, or_queries)) - lookup_params['complex'] = reduce(operator.and_, complex_queries) - if opts.one_to_one_field: - lookup_params.update(opts.one_to_one_field.rel.limit_choices_to) - self.lookup_params = lookup_params - -def change_list(request, app_label, module_name): - try: - cl = ChangeList(request, app_label, module_name) - except IncorrectLookupParameters: - return HttpResponseRedirect(request.path) - - c = Context(request, { - 'title': cl.title, - 'is_popup': cl.is_popup, - 'cl' : cl - }) - c.update({'has_add_permission': c['perms'][app_label][cl.opts.get_add_permission()]}), - return render_to_response(['admin/%s/%s/change_list' % (app_label, cl.opts.object_name.lower()), - 'admin/%s/change_list' % app_label, - 'admin/change_list'], context_instance=c) -change_list = staff_member_required(change_list) - -use_raw_id_admin = lambda field: isinstance(field.rel, (meta.ManyToOneRel, meta.ManyToManyRel)) and field.rel.raw_id_admin + myappend('_' + item) + return "".join(res) -def get_javascript_imports(opts,auto_populated_fields, ordered_objects, field_sets): +def get_javascript_imports(opts, auto_populated_fields, field_sets): # Put in any necessary JavaScript imports. js = ['js/core.js', 'js/admin/RelatedObjectLookups.js'] if auto_populated_fields: js.append('js/urlify.js') - if opts.has_field_type(meta.DateTimeField) or opts.has_field_type(meta.TimeField) or opts.has_field_type(meta.DateField): + if opts.has_field_type(models.DateTimeField) or opts.has_field_type(models.TimeField) or opts.has_field_type(models.DateField): js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js']) - if ordered_objects: + if opts.get_ordered_objects(): js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js']) if opts.admin.js: js.extend(opts.admin.js) @@ -264,29 +96,30 @@ def get_javascript_imports(opts,auto_populated_fields, ordered_objects, field_se for field_set in field_sets: if not seen_collapse and 'collapse' in field_set.classes: seen_collapse = True - js.append('js/admin/CollapsedFieldsets.js' ) + js.append('js/admin/CollapsedFieldsets.js') for field_line in field_set: try: for f in field_line: - if f.rel and isinstance(f, meta.ManyToManyField) and f.rel.filter_interface: + if f.rel and isinstance(f, models.ManyToManyField) and f.rel.filter_interface: js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js']) raise StopIteration except StopIteration: break return js -class AdminBoundField(BoundField): +class AdminBoundField(object): def __init__(self, field, field_mapping, original): - super(AdminBoundField, self).__init__(field, field_mapping, original) - + self.field = field + self.original = original + self.form_fields = [field_mapping[name] for name in self.field.get_manipulator_field_names('')] self.element_id = self.form_fields[0].get_id() - self.has_label_first = not isinstance(self.field, meta.BooleanField) + self.has_label_first = not isinstance(self.field, models.BooleanField) self.raw_id_admin = use_raw_id_admin(field) - self.is_date_time = isinstance(field, meta.DateTimeField) - self.is_file_field = isinstance(field, meta.FileField) - self.needs_add_label = field.rel and isinstance(field.rel, meta.ManyToOneRel) or isinstance(field.rel, meta.ManyToManyRel) and field.rel.to.admin - self.hidden = isinstance(self.field, meta.AutoField) + self.is_date_time = isinstance(field, models.DateTimeField) + self.is_file_field = isinstance(field, models.FileField) + self.needs_add_label = field.rel and isinstance(field.rel, models.ManyToOneRel) or isinstance(field.rel, models.ManyToManyRel) and field.rel.to._meta.admin + self.hidden = isinstance(self.field, models.AutoField) self.first = False classes = [] @@ -298,26 +131,22 @@ class AdminBoundField(BoundField): self.cell_class_attribute = ' class="%s" ' % ' '.join(classes) self._repr_filled = False - def _fetch_existing_display(self, func_name): - class_dict = self.original.__class__.__dict__ - func = class_dict.get(func_name) - return func(self.original) + if field.rel: + self.related_url = '../../../%s/%s/' % (field.rel.to._meta.app_label, field.rel.to._meta.object_name.lower()) - def _fill_existing_display(self): - if getattr(self, '_display_filled', False): - return - # HACK - if isinstance(self.field.rel, meta.ManyToOneRel): - func_name = 'get_%s' % self.field.name - self._display = self._fetch_existing_display(func_name) - elif isinstance(self.field.rel, meta.ManyToManyRel): - func_name = 'get_%s_list' % self.field.rel.singular - self._display = ", ".join([str(obj) for obj in self._fetch_existing_display(func_name)]) - self._display_filled = True + def original_value(self): + if self.original: + return self.original.__dict__[self.field.column] def existing_display(self): - self._fill_existing_display() - return self._display + try: + return self._display + except AttributeError: + if isinstance(self.field.rel, models.ManyToOneRel): + self._display = getattr(self.original, 'get_%s' % self.field.name)() + elif isinstance(self.field.rel, models.ManyToManyRel): + self._display = ", ".join([str(obj) for obj in getattr(self.original, 'get_%s_list' % self.field.rel.singular)()]) + return self._display def __repr__(self): return repr(self.__dict__) @@ -325,94 +154,114 @@ class AdminBoundField(BoundField): def html_error_list(self): return " ".join([form_field.html_error_list() for form_field in self.form_fields if form_field.errors]) -class AdminBoundFieldLine(BoundFieldLine): + def original_url(self): + if self.is_file_field and self.original and self.field.attname: + url_method = getattr(self.original, 'get_%s_url' % self.field.attname) + if callable(url_method): + return url_method() + return '' + +class AdminBoundFieldLine(object): def __init__(self, field_line, field_mapping, original): - super(AdminBoundFieldLine, self).__init__(field_line, field_mapping, original, AdminBoundField) + self.bound_fields = [field.bind(field_mapping, original, AdminBoundField) for field in field_line] for bound_field in self: bound_field.first = True break -class AdminBoundFieldSet(BoundFieldSet): - def __init__(self, field_set, field_mapping, original): - super(AdminBoundFieldSet, self).__init__(field_set, field_mapping, original, AdminBoundFieldLine) - -class BoundManipulator(object): - def __init__(self, opts, manipulator, field_mapping): - self.inline_related_objects = opts.get_followed_related_objects(manipulator.follow) - self.original = hasattr(manipulator, 'original_object') and manipulator.original_object or None - self.bound_field_sets = [field_set.bind(field_mapping, self.original, AdminBoundFieldSet) - for field_set in opts.admin.get_field_sets(opts)] - self.ordered_objects = opts.get_ordered_objects()[:] - -class AdminBoundManipulator(BoundManipulator): - def __init__(self, opts, manipulator, field_mapping): - super(AdminBoundManipulator, self).__init__(opts, manipulator, field_mapping) - field_sets = opts.admin.get_field_sets(opts) - - self.auto_populated_fields = [f for f in opts.fields if f.prepopulate_from] - self.javascript_imports = get_javascript_imports(opts, self.auto_populated_fields, self.ordered_objects, field_sets); - - self.coltype = self.ordered_objects and 'colMS' or 'colM' - self.has_absolute_url = hasattr(opts.get_model_module().Klass, 'get_absolute_url') - self.form_enc_attrib = opts.has_field_type(meta.FileField) and \ - 'enctype="multipart/form-data" ' or '' + def __iter__(self): + for bound_field in self.bound_fields: + yield bound_field - self.first_form_field_id = self.bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id(); - self.ordered_object_pk_names = [o.pk.name for o in self.ordered_objects] + def __len__(self): + return len(self.bound_fields) - self.save_on_top = opts.admin.save_on_top - self.save_as = opts.admin.save_as +class AdminBoundFieldSet(object): + def __init__(self, field_set, field_mapping, original): + self.name = field_set.name + self.classes = field_set.classes + self.description = field_set.description + self.bound_field_lines = [field_line.bind(field_mapping, original, AdminBoundFieldLine) for field_line in field_set] - self.content_type_id = opts.get_content_type_id() - self.verbose_name_plural = opts.verbose_name_plural - self.verbose_name = opts.verbose_name - self.object_name = opts.object_name + def __iter__(self): + for bound_field_line in self.bound_field_lines: + yield bound_field_line - def get_ordered_object_pk(self, ordered_obj): - for name in self.ordered_object_pk_names: - if hasattr(ordered_obj, name): - return str(getattr(ordered_obj, name)) - return "" + def __len__(self): + return len(self.bound_field_lines) -def render_change_form(opts, manipulator, app_label, context, add=False, change=False, show_delete=False, form_url=''): +def render_change_form(model, manipulator, context, add=False, change=False, form_url=''): + opts = model._meta + app_label = opts.app_label + auto_populated_fields = [f for f in opts.fields if f.prepopulate_from] + field_sets = opts.admin.get_field_sets(opts) + original = getattr(manipulator, 'original_object', None) + bound_field_sets = [field_set.bind(context['form'], original, AdminBoundFieldSet) for field_set in field_sets] + first_form_field_id = bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id(); + ordered_objects = opts.get_ordered_objects() + inline_related_objects = opts.get_followed_related_objects(manipulator.follow) extra_context = { 'add': add, 'change': change, - 'bound_manipulator': AdminBoundManipulator(opts, manipulator, context['form']), 'has_delete_permission': context['perms'][app_label][opts.get_delete_permission()], + 'has_change_permission': context['perms'][app_label][opts.get_change_permission()], + 'has_file_field': opts.has_field_type(models.FileField), + 'has_absolute_url': hasattr(model, 'get_absolute_url'), + 'auto_populated_fields': auto_populated_fields, + 'bound_field_sets': bound_field_sets, + 'first_form_field_id': first_form_field_id, + 'javascript_imports': get_javascript_imports(opts, auto_populated_fields, field_sets), + 'ordered_objects': ordered_objects, + 'inline_related_objects': inline_related_objects, 'form_url': form_url, - 'app_label': app_label, + 'opts': opts, + 'content_type_id': ContentType.objects.get_for_model(model).id, } context.update(extra_context) - return render_to_response(["admin/%s/%s/change_form" % (app_label, opts.object_name.lower() ), - "admin/%s/change_form" % app_label , - "admin/change_form"], context_instance=context) + return render_to_response([ + "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()), + "admin/%s/change_form.html" % app_label, + "admin/change_form.html"], context_instance=context) + +def index(request): + return render_to_response('admin/index.html', {'title': _('Site administration')}, context_instance=template.RequestContext(request)) +index = staff_member_required(never_cache(index)) -def log_add_message(user, opts,manipulator,new_object): - pk_value = getattr(new_object, opts.pk.attname) - log.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), log.ADDITION) +def add_stage(request, app_label, model_name, show_delete=False, form_url='', post_url=None, post_url_continue='../%s/', object_id_override=None): + model = models.get_model(app_label, model_name) + if model is None: + raise Http404, "App %r, model %r, not found" % (app_label, model_name) + opts = model._meta -def add_stage(request, app_label, module_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None): - mod, opts = _get_mod_opts(app_label, module_name) if not request.user.has_perm(app_label + '.' + opts.get_add_permission()): raise PermissionDenied - manipulator = mod.AddManipulator() + + if post_url is None: + if request.user.has_perm(app_label + '.' + opts.get_change_permission()): + # redirect to list view + post_url = '../' + else: + # Object list will give 'Permission Denied', so go back to admin home + post_url = '../../../' + + manipulator = model.AddManipulator() if request.POST: new_data = request.POST.copy() - if opts.has_field_type(meta.FileField): + + if opts.has_field_type(models.FileField): new_data.update(request.FILES) + errors = manipulator.get_validation_errors(new_data) manipulator.do_html2python(new_data) - if not errors and not request.POST.has_key("_preview"): + if not errors: new_object = manipulator.save(new_data) - log_add_message(request.user, opts,manipulator,new_object) - msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name':opts.verbose_name, 'obj':new_object} - pk_value = getattr(new_object,opts.pk.attname) + pk_value = new_object._get_pk_val() + LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, str(new_object), ADDITION) + msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': opts.verbose_name, 'obj': new_object} # Here, we distinguish between different save types by checking for # the presence of keys in request.POST. if request.POST.has_key("_continue"): - request.user.add_message(msg + ' ' + _("You may edit it again below.")) + request.user.message_set.create(message=msg + ' ' + _("You may edit it again below.")) if request.POST.has_key("_popup"): post_url_continue += "?_popup=1" return HttpResponseRedirect(post_url_continue % pk_value) @@ -420,10 +269,10 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, %s, "%s");</script>' % \ (pk_value, str(new_object).replace('"', '\\"'))) elif request.POST.has_key("_addanother"): - request.user.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name)) + request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name)) return HttpResponseRedirect(request.path) else: - request.user.add_message(msg) + request.user.message_set.create(message=msg) return HttpResponseRedirect(post_url) else: # Add default data. @@ -435,73 +284,80 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p errors = {} # Populate the FormWrapper. - form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline=True) + form = forms.FormWrapper(manipulator, new_data, errors) - c = Context(request, { + c = template.RequestContext(request, { 'title': _('Add %s') % opts.verbose_name, 'form': form, 'is_popup': request.REQUEST.has_key('_popup'), 'show_delete': show_delete, }) + if object_id_override is not None: c['object_id'] = object_id_override - return render_change_form(opts, manipulator, app_label, c, add=True) -add_stage = staff_member_required(add_stage) + return render_change_form(model, manipulator, c, add=True) +add_stage = staff_member_required(never_cache(add_stage)) -def log_change_message(user, opts,manipulator,new_object): - pk_value = getattr(new_object, opts.pk.column) - # Construct the change message. - change_message = [] - if manipulator.fields_added: - change_message.append(_('Added %s.') % get_text_list(manipulator.fields_added, _('and'))) - if manipulator.fields_changed: - change_message.append(_('Changed %s.') % get_text_list(manipulator.fields_changed, _('and'))) - if manipulator.fields_deleted: - change_message.append(_('Deleted %s.') % get_text_list(manipulator.fields_deleted, _('and'))) - change_message = ' '.join(change_message) - if not change_message: - change_message = _('No fields changed.') - log.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), log.CHANGE, change_message) +def change_stage(request, app_label, model_name, object_id): + model = models.get_model(app_label, model_name) + object_id = unquote(object_id) + if model is None: + raise Http404, "App %r, model %r, not found" % (app_label, model_name) + opts = model._meta -def change_stage(request, app_label, module_name, object_id): - mod, opts = _get_mod_opts(app_label, module_name) if not request.user.has_perm(app_label + '.' + opts.get_change_permission()): raise PermissionDenied + if request.POST and request.POST.has_key("_saveasnew"): - return add_stage(request, app_label, module_name, form_url='../add/') + return add_stage(request, app_label, model_name, form_url='../../add/') + try: - manipulator = mod.ChangeManipulator(object_id) + manipulator = model.ChangeManipulator(object_id) except ObjectDoesNotExist: raise Http404 if request.POST: new_data = request.POST.copy() - if opts.has_field_type(meta.FileField): + + if opts.has_field_type(models.FileField): new_data.update(request.FILES) errors = manipulator.get_validation_errors(new_data) - manipulator.do_html2python(new_data) - if not errors and not request.POST.has_key("_preview"): + + if not errors: new_object = manipulator.save(new_data) - log_change_message(request.user,opts,manipulator,new_object) - msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj':new_object} - pk_value = getattr(new_object,opts.pk.attname) + pk_value = new_object._get_pk_val() + + # Construct the change message. + change_message = [] + if manipulator.fields_added: + change_message.append(_('Added %s.') % get_text_list(manipulator.fields_added, _('and'))) + if manipulator.fields_changed: + change_message.append(_('Changed %s.') % get_text_list(manipulator.fields_changed, _('and'))) + if manipulator.fields_deleted: + change_message.append(_('Deleted %s.') % get_text_list(manipulator.fields_deleted, _('and'))) + change_message = ' '.join(change_message) + if not change_message: + change_message = _('No fields changed.') + LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, str(new_object), CHANGE, change_message) + + msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj': new_object} if request.POST.has_key("_continue"): - request.user.add_message(msg + ' ' + _("You may edit it again below.")) + request.user.message_set.create(message=msg + ' ' + _("You may edit it again below.")) if request.REQUEST.has_key('_popup'): return HttpResponseRedirect(request.path + "?_popup=1") else: return HttpResponseRedirect(request.path) elif request.POST.has_key("_saveasnew"): - request.user.add_message(_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object}) + request.user.message_set.create(message=_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object}) return HttpResponseRedirect("../%s/" % pk_value) elif request.POST.has_key("_addanother"): - request.user.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name)) + request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name)) return HttpResponseRedirect("../add/") else: - request.user.add_message(msg) + request.user.message_set.create(message=msg) return HttpResponseRedirect("../") else: # Populate new_data with a "flattened" version of the current data. @@ -519,7 +375,7 @@ def change_stage(request, app_label, module_name, object_id): errors = {} # Populate the FormWrapper. - form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline = True) + form = forms.FormWrapper(manipulator, new_data, errors) form.original = manipulator.original_object form.order_objects = [] @@ -528,20 +384,19 @@ def change_stage(request, app_label, module_name, object_id): wrt = related.opts.order_with_respect_to if wrt and wrt.rel and wrt.rel.to == opts: func = getattr(manipulator.original_object, 'get_%s_list' % - related.get_method_name_part()) + related.get_accessor_name()) orig_list = func() form.order_objects.extend(orig_list) - c = Context(request, { + c = template.RequestContext(request, { 'title': _('Change %s') % opts.verbose_name, 'form': form, 'object_id': object_id, 'original': manipulator.original_object, - 'is_popup' : request.REQUEST.has_key('_popup') + 'is_popup': request.REQUEST.has_key('_popup'), }) - - return render_change_form(opts,manipulator, app_label, c, change=True) -change_stage = staff_member_required(change_stage) + return render_change_form(model, manipulator, c, change=True) +change_stage = staff_member_required(never_cache(change_stage)) def _nest_help(obj, depth, val): current = obj @@ -559,10 +414,10 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current if related.opts in opts_seen: continue opts_seen.append(related.opts) - rel_opts_name = related.get_method_name_part() - if isinstance(related.field.rel, meta.OneToOneRel): + rel_opts_name = related.get_accessor_name() + if isinstance(related.field.rel, models.OneToOneRel): try: - sub_obj = getattr(obj, 'get_%s' % rel_opts_name)() + sub_obj = getattr(obj, rel_opts_name) except ObjectDoesNotExist: pass else: @@ -579,12 +434,12 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current else: # Display a link to the admin page. nh(deleted_objects, current_depth, ['%s: <a href="../../../../%s/%s/%s/">%s</a>' % \ - (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.module_name, - getattr(sub_obj, related.opts.pk.attname), sub_obj), []]) + (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.object_name.lower(), + sub_obj._get_pk_val(), sub_obj), []]) _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2) else: has_related_objs = False - for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)(): + for sub_obj in getattr(obj, rel_opts_name).all(): has_related_objs = True if related.field.rel.edit_inline or not related.opts.admin: # Don't display link to edit, because it either has no @@ -593,7 +448,7 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current else: # Display a link to the admin page. nh(deleted_objects, current_depth, ['%s: <a href="../../../../%s/%s/%s/">%s</a>' % \ - (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.module_name, getattr(sub_obj, related.opts.pk.attname), escape(str(sub_obj))), []]) + (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(str(sub_obj))), []]) _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2) # If there were related objects, and the user doesn't have # permission to delete them, add the missing perm to perms_needed. @@ -605,21 +460,21 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current if related.opts in opts_seen: continue opts_seen.append(related.opts) - rel_opts_name = related.get_method_name_part() + rel_opts_name = related.get_accessor_name() has_related_objs = False - for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)(): + for sub_obj in getattr(obj, rel_opts_name).all(): has_related_objs = True if related.field.rel.edit_inline or not related.opts.admin: # Don't display link to edit, because it either has no # admin or is edited inline. nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \ - {'fieldname': related.field.name, 'name': related.opts.verbose_name, 'obj': escape(str(sub_obj))}, []]) + {'fieldname': related.field.verbose_name, 'name': related.opts.verbose_name, 'obj': escape(str(sub_obj))}, []]) else: # Display a link to the admin page. nh(deleted_objects, current_depth, [ - (_('One or more %(fieldname)s in %(name)s:') % {'fieldname': related.field.name, 'name':related.opts.verbose_name}) + \ + (_('One or more %(fieldname)s in %(name)s:') % {'fieldname': related.field.verbose_name, 'name':related.opts.verbose_name}) + \ (' <a href="../../../../%s/%s/%s/">%s</a>' % \ - (related.opts.app_label, related.opts.module_name, getattr(sub_obj, related.opts.pk.attname), escape(str(sub_obj)))), []]) + (related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(str(sub_obj)))), []]) # If there were related objects, and the user doesn't have # permission to change them, add the missing perm to perms_needed. if related.opts.admin and has_related_objs: @@ -627,12 +482,16 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current if not user.has_perm(p): perms_needed.add(related.opts.verbose_name) -def delete_stage(request, app_label, module_name, object_id): +def delete_stage(request, app_label, model_name, object_id): import sets - mod, opts = _get_mod_opts(app_label, module_name) + model = models.get_model(app_label, model_name) + object_id = unquote(object_id) + if model is None: + raise Http404, "App %r, model %r, not found" % (app_label, model_name) + opts = model._meta if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()): raise PermissionDenied - obj = get_object_or_404(mod, pk=object_id) + obj = get_object_or_404(model, pk=object_id) # Populate deleted_objects, a data structure of all related objects that # will also be deleted. @@ -645,28 +504,240 @@ def delete_stage(request, app_label, module_name, object_id): raise PermissionDenied obj_display = str(obj) obj.delete() - log.log_action(request.user.id, opts.get_content_type_id(), object_id, obj_display, log.DELETION) - request.user.add_message(_('The %(name)s "%(obj)s" was deleted successfully.') % {'name':opts.verbose_name, 'obj':obj_display}) + LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, object_id, obj_display, DELETION) + request.user.message_set.create(message=_('The %(name)s "%(obj)s" was deleted successfully.') % {'name': opts.verbose_name, 'obj': obj_display}) return HttpResponseRedirect("../../") - return render_to_response('admin/delete_confirmation', { + extra_context = { "title": _("Are you sure?"), "object_name": opts.verbose_name, "object": obj, "deleted_objects": deleted_objects, "perms_lacking": perms_needed, - }, context_instance=Context(request)) -delete_stage = staff_member_required(delete_stage) + "opts": model._meta, + } + return render_to_response(["admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower() ), + "admin/%s/delete_confirmation.html" % app_label , + "admin/delete_confirmation.html"], extra_context, context_instance=template.RequestContext(request)) +delete_stage = staff_member_required(never_cache(delete_stage)) -def history(request, app_label, module_name, object_id): - mod, opts = _get_mod_opts(app_label, module_name) - action_list = log.get_list(object_id__exact=object_id, content_type__id__exact=opts.get_content_type_id(), - order_by=("action_time",), select_related=True) +def history(request, app_label, model_name, object_id): + model = models.get_model(app_label, model_name) + object_id = unquote(object_id) + if model is None: + raise Http404, "App %r, model %r, not found" % (app_label, model_name) + action_list = LogEntry.objects.filter(object_id=object_id, + content_type__id__exact=ContentType.objects.get_for_model(model).id).select_related().order_by('action_time') # If no history was found, see whether this object even exists. - obj = get_object_or_404(mod, pk=object_id) - return render_to_response('admin/object_history', { + obj = get_object_or_404(model, pk=object_id) + extra_context = { 'title': _('Change history: %s') % obj, 'action_list': action_list, - 'module_name': capfirst(opts.verbose_name_plural), + 'module_name': capfirst(model._meta.verbose_name_plural), 'object': obj, - }, context_instance=Context(request)) -history = staff_member_required(history) + } + return render_to_response(["admin/%s/%s/object_history.html" % (app_label, model._meta.object_name.lower()), + "admin/%s/object_history.html" % app_label , + "admin/object_history.html"], extra_context, context_instance=template.RequestContext(request)) +history = staff_member_required(never_cache(history)) + +class ChangeList(object): + def __init__(self, request, model): + self.model = model + self.opts = model._meta + self.lookup_opts = self.opts + self.manager = self.opts.admin.manager + + # Get search parameters from the query string. + try: + self.page_num = int(request.GET.get(PAGE_VAR, 0)) + except ValueError: + self.page_num = 0 + self.show_all = request.GET.has_key(ALL_VAR) + self.is_popup = request.GET.has_key(IS_POPUP_VAR) + self.params = dict(request.GET.items()) + if self.params.has_key(PAGE_VAR): + del self.params[PAGE_VAR] + + self.order_field, self.order_type = self.get_ordering() + self.query = request.GET.get(SEARCH_VAR, '') + self.query_set = self.get_query_set() + self.get_results(request) + self.title = (self.is_popup and _('Select %s') % self.opts.verbose_name or _('Select %s to change') % self.opts.verbose_name) + self.filter_specs, self.has_filters = self.get_filters(request) + self.pk_attname = self.lookup_opts.pk.attname + + def get_filters(self, request): + filter_specs = [] + if self.lookup_opts.admin.list_filter and not self.opts.one_to_one_field: + filter_fields = [self.lookup_opts.get_field(field_name) \ + for field_name in self.lookup_opts.admin.list_filter] + for f in filter_fields: + spec = FilterSpec.create(f, request, self.params) + if spec and spec.has_output(): + filter_specs.append(spec) + return filter_specs, bool(filter_specs) + + def get_query_string(self, new_params={}, remove=[]): + p = self.params.copy() + for r in remove: + for k in p.keys(): + if k.startswith(r): + del p[k] + for k, v in new_params.items(): + if p.has_key(k) and v is None: + del p[k] + elif v is not None: + p[k] = v + return '?' + '&'.join(['%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20') + + def get_results(self, request): + paginator = ObjectPaginator(self.query_set, self.lookup_opts.admin.list_per_page) + + # Get the number of objects, with admin filters applied. + try: + result_count = paginator.hits + # Naked except! Because we don't have any other way of validating + # "params". They might be invalid if the keyword arguments are + # incorrect, or if the values are not in the correct type (which would + # result in a database error). + except: + raise IncorrectLookupParameters + + # Get the total number of objects, with no admin filters applied. + # Perform a slight optimization: Check to see whether any filters were + # given. If not, use paginator.hits to calculate the number of objects, + # because we've already done paginator.hits and the value is cached. + if isinstance(self.query_set._filters, models.Q) and not self.query_set._filters.kwargs: + full_result_count = result_count + else: + full_result_count = self.manager.count() + + can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED + multi_page = result_count > self.lookup_opts.admin.list_per_page + + # Get the list of objects to display on this page. + if (self.show_all and can_show_all) or not multi_page: + result_list = list(self.query_set) + else: + try: + result_list = paginator.get_page(self.page_num) + except InvalidPage: + result_list = () + + self.result_count = result_count + self.full_result_count = full_result_count + self.result_list = result_list + self.can_show_all = can_show_all + self.multi_page = multi_page + self.paginator = paginator + + def get_ordering(self): + lookup_opts, params = self.lookup_opts, self.params + # For ordering, first check the "ordering" parameter in the admin options, + # then check the object's default ordering. If neither of those exist, + # order descending by ID by default. Finally, look for manually-specified + # ordering from the query string. + ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name] + + # Normalize it to new-style ordering. + ordering = handle_legacy_orderlist(ordering) + + if ordering[0].startswith('-'): + order_field, order_type = ordering[0][1:], 'desc' + else: + order_field, order_type = ordering[0], 'asc' + if params.has_key(ORDER_VAR): + try: + try: + f = lookup_opts.get_field(lookup_opts.admin.list_display[int(params[ORDER_VAR])]) + except models.FieldDoesNotExist: + pass + else: + if not isinstance(f.rel, models.ManyToOneRel) or not f.null: + order_field = f.name + except (IndexError, ValueError): + pass # Invalid ordering specified. Just use the default. + if params.has_key(ORDER_TYPE_VAR) and params[ORDER_TYPE_VAR] in ('asc', 'desc'): + order_type = params[ORDER_TYPE_VAR] + return order_field, order_type + + def get_query_set(self): + qs = self.manager.get_query_set() + lookup_params = self.params.copy() # a dictionary of the query string + for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR): + if lookup_params.has_key(i): + del lookup_params[i] + + # Apply lookup parameters from the query string. + qs = qs.filter(**lookup_params) + + # Use select_related() if one of the list_display options is a field + # with a relationship. + if self.lookup_opts.admin.list_select_related: + qs = qs.select_related() + else: + for field_name in self.lookup_opts.admin.list_display: + try: + f = self.lookup_opts.get_field(field_name) + except models.FieldDoesNotExist: + pass + else: + if isinstance(f.rel, models.ManyToOneRel): + qs = qs.select_related() + break + + # Calculate lookup_order_field. + # If the order-by field is a field with a relationship, order by the + # value in the related table. + lookup_order_field = self.order_field + try: + f = self.lookup_opts.get_field(self.order_field, many_to_many=False) + except models.FieldDoesNotExist: + pass + else: + if isinstance(f.rel, models.OneToOneRel): + # For OneToOneFields, don't try to order by the related object's ordering criteria. + pass + elif isinstance(f.rel, models.ManyToOneRel): + rel_ordering = f.rel.to._meta.ordering and f.rel.to._meta.ordering[0] or f.rel.to._meta.pk.column + lookup_order_field = '%s.%s' % (f.rel.to._meta.db_table, rel_ordering) + + # Set ordering. + qs = qs.order_by((self.order_type == 'desc' and '-' or '') + lookup_order_field) + + # Apply keyword searches. + if self.lookup_opts.admin.search_fields and self.query: + for bit in self.query.split(): + or_queries = [models.Q(**{'%s__icontains' % field_name: bit}) for field_name in self.lookup_opts.admin.search_fields] + other_qs = QuerySet(self.model) + other_qs = other_qs.filter(reduce(operator.or_, or_queries)) + qs = qs & other_qs + + if self.opts.one_to_one_field: + qs = qs.filter(**self.opts.one_to_one_field.rel.limit_choices_to) + + return qs + + def url_for_result(self, result): + return "%s/" % quote(getattr(result, self.pk_attname)) + +def change_list(request, app_label, model_name): + model = models.get_model(app_label, model_name) + if model is None: + raise Http404, "App %r, model %r, not found" % (app_label, model_name) + if not request.user.has_perm(app_label + '.' + model._meta.get_change_permission()): + raise PermissionDenied + try: + cl = ChangeList(request, model) + except IncorrectLookupParameters: + return HttpResponseRedirect(request.path) + c = template.RequestContext(request, { + 'title': cl.title, + 'is_popup': cl.is_popup, + 'cl': cl, + }) + c.update({'has_add_permission': c['perms'][app_label][cl.opts.get_add_permission()]}), + return render_to_response(['admin/%s/%s/change_list.html' % (app_label, cl.opts.object_name.lower()), + 'admin/%s/change_list.html' % app_label, + 'admin/change_list.html'], context_instance=c) +change_list = staff_member_required(never_cache(change_list)) diff --git a/django/contrib/admin/views/template.py b/django/contrib/admin/views/template.py index 3effd57c10..f73b9e4218 100644 --- a/django/contrib/admin/views/template.py +++ b/django/contrib/admin/views/template.py @@ -1,9 +1,9 @@ from django.contrib.admin.views.decorators import staff_member_required -from django.core import formfields, validators -from django.core import template -from django.core.template import loader -from django.core.extensions import DjangoContext, render_to_response -from django.models.core import sites +from django.core import validators +from django import template, forms +from django.template import loader +from django.shortcuts import render_to_response +from django.contrib.sites.models import Site from django.conf import settings def template_validator(request): @@ -23,19 +23,19 @@ def template_validator(request): errors = manipulator.get_validation_errors(new_data) if not errors: request.user.add_message('The template is valid.') - return render_to_response('admin/template_validator', { + return render_to_response('admin/template_validator.html', { 'title': 'Template validator', - 'form': formfields.FormWrapper(manipulator, new_data, errors), - }, context_instance=DjangoContext(request)) + 'form': forms.FormWrapper(manipulator, new_data, errors), + }, context_instance=template.RequestContext(request)) template_validator = staff_member_required(template_validator) -class TemplateValidator(formfields.Manipulator): +class TemplateValidator(forms.Manipulator): def __init__(self, settings_modules): self.settings_modules = settings_modules - site_list = sites.get_in_bulk(settings_modules.keys()).values() + site_list = Site.objects.get_in_bulk(settings_modules.keys()).values() self.fields = ( - formfields.SelectField('site', is_required=True, choices=[(s.id, s.name) for s in site_list]), - formfields.LargeTextField('template', is_required=True, rows=25, validator_list=[self.isValidTemplate]), + forms.SelectField('site', is_required=True, choices=[(s.id, s.name) for s in site_list]), + forms.LargeTextField('template', is_required=True, rows=25, validator_list=[self.isValidTemplate]), ) def isValidTemplate(self, field_data, all_data): |
