Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

gis: Merged revisions 7921,7926-7928,7938-7941,7945-7947,7949-7950,79…

…52,7955-7956,7961,7964-7968,7970-7978 via svnmerge from trunk.

This includes the newforms-admin branch, and thus is backwards-incompatible.  The geographic admin is _not_ in this changeset, and is forthcoming.


git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@7979 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 149e731c3c5a2cc96b7d3c72070401df6c2a238e 1 parent 5bf3565
@jbronn jbronn authored
Showing with 9,426 additions and 3,601 deletions.
  1. +10 −0 AUTHORS
  2. +7 −1 django/conf/project_template/urls.py
  3. +16 −0 django/contrib/admin/__init__.py
  4. +14 −14 django/contrib/admin/filterspecs.py
  5. +21 −0 django/contrib/admin/media/css/forms.css
  6. +0 −81 django/contrib/admin/media/js/SelectFilter.js
  7. +1 −1  django/contrib/admin/media/js/admin/CollapsedFieldsets.js
  8. +2 −2 django/contrib/admin/media/js/admin/RelatedObjectLookups.js
  9. +2 −1  django/contrib/admin/models.py
  10. +795 −0 django/contrib/admin/options.py
  11. +349 −0 django/contrib/admin/sites.py
  12. +9 −4 django/contrib/admin/templates/admin/auth/user/add_form.html
  13. +7 −6 django/contrib/admin/templates/admin/auth/user/change_password.html
  14. +1 −8 django/contrib/admin/templates/admin/base.html
  15. +39 −30 django/contrib/admin/templates/admin/change_form.html
  16. +17 −1 django/contrib/admin/templates/admin/change_list.html
  17. +2 −0  django/contrib/admin/templates/admin/delete_confirmation.html
  18. +26 −0 django/contrib/admin/templates/admin/edit_inline/stacked.html
  19. +64 −0 django/contrib/admin/templates/admin/edit_inline/tabular.html
  20. +0 −16 django/contrib/admin/templates/admin/edit_inline_stacked.html
  21. +0 −44 django/contrib/admin/templates/admin/edit_inline_tabular.html
  22. +0 −10 django/contrib/admin/templates/admin/field_line.html
  23. +0 −7 django/contrib/admin/templates/admin/filters.html
  24. +17 −0 django/contrib/admin/templates/admin/includes/fieldset.html
  25. +4 −3 django/contrib/admin/templates/admin/index.html
  26. +0 −2  django/contrib/admin/templates/admin/invalid_setup.html
  27. +3 −1 django/contrib/admin/templates/admin/login.html
  28. +1 −7 django/contrib/admin/templates/admin/object_history.html
  29. +1 −1  django/contrib/admin/templates/admin/search_form.html
  30. +1 −0  django/contrib/admin/templates/admin_doc/index.html
  31. +1 −0  django/contrib/admin/templates/admin_doc/view_index.html
  32. +1 −0  django/contrib/admin/templates/registration/password_change_done.html
  33. +1 −0  django/contrib/admin/templates/registration/password_change_form.html
  34. +1 −1  django/contrib/admin/templates/registration/password_reset_form.html
  35. +0 −5 django/contrib/admin/templates/widget/date_time.html
  36. +0 −1  django/contrib/admin/templates/widget/default.html
  37. +0 −4 django/contrib/admin/templates/widget/file.html
  38. +0 −20 django/contrib/admin/templates/widget/foreign.html
  39. +0 −1  django/contrib/admin/templates/widget/many_to_many.html
  40. +0 −2  django/contrib/admin/templates/widget/one_to_one.html
  41. +7 −11 django/contrib/admin/templatetags/admin_list.py
  42. +4 −236 django/contrib/admin/templatetags/admin_modify.py
  43. +0 −81 django/contrib/admin/templatetags/adminapplist.py
  44. +0 −43 django/contrib/admin/urls.py
  45. +139 −0 django/contrib/admin/util.py
  46. +280 −0 django/contrib/admin/validation.py
  47. +0 −78 django/contrib/admin/views/auth.py
  48. +0 −1  django/contrib/admin/views/decorators.py
  49. +40 −582 django/contrib/admin/views/main.py
  50. +211 −0 django/contrib/admin/widgets.py
  51. 0  {tests/regressiontests/invalid_admin_options → django/contrib/admindocs}/__init__.py
  52. +15 −0 django/contrib/admindocs/urls.py
  53. 0  django/contrib/{admin → admindocs}/utils.py
  54. +38 −14 django/contrib/{admin/views/doc.py → admindocs/views.py}
  55. +66 −0 django/contrib/auth/admin.py
  56. +140 −95 django/contrib/auth/forms.py
  57. +4 −21 django/contrib/auth/models.py
  58. +8 −0 django/contrib/auth/tests/__init__.py
  59. +5 −3 django/contrib/auth/{tests.py → tests/basic.py}
  60. +135 −0 django/contrib/auth/tests/forms.py
  61. +61 −30 django/contrib/auth/views.py
  62. +30 −0 django/contrib/comments/admin.py
  63. +2 −24 django/contrib/comments/models.py
  64. +50 −2 django/contrib/comments/views/comments.py
  65. +5 −3 django/contrib/databrowse/plugins/calendars.py
  66. +15 −0 django/contrib/flatpages/admin.py
  67. +1 −10 django/contrib/flatpages/models.py
  68. +1 −1  django/contrib/formtools/tests.py
  69. +1 −1  django/contrib/formtools/wizard.py
  70. +2 −2 django/contrib/localflavor/ar/forms.py
  71. +3 −3 django/contrib/localflavor/au/forms.py
  72. +2 −2 django/contrib/localflavor/br/forms.py
  73. +3 −3 django/contrib/localflavor/ca/forms.py
  74. +2 −2 django/contrib/localflavor/ch/forms.py
  75. +2 −2 django/contrib/localflavor/cl/forms.py
  76. +2 −2 django/contrib/localflavor/de/forms.py
  77. +2 −2 django/contrib/localflavor/es/forms.py
  78. +2 −2 django/contrib/localflavor/fi/forms.py
  79. +2 −2 django/contrib/localflavor/fr/forms.py
  80. +1 −1  django/contrib/localflavor/generic/forms.py
  81. +2 −2 django/contrib/localflavor/in_/forms.py
  82. +3 −3 django/contrib/localflavor/is_/forms.py
  83. +2 −2 django/contrib/localflavor/it/forms.py
  84. +2 −2 django/contrib/localflavor/jp/forms.py
  85. +1 −1  django/contrib/localflavor/mx/forms.py
  86. +2 −2 django/contrib/localflavor/nl/forms.py
  87. +2 −2 django/contrib/localflavor/no/forms.py
  88. +2 −2 django/contrib/localflavor/pe/forms.py
  89. +2 −2 django/contrib/localflavor/pl/forms.py
  90. +1 −1  django/contrib/localflavor/sk/forms.py
  91. +2 −2 django/contrib/localflavor/uk/forms.py
  92. +2 −2 django/contrib/localflavor/us/forms.py
  93. +2 −2 django/contrib/localflavor/za/forms.py
  94. +17 −7 django/contrib/redirects/models.py
  95. +9 −0 django/contrib/sites/admin.py
  96. +2 −5 django/contrib/sites/models.py
  97. +30 −16 django/core/handlers/base.py
  98. +0 −62 django/core/management/validation.py
  99. +3 −1 django/core/serializers/base.py
  100. +6 −3 django/core/serializers/json.py
  101. +3 −7 django/core/validators.py
  102. +1 −0  django/db/backends/__init__.py
  103. +0 −1  django/db/backends/mysql/creation.py
  104. +0 −215 django/db/backends/mysql_old/base.py
  105. +0 −14 django/db/backends/mysql_old/client.py
  106. +0 −29 django/db/backends/mysql_old/creation.py
  107. +0 −96 django/db/backends/mysql_old/introspection.py
  108. +0 −1  django/db/backends/oracle/creation.py
  109. +0 −1  django/db/backends/postgresql/creation.py
  110. +8 −0 django/db/backends/sqlite3/base.py
  111. +0 −1  django/db/backends/sqlite3/creation.py
  112. +1 −1  django/db/models/__init__.py
  113. +4 −11 django/db/models/base.py
  114. +39 −33 django/db/models/fields/__init__.py
  115. +22 −57 django/db/models/fields/related.py
  116. +3 −6 django/db/models/manipulators.py
  117. +0 −75 django/db/models/options.py
  118. +3 −4 django/db/models/query.py
  119. +8 −2 django/db/models/sql/query.py
  120. +17 −1 django/forms/__init__.py
  121. 0  django/{newforms → forms}/extras/__init__.py
  122. +1 −1  django/{newforms → forms}/extras/widgets.py
  123. +17 −8 django/{newforms → forms}/fields.py
  124. +48 −3 django/{newforms → forms}/forms.py
  125. +292 −0 django/forms/formsets.py
  126. +215 −7 django/{newforms → forms}/models.py
  127. 0  django/{newforms → forms}/util.py
  128. +198 −24 django/{newforms → forms}/widgets.py
  129. +7 −17 django/newforms/__init__.py
  130. +4 −2 django/test/utils.py
  131. +89 −0 django/utils/datetime_safe.py
  132. +11 −3 django/utils/tzinfo.py
  133. +202 −169 django/views/debug.py
  134. +3 −0  django/views/generic/__init__.py
  135. +139 −113 django/views/generic/create_update.py
  136. +678 −0 docs/admin.txt
  137. +22 −22 docs/authentication.txt
  138. +5 −0 docs/contenttypes.txt
  139. +0 −1  docs/custom_model_fields.txt
  140. +48 −26 docs/generic_views.txt
  141. +23 −478 docs/model-api.txt
  142. +122 −0 docs/modelforms.txt
  143. +648 −2 docs/newforms.txt
  144. +6 −6 docs/settings.txt
  145. +82 −81 docs/tutorial02.txt
  146. +1 −1  docs/upload_handling.txt
  147. +1 −1  tests/modeltests/fixtures/models.py
  148. +1 −3 tests/modeltests/invalid_models/models.py
  149. +1 −1  tests/modeltests/lookup/models.py
  150. +1 −1  tests/modeltests/many_to_one/models.py
  151. +23 −5 tests/modeltests/model_forms/models.py
  152. 0  {django/db/backends/mysql_old → tests/modeltests/model_formsets}/__init__.py
  153. +324 −0 tests/modeltests/model_formsets/models.py
  154. +2 −2 tests/modeltests/test_client/views.py
  155. +1 −1  tests/modeltests/transactions/models.py
  156. 0  tests/regressiontests/admin_ordering/__init__.py
  157. +46 −0 tests/regressiontests/admin_ordering/models.py
  158. +2 −2 tests/regressiontests/admin_scripts/tests.py
  159. 0  tests/regressiontests/admin_views/__init__.py
  160. +81 −0 tests/regressiontests/admin_views/fixtures/admin-views-users.xml
  161. +6 −0 tests/regressiontests/admin_views/fixtures/string-primary-key.xml
  162. +61 −0 tests/regressiontests/admin_views/models.py
  163. +362 −0 tests/regressiontests/admin_views/tests.py
  164. +7 −0 tests/regressiontests/admin_views/urls.py
  165. 0  tests/regressiontests/admin_widgets/__init__.py
  166. +85 −0 tests/regressiontests/admin_widgets/models.py
  167. 0  tests/regressiontests/datetime_safe/__init__.py
  168. 0  tests/regressiontests/datetime_safe/models.py
  169. +37 −0 tests/regressiontests/datetime_safe/tests.py
  170. +1 −1  tests/regressiontests/forms/error_messages.py
  171. +4 −4 tests/regressiontests/forms/extra.py
  172. +58 −17 tests/regressiontests/forms/fields.py
  173. +73 −1 tests/regressiontests/forms/forms.py
  174. +575 −0 tests/regressiontests/forms/formsets.py
  175. +359 −0 tests/regressiontests/forms/media.py
  176. +1 −1  tests/regressiontests/forms/models.py
  177. +1 −1  tests/regressiontests/forms/regressions.py
  178. +4 −0 tests/regressiontests/forms/tests.py
  179. +3 −3 tests/regressiontests/forms/util.py
  180. +160 −2 tests/regressiontests/forms/widgets.py
  181. 0  tests/regressiontests/inline_formsets/__init__.py
  182. +55 −0 tests/regressiontests/inline_formsets/models.py
  183. +0 −337 tests/regressiontests/invalid_admin_options/models.py
  184. +2 −0  tests/regressiontests/mail/__init__.py
  185. +1 −0  tests/regressiontests/mail/models.py
  186. +41 −0 tests/regressiontests/mail/tests.py
  187. 0  tests/regressiontests/modeladmin/__init__.py
  188. +876 −0 tests/regressiontests/modeladmin/models.py
  189. +45 −19 tests/regressiontests/queries/models.py
  190. +28 −1 tests/regressiontests/views/fixtures/testdata.json
  191. +19 −5 tests/regressiontests/views/models.py
  192. +2 −1  tests/regressiontests/views/tests/__init__.py
  193. +211 −0 tests/regressiontests/views/tests/generic/create_update.py
  194. +52 −20 tests/regressiontests/views/urls.py
  195. +24 −0 tests/regressiontests/views/views.py
  196. +1 −0  tests/templates/custom_admin/change_form.html
  197. +7 −0 tests/templates/custom_admin/change_list.html
  198. +1 −0  tests/templates/custom_admin/delete_confirmation.html
  199. +6 −0 tests/templates/custom_admin/index.html
  200. +6 −0 tests/templates/custom_admin/login.html
  201. +1 −0  tests/templates/custom_admin/object_history.html
  202. +1 −0  tests/templates/views/article_confirm_delete.html
  203. +1 −1  tests/templates/views/article_detail.html
  204. +3 −0  tests/templates/views/article_form.html
  205. +1 −0  tests/templates/views/urlarticle_detail.html
  206. +3 −0  tests/templates/views/urlarticle_form.html
  207. +3 −0  tests/urls.py
View
10 AUTHORS
@@ -74,6 +74,7 @@ answer newbie questions, and generally made Django that much better:
Arvis Bickovskis <viestards.lists@gmail.com>
Paul Bissex <http://e-scribe.com/>
Simon Blanchard
+ David Blewett <david@dawninglight.net>
Matt Boersma <ogghead@gmail.com>
boobsd@gmail.com
Andrew Brehaut <http://brehaut.net/blog>
@@ -172,6 +173,7 @@ answer newbie questions, and generally made Django that much better:
Espen Grindhaug <http://grindhaug.org/>
Thomas Güttler <hv@tbz-pariv.de>
dAniel hAhler
+ hambaloney
Brian Harring <ferringb@gmail.com>
Brant Harris
Hawkeye
@@ -194,6 +196,7 @@ answer newbie questions, and generally made Django that much better:
Baurzhan Ismagulov <ibr@radix50.net>
james_027@yahoo.com
jcrasta@gmail.com
+ jdetaeye
Zak Johnson <zakj@nox.cx>
Nis Jørgensen <nis@superlativ.dk>
Michael Josephson <http://www.sdjournal.com/>
@@ -241,11 +244,13 @@ answer newbie questions, and generally made Django that much better:
Waylan Limberg <waylan@gmail.com>
limodou
Philip Lindborg <philip.lindborg@gmail.com>
+ Simon Litchfield <simon@quo.com.au>
Daniel Lindsley <polarcowz@gmail.com>
Trey Long <trey@ktrl.com>
msaelices <msaelices@gmail.com>
Matt McClanahan <http://mmcc.cx/>
Martin Maney <http://www.chipy.org/Martin_Maney>
+ Petr Marhoun <petr.marhoun@gmail.com>
masonsimon+django@gmail.com
Manuzhai
Petr Marhoun <petr.marhoun@gmail.com>
@@ -258,6 +263,7 @@ answer newbie questions, and generally made Django that much better:
mattycakes@gmail.com
Jason McBrayer <http://www.carcosa.net/jason/>
mccutchen@gmail.com
+ Christian Metts
michael.mcewan@gmail.com
michal@plovarna.cz
Slawek Mikula <slawek dot mikula at gmail dot com>
@@ -270,6 +276,7 @@ answer newbie questions, and generally made Django that much better:
Eric Moritz <http://eric.themoritzfamily.com/>
mrmachine <real.human@mrmachine.net>
Robin Munn <http://www.geekforgod.com/>
+ msundstr
Robert Myers <myer0052@gmail.com>
Nebojša Dorđević
Doug Napoleone <doug@dougma.com>
@@ -290,6 +297,7 @@ answer newbie questions, and generally made Django that much better:
peter@mymart.com
pgross@thoughtworks.com
phaedo <http://phaedo.cx/>
+ Julien Phalip <http://www.julienphalip.com>
phil@produxion.net
phil.h.smith@gmail.com
Gustavo Picon
@@ -298,6 +306,7 @@ answer newbie questions, and generally made Django that much better:
Mihai Preda <mihai_preda@yahoo.com>
Daniel Poelzleithner <http://poelzi.org/>
polpak@yahoo.com
+ Matthias Pronk <django@masida.nl>
Jyrki Pulliainen <jyrki.pulliainen@gmail.com>
Johann Queuniet <johann.queuniet@adh.naellia.eu>
Jan Rademaker
@@ -314,6 +323,7 @@ answer newbie questions, and generally made Django that much better:
Matt Riggott
Henrique Romano <onaiort@gmail.com>
Armin Ronacher
+ Daniel Roseman <http://roseman.org.uk/>
Brian Rosner <brosner@gmail.com>
Oliver Rutherfurd <http://rutherfurd.net/>
ryankanno
View
8 django/conf/project_template/urls.py
@@ -1,9 +1,15 @@
from django.conf.urls.defaults import *
+# Uncomment this for admin:
+#from django.contrib import admin
+
urlpatterns = patterns('',
# Example:
# (r'^{{ project_name }}/', include('{{ project_name }}.foo.urls')),
+ # Uncomment this for admin docs:
+ #(r'^admin/doc/', include('django.contrib.admindocs.urls')),
+
# Uncomment this for admin:
-# (r'^admin/', include('django.contrib.admin.urls')),
+ #('^admin/(.*)', admin.site.root),
)
View
16 django/contrib/admin/__init__.py
@@ -0,0 +1,16 @@
+from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL
+from django.contrib.admin.options import StackedInline, TabularInline
+from django.contrib.admin.sites import AdminSite, site
+
+def autodiscover():
+ """
+ Auto-discover INSTALLED_APPS admin.py modules and fail silently when
+ not present. This forces an import on them to register any admin bits they
+ may want.
+ """
+ from django.conf import settings
+ for app in settings.INSTALLED_APPS:
+ try:
+ __import__("%s.admin" % app)
+ except ImportError:
+ pass
View
28 django/contrib/admin/filterspecs.py
@@ -15,7 +15,7 @@
class FilterSpec(object):
filter_specs = []
- def __init__(self, f, request, params, model):
+ def __init__(self, f, request, params, model, model_admin):
self.field = f
self.params = params
@@ -23,10 +23,10 @@ def register(cls, test, factory):
cls.filter_specs.append((test, factory))
register = classmethod(register)
- def create(cls, f, request, params, model):
+ def create(cls, f, request, params, model, model_admin):
for test, factory in cls.filter_specs:
if test(f):
- return factory(f, request, params, model)
+ return factory(f, request, params, model, model_admin)
create = classmethod(create)
def has_output(self):
@@ -52,8 +52,8 @@ def output(self, cl):
return mark_safe("".join(t))
class RelatedFilterSpec(FilterSpec):
- def __init__(self, f, request, params, model):
- super(RelatedFilterSpec, self).__init__(f, request, params, model)
+ def __init__(self, f, request, params, model, model_admin):
+ super(RelatedFilterSpec, self).__init__(f, request, params, model, model_admin)
if isinstance(f, models.ManyToManyField):
self.lookup_title = f.rel.to._meta.verbose_name
else:
@@ -81,8 +81,8 @@ def choices(self, cl):
FilterSpec.register(lambda f: bool(f.rel), RelatedFilterSpec)
class ChoicesFilterSpec(FilterSpec):
- def __init__(self, f, request, params, model):
- super(ChoicesFilterSpec, self).__init__(f, request, params, model)
+ def __init__(self, f, request, params, model, model_admin):
+ super(ChoicesFilterSpec, self).__init__(f, request, params, model, model_admin)
self.lookup_kwarg = '%s__exact' % f.name
self.lookup_val = request.GET.get(self.lookup_kwarg, None)
@@ -98,8 +98,8 @@ def choices(self, cl):
FilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec)
class DateFieldFilterSpec(FilterSpec):
- def __init__(self, f, request, params, model):
- super(DateFieldFilterSpec, self).__init__(f, request, params, model)
+ def __init__(self, f, request, params, model, model_admin):
+ super(DateFieldFilterSpec, self).__init__(f, request, params, model, model_admin)
self.field_generic = '%s__' % self.field.name
@@ -133,8 +133,8 @@ def choices(self, cl):
FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec)
class BooleanFieldFilterSpec(FilterSpec):
- def __init__(self, f, request, params, model):
- super(BooleanFieldFilterSpec, self).__init__(f, request, params, model)
+ def __init__(self, f, request, params, model, model_admin):
+ super(BooleanFieldFilterSpec, self).__init__(f, request, params, model, model_admin)
self.lookup_kwarg = '%s__exact' % f.name
self.lookup_kwarg2 = '%s__isnull' % f.name
self.lookup_val = request.GET.get(self.lookup_kwarg, None)
@@ -159,10 +159,10 @@ def choices(self, cl):
# if a field is eligible to use the BooleanFieldFilterSpec, that'd be much
# more appropriate, and the AllValuesFilterSpec won't get used for it.
class AllValuesFilterSpec(FilterSpec):
- def __init__(self, f, request, params, model):
- super(AllValuesFilterSpec, self).__init__(f, request, params, model)
+ def __init__(self, f, request, params, model, model_admin):
+ super(AllValuesFilterSpec, self).__init__(f, request, params, model, model_admin)
self.lookup_val = request.GET.get(f.name, None)
- self.lookup_choices = model._meta.admin.manager.distinct().order_by(f.name).values(f.name)
+ self.lookup_choices = model_admin.queryset(request).distinct().order_by(f.name).values(f.name)
def title(self):
return self.field.verbose_name
View
21 django/contrib/admin/media/css/forms.css
@@ -58,3 +58,24 @@ fieldset.monospace textarea { font-family:"Bitstream Vera Sans Mono",Monaco,"Cou
.vLargeTextField, .vXMLLargeTextField { width:48em; }
.flatpages-flatpage #id_content { height:40.2em; }
.module table .vPositiveSmallIntegerField { width:2.2em; }
+
+/* x unsorted */
+.inline-group {padding:10px; padding-bottom:5px; background:#eee; margin:10px 0;}
+.inline-group h3.header {margin:-5px -10px 5px -10px; background:#bbb; color:#fff; padding:2px 5px 3px 5px; font-size:11px}
+.inline-related {margin-bottom:15px; position:relative;}
+.last-related {margin-bottom:0px;}
+.inline-related h2 { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; font-weight:bold; color:#888; }
+.inline-related h2 b {font-weight:normal; color:#aaa;}
+.inline-related h2 span.delete {padding-left:20px; position:absolute; top:0px; right:5px;}
+.inline-related h2 span.delete label {margin-left:2px; padding-top:1px;}
+.inline-related fieldset {background:#fbfbfb;}
+.inline-related fieldset.module h2 { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; font-weight:bold; background:#bcd; color:#fff; }
+.inline-related.tabular fieldset.module table {width:100%;}
+
+.inline-group .tabular tr.has_original td {padding-top:2em;}
+.inline-group .tabular tr td.original { padding:2px 0 0 0; width:0; _position:relative; }
+.inline-group .tabular th.original {width:0px; padding:0;}
+.inline-group .tabular td.original p {position:absolute; left:0; height:1.1em; padding:2px 7px; overflow:hidden; font-size:9px; font-weight:bold; color:#666; _width:700px; }
+.inline-group ul.tools {padding:0; margin: 0; list-style:none;}
+.inline-group ul.tools li {display:inline; padding:0 5px;}
+.inline-group ul.tools a.add {background:url(../img/admin/icon_addlink.gif) 0 50% no-repeat; padding-left:14px;}
View
81 django/contrib/admin/media/js/SelectFilter.js
@@ -1,81 +0,0 @@
-/*
-SelectFilter - Turns a multiple-select box into a filter interface.
-
-Requires SelectBox.js and addevent.js.
-*/
-
-function findForm(node) {
- // returns the node of the form containing the given node
- if (node.tagName.toLowerCase() != 'form') {
- return findForm(node.parentNode);
- }
- return node;
-}
-
-var SelectFilter = {
- init: function(field_id) {
- var from_box = document.getElementById(field_id);
- from_box.id += '_from'; // change its ID
- // Create the INPUT input box
- var input_box = document.createElement('input');
- input_box.id = field_id + '_input';
- input_box.setAttribute('type', 'text');
- from_box.parentNode.insertBefore(input_box, from_box);
- from_box.parentNode.insertBefore(document.createElement('br'), input_box.nextSibling);
- // Create the TO box
- var to_box = document.createElement('select');
- to_box.id = field_id + '_to';
- to_box.setAttribute('multiple', 'multiple');
- to_box.setAttribute('size', from_box.size);
- from_box.parentNode.insertBefore(to_box, from_box.nextSibling);
- to_box.setAttribute('name', from_box.getAttribute('name'));
- from_box.setAttribute('name', from_box.getAttribute('name') + '_old');
- // Give the filters a CSS hook
- from_box.setAttribute('class', 'filtered');
- to_box.setAttribute('class', 'filtered');
- // Set up the JavaScript event handlers for the select box filter interface
- addEvent(input_box, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); });
- addEvent(input_box, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); });
- addEvent(from_box, 'dblclick', function() { SelectBox.move(field_id + '_from', field_id + '_to'); });
- addEvent(from_box, 'focus', function() { input_box.focus(); });
- addEvent(to_box, 'dblclick', function() { SelectBox.move(field_id + '_to', field_id + '_from'); });
- addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); });
- SelectBox.init(field_id + '_from');
- SelectBox.init(field_id + '_to');
- // Move selected from_box options to to_box
- SelectBox.move(field_id + '_from', field_id + '_to');
- },
- filter_key_up: function(event, field_id) {
- from = document.getElementById(field_id + '_from');
- // don't submit form if user pressed Enter
- if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) {
- from.selectedIndex = 0;
- SelectBox.move(field_id + '_from', field_id + '_to');
- from.selectedIndex = 0;
- return false;
- }
- var temp = from.selectedIndex;
- SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value);
- from.selectedIndex = temp;
- return true;
- },
- filter_key_down: function(event, field_id) {
- from = document.getElementById(field_id + '_from');
- // right arrow -- move across
- if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) {
- var old_index = from.selectedIndex;
- SelectBox.move(field_id + '_from', field_id + '_to');
- from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index;
- return false;
- }
- // down arrow -- wrap around
- if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) {
- from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
- }
- // up arrow -- wrap around
- if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) {
- from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1;
- }
- return true;
- }
-}
View
2  django/contrib/admin/media/js/admin/CollapsedFieldsets.js
@@ -47,7 +47,7 @@ var CollapsedFieldsets = {
// Returns true if any fields in the fieldset have validation errors.
var divs = fs.getElementsByTagName('div');
for (var i=0; i<divs.length; i++) {
- if (divs[i].className.match(/\berror\b/)) {
+ if (divs[i].className.match(/\berrors\b/)) {
return true;
}
}
View
4 django/contrib/admin/media/js/admin/RelatedObjectLookups.js
@@ -1,4 +1,4 @@
-// Handles related-objects functionality: lookup link for raw_id_admin=True
+// Handles related-objects functionality: lookup link for raw_id_fields
// and Add Another links.
function html_unescape(text) {
@@ -29,7 +29,7 @@ function showRelatedObjectLookupPopup(triggeringLink) {
function dismissRelatedLookupPopup(win, chosenId) {
var name = win.name.replace(/___/g, '.');
var elem = document.getElementById(name);
- if (elem.className.indexOf('vRawIdAdminField') != -1 && elem.value) {
+ if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) {
elem.value += ',' + chosenId;
} else {
document.getElementById(name).value = chosenId;
View
3  django/contrib/admin/models.py
@@ -1,6 +1,7 @@
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
+from django.contrib.admin.util import quote
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode
from django.utils.safestring import mark_safe
@@ -50,4 +51,4 @@ 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 mark_safe(u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, self.object_id))
+ return mark_safe(u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, quote(self.object_id)))
View
795 django/contrib/admin/options.py
@@ -0,0 +1,795 @@
+from django import oldforms, template
+from django import forms
+from django.forms.formsets import all_valid
+from django.forms.models import modelform_factory, inlineformset_factory
+from django.forms.models import BaseInlineFormset
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.admin import widgets
+from django.contrib.admin.util import quote, unquote, get_deleted_objects
+from django.core.exceptions import ImproperlyConfigured, PermissionDenied
+from django.db import models, transaction
+from django.http import Http404, HttpResponse, HttpResponseRedirect
+from django.shortcuts import get_object_or_404, render_to_response
+from django.utils.html import escape
+from django.utils.safestring import mark_safe
+from django.utils.text import capfirst, get_text_list
+from django.utils.translation import ugettext as _
+from django.utils.encoding import force_unicode
+import sets
+
+HORIZONTAL, VERTICAL = 1, 2
+# returns the <ul> class for a given radio_admin field
+get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
+
+class IncorrectLookupParameters(Exception):
+ pass
+
+def flatten_fieldsets(fieldsets):
+ """Returns a list of field names from an admin fieldsets structure."""
+ field_names = []
+ for name, opts in fieldsets:
+ for field in opts['fields']:
+ # type checking feels dirty, but it seems like the best way here
+ if type(field) == tuple:
+ field_names.extend(field)
+ else:
+ field_names.append(field)
+ return field_names
+
+class AdminForm(object):
+ def __init__(self, form, fieldsets, prepopulated_fields):
+ self.form, self.fieldsets = form, fieldsets
+ self.prepopulated_fields = [{
+ 'field': form[field_name],
+ 'dependencies': [form[f] for f in dependencies]
+ } for field_name, dependencies in prepopulated_fields.items()]
+
+ def __iter__(self):
+ for name, options in self.fieldsets:
+ yield Fieldset(self.form, name, **options)
+
+ def first_field(self):
+ for bf in self.form:
+ return bf
+
+ def _media(self):
+ media = self.form.media
+ for fs in self:
+ media = media + fs.media
+ return media
+ media = property(_media)
+
+class Fieldset(object):
+ def __init__(self, form, name=None, fields=(), classes=(), description=None):
+ self.form = form
+ self.name, self.fields = name, fields
+ self.classes = u' '.join(classes)
+ self.description = description
+
+ def _media(self):
+ from django.conf import settings
+ if 'collapse' in self.classes:
+ return forms.Media(js=['%sjs/admin/CollapsedFieldsets.js' % settings.ADMIN_MEDIA_PREFIX])
+ return forms.Media()
+ media = property(_media)
+
+ def __iter__(self):
+ for field in self.fields:
+ yield Fieldline(self.form, field)
+
+class Fieldline(object):
+ def __init__(self, form, field):
+ self.form = form # A django.forms.Form instance
+ if isinstance(field, basestring):
+ self.fields = [field]
+ else:
+ self.fields = field
+
+ def __iter__(self):
+ for i, field in enumerate(self.fields):
+ yield AdminField(self.form, field, is_first=(i == 0))
+
+ def errors(self):
+ return mark_safe(u'\n'.join([self.form[f].errors.as_ul() for f in self.fields]))
+
+class AdminField(object):
+ def __init__(self, form, field, is_first):
+ self.field = form[field] # A django.forms.BoundField instance
+ self.is_first = is_first # Whether this field is first on the line
+ self.is_checkbox = isinstance(self.field.field.widget, forms.CheckboxInput)
+
+ def label_tag(self):
+ classes = []
+ if self.is_checkbox:
+ classes.append(u'vCheckboxLabel')
+ contents = escape(self.field.label)
+ else:
+ contents = force_unicode(escape(self.field.label)) + u':'
+ if self.field.field.required:
+ classes.append(u'required')
+ if not self.is_first:
+ classes.append(u'inline')
+ attrs = classes and {'class': u' '.join(classes)} or {}
+ return self.field.label_tag(contents=contents, attrs=attrs)
+
+class BaseModelAdmin(object):
+ """Functionality common to both ModelAdmin and InlineAdmin."""
+ raw_id_fields = ()
+ fields = None
+ fieldsets = None
+ form = forms.ModelForm
+ filter_vertical = ()
+ filter_horizontal = ()
+ radio_fields = {}
+ prepopulated_fields = {}
+
+ def formfield_for_dbfield(self, db_field, **kwargs):
+ """
+ Hook for specifying the form Field instance for a given database Field
+ instance.
+
+ If kwargs are given, they're passed to the form Field's constructor.
+ """
+ # For DateTimeFields, use a special field and widget.
+ if isinstance(db_field, models.DateTimeField):
+ kwargs['form_class'] = forms.SplitDateTimeField
+ kwargs['widget'] = widgets.AdminSplitDateTime()
+ return db_field.formfield(**kwargs)
+
+ # For DateFields, add a custom CSS class.
+ if isinstance(db_field, models.DateField):
+ kwargs['widget'] = widgets.AdminDateWidget
+ return db_field.formfield(**kwargs)
+
+ # For TimeFields, add a custom CSS class.
+ if isinstance(db_field, models.TimeField):
+ kwargs['widget'] = widgets.AdminTimeWidget
+ return db_field.formfield(**kwargs)
+
+ # For FileFields and ImageFields add a link to the current file.
+ if isinstance(db_field, models.ImageField) or isinstance(db_field, models.FileField):
+ kwargs['widget'] = widgets.AdminFileWidget
+ return db_field.formfield(**kwargs)
+
+ # For ForeignKey or ManyToManyFields, use a special widget.
+ if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
+ if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields:
+ kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel)
+ elif isinstance(db_field, models.ForeignKey) and db_field.name in self.radio_fields:
+ kwargs['widget'] = widgets.AdminRadioSelect(attrs={
+ 'class': get_ul_class(self.radio_fields[db_field.name]),
+ })
+ kwargs['empty_label'] = db_field.blank and _('None') or None
+ else:
+ if isinstance(db_field, models.ManyToManyField):
+ if db_field.name in self.raw_id_fields:
+ kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel)
+ kwargs['help_text'] = ''
+ elif db_field.name in (self.filter_vertical + self.filter_horizontal):
+ kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
+ # Wrap the widget's render() method with a method that adds
+ # extra HTML to the end of the rendered output.
+ formfield = db_field.formfield(**kwargs)
+ # Don't wrap raw_id fields. Their add function is in the popup window.
+ if not db_field.name in self.raw_id_fields:
+ formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
+ return formfield
+
+ if db_field.choices and db_field.name in self.radio_fields:
+ kwargs['widget'] = widgets.AdminRadioSelect(
+ choices=db_field.get_choices(include_blank=db_field.blank,
+ blank_choice=[('', _('None'))]),
+ attrs={
+ 'class': get_ul_class(self.radio_fields[db_field.name]),
+ }
+ )
+
+ # For any other type of field, just call its formfield() method.
+ return db_field.formfield(**kwargs)
+
+ def _declared_fieldsets(self):
+ if self.fieldsets:
+ return self.fieldsets
+ elif self.fields:
+ return [(None, {'fields': self.fields})]
+ return None
+ declared_fieldsets = property(_declared_fieldsets)
+
+class ModelAdmin(BaseModelAdmin):
+ "Encapsulates all admin options and functionality for a given model."
+ __metaclass__ = forms.MediaDefiningClass
+
+ list_display = ('__str__',)
+ list_display_links = ()
+ list_filter = ()
+ list_select_related = False
+ list_per_page = 100
+ search_fields = ()
+ date_hierarchy = None
+ save_as = False
+ save_on_top = False
+ ordering = None
+ inlines = []
+
+ # Custom templates (designed to be over-ridden in subclasses)
+ change_form_template = None
+ change_list_template = None
+ delete_confirmation_template = None
+ object_history_template = None
+
+ def __init__(self, model, admin_site):
+ self.model = model
+ self.opts = model._meta
+ self.admin_site = admin_site
+ self.inline_instances = []
+ for inline_class in self.inlines:
+ inline_instance = inline_class(self.model, self.admin_site)
+ self.inline_instances.append(inline_instance)
+ super(ModelAdmin, self).__init__()
+
+ def __call__(self, request, url):
+ # Check that LogEntry, ContentType and the auth context processor are installed.
+ from django.conf import settings
+ if settings.DEBUG:
+ from django.contrib.admin.models import LogEntry
+ if not LogEntry._meta.installed:
+ raise ImproperlyConfigured("Put 'django.contrib.admin' in your INSTALLED_APPS setting in order to use the admin application.")
+ if not ContentType._meta.installed:
+ raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in your INSTALLED_APPS setting in order to use the admin application.")
+ if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS:
+ raise ImproperlyConfigured("Put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.")
+
+ # Delegate to the appropriate method, based on the URL.
+ if url is None:
+ return self.changelist_view(request)
+ elif url.endswith('add'):
+ return self.add_view(request)
+ elif url.endswith('history'):
+ return self.history_view(request, unquote(url[:-8]))
+ elif url.endswith('delete'):
+ return self.delete_view(request, unquote(url[:-7]))
+ else:
+ return self.change_view(request, unquote(url))
+
+ def _media(self):
+ from django.conf import settings
+
+ js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
+ if self.prepopulated_fields:
+ js.append('js/urlify.js')
+ if self.opts.get_ordered_objects():
+ js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
+ if self.filter_vertical or self.filter_horizontal:
+ js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
+
+ return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
+ media = property(_media)
+
+ def has_add_permission(self, request):
+ "Returns True if the given request has permission to add an object."
+ opts = self.opts
+ return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission())
+
+ def has_change_permission(self, request, obj=None):
+ """
+ Returns True if the given request has permission to change the given
+ Django model instance.
+
+ If `obj` is None, this should return True if the given request has
+ permission to change *any* object of the given type.
+ """
+ opts = self.opts
+ return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission())
+
+ def has_delete_permission(self, request, obj=None):
+ """
+ Returns True if the given request has permission to change the given
+ Django model instance.
+
+ If `obj` is None, this should return True if the given request has
+ permission to delete *any* object of the given type.
+ """
+ opts = self.opts
+ return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission())
+
+ def queryset(self, request):
+ """
+ Returns a QuerySet of all model instances that can be edited by the
+ admin site. This is used by changelist_view.
+ """
+ qs = self.model._default_manager.get_query_set()
+ # TODO: this should be handled by some parameter to the ChangeList.
+ ordering = self.ordering or () # otherwise we might try to *None, which is bad ;)
+ if ordering:
+ qs = qs.order_by(*ordering)
+ return qs
+
+ def get_fieldsets(self, request, obj=None):
+ "Hook for specifying fieldsets for the add form."
+ if self.declared_fieldsets:
+ return self.declared_fieldsets
+ form = self.get_form(request)
+ return [(None, {'fields': form.base_fields.keys()})]
+
+ def get_form(self, request, obj=None):
+ """
+ Returns a Form class for use in the admin add view. This is used by
+ add_view and change_view.
+ """
+ if self.declared_fieldsets:
+ fields = flatten_fieldsets(self.declared_fieldsets)
+ else:
+ fields = None
+ return modelform_factory(self.model, form=self.form, fields=fields, formfield_callback=self.formfield_for_dbfield)
+
+ def get_formsets(self, request, obj=None):
+ for inline in self.inline_instances:
+ yield inline.get_formset(request, obj)
+
+ def save_add(self, request, form, formsets, post_url_continue):
+ """
+ Saves the object in the "add" stage and returns an HttpResponseRedirect.
+
+ `form` is a bound Form instance that's verified to be valid.
+ """
+ from django.contrib.admin.models import LogEntry, ADDITION
+ opts = self.model._meta
+ new_object = form.save(commit=True)
+
+ if formsets:
+ for formset in formsets:
+ # HACK: it seems like the parent obejct should be passed into
+ # a method of something, not just set as an attribute
+ formset.instance = new_object
+ formset.save()
+
+ pk_value = new_object._get_pk_val()
+ LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(self.model).id, pk_value, force_unicode(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.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)
+
+ if request.POST.has_key("_popup"):
+ return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
+ # escape() calls force_unicode.
+ (escape(pk_value), escape(new_object)))
+ elif request.POST.has_key("_addanother"):
+ request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
+ return HttpResponseRedirect(request.path)
+ else:
+ request.user.message_set.create(message=msg)
+ # Figure out where to redirect. If the user has change permission,
+ # redirect to the change-list page for this object. Otherwise,
+ # redirect to the admin index.
+ if self.has_change_permission(request, None):
+ post_url = '../'
+ else:
+ post_url = '../../../'
+ return HttpResponseRedirect(post_url)
+ save_add = transaction.commit_on_success(save_add)
+
+ def save_change(self, request, form, formsets=None):
+ """
+ Saves the object in the "change" stage and returns an HttpResponseRedirect.
+
+ `form` is a bound Form instance that's verified to be valid.
+
+ `formsets` is a sequence of InlineFormSet instances that are verified to be valid.
+ """
+ from django.contrib.admin.models import LogEntry, CHANGE
+ opts = self.model._meta
+ new_object = form.save(commit=True)
+ pk_value = new_object._get_pk_val()
+
+ if formsets:
+ for formset in formsets:
+ formset.save()
+
+ # Construct the change message.
+ change_message = []
+ if form.changed_data:
+ change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and')))
+
+ if formsets:
+ for formset in formsets:
+ for added_object in formset.new_objects:
+ change_message.append(_('Added %(name)s "%(object)s".')
+ % {'name': added_object._meta.verbose_name,
+ 'object': added_object})
+ for changed_object, changed_fields in formset.changed_objects:
+ change_message.append(_('Changed %(list)s for %(name)s "%(object)s".')
+ % {'list': get_text_list(changed_fields, _('and')),
+ 'name': changed_object._meta.verbose_name,
+ 'object': changed_object})
+ for deleted_object in formset.deleted_objects:
+ change_message.append(_('Deleted %(name)s "%(object)s".')
+ % {'name': deleted_object._meta.verbose_name,
+ 'object': deleted_object})
+ 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(self.model).id, pk_value, force_unicode(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.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.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.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
+ return HttpResponseRedirect("../add/")
+ else:
+ request.user.message_set.create(message=msg)
+ return HttpResponseRedirect("../")
+ save_change = transaction.commit_on_success(save_change)
+
+ def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
+ opts = self.model._meta
+ app_label = opts.app_label
+ ordered_objects = opts.get_ordered_objects()
+ context.update({
+ 'add': add,
+ 'change': change,
+ 'has_add_permission': self.has_add_permission(request),
+ 'has_change_permission': self.has_change_permission(request, obj),
+ 'has_delete_permission': self.has_delete_permission(request, obj),
+ 'has_file_field': True, # FIXME - this should check if form or formsets have a FileField,
+ 'has_absolute_url': hasattr(self.model, 'get_absolute_url'),
+ 'ordered_objects': ordered_objects,
+ 'form_url': mark_safe(form_url),
+ 'opts': opts,
+ 'content_type_id': ContentType.objects.get_for_model(self.model).id,
+ 'save_as': self.save_as,
+ 'save_on_top': self.save_on_top,
+ 'root_path': self.admin_site.root_path,
+ })
+ return render_to_response(self.change_form_template or [
+ "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
+ "admin/%s/change_form.html" % app_label,
+ "admin/change_form.html"
+ ], context, context_instance=template.RequestContext(request))
+
+ def add_view(self, request, form_url='', extra_context=None):
+ "The 'add' admin view for this model."
+ model = self.model
+ opts = model._meta
+ app_label = opts.app_label
+
+ if not self.has_add_permission(request):
+ raise PermissionDenied
+
+ if self.has_change_permission(request, None):
+ # redirect to list view
+ post_url = '../'
+ else:
+ # Object list will give 'Permission Denied', so go back to admin home
+ post_url = '../../../'
+
+ ModelForm = self.get_form(request)
+ inline_formsets = []
+ obj = self.model()
+ if request.method == 'POST':
+ form = ModelForm(request.POST, request.FILES)
+ for FormSet in self.get_formsets(request):
+ inline_formset = FormSet(data=request.POST, files=request.FILES,
+ instance=obj, save_as_new=request.POST.has_key("_saveasnew"))
+ inline_formsets.append(inline_formset)
+ if all_valid(inline_formsets) and form.is_valid():
+ return self.save_add(request, form, inline_formsets, '../%s/')
+ else:
+ form = ModelForm(initial=dict(request.GET.items()))
+ for FormSet in self.get_formsets(request):
+ inline_formset = FormSet(instance=obj)
+ inline_formsets.append(inline_formset)
+
+ adminForm = AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields)
+ media = self.media + adminForm.media
+ for fs in inline_formsets:
+ media = media + fs.media
+
+ inline_admin_formsets = []
+ for inline, formset in zip(self.inline_instances, inline_formsets):
+ fieldsets = list(inline.get_fieldsets(request))
+ inline_admin_formset = InlineAdminFormSet(inline, formset, fieldsets)
+ inline_admin_formsets.append(inline_admin_formset)
+
+ context = {
+ 'title': _('Add %s') % opts.verbose_name,
+ 'adminform': adminForm,
+ 'is_popup': request.REQUEST.has_key('_popup'),
+ 'show_delete': False,
+ 'media': mark_safe(media),
+ 'inline_admin_formsets': inline_admin_formsets,
+ 'errors': AdminErrorList(form, inline_formsets),
+ 'root_path': self.admin_site.root_path,
+ }
+ context.update(extra_context or {})
+ return self.render_change_form(request, context, add=True)
+
+ def change_view(self, request, object_id, extra_context=None):
+ "The 'change' admin view for this model."
+ model = self.model
+ opts = model._meta
+ app_label = opts.app_label
+
+ try:
+ obj = model._default_manager.get(pk=object_id)
+ except model.DoesNotExist:
+ # Don't raise Http404 just yet, because we haven't checked
+ # permissions yet. We don't want an unauthenticated user to be able
+ # to determine whether a given object exists.
+ obj = None
+
+ if not self.has_change_permission(request, obj):
+ raise PermissionDenied
+
+ if obj is None:
+ raise Http404('%s object with primary key %r does not exist.' % (opts.verbose_name, escape(object_id)))
+
+ if request.POST and request.POST.has_key("_saveasnew"):
+ return self.add_view(request, form_url='../../add/')
+
+ ModelForm = self.get_form(request, obj)
+ inline_formsets = []
+ if request.method == 'POST':
+ form = ModelForm(request.POST, request.FILES, instance=obj)
+ for FormSet in self.get_formsets(request, obj):
+ inline_formset = FormSet(request.POST, request.FILES, instance=obj)
+ inline_formsets.append(inline_formset)
+
+ if all_valid(inline_formsets) and form.is_valid():
+ return self.save_change(request, form, inline_formsets)
+ else:
+ form = ModelForm(instance=obj)
+ for FormSet in self.get_formsets(request, obj):
+ inline_formset = FormSet(instance=obj)
+ inline_formsets.append(inline_formset)
+
+ adminForm = AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
+ media = self.media + adminForm.media
+ for fs in inline_formsets:
+ media = media + fs.media
+
+ inline_admin_formsets = []
+ for inline, formset in zip(self.inline_instances, inline_formsets):
+ fieldsets = list(inline.get_fieldsets(request, obj))
+ inline_admin_formset = InlineAdminFormSet(inline, formset, fieldsets)
+ inline_admin_formsets.append(inline_admin_formset)
+
+ context = {
+ 'title': _('Change %s') % opts.verbose_name,
+ 'adminform': adminForm,
+ 'object_id': object_id,
+ 'original': obj,
+ 'is_popup': request.REQUEST.has_key('_popup'),
+ 'media': mark_safe(media),
+ 'inline_admin_formsets': inline_admin_formsets,
+ 'errors': AdminErrorList(form, inline_formsets),
+ 'root_path': self.admin_site.root_path,
+ }
+ context.update(extra_context or {})
+ return self.render_change_form(request, context, change=True, obj=obj)
+
+ def changelist_view(self, request, extra_context=None):
+ "The 'change list' admin view for this model."
+ from django.contrib.admin.views.main import ChangeList, ERROR_FLAG
+ opts = self.model._meta
+ app_label = opts.app_label
+ if not self.has_change_permission(request, None):
+ raise PermissionDenied
+ try:
+ cl = ChangeList(request, self.model, self.list_display, self.list_display_links, self.list_filter,
+ self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self)
+ except IncorrectLookupParameters:
+ # Wacky lookup parameters were given, so redirect to the main
+ # changelist page, without parameters, and pass an 'invalid=1'
+ # parameter via the query string. If wacky parameters were given and
+ # the 'invalid=1' parameter was already in the query string, something
+ # is screwed up with the database, so display an error page.
+ if ERROR_FLAG in request.GET.keys():
+ return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
+ return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
+
+ context = {
+ 'title': cl.title,
+ 'is_popup': cl.is_popup,
+ 'cl': cl,
+ 'has_add_permission': self.has_add_permission(request),
+ 'root_path': self.admin_site.root_path,
+ }
+ context.update(extra_context or {})
+ return render_to_response(self.change_list_template or [
+ 'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
+ 'admin/%s/change_list.html' % app_label,
+ 'admin/change_list.html'
+ ], context, context_instance=template.RequestContext(request))
+
+ def delete_view(self, request, object_id, extra_context=None):
+ "The 'delete' admin view for this model."
+ from django.contrib.admin.models import LogEntry, DELETION
+ opts = self.model._meta
+ app_label = opts.app_label
+
+ try:
+ obj = self.model._default_manager.get(pk=object_id)
+ except self.model.DoesNotExist:
+ # Don't raise Http404 just yet, because we haven't checked
+ # permissions yet. We don't want an unauthenticated user to be able
+ # to determine whether a given object exists.
+ obj = None
+
+ if not self.has_delete_permission(request, obj):
+ raise PermissionDenied
+
+ if obj is None:
+ raise Http404('%s object with primary key %r does not exist.' % (opts.verbose_name, escape(object_id)))
+
+ # Populate deleted_objects, a data structure of all related objects that
+ # will also be deleted.
+ deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), quote(object_id), escape(obj))), []]
+ perms_needed = sets.Set()
+ get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site)
+
+ if request.POST: # The user has already confirmed the deletion.
+ if perms_needed:
+ raise PermissionDenied
+ obj_display = str(obj)
+ obj.delete()
+ LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(self.model).id, object_id, obj_display, DELETION)
+ request.user.message_set.create(message=_('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)})
+ if not self.has_change_permission(request, None):
+ return HttpResponseRedirect("../../../../")
+ return HttpResponseRedirect("../../")
+
+ context = {
+ "title": _("Are you sure?"),
+ "object_name": opts.verbose_name,
+ "object": obj,
+ "deleted_objects": deleted_objects,
+ "perms_lacking": perms_needed,
+ "opts": opts,
+ "root_path": self.admin_site.root_path,
+ }
+ context.update(extra_context or {})
+ return render_to_response(self.delete_confirmation_template or [
+ "admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
+ "admin/%s/delete_confirmation.html" % app_label,
+ "admin/delete_confirmation.html"
+ ], context, context_instance=template.RequestContext(request))
+
+ def history_view(self, request, object_id, extra_context=None):
+ "The 'history' admin view for this model."
+ from django.contrib.admin.models import LogEntry
+ model = self.model
+ opts = model._meta
+ 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(model, pk=object_id)
+ context = {
+ 'title': _('Change history: %s') % force_unicode(obj),
+ 'action_list': action_list,
+ 'module_name': capfirst(opts.verbose_name_plural),
+ 'object': obj,
+ 'root_path': self.admin_site.root_path,
+ }
+ context.update(extra_context or {})
+ return render_to_response(self.object_history_template or [
+ "admin/%s/%s/object_history.html" % (opts.app_label, opts.object_name.lower()),
+ "admin/%s/object_history.html" % opts.app_label,
+ "admin/object_history.html"
+ ], context, context_instance=template.RequestContext(request))
+
+class InlineModelAdmin(BaseModelAdmin):
+ """
+ Options for inline editing of ``model`` instances.
+
+ Provide ``name`` to specify the attribute name of the ``ForeignKey`` from
+ ``model`` to its parent. This is required if ``model`` has more than one
+ ``ForeignKey`` to its parent.
+ """
+ model = None
+ fk_name = None
+ formset = BaseInlineFormset
+ extra = 3
+ max_num = 0
+ template = None
+ verbose_name = None
+ verbose_name_plural = None
+
+ def __init__(self, parent_model, admin_site):
+ self.admin_site = admin_site
+ self.parent_model = parent_model
+ self.opts = self.model._meta
+ super(InlineModelAdmin, self).__init__()
+ if self.verbose_name is None:
+ self.verbose_name = self.model._meta.verbose_name
+ if self.verbose_name_plural is None:
+ self.verbose_name_plural = self.model._meta.verbose_name_plural
+
+ def get_formset(self, request, obj=None):
+ """Returns a BaseInlineFormSet class for use in admin add/change views."""
+ if self.declared_fieldsets:
+ fields = flatten_fieldsets(self.declared_fieldsets)
+ else:
+ fields = None
+ return inlineformset_factory(self.parent_model, self.model,
+ form=self.form, formset=self.formset, fk_name=self.fk_name,
+ fields=fields, formfield_callback=self.formfield_for_dbfield,
+ extra=self.extra, max_num=self.max_num)
+
+ def get_fieldsets(self, request, obj=None):
+ if self.declared_fieldsets:
+ return self.declared_fieldsets
+ form = self.get_formset(request).form
+ return [(None, {'fields': form.base_fields.keys()})]
+
+class StackedInline(InlineModelAdmin):
+ template = 'admin/edit_inline/stacked.html'
+
+class TabularInline(InlineModelAdmin):
+ template = 'admin/edit_inline/tabular.html'
+
+class InlineAdminFormSet(object):
+ """
+ A wrapper around an inline formset for use in the admin system.
+ """
+ def __init__(self, inline, formset, fieldsets):
+ self.opts = inline
+ self.formset = formset
+ self.fieldsets = fieldsets
+
+ def __iter__(self):
+ for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()):
+ yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, original)
+ for form in self.formset.extra_forms:
+ yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, None)
+
+ def fields(self):
+ for field_name in flatten_fieldsets(self.fieldsets):
+ yield self.formset.form.base_fields[field_name]
+
+class InlineAdminForm(AdminForm):
+ """
+ A wrapper around an inline form for use in the admin system.
+ """
+ def __init__(self, formset, form, fieldsets, prepopulated_fields, original):
+ self.formset = formset
+ self.original = original
+ self.show_url = original and hasattr(original, 'get_absolute_url')
+ super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields)
+
+ def pk_field(self):
+ return AdminField(self.form, self.formset._pk_field_name, False)
+
+ def deletion_field(self):
+ from django.forms.formsets import DELETION_FIELD_NAME
+ return AdminField(self.form, DELETION_FIELD_NAME, False)
+
+ def ordering_field(self):
+ from django.forms.formsets import ORDERING_FIELD_NAME
+ return AdminField(self.form, ORDERING_FIELD_NAME, False)
+
+class AdminErrorList(forms.util.ErrorList):
+ """
+ Stores all errors for the form/formsets in an add/change stage view.
+ """
+ def __init__(self, form, inline_formsets):
+ if form.is_bound:
+ self.extend(form.errors.values())
+ for inline_formset in inline_formsets:
+ self.extend(inline_formset.non_form_errors())
+ for errors_in_inline_form in inline_formset.errors:
+ self.extend(errors_in_inline_form.values())
View
349 django/contrib/admin/sites.py
@@ -0,0 +1,349 @@
+from django import http, template
+from django.contrib.admin import ModelAdmin
+from django.contrib.auth import authenticate, login
+from django.db.models.base import ModelBase
+from django.shortcuts import render_to_response
+from django.utils.safestring import mark_safe
+from django.utils.text import capfirst
+from django.utils.translation import ugettext_lazy, ugettext as _
+from django.views.decorators.cache import never_cache
+from django.conf import settings
+import base64
+import cPickle as pickle
+import datetime
+import md5
+import re
+
+ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
+LOGIN_FORM_KEY = 'this_is_the_login_form'
+
+USER_CHANGE_PASSWORD_URL_RE = re.compile('auth/user/(\d+)/password')
+
+class AlreadyRegistered(Exception):
+ pass
+
+class NotRegistered(Exception):
+ pass
+
+def _encode_post_data(post_data):
+ from django.conf import settings
+ pickled = pickle.dumps(post_data)
+ pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
+ return base64.encodestring(pickled + pickled_md5)
+
+def _decode_post_data(encoded_data):
+ from django.conf import settings
+ encoded_data = base64.decodestring(encoded_data)
+ pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
+ 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)
+
+class AdminSite(object):
+ """
+ An AdminSite object encapsulates an instance of the Django admin application, ready
+ to be hooked in to your URLConf. Models are registered with the AdminSite using the
+ register() method, and the root() method can then be used as a Django view function
+ that presents a full admin interface for the collection of registered models.
+ """
+
+ index_template = None
+ login_template = None
+
+ def __init__(self):
+ self._registry = {} # model_class class -> admin_class instance
+
+ def register(self, model_or_iterable, admin_class=None, **options):
+ """
+ Registers the given model(s) with the given admin class.
+
+ The model(s) should be Model classes, not instances.
+
+ If an admin class isn't given, it will use ModelAdmin (the default
+ admin options). If keyword arguments are given -- e.g., list_display --
+ they'll be applied as options to the admin class.
+
+ If a model is already registered, this will raise AlreadyRegistered.
+ """
+ do_validate = admin_class and settings.DEBUG
+ if do_validate:
+ # don't import the humongous validation code unless required
+ from django.contrib.admin.validation import validate
+ admin_class = admin_class or ModelAdmin
+ # TODO: Handle options
+ if isinstance(model_or_iterable, ModelBase):
+ model_or_iterable = [model_or_iterable]
+ for model in model_or_iterable:
+ if model in self._registry:
+ raise AlreadyRegistered('The model %s is already registered' % model.__name__)
+ if do_validate:
+ validate(admin_class, model)
+ self._registry[model] = admin_class(model, self)
+
+ def unregister(self, model_or_iterable):
+ """
+ Unregisters the given model(s).
+
+ If a model isn't already registered, this will raise NotRegistered.
+ """
+ if isinstance(model_or_iterable, ModelBase):
+ model_or_iterable = [model_or_iterable]
+ for model in model_or_iterable:
+ if model not in self._registry:
+ raise NotRegistered('The model %s is not registered' % model.__name__)
+ del self._registry[model]
+
+ def has_permission(self, request):
+ """
+ Returns True if the given HttpRequest has permission to view
+ *at least one* page in the admin site.
+ """
+ return request.user.is_authenticated() and request.user.is_staff
+
+ def root(self, request, url):
+ """
+ Handles main URL routing for the admin app.
+
+ `url` is the remainder of the URL -- e.g. 'comments/comment/'.
+ """
+ if request.method == 'GET' and not request.path.endswith('/'):
+ return http.HttpResponseRedirect(request.path + '/')
+
+ # Figure out the admin base URL path and stash it for later use
+ self.root_path = re.sub(re.escape(url) + '$', '', request.path)
+
+ url = url.rstrip('/') # Trim trailing slash, if it exists.
+
+ # The 'logout' view doesn't require that the person is logged in.
+ if url == 'logout':
+ return self.logout(request)
+
+ # Check permission to continue or display login form.
+ if not self.has_permission(request):
+ return self.login(request)
+
+ if url == '':
+ return self.index(request)
+ elif url == 'password_change':
+ return self.password_change(request)
+ elif url == 'password_change/done':
+ return self.password_change_done(request)
+ elif url == 'jsi18n':
+ return self.i18n_javascript(request)
+ # urls starting with 'r/' are for the "show in web" links
+ elif url.startswith('r/'):
+ from django.views.defaults import shortcut
+ return shortcut(request, *url.split('/')[1:])
+ else:
+ match = USER_CHANGE_PASSWORD_URL_RE.match(url)
+ if match:
+ return self.user_change_password(request, match.group(1))
+
+ if '/' in url:
+ return self.model_page(request, *url.split('/', 2))
+
+ raise http.Http404('The requested admin page does not exist.')
+
+ def model_page(self, request, app_label, model_name, rest_of_url=None):
+ """
+ Handles the model-specific functionality of the admin site, delegating
+ to the appropriate ModelAdmin class.
+ """
+ from django.db import models
+ model = models.get_model(app_label, model_name)
+ if model is None:
+ raise http.Http404("App %r, model %r, not found." % (app_label, model_name))
+ try:
+ admin_obj = self._registry[model]
+ except KeyError:
+ raise http.Http404("This model exists but has not been registered with the admin site.")
+ return admin_obj(request, rest_of_url)
+ model_page = never_cache(model_page)
+
+ def password_change(self, request):
+ """
+ Handles the "change password" task -- both form display and validation.
+ """
+ from django.contrib.auth.views import password_change
+ return password_change(request)
+
+ def password_change_done(self, request):
+ """
+ Displays the "success" page after a password change.
+ """
+ from django.contrib.auth.views import password_change_done
+ return password_change_done(request)
+
+ def user_change_password(self, request, id):
+ """
+ Handles the "user change password" task
+ """
+ from django.contrib.auth.views import user_change_password
+ return user_change_password(request, id)
+
+ def i18n_javascript(self, request):
+ """
+ Displays the i18n JavaScript that the Django admin requires.
+
+ This takes into account the USE_I18N setting. If it's set to False, the
+ generated JavaScript will be leaner and faster.
+ """
+ from django.conf import settings
+ if settings.USE_I18N:
+ from django.views.i18n import javascript_catalog
+ else:
+ from django.views.i18n import null_javascript_catalog as javascript_catalog
+ return javascript_catalog(request, packages='django.conf')
+
+ def logout(self, request):
+ """
+ Logs out the user for the given HttpRequest.
+
+ This should *not* assume the user is already logged in.
+ """
+ from django.contrib.auth.views import logout
+ return logout(request)
+ logout = never_cache(logout)
+
+ def login(self, request):
+ """
+ Displays the login form for the given HttpRequest.
+ """
+ from django.contrib.auth.models import User
+
+ # If this isn't already the login page, display it.
+ if not request.POST.has_key(LOGIN_FORM_KEY):
+ if request.POST:
+ message = _("Please log in again, because your session has expired. Don't worry: Your submission has been saved.")
+ else:
+ message = ""
+ return self.display_login_form(request, message)
+
+ # Check that the user accepts cookies.
+ if not request.session.test_cookie_worked():
+ message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.")
+ return self.display_login_form(request, message)
+
+ # Check the password.
+ username = request.POST.get('username', None)
+ password = request.POST.get('password', None)
+ user = authenticate(username=username, password=password)
+ if user is None:
+ message = ERROR_MESSAGE
+ if u'@' in username:
+ # Mistakenly entered e-mail address instead of username? Look it up.
+ try:
+ user = User.objects.get(email=username)
+ except (User.DoesNotExist, User.MultipleObjectsReturned):
+ message = _("Usernames cannot contain the '@' character.")
+ else:
+ if user.check_password(password):
+ message = _("Your e-mail address is not your username."
+ " Try '%s' instead.") % user.username
+ else:
+ message = _("Usernames cannot contain the '@' character.")
+ return self.display_login_form(request, message)
+
+ # The user data is correct; log in the user in and continue.
+ else:
+ if user.is_active and user.is_staff:
+ login(request, user)
+ # TODO: set last_login with an event.
+ user.last_login = datetime.datetime.now()
+ user.save()
+ if request.POST.has_key('post_data'):
+ post_data = _decode_post_data(request.POST['post_data'])
+ if post_data and not post_data.has_key(LOGIN_FORM_KEY):
+ # overwrite request.POST with the saved post_data, and continue
+ request.POST = post_data
+ request.user = user
+ return self.root(request, request.path.split(self.root_path)[-1])
+ else:
+ request.session.delete_test_cookie()
+ return http.HttpResponseRedirect(request.path)
+ else:
+ return self.display_login_form(request, ERROR_MESSAGE)
+ login = never_cache(login)
+
+ def index(self, request, extra_context=None):
+ """
+ Displays the main admin index page, which lists all of the installed
+ apps that have been registered in this site.
+ """
+ app_dict = {}
+ user = request.user
+ for model, model_admin in self._registry.items():
+ app_label = model._meta.app_label
+ has_module_perms = user.has_module_perms(app_label)
+
+ if has_module_perms:
+ perms = {
+ 'add': model_admin.has_add_permission(request),
+ 'change': model_admin.has_change_permission(request),
+ 'delete': model_admin.has_delete_permission(request),
+ }
+
+ # Check whether user has any perm for this module.
+ # If so, add the module to the model_list.
+ if True in perms.values():
+ model_dict = {
+ 'name': capfirst(model._meta.verbose_name_plural),
+ 'admin_url': mark_safe('%s/%s/' % (app_label, model.__name__.lower())),
+ 'perms': perms,
+ }
+ if app_label in app_dict:
+ app_dict[app_label]['models'].append(model_dict)
+ else:
+ app_dict[app_label] = {
+ 'name': app_label.title(),
+ 'has_module_perms': has_module_perms,
+ 'models': [model_dict],
+ }
+
+ # Sort the apps alphabetically.
+ app_list = app_dict.values()
+ app_list.sort(lambda x, y: cmp(x['name'], y['name']))
+
+ # Sort the models alphabetically within each app.
+ for app in app_list:
+ app['models'].sort(lambda x, y: cmp(x['name'], y['name']))
+
+ context = {
+ 'title': _('Site administration'),
+ 'app_list': app_list,
+ 'root_path': self.root_path,
+ }
+ context.update(extra_context or {})
+ return render_to_response(self.index_template or 'admin/index.html', context,
+ context_instance=template.RequestContext(request)
+ )
+ index = never_cache(index)
+
+ def display_login_form(self, request, error_message='', extra_context=None):
+ request.session.set_test_cookie()
+ if request.POST and request.POST.has_key('post_data'):
+ # User has failed login BUT has previously saved post data.
+ post_data = request.POST['post_data']
+ elif request.POST:
+ # User's session must have expired; save their post data.
+ post_data = _encode_post_data(request.POST)
+ else:
+ post_data = _encode_post_data({})
+
+ context = {
+ 'title': _('Log in'),
+ 'app_path': request.path,
+ 'post_data': post_data,
+ 'error_message': error_message,
+ 'root_path': self.root_path,
+ }
+ context.update(extra_context or {})
+ return render_to_response(self.login_template or 'admin/login.html', context,
+ context_instance=template.RequestContext(request)
+ )
+
+
+# This global object represents the default admin site, for the common case.
+# You can instantiate AdminSite in your own code to create a custom admin site.
+site = AdminSite()
View
13 django/contrib/admin/templates/admin/auth/user/add_form.html
@@ -8,21 +8,26 @@
<fieldset class="module aligned">
<div class="form-row">
- {{ form.username.html_error_list }}
+ {{ form.username.errors }}
+ {# TODO: get required class on label_tag #}
<label for="id_username" class="required">{% trans 'Username' %}:</label> {{ form.username }}
- <p class="help">{{ username_help_text }}</p>
+ <p class="help">{{ form.username.help_text }}</p>
</div>
<div class="form-row">
- {{ form.password1.html_error_list }}
+ {{ form.password1.errors }}
+ {# TODO: get required class on label_tag #}
<label for="id_password1" class="required">{% trans 'Password' %}:</label> {{ form.password1 }}
</div>
<div class="form-row">
- {{ form.password2.html_error_list }}
+ {{ form.password2.errors }}
+ {# TODO: get required class on label_tag #}
<label for="id_password2" class="required">{% trans 'Password (again)' %}:</label> {{ form.password2 }}
<p class="help">{% trans 'Enter the same password as above, for verification.' %}</p>
</div>
+<script type="text/javascript">document.getElementById("id_username").focus();</script>
+
</fieldset>
{% endblock %}
View
13 django/contrib/admin/templates/admin/auth/user/change_password.html
@@ -2,7 +2,6 @@
{% load i18n admin_modify adminmedia %}
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="../../../../jsi18n/"></script>
-{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %}
{% endblock %}
{% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %}
{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
@@ -18,9 +17,9 @@
<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
<div>
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
-{% if form.error_dict %}
+{% if form.errors %}
<p class="errornote">
- {% blocktrans count form.error_dict.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
+ {% blocktrans count form.errors.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
</p>
{% endif %}
@@ -29,12 +28,14 @@
<fieldset class="module aligned">
<div class="form-row">
- {{ form.password1.html_error_list }}
+ {{ form.password1.errors }}
+ {# TODO: get required class on label_tag #}
<label for="id_password1" class="required">{% trans 'Password' %}:</label> {{ form.password1 }}
</div>
<div class="form-row">
- {{ form.password2.html_error_list }}
+ {{ form.password2.errors }}
+ {# TODO: get required class on label_tag #}
<label for="id_password2" class="required">{% trans 'Password (again)' %}:</label> {{ form.password2 }}
<p class="help">{% trans 'Enter the same password as above, for verification.' %}</p>
</div>
@@ -45,7 +46,7 @@
<input type="submit" value="{% trans 'Change password' %}" class="default" />
</div>
-<script type="text/javascript">document.getElementById("{{ first_form_field_id }}").focus();</script>
+<script type="text/javascript">document.getElementById("id_password1").focus();</script>
</div>
</form></div>
{% endblock %}
View
9 django/contrib/admin/templates/admin/base.html
@@ -22,14 +22,7 @@
{% block branding %}{% endblock %}
</div>
{% if user.is_authenticated and user.is_staff %}
- <div id="user-tools">
- {% trans 'Welcome,' %} <strong>{% if user.first_name %}{{ user.first_name|escape }}{% else %}{{ user.username }}{% endif %}</strong>.
- {% block userlinks %}
- <a href="{% url django.contrib.admin.views.doc.doc_index %}">{% trans 'Documentation' %}</a>
- / <a href="{% url django.contrib.auth.views.password_change %}">{% trans 'Change password' %}</a>
- / <a href="{% url django.contrib.auth.views.logout %}">{% trans 'Log out' %}</a>
- {% endblock %}
- </div>
+ <div id="user-tools">{% trans 'Welcome,' %} <strong>{% if user.first_name %}{{ user.first_name|escape }}{% else %}{{ user.username }}{% endif %}</strong>. {% block userlinks %}<a href="{{ root_path }}doc/">{% trans 'Documentation' %}</a> / <a href="{{ root_path }}password_change/">{% trans 'Change password' %}</a> / <a href="{{ root_path }}logout/">{% trans 'Log out' %}</a>{% endblock %}</div>
{% endif %}
{% block nav-global %}{% endblock %}
</div>
View
69 django/contrib/admin/templates/admin/change_form.html
@@ -1,12 +1,17 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_modify adminmedia %}
+
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="../../../jsi18n/"></script>
-{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %}
+{{ media }}
{% 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 breadcrumbs %}{% if not is_popup %}
<div class="breadcrumbs">
<a href="../../../">{% trans "Home" %}</a> &rsaquo;
@@ -14,6 +19,7 @@
{% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
</div>
{% endif %}{% endblock %}
+
{% block content %}<div id="content-main">
{% block object-tools %}
{% if change %}{% if not is_popup %}
@@ -25,45 +31,48 @@
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
<div>
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
-{% if opts.admin.save_on_top %}{% submit_row %}{% endif %}
-{% if form.error_dict %}
+{% if save_on_top %}{% submit_row %}{% endif %}
+{% if errors %}
<p class="errornote">
- {% blocktrans count form.error_dict.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
+ {% blocktrans count errors.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
</p>
+ <ul class="errorlist">{% for error in adminform.form.non_field_errors %}<li>{{ error }}</li>{% endfor %}</ul>
{% endif %}
-{% 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|safe }}</div>{% endif %}
- {% for bound_field_line in bound_field_set %}
- {% admin_field_line bound_field_line %}
- {% for bound_field in bound_field_line %}
- {% filter_interface_script_maybe bound_field %}
- {% endfor %}
- {% endfor %}
- </fieldset>
+
+{% for fieldset in adminform %}
+ {% include "admin/includes/fieldset.html" %}
{% endfor %}
+
{% block after_field_sets %}{% endblock %}
-{% if change %}
- {% 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 %}
- <p><label for="id_order_">{% trans "Order:" %}</label> {{ form.order_ }}</p>
- </div></fieldset>
- {% endif %}
-{% endif %}
-{% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %}
+
+{% for inline_admin_formset in inline_admin_formsets %}
+ {% include inline_admin_formset.opts.template %}
+{% endfor %}
+
{% block after_related_objects %}{% endblock %}
+
{% submit_row %}
+
{% if add %}
- <script type="text/javascript">document.getElementById("{{ first_form_field_id }}").focus();</script>
+ <script type="text/javascript">document.getElementById("{{ adminform.first_field.auto_id }}").focus();</script>
{% endif %}
-{% if auto_populated_fields %}
- <script type="text/javascript">
- {% auto_populated_field_script auto_populated_fields change %}
- </script>
+
+{# JavaScript for prepopulated fields #}
+
+{% if add %}
+<script type="text/javascript">
+{% for field in adminform.prepopulated_fields %}
+ document.getElementById("{{ field.field.auto_id }}").onchange = function() { this._changed = true; };
+ {% for dependency in field.dependencies %}
+ document.getElementById("{{ dependency.auto_id }}").onkeyup = function() {
+ var e = document.getElementById("{{ field.field.auto_id }}");
+ if (!e._changed) { e.value = URLify({% for innerdep in field.dependencies %}document.getElementById("{{ innerdep.auto_id }}").value{% if not forloop.last %} + ' ' + {% endif %}{% endfor %}, {{ field.field.field.max_length }}); }
+ }
+ {% endfor %}
+{% endfor %}
+</script>
{% endif %}
+
</div>
</form></div>
{% endblock %}
View
18 django/contrib/admin/templates/admin/change_list.html
@@ -1,9 +1,14 @@
{% extends "admin/base_site.html" %}
{% load adminmedia admin_list i18n %}
+
{% block stylesheet %}{% admin_media_prefix %}css/changelists.css{% endblock %}
+
{% block bodyclass %}change-list{% endblock %}
+
{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; {{ cl.opts.verbose_name_plural|capfirst|escape }}</div>{% endblock %}{% endif %}
+
{% block coltype %}flex{% endblock %}
+
{% block content %}
<div id="content-main">
{% block object-tools %}
@@ -14,7 +19,18 @@
<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
{% block search %}{% search_form cl %}{% endblock %}
{% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %}
-{% block filters %}{% filters cl %}{% endblock %}
+
+{% block filters %}
+{% if cl.has_filters %}
+<div id="changelist-filter">
+<h2>{% trans 'Filter' %}</h2>
+{% for spec in cl.filter_specs %}
+ {% admin_list_filter cl spec %}
+{% endfor %}
+</div>
+{% endif %}
+{% endblock %}
+
{% block result_list %}{% result_list cl %}{% endblock %}
{% block pagination %}{% pagination cl %}{% endblock %}
</div>
View
2  django/contrib/admin/templates/admin/delete_confirmation.html
@@ -1,5 +1,6 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
+
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="../../../../">{% trans "Home" %}</a> &rsaquo;
@@ -8,6 +9,7 @@
{% trans 'Delete' %}
</div>
{% endblock %}
+
{% block content %}
{% if perms_lacking %}
<p>{% blocktrans with object|escape as escaped_object %}Deleting the {{ object_name }} '{{ escaped_object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}</p>
View
26 django/contrib/admin/templates/admin/edit_inline/stacked.html
@@ -0,0 +1,26 @@
+{% load i18n %}
+<div class="inline-group">
+{{ inline_admin_formset.formset.management_form }}
+{# <h3 class="header">{{ inline_admin_formset.opts.verbose_name_plural|title }}</h3> #}
+{{ inline_admin_formset.formset.non_form_errors }}
+
+{% for inline_admin_form in inline_admin_formset %}
+<div class="inline-related {% if forloop.last %}last-related{% endif %}">
+ <h2><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>&nbsp;{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %} #{{ forloop.counter }}{% endif %}
+ {% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
+ </h2>
+ {% if inline_admin_form.show_url %}
+ <p><a href="/r/{{ inline_admin_form.original.content_type_id }}/{{ inline_admin_form.original.id }}/">View on site</a></p>
+ {% endif %}
+
+ {% for fieldset in inline_admin_form %}
+ {% include "admin/includes/fieldset.html" %}
+ {% endfor %}
+ {{ inline_admin_form.pk_field.field }}
+</div>
+{% endfor %}
+
+{# <ul class="tools"> #}
+{# <li><a class="add" href="">Add another {{ inline_admin_formset.opts.verbose_name|title }}</a></li> #}
+{# </ul> #}
+</div>
View
64 django/contrib/admin/templates/admin/edit_inline/tabular.html
@@ -0,0 +1,64 @@
+{% load i18n %}
+<div class="inline-group">
+ <div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
+{{ inline_admin_formset.formset.management_form }}
+<fieldset class="module">
+ <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst|escape }}</h2>
+ {{ inline_admin_formset.formset.non_form_errors }}
+ <table>
+ <thead><tr>
+ {% for field in inline_admin_formset.fields %}
+ {% if not field.is_hidden %}
+ <th {% if forloop.first %}colspan="2"{% endif %}>{{ field.label|capfirst|escape }}</th>
+ {% endif %}
+ {% endfor %}
+ {% if inline_admin_formset.formset.can_delete %}<th>{% trans "Delete" %}?</th>{% endif %}
+ </tr></thead>
+
+ {% for inline_admin_form in inline_admin_formset %}
+
+ <tr class="{% cycle row1,row2 %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}">
+
+ <td class="original">{% if inline_admin_form.original or inline_admin_form.show_url %}<p>
+ {% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %}
+ {% if inline_admin_form.show_url %}<a href="/r/{{ inline_admin_form.original.content_type_id }}/{{ inline_admin_form.original.id }}/">View on site</a>{% endif %}
+ </p>{% endif %}
+ {{ inline_admin_form.pk_field.field }}
+ {% spaceless %}
+ {% for fieldset in inline_admin_form %}
+ {% for line in fieldset %}
+ {% for field in line %}
+ {% if field.is_hidden %} {{ field.field }} {% endif %}
+ {% endfor %}