Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge remote-tracking branch 'core/master' into schema-alteration

Conflicts:
	django/db/models/fields/related.py
  • Loading branch information...
commit 3c296382b8dea5de7f4e1e11b66bd7cecaf2ee51 2 parents 7609e0b + 357d62d
@andrewgodwin andrewgodwin authored
Showing with 6,377 additions and 3,076 deletions.
  1. +28 −5 AUTHORS
  2. +1 −1  django/__init__.py
  3. +4 −1 django/conf/__init__.py
  4. +1 −1  django/conf/global_settings.py
  5. +100 −88 django/conf/locale/en/LC_MESSAGES/django.po
  6. +2 −1  django/conf/urls/__init__.py
  7. +6 −0 django/contrib/admin/exceptions.py
  8. +1 −1  django/contrib/admin/filters.py
  9. +2 −2 django/contrib/admin/helpers.py
  10. +43 −38 django/contrib/admin/locale/en/LC_MESSAGES/django.po
  11. +69 −16 django/contrib/admin/options.py
  12. +2 −8 django/contrib/admin/sites.py
  13. +1 −1  django/contrib/admin/templates/admin/auth/user/change_password.html
  14. +1 −1  django/contrib/admin/templates/admin/change_form.html
  15. +1 −1  django/contrib/admin/templates/admin/change_list.html
  16. +1 −1  django/contrib/admin/templates/admin/includes/fieldset.html
  17. +1 −1  django/contrib/admin/templates/admin/login.html
  18. +1 −1  django/contrib/admin/templates/registration/password_change_form.html
  19. +1 −1  django/contrib/admin/templates/registration/password_reset_email.html
  20. +1 −1  django/contrib/admin/templatetags/admin_list.py
  21. +1 −1  django/contrib/admin/templatetags/log.py
  22. +10 −4 django/contrib/admin/util.py
  23. +397 −362 django/contrib/admin/validation.py
  24. +32 −43 django/contrib/admin/views/main.py
  25. +192 −13 django/contrib/admindocs/locale/en/LC_MESSAGES/django.po
  26. +23 −0 django/contrib/admindocs/middleware.py
  27. +12 −12 django/contrib/admindocs/templates/admin_doc/index.html
  28. +4 −4 django/contrib/admindocs/templates/admin_doc/missing_docutils.html
  29. +5 −5 django/contrib/admindocs/templates/admin_doc/model_detail.html
  30. +3 −3 django/contrib/admindocs/templates/admin_doc/model_index.html
  31. +5 −5 django/contrib/admindocs/templates/admin_doc/template_detail.html
  32. +5 −5 django/contrib/admindocs/templates/admin_doc/template_filter_index.html
  33. +5 −5 django/contrib/admindocs/templates/admin_doc/template_tag_index.html
  34. +4 −4 django/contrib/admindocs/templates/admin_doc/view_detail.html
  35. +5 −7 django/contrib/admindocs/templates/admin_doc/view_index.html
  36. +3 −2 django/contrib/admindocs/views.py
  37. +8 −5 django/contrib/auth/__init__.py
  38. +1 −1  django/contrib/auth/forms.py
  39. +1 −1  django/contrib/auth/management/__init__.py
  40. +1 −1  django/contrib/auth/models.py
  41. +52 −1 django/contrib/auth/tests/test_auth_backends.py
  42. +1 −1  django/contrib/auth/tests/test_custom_user.py
  43. +2 −0  django/contrib/auth/tests/test_management.py
  44. +222 −79 django/contrib/auth/tests/test_views.py
  45. +11 −0 django/contrib/auth/tests/urls.py
  46. +12 −3 django/contrib/auth/views.py
  47. +6 −4 django/contrib/comments/locale/en/LC_MESSAGES/django.po
  48. +1 −1  django/contrib/comments/templates/comments/preview.html
  49. +25 −12 django/contrib/contenttypes/generic.py
  50. +4 −3 django/contrib/contenttypes/models.py
  51. +7 −0 django/contrib/contenttypes/tests.py
  52. +0 −2  django/contrib/flatpages/views.py
  53. +6 −0 django/contrib/formtools/exceptions.py
  54. +2 −2 django/contrib/formtools/wizard/storage/cookie.py
  55. +35 −46 django/contrib/gis/db/backends/postgis/operations.py
  56. +5 −5 django/contrib/gis/geos/geometry.py
  57. +1 −1  django/contrib/gis/utils/layermapping.py
  58. +21 −15 django/contrib/humanize/locale/en/LC_MESSAGES/django.po
  59. +12 −6 django/contrib/humanize/templatetags/humanize.py
  60. +16 −14 django/contrib/humanize/tests.py
  61. +11 −3 django/contrib/sessions/backends/base.py
  62. +8 −1 django/contrib/sessions/backends/cached_db.py
  63. +8 −2 django/contrib/sessions/backends/db.py
  64. +11 −3 django/contrib/sessions/backends/file.py
  65. +11 −0 django/contrib/sessions/exceptions.py
  66. +26 −7 django/contrib/sessions/tests.py
  67. +1 −1  django/contrib/sitemaps/__init__.py
  68. +1 −1  django/contrib/staticfiles/finders.py
  69. +3 −5 django/contrib/staticfiles/management/commands/collectstatic.py
  70. +7 −3 django/core/cache/backends/base.py
  71. +9 −6 django/core/cache/backends/db.py
  72. +4 −4 django/core/cache/backends/dummy.py
  73. +8 −7 django/core/cache/backends/filebased.py
  74. +9 −10 django/core/cache/backends/locmem.py
  75. +16 −6 django/core/cache/backends/memcached.py
  76. +27 −7 django/core/exceptions.py
  77. +1 −1  django/core/files/locks.py
  78. +3 −3 django/core/files/move.py
  79. +2 −2 django/core/files/storage.py
  80. +23 −6 django/core/handlers/base.py
  81. +4 −63 django/core/handlers/wsgi.py
  82. +7 −7 django/core/management/base.py
  83. +2 −2 django/core/management/commands/createcachetable.py
  84. +20 −2 django/core/management/commands/dumpdata.py
  85. +1 −1  django/core/management/commands/flush.py
  86. +168 −133 django/core/management/commands/loaddata.py
  87. +3 −16 django/core/management/commands/makemessages.py
  88. +1 −1  django/core/management/commands/runserver.py
  89. +5 −1 django/core/management/commands/syncdb.py
  90. +1 −1  django/core/management/commands/test.py
  91. +16 −2 django/core/management/sql.py
  92. +4 −2 django/core/management/validation.py
  93. +3 −1 django/core/paginator.py
  94. +4 −2 django/core/urlresolvers.py
  95. +0 −24 django/core/xheaders.py
  96. +25 −9 django/db/__init__.py
  97. +8 −7 django/db/backends/__init__.py
  98. +1 −1  django/db/backends/creation.py
  99. +1 −2  django/db/backends/mysql/compiler.py
  100. +18 −4 django/db/backends/oracle/base.py
  101. +2 −1  django/db/backends/oracle/creation.py
  102. +16 −1 django/db/backends/oracle/introspection.py
  103. +2 −2 django/db/backends/postgresql_psycopg2/operations.py
  104. +1 −1  django/db/backends/util.py
  105. +1 −1  django/db/models/__init__.py
  106. +11 −11 django/db/models/base.py
  107. +2 −0  django/db/models/fields/__init__.py
  108. +8 −10 django/db/models/fields/related.py
  109. +21 −1 django/db/models/loading.py
  110. +6 −0 django/db/models/manager.py
  111. +84 −153 django/db/models/query.py
  112. +1 −0  django/db/models/signals.py
  113. +1 −1  django/db/models/sql/aggregates.py
  114. +3 −3 django/db/models/sql/compiler.py
  115. +56 −49 django/db/models/sql/query.py
  116. +18 −8 django/db/models/sql/where.py
  117. +17 −0 django/db/transaction.py
  118. +33 −12 django/db/utils.py
  119. +20 −2 django/forms/fields.py
  120. +7 −4 django/forms/forms.py
  121. +9 −5 django/forms/formsets.py
  122. +34 −18 django/forms/models.py
  123. +8 −7 django/http/multipartparser.py
  124. +10 −4 django/http/request.py
  125. +69 −5 django/http/response.py
  126. +2 −2 django/http/utils.py
  127. +6 −5 django/middleware/cache.py
  128. +18 −11 django/middleware/common.py
  129. +15 −27 django/middleware/csrf.py
  130. +5 −22 django/middleware/doc.py
  131. +7 −2 django/middleware/locale.py
  132. +16 −12 django/template/defaultfilters.py
  133. +12 −6 django/test/client.py
  134. +68 −2 django/test/simple.py
  135. +353 −344 django/test/testcases.py
  136. +51 −0 django/test/utils.py
  137. +1 −1  django/utils/_os.py
  138. +2 −6 django/utils/crypto.py
  139. +14 −5 django/utils/functional.py
  140. +33 −2 django/utils/html.py
  141. +5 −2 django/utils/http.py
  142. +1 −1  django/utils/image.py
  143. +1 −2  django/utils/ipv6.py
  144. +7 −2 django/utils/log.py
  145. +7 −3 django/utils/safestring.py
  146. +4 −3 django/utils/timesince.py
  147. +44 −28 django/utils/translation/trans_real.py
  148. +25 −12 django/views/debug.py
  149. +1 −1  django/views/decorators/csrf.py
  150. +15 −0 django/views/defaults.py
  151. +4 −1 django/views/generic/base.py
  152. +6 −4 django/views/generic/detail.py
  153. +0 −14 django/views/generic/edit.py
  154. +2 −2 django/views/generic/list.py
  155. +37 −31 django/views/i18n.py
  156. +1 −1  docs/_ext/djangodocs.py
  157. +1 −1  docs/conf.py
  158. +0 −6 docs/faq/admin.txt
  159. +36 −15 docs/howto/custom-template-tags.txt
  160. +19 −3 docs/howto/deployment/checklist.txt
  161. +0 −1  docs/howto/deployment/wsgi/uwsgi.txt
  162. +5 −0 docs/howto/error-reporting.txt
  163. +1 −0  docs/howto/index.txt
  164. +14 −2 docs/howto/static-files/index.txt
  165. +91 −0 docs/howto/upgrade-version.txt
  166. +98 −99 docs/internals/committers.txt
  167. +3 −2 docs/internals/contributing/triaging-tickets.txt
  168. +7 −13 docs/internals/contributing/writing-code/unit-tests.txt
  169. +10 −4 docs/internals/deprecation.txt
  170. +45 −31 docs/internals/git.txt
  171. +41 −8 docs/internals/howto-release-django.txt
  172. +36 −40 docs/internals/release-process.txt
  173. +25 −15 docs/intro/overview.txt
  174. +2 −0  docs/intro/tutorial01.txt
  175. +16 −0 docs/intro/tutorial02.txt
  176. +6 −1 docs/intro/tutorial03.txt
  177. +2 −0  docs/intro/tutorial04.txt
  178. +2 −2 docs/intro/tutorial05.txt
  179. +2 −3 docs/ref/class-based-views/base.txt
  180. +0 −7 docs/ref/class-based-views/generic-date-based.txt
  181. +0 −1  docs/ref/class-based-views/generic-display.txt
  182. +0 −4 docs/ref/class-based-views/generic-editing.txt
  183. +1 −1  docs/ref/class-based-views/index.txt
  184. +1 −1  docs/ref/class-based-views/mixins-editing.txt
  185. +11 −0 docs/ref/class-based-views/mixins-simple.txt
  186. +5 −6 docs/ref/contrib/admin/admindocs.txt
  187. +181 −18 docs/ref/contrib/admin/index.txt
  188. +17 −2 docs/ref/contrib/contenttypes.txt
  189. +2 −1  docs/ref/contrib/csrf.txt
  190. +1 −0  docs/ref/contrib/formtools/form-preview.txt
  191. +9 −2 docs/ref/contrib/formtools/form-wizard.txt
  192. +1 −3 docs/ref/contrib/gis/geoip.txt
  193. +8 −2 docs/ref/contrib/gis/install/create_template_postgis-1.5.sh
  194. +0 −1  docs/ref/contrib/messages.txt
  195. +40 −0 docs/ref/contrib/sitemaps.txt
  196. +19 −15 docs/ref/contrib/syndication.txt
  197. +7 −6 docs/ref/databases.txt
  198. +11 −0 docs/ref/django-admin.txt
  199. +25 −4 docs/ref/exceptions.txt
  200. +53 −48 docs/ref/forms/api.txt
  201. +8 −0 docs/ref/forms/fields.txt
  202. +11 −7 docs/ref/forms/models.txt
  203. +9 −0 docs/ref/forms/validation.txt
  204. +3 −0  docs/ref/forms/widgets.txt
  205. +0 −13 docs/ref/middleware.txt
  206. +25 −7 docs/ref/models/fields.txt
  207. +10 −0 docs/ref/models/instances.txt
  208. +6 −0 docs/ref/models/options.txt
  209. +90 −2 docs/ref/models/querysets.txt
  210. +6 −2 docs/ref/models/relations.txt
  211. +36 −14 docs/ref/request-response.txt
  212. +14 −19 docs/ref/settings.txt
  213. +47 −0 docs/ref/signals.txt
  214. +3 −0  docs/ref/template-response.txt
  215. +7 −0 docs/ref/templates/api.txt
  216. +2 −0  docs/ref/templates/builtins.txt
  217. +23 −5 docs/ref/utils.txt
  218. +1 −1  docs/releases/1.3-alpha-1.txt
  219. +1 −1  docs/releases/1.3.txt
  220. +4 −4 docs/releases/1.4.txt
  221. +226 −43 docs/releases/1.6.txt
  222. +5 −4 docs/releases/index.txt
  223. +6 −2 docs/topics/auth/customizing.txt
  224. +21 −20 docs/topics/cache.txt
  225. +4 −2 docs/topics/class-based-views/generic-display.txt
  226. +1 −0  docs/topics/class-based-views/generic-editing.txt
  227. +8 −0 docs/topics/class-based-views/mixins.txt
  228. +24 −16 docs/topics/db/aggregation.txt
  229. +14 −8 docs/topics/db/managers.txt
  230. +44 −8 docs/topics/db/models.txt
  231. +44 −4 docs/topics/db/queries.txt
  232. +34 −28 docs/topics/db/transactions.txt
  233. +2 −0  docs/topics/files.txt
  234. +28 −1 docs/topics/forms/formsets.txt
  235. +5 −0 docs/topics/forms/media.txt
  236. +68 −1 docs/topics/forms/modelforms.txt
  237. +2 −0  docs/topics/http/file-uploads.txt
  238. +18 −0 docs/topics/http/sessions.txt
  239. +31 −0 docs/topics/http/urls.txt
  240. +28 −0 docs/topics/http/views.txt
  241. +2 −0  docs/topics/i18n/timezones.txt
  242. +42 −6 docs/topics/i18n/translation.txt
  243. +30 −1 docs/topics/logging.txt
  244. +2 −2 docs/topics/python3.txt
  245. +3 −1 docs/topics/testing/advanced.txt
  246. +178 −97 docs/topics/testing/overview.txt
  247. +1 −1  setup.py
  248. +5 −0 tests/admin_changelist/admin.py
  249. +66 −4 tests/admin_changelist/tests.py
  250. 0  tests/{special_headers → admin_docs}/__init__.py
  251. +0 −3  tests/{special_headers → admin_docs}/fixtures/data.xml
  252. 0  tests/admin_docs/models.py
  253. +45 −0 tests/admin_docs/tests.py
  254. +11 −0 tests/admin_docs/urls.py
  255. +1 −9 tests/{special_headers → admin_docs}/views.py
  256. +17 −0 tests/admin_inlines/admin.py
  257. +6 −0 tests/admin_inlines/models.py
  258. +19 −1 tests/admin_inlines/tests.py
  259. +30 −9 tests/admin_scripts/tests.py
  260. +25 −12 tests/admin_util/tests.py
  261. +43 −37 tests/admin_validation/tests.py
  262. +33 −4 tests/admin_views/admin.py
  263. +9 −0 tests/admin_views/models.py
  264. +113 −23 tests/admin_views/tests.py
  265. +12 −0 tests/admin_widgets/tests.py
  266. +7 −1 tests/aggregation_regress/tests.py
  267. +7 −4 tests/backends/tests.py
  268. +3 −1 tests/base/models.py
  269. +8 −0 tests/basic/models.py
  270. +15 −19 tests/basic/tests.py
  271. +37 −7 tests/cache/tests.py
  272. +6 −0 tests/commands_sql/models.py
  273. +19 −17 tests/commands_sql/tests.py
  274. +1 −1  tests/comment_tests/tests/test_comment_form.py
  275. +1 −1  tests/comment_tests/tests/test_templatetags.py
  276. +52 −12 tests/csrf_tests/tests.py
  277. +2 −2 tests/custom_managers/tests.py
  278. +3 −3 tests/datatypes/tests.py
  279. +5 −5 tests/decorators/tests.py
  280. +51 −43 tests/defaultfilters/tests.py
  281. +19 −0 tests/defer_regress/models.py
  282. +66 −58 tests/defer_regress/tests.py
  283. +1 −1  tests/dispatch/tests/test_dispatcher.py
  284. +1 −1  tests/field_defaults/tests.py
  285. +5 −5 tests/field_subclassing/tests.py
  286. +3 −3 tests/file_storage/tests.py
  287. +61 −7 tests/fixtures/tests.py
  288. +8 −1 tests/fixtures_model_package/tests.py
  289. +10 −8 tests/fixtures_regress/tests.py
  290. +5 −0 tests/foreign_object/tests.py
  291. +8 −0 tests/forms_tests/tests/test_extra.py
  292. +9 −0 tests/forms_tests/tests/test_fields.py
  293. +18 −1 tests/forms_tests/tests/test_forms.py
  294. +55 −1 tests/forms_tests/tests/test_formsets.py
  295. +2 −1  tests/forms_tests/tests/test_regressions.py
  296. +39 −3 tests/forms_tests/tests/tests.py
  297. +19 −0 tests/generic_relations/models.py
  298. +133 −7 tests/generic_relations/tests.py
  299. +33 −0 tests/generic_relations_regress/models.py
  300. +90 −13 tests/generic_relations_regress/tests.py
Sorry, we could not display the entire diff because too many files (383) changed.
View
33 AUTHORS
@@ -12,18 +12,25 @@ The PRIMARY AUTHORS are (and/or have been):
* Luke Plant
* Russell Keith-Magee
* Robert Wittams
+ * James Bennett
* Gary Wilson
+ * Matt Boersma
+ * Ian Kelly
+ * Joseph Kocherhans
* Brian Rosner
* Justin Bronn
* Karen Tracey
* Jannis Leidel
* James Tauber
* Alex Gaynor
+ * Simon Meers
* Andrew Godwin
* Carl Meyer
* Ramiro Morales
+ * Gabriel Hurley
* Chris Beaven
* Honza Král
+ * Tim Graham
* Idan Gazit
* Paul McMillan
* Julien Phalip
@@ -36,6 +43,7 @@ The PRIMARY AUTHORS are (and/or have been):
* Preston Holmes
* Simon Charette
* Donald Stufft
+ * Daniel Lindsley
* Marc Tamlyn
More information on the main contributors to Django can be found in
@@ -84,14 +92,15 @@ answer newbie questions, and generally made Django that much better:
Randy Barlow <randy@electronsweatshop.com>
Scott Barr <scott@divisionbyzero.com.au>
Jiri Barton
+ Jorge Bastida <me@jorgebastida.com>
Ned Batchelder <http://www.nedbatchelder.com/>
batiste@dosimple.ch
Batman
Brian Beck <http://blog.brianbeck.com/>
Shannon -jj Behrens <http://jjinux.blogspot.com/>
Esdras Beleza <linux@esdrasbeleza.com>
+ Božidar Benko <bbenko@gmail.com>
Chris Bennett <chrisrbennett@yahoo.com>
- James Bennett
Danilo Bargen
Shai Berger <shai@platonix.com>
berto
@@ -102,9 +111,9 @@ answer newbie questions, and generally made Django that much better:
Paul Bissex <http://e-scribe.com/>
Loïc Bistuer <loic.bistuer@sixmedia.com>
Simon Blanchard
+ Jérémie Blaser <blaserje@gmail.com>
Craig Blaszczyk <masterjakul@gmail.com>
David Blewett <david@dawninglight.net>
- Matt Boersma <matt@sprout.org>
Artem Gnilov <boobsd@gmail.com>
Matías Bordese
Nate Bragg <jonathan.bragg@alum.rpi.edu>
@@ -117,6 +126,7 @@ answer newbie questions, and generally made Django that much better:
bthomas
btoll@bestweb.net
Jonathan Buchanan <jonathan.buchanan@gmail.com>
+ Jacob Burch <jacobburch@gmail.com>
Keith Bussell <kbussell@gmail.com>
C8E
Chris Cahoon <chris.cahoon@gmail.com>
@@ -149,6 +159,7 @@ answer newbie questions, and generally made Django that much better:
Paul Collins <paul.collins.iii@gmail.com>
Robert Coup
Deric Crago <deric.crago@gmail.com>
+ Brian Fabian Crain <http://www.bfc.do/>
David Cramer <dcramer@gmail.com>
Pete Crosier <pete.crosier@gmail.com>
Matt Croydon <http://www.postneo.com/>
@@ -156,6 +167,7 @@ answer newbie questions, and generally made Django that much better:
Leah Culver <leah.culver@gmail.com>
Raúl Cumplido <raulcumplido@gmail.com>
flavio.curella@gmail.com
+ Tome Cvitan <tome@cvitan.com>
John D'Agostino <john.dagostino@gmail.com>
dackze+django@gmail.com
Jim Dalton <jim.dalton@gmail.com>
@@ -188,6 +200,7 @@ answer newbie questions, and generally made Django that much better:
J. Clifford Dyer <jcd@sdf.lonestar.org>
Clint Ecker
Nick Efford <nick@efford.org>
+ Marc Egli <frog32@me.com>
eibaan@gmail.com
David Eklund
Julia Elman
@@ -212,6 +225,7 @@ answer newbie questions, and generally made Django that much better:
Stefane Fermgier <sf@fermigier.com>
J. Pablo Fernandez <pupeno@pupeno.com>
Maciej Fijalkowski
+ Leandra Finger <leandra.finger@gmail.com>
Juan Pedro Fisanotti <fisadev@gmail.com>
Ben Firshman <ben@firshman.co.uk>
Matthew Flanagan <http://wadofstuff.blogspot.com>
@@ -239,6 +253,7 @@ answer newbie questions, and generally made Django that much better:
pradeep.gowda@gmail.com
Collin Grady <collin@collingrady.com>
Gabriel Grant <g@briel.ca>
+ Martin Green
Daniel Greenfeld
Simon Greenhill <dev@simon.net.nz>
Owen Griffiths
@@ -268,6 +283,7 @@ answer newbie questions, and generally made Django that much better:
Eric Holscher <http://ericholscher.com>
Ian Holsman <http://feh.holsman.net/>
Kieran Holland <http://www.kieranholland.com>
+ Markus Holtermann <http://markusholtermann.eu>
Sung-Jin Hong <serialx.net@gmail.com>
Leo "hylje" Honkanen <sealage@gmail.com>
Matt Hoskins <skaffenuk@googlemail.com>
@@ -278,7 +294,6 @@ answer newbie questions, and generally made Django that much better:
Rob Hudson <http://rob.cogit8.org/>
Jason Huggins <http://www.jrandolph.com/blog/>
Jeff Hui <jeffkhui@gmail.com>
- Gabriel Hurley <gabriel@strikeawe.com>
Hyun Mi Ae
Ibon <ibonso@gmail.com>
Tom Insam
@@ -327,12 +342,12 @@ answer newbie questions, and generally made Django that much better:
Meir Kriheli <http://mksoft.co.il/>
Bruce Kroeze <http://coderseye.com/>
krzysiek.pawlik@silvermedia.pl
- Joseph Kocherhans
konrad@gwu.edu
knox <christobzr@gmail.com>
David Krauth
Kevin Kubasik <kevin@kubasik.net>
kurtiss@meetro.com
+ Vladimir Kuzma <vladimirkuzma.ch@gmail.com>
Denis Kuzmichyov <kuzmichyov@gmail.com>
Panos Laganakos <panos.laganakos@gmail.com>
Nick Lane <nick.lane.au@gmail.com>
@@ -360,7 +375,6 @@ answer newbie questions, and generally made Django that much better:
limodou
Philip Lindborg <philip.lindborg@gmail.com>
Simon Litchfield <simon@quo.com.au>
- Daniel Lindsley <daniel@toastdriven.com>
Trey Long <trey@ktrl.com>
Laurent Luce <http://www.laurentluce.com>
Martin Mahner <http://www.mahner.org/>
@@ -399,6 +413,7 @@ answer newbie questions, and generally made Django that much better:
Slawek Mikula <slawek dot mikula at gmail dot com>
Katie Miller <katie@sub50.com>
Shawn Milochik <shawn@milochik.com>
+ Baptiste Mispelon <bmispelon@gmail.com>
mitakummaa@gmail.com
Taylor Mitchell <taylor.mitchell@gmail.com>
mmarshall
@@ -458,6 +473,7 @@ answer newbie questions, and generally made Django that much better:
Jyrki Pulliainen <jyrki.pulliainen@gmail.com>
Thejaswi Puthraya <thejaswi.puthraya@gmail.com>
Johann Queuniet <johann.queuniet@adh.naellia.eu>
+ Ram Rachum <ram@rachum.com>
Jan Rademaker
Michael Radziej <mir@noris.de>
Laurent Rahuel <laurent.rahuel@gmail.com>
@@ -465,6 +481,7 @@ answer newbie questions, and generally made Django that much better:
Luciano Ramalho
Amit Ramon <amit.ramon@gmail.com>
Philippe Raoult <philippe.raoult@n2nsoft.com>
+ Senko Rašić <senko.rasic@dobarkod.hr>
Massimiliano Ravelli <massimiliano.ravelli@gmail.com>
Brian Ray <http://brianray.chipy.org/>
Lee Reilly <lee@leereilly.net>
@@ -480,6 +497,7 @@ answer newbie questions, and generally made Django that much better:
Alex Robbins <alexander.j.robbins@gmail.com>
Matt Robenolt <m@robenolt.com>
Henrique Romano <onaiort@gmail.com>
+ Erik Romijn <django@solidlinks.nl>
Armin Ronacher
Daniel Roseman <http://roseman.org.uk/>
Rozza <ross.lawley@gmail.com>
@@ -499,6 +517,7 @@ answer newbie questions, and generally made Django that much better:
Bernd Schlapsi
schwank@gmail.com
scott@staplefish.com
+ Olivier Sels <olivier.sels@gmail.com>
Ilya Semenov <semenov@inetss.com>
Aleksandra Sendecka <asendecka@hauru.eu>
serbaut@gmail.com
@@ -523,11 +542,13 @@ answer newbie questions, and generally made Django that much better:
Don Spaulding <donspauldingii@gmail.com>
Calvin Spealman <ironfroggy@gmail.com>
Dane Springmeyer
+ Silvan Spross <silvan.spross@gmail.com>
Bjørn Stabell <bjorn@exoweb.net>
Georgi Stanojevski <glisha@gmail.com>
starrynight <cmorgh@gmail.com>
Vasiliy Stavenko <stavenko@gmail.com>
Thomas Steinacher <http://www.eggdrop.ch/>
+ Emil Stenström <em@kth.se>
Johan C. Stöver <johan@nilling.nl>
Nowell Strite <http://nowell.strite.org/>
Thomas Stromberg <tstromberg@google.com>
@@ -573,12 +594,14 @@ answer newbie questions, and generally made Django that much better:
I.S. van Oostveen <v.oostveen@idca.nl>
viestards.lists@gmail.com
George Vilches <gav@thataddress.com>
+ Simeon Visser <http://simeonvisser.com>
Vlado <vlado@labath.org>
Zachary Voase <zacharyvoase@gmail.com>
Marijn Vriens <marijn@metronomo.cl>
Milton Waddams
Chris Wagner <cw264701@ohio.edu>
Rick Wagner <rwagner@physics.ucsd.edu>
+ Gavin Wahl <gavinwahl@gmail.com>
wam-djangobug@wamber.net
Wang Chun <wangchun@exoweb.net>
Filip Wasilewski <filip.wasilewski@gmail.com>
View
2  django/__init__.py
@@ -1,4 +1,4 @@
-VERSION = (1, 6, 0, 'alpha', 0)
+VERSION = (1, 6, 0, 'alpha', 1)
def get_version(*args, **kwargs):
# Don't litter django/__init__.py with all the get_version stuff.
View
5 django/conf/__init__.py
@@ -127,7 +127,10 @@ def __init__(self, settings_module):
try:
mod = importlib.import_module(self.SETTINGS_MODULE)
except ImportError as e:
- raise ImportError("Could not import settings '%s' (Is it on sys.path?): %s" % (self.SETTINGS_MODULE, e))
+ raise ImportError(
+ "Could not import settings '%s' (Is it on sys.path? Is there an import error in the settings file?): %s"
+ % (self.SETTINGS_MODULE, e)
+ )
# Settings that should be converted into tuples if they're mistakenly entered
# as strings.
View
2  django/conf/global_settings.py
@@ -131,7 +131,7 @@
)
# Languages using BiDi (right-to-left) layout
-LANGUAGES_BIDI = ("he", "ar", "fa")
+LANGUAGES_BIDI = ("he", "ar", "fa", "ur")
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
View
188 django/conf/locale/en/LC_MESSAGES/django.po
@@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-05-02 16:17+0200\n"
+"POT-Creation-Date: 2013-05-25 14:27+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English <en@li.org>\n"
@@ -337,7 +337,7 @@ msgstr ""
msgid "Enter a valid value."
msgstr ""
-#: core/validators.py:53 forms/fields.py:640
+#: core/validators.py:53 forms/fields.py:639
msgid "Enter a valid URL."
msgstr ""
@@ -362,7 +362,7 @@ msgstr ""
msgid "Enter a valid IPv4 or IPv6 address."
msgstr ""
-#: core/validators.py:175 db/models/fields/__init__.py:704
+#: core/validators.py:175 db/models/fields/__init__.py:706
msgid "Enter only digits separated by commas."
msgstr ""
@@ -408,7 +408,7 @@ msgstr[1] ""
msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s."
msgstr ""
-#: db/models/base.py:905 forms/models.py:605
+#: db/models/base.py:905 forms/models.py:643
msgid "and"
msgstr ""
@@ -435,156 +435,156 @@ msgstr ""
msgid "Field of type: %(field_type)s"
msgstr ""
-#: db/models/fields/__init__.py:568 db/models/fields/__init__.py:1034
+#: db/models/fields/__init__.py:570 db/models/fields/__init__.py:1036
msgid "Integer"
msgstr ""
-#: db/models/fields/__init__.py:572 db/models/fields/__init__.py:1032
+#: db/models/fields/__init__.py:574 db/models/fields/__init__.py:1034
#, python-format
msgid "'%s' value must be an integer."
msgstr ""
-#: db/models/fields/__init__.py:620
+#: db/models/fields/__init__.py:622
#, python-format
msgid "'%s' value must be either True or False."
msgstr ""
-#: db/models/fields/__init__.py:622
+#: db/models/fields/__init__.py:624
msgid "Boolean (Either True or False)"
msgstr ""
-#: db/models/fields/__init__.py:671
+#: db/models/fields/__init__.py:673
#, python-format
msgid "String (up to %(max_length)s)"
msgstr ""
-#: db/models/fields/__init__.py:699
+#: db/models/fields/__init__.py:701
msgid "Comma-separated integers"
msgstr ""
-#: db/models/fields/__init__.py:713
+#: db/models/fields/__init__.py:715
#, python-format
msgid "'%s' value has an invalid date format. It must be in YYYY-MM-DD format."
msgstr ""
-#: db/models/fields/__init__.py:715 db/models/fields/__init__.py:803
+#: db/models/fields/__init__.py:717 db/models/fields/__init__.py:805
#, python-format
msgid ""
"'%s' value has the correct format (YYYY-MM-DD) but it is an invalid date."
msgstr ""
-#: db/models/fields/__init__.py:718
+#: db/models/fields/__init__.py:720
msgid "Date (without time)"
msgstr ""
-#: db/models/fields/__init__.py:801
+#: db/models/fields/__init__.py:803
#, python-format
msgid ""
"'%s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[."
"uuuuuu]][TZ] format."
msgstr ""
-#: db/models/fields/__init__.py:805
+#: db/models/fields/__init__.py:807
#, python-format
msgid ""
"'%s' value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) but "
"it is an invalid date/time."
msgstr ""
-#: db/models/fields/__init__.py:809
+#: db/models/fields/__init__.py:811
msgid "Date (with time)"
msgstr ""
-#: db/models/fields/__init__.py:898
+#: db/models/fields/__init__.py:900
#, python-format
msgid "'%s' value must be a decimal number."
msgstr ""
-#: db/models/fields/__init__.py:900
+#: db/models/fields/__init__.py:902
msgid "Decimal number"
msgstr ""
-#: db/models/fields/__init__.py:957
+#: db/models/fields/__init__.py:959
msgid "Email address"
msgstr ""
-#: db/models/fields/__init__.py:976
+#: db/models/fields/__init__.py:978
msgid "File path"
msgstr ""
-#: db/models/fields/__init__.py:1003
+#: db/models/fields/__init__.py:1005
#, python-format
msgid "'%s' value must be a float."
msgstr ""
-#: db/models/fields/__init__.py:1005
+#: db/models/fields/__init__.py:1007
msgid "Floating point number"
msgstr ""
-#: db/models/fields/__init__.py:1066
+#: db/models/fields/__init__.py:1068
msgid "Big (8 byte) integer"
msgstr ""
-#: db/models/fields/__init__.py:1080
+#: db/models/fields/__init__.py:1082
msgid "IPv4 address"
msgstr ""
-#: db/models/fields/__init__.py:1096
+#: db/models/fields/__init__.py:1098
msgid "IP address"
msgstr ""
-#: db/models/fields/__init__.py:1139
+#: db/models/fields/__init__.py:1141
#, python-format
msgid "'%s' value must be either None, True or False."
msgstr ""
-#: db/models/fields/__init__.py:1141
+#: db/models/fields/__init__.py:1143
msgid "Boolean (Either True, False or None)"
msgstr ""
-#: db/models/fields/__init__.py:1190
+#: db/models/fields/__init__.py:1192
msgid "Positive integer"
msgstr ""
-#: db/models/fields/__init__.py:1201
+#: db/models/fields/__init__.py:1203
msgid "Positive small integer"
msgstr ""
-#: db/models/fields/__init__.py:1212
+#: db/models/fields/__init__.py:1214
#, python-format
msgid "Slug (up to %(max_length)s)"
msgstr ""
-#: db/models/fields/__init__.py:1230
+#: db/models/fields/__init__.py:1232
msgid "Small integer"
msgstr ""
-#: db/models/fields/__init__.py:1236
+#: db/models/fields/__init__.py:1238
msgid "Text"
msgstr ""
-#: db/models/fields/__init__.py:1254
+#: db/models/fields/__init__.py:1256
#, python-format
msgid ""
"'%s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] format."
msgstr ""
-#: db/models/fields/__init__.py:1256
+#: db/models/fields/__init__.py:1258
#, python-format
msgid ""
"'%s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an invalid "
"time."
msgstr ""
-#: db/models/fields/__init__.py:1259
+#: db/models/fields/__init__.py:1261
msgid "Time"
msgstr ""
-#: db/models/fields/__init__.py:1321
+#: db/models/fields/__init__.py:1323
msgid "URL"
msgstr ""
-#: db/models/fields/__init__.py:1338
+#: db/models/fields/__init__.py:1340
msgid "Raw binary data"
msgstr ""
@@ -596,55 +596,50 @@ msgstr ""
msgid "Image"
msgstr ""
-#: db/models/fields/related.py:1133
+#: db/models/fields/related.py:1118
#, python-format
msgid "Model %(model)s with pk %(pk)r does not exist."
msgstr ""
-#: db/models/fields/related.py:1135
+#: db/models/fields/related.py:1120
msgid "Foreign Key (type determined by related field)"
msgstr ""
-#: db/models/fields/related.py:1272
+#: db/models/fields/related.py:1257
msgid "One-to-one relationship"
msgstr ""
-#: db/models/fields/related.py:1339
+#: db/models/fields/related.py:1324
msgid "Many-to-many relationship"
msgstr ""
-#: db/models/fields/related.py:1366
-msgid ""
-"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
-msgstr ""
-
#: forms/fields.py:56
msgid "This field is required."
msgstr ""
-#: forms/fields.py:225
+#: forms/fields.py:227
msgid "Enter a whole number."
msgstr ""
-#: forms/fields.py:266 forms/fields.py:294
+#: forms/fields.py:268 forms/fields.py:296
msgid "Enter a number."
msgstr ""
-#: forms/fields.py:296
+#: forms/fields.py:298
#, python-format
msgid "Ensure that there are no more than %(max)s digit in total."
msgid_plural "Ensure that there are no more than %(max)s digits in total."
msgstr[0] ""
msgstr[1] ""
-#: forms/fields.py:300
+#: forms/fields.py:302
#, python-format
msgid "Ensure that there are no more than %(max)s decimal place."
msgid_plural "Ensure that there are no more than %(max)s decimal places."
msgstr[0] ""
msgstr[1] ""
-#: forms/fields.py:304
+#: forms/fields.py:306
#, python-format
msgid ""
"Ensure that there are no more than %(max)s digit before the decimal point."
@@ -653,31 +648,31 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: forms/fields.py:406 forms/fields.py:1058
+#: forms/fields.py:408 forms/fields.py:1064
msgid "Enter a valid date."
msgstr ""
-#: forms/fields.py:430 forms/fields.py:1059
+#: forms/fields.py:432 forms/fields.py:1065
msgid "Enter a valid time."
msgstr ""
-#: forms/fields.py:451
+#: forms/fields.py:454
msgid "Enter a valid date/time."
msgstr ""
-#: forms/fields.py:525
+#: forms/fields.py:531
msgid "No file was submitted. Check the encoding type on the form."
msgstr ""
-#: forms/fields.py:526
+#: forms/fields.py:532
msgid "No file was submitted."
msgstr ""
-#: forms/fields.py:527
+#: forms/fields.py:533
msgid "The submitted file is empty."
msgstr ""
-#: forms/fields.py:529
+#: forms/fields.py:535
#, python-format
msgid "Ensure this filename has at most %(max)d character (it has %(length)d)."
msgid_plural ""
@@ -685,22 +680,22 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: forms/fields.py:532
+#: forms/fields.py:538
msgid "Please either submit a file or check the clear checkbox, not both."
msgstr ""
-#: forms/fields.py:593
+#: forms/fields.py:599
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
-#: forms/fields.py:746 forms/fields.py:824 forms/models.py:1042
+#: forms/fields.py:749 forms/fields.py:828 forms/models.py:1096
#, python-format
msgid "Select a valid choice. %(value)s is not one of the available choices."
msgstr ""
-#: forms/fields.py:825 forms/fields.py:928 forms/models.py:1041
+#: forms/fields.py:829 forms/fields.py:933 forms/models.py:1095
msgid "Enter a list of values."
msgstr ""
@@ -709,53 +704,60 @@ msgstr ""
msgid "(Hidden field %(name)s) %(error)s"
msgstr ""
-#: forms/formsets.py:305
+#: forms/formsets.py:310
#, python-format
-msgid "Please submit %s or fewer forms."
-msgstr ""
+msgid "Please submit %d or fewer forms."
+msgid_plural "Please submit %d or fewer forms."
+msgstr[0] ""
+msgstr[1] ""
-#: forms/formsets.py:331 forms/formsets.py:333
+#: forms/formsets.py:337 forms/formsets.py:339
msgid "Order"
msgstr ""
-#: forms/formsets.py:335
+#: forms/formsets.py:341
msgid "Delete"
msgstr ""
-#: forms/models.py:599
+#: forms/models.py:637
#, python-format
msgid "Please correct the duplicate data for %(field)s."
msgstr ""
-#: forms/models.py:603
+#: forms/models.py:641
#, python-format
msgid "Please correct the duplicate data for %(field)s, which must be unique."
msgstr ""
-#: forms/models.py:609
+#: forms/models.py:647
#, python-format
msgid ""
"Please correct the duplicate data for %(field_name)s which must be unique "
"for the %(lookup)s in %(date_field)s."
msgstr ""
-#: forms/models.py:617
+#: forms/models.py:655
msgid "Please correct the duplicate values below."
msgstr ""
-#: forms/models.py:883
+#: forms/models.py:937
msgid "The inline foreign key did not match the parent instance primary key."
msgstr ""
-#: forms/models.py:947
+#: forms/models.py:1001
msgid "Select a valid choice. That choice is not one of the available choices."
msgstr ""
-#: forms/models.py:1044
+#: forms/models.py:1098
#, python-format
msgid "\"%(pk)s\" is not a valid value for a primary key."
msgstr ""
+#: forms/models.py:1109
+msgid ""
+"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
+msgstr ""
+
#: forms/util.py:84
#, python-format
msgid ""
@@ -791,34 +793,34 @@ msgstr ""
msgid "yes,no,maybe"
msgstr ""
-#: template/defaultfilters.py:813 template/defaultfilters.py:824
+#: template/defaultfilters.py:813 template/defaultfilters.py:825
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] ""
msgstr[1] ""
-#: template/defaultfilters.py:826
+#: template/defaultfilters.py:827
#, python-format
msgid "%s KB"
msgstr ""
-#: template/defaultfilters.py:828
+#: template/defaultfilters.py:829
#, python-format
msgid "%s MB"
msgstr ""
-#: template/defaultfilters.py:830
+#: template/defaultfilters.py:831
#, python-format
msgid "%s GB"
msgstr ""
-#: template/defaultfilters.py:832
+#: template/defaultfilters.py:833
#, python-format
msgid "%s TB"
msgstr ""
-#: template/defaultfilters.py:833
+#: template/defaultfilters.py:835
#, python-format
msgid "%s PB"
msgstr ""
@@ -1119,6 +1121,16 @@ msgctxt "alt. month"
msgid "December"
msgstr ""
+#: utils/image.py:105
+#, python-format
+msgid "Neither Pillow nor PIL could be imported: %s"
+msgstr ""
+
+#: utils/image.py:127
+#, python-format
+msgid "The '_imaging' module for the PIL could not be imported: %s"
+msgstr ""
+
#: utils/text.py:70
#, python-format
msgctxt "String to return when truncating text"
@@ -1130,53 +1142,53 @@ msgid "or"
msgstr ""
#. Translators: This string is used as a separator between list elements
-#: utils/text.py:242 utils/timesince.py:54
+#: utils/text.py:242 utils/timesince.py:55
msgid ", "
msgstr ""
-#: utils/timesince.py:22
+#: utils/timesince.py:23
#, python-format
msgid "%d year"
msgid_plural "%d years"
msgstr[0] ""
msgstr[1] ""
-#: utils/timesince.py:23
+#: utils/timesince.py:24
#, python-format
msgid "%d month"
msgid_plural "%d months"
msgstr[0] ""
msgstr[1] ""
-#: utils/timesince.py:24
+#: utils/timesince.py:25
#, python-format
msgid "%d week"
msgid_plural "%d weeks"
msgstr[0] ""
msgstr[1] ""
-#: utils/timesince.py:25
+#: utils/timesince.py:26
#, python-format
msgid "%d day"
msgid_plural "%d days"
msgstr[0] ""
msgstr[1] ""
-#: utils/timesince.py:26
+#: utils/timesince.py:27
#, python-format
msgid "%d hour"
msgid_plural "%d hours"
msgstr[0] ""
msgstr[1] ""
-#: utils/timesince.py:27
+#: utils/timesince.py:28
#, python-format
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
msgstr[1] ""
-#: utils/timesince.py:43
+#: utils/timesince.py:44
msgid "0 minutes"
msgstr ""
View
3  django/conf/urls/__init__.py
@@ -5,8 +5,9 @@
from django.utils import six
-__all__ = ['handler403', 'handler404', 'handler500', 'include', 'patterns', 'url']
+__all__ = ['handler400', 'handler403', 'handler404', 'handler500', 'include', 'patterns', 'url']
+handler400 = 'django.views.defaults.bad_request'
handler403 = 'django.views.defaults.permission_denied'
handler404 = 'django.views.defaults.page_not_found'
handler500 = 'django.views.defaults.server_error'
View
6 django/contrib/admin/exceptions.py
@@ -0,0 +1,6 @@
+from django.core.exceptions import SuspiciousOperation
+
+
+class DisallowedModelAdminLookup(SuspiciousOperation):
+ """Invalid filter was passed to admin view via URL querystring"""
+ pass
View
2  django/contrib/admin/filters.py
@@ -216,7 +216,7 @@ def choices(self, cl):
}
FieldListFilter.register(lambda f: (
- hasattr(f, 'rel') and bool(f.rel) or
+ bool(f.rel) if hasattr(f, 'rel') else
isinstance(f, models.related.RelatedObject)), RelatedFieldListFilter)
View
4 django/contrib/admin/helpers.py
@@ -131,7 +131,7 @@ def label_tag(self):
classes.append('required')
if not self.is_first:
classes.append('inline')
- attrs = classes and {'class': ' '.join(classes)} or {}
+ attrs = {'class': ' '.join(classes)} if classes else {}
return self.field.label_tag(contents=mark_safe(contents), attrs=attrs)
def errors(self):
@@ -144,7 +144,7 @@ def __init__(self, form, field, is_first, model_admin=None):
# {{ field.name }} must be a useful class name to identify the field.
# For convenience, store other field-related data here too.
if callable(field):
- class_name = field.__name__ != '<lambda>' and field.__name__ or ''
+ class_name = field.__name__ if field.__name__ != '<lambda>' else ''
else:
class_name = field
self.field = {
View
81 django/contrib/admin/locale/en/LC_MESSAGES/django.po
@@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-05-02 16:18+0200\n"
+"POT-Creation-Date: 2013-05-25 14:19+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English <en@li.org>\n"
@@ -18,12 +18,12 @@ msgstr ""
msgid "Successfully deleted %(count)d %(items)s."
msgstr ""
-#: actions.py:61 options.py:1365
+#: actions.py:61 options.py:1418
#, python-format
msgid "Cannot delete %(name)s"
msgstr ""
-#: actions.py:63 options.py:1367
+#: actions.py:63 options.py:1420
msgid "Are you sure?"
msgstr ""
@@ -130,157 +130,157 @@ msgstr ""
msgid "LogEntry Object"
msgstr ""
-#: options.py:163 options.py:192
+#: options.py:173 options.py:202
msgid "None"
msgstr ""
-#: options.py:710
+#: options.py:763
#, python-format
msgid "Changed %s."
msgstr ""
-#: options.py:710 options.py:720 options.py:1514
+#: options.py:763 options.py:773 options.py:1570
msgid "and"
msgstr ""
-#: options.py:715
+#: options.py:768
#, python-format
msgid "Added %(name)s \"%(object)s\"."
msgstr ""
-#: options.py:719
+#: options.py:772
#, python-format
msgid "Changed %(list)s for %(name)s \"%(object)s\"."
msgstr ""
-#: options.py:724
+#: options.py:777
#, python-format
msgid "Deleted %(name)s \"%(object)s\"."
msgstr ""
-#: options.py:728
+#: options.py:781
msgid "No fields changed."
msgstr ""
-#: options.py:831 options.py:874
+#: options.py:884 options.py:927
#, python-format
msgid ""
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
msgstr ""
-#: options.py:849
+#: options.py:902
#, python-format
msgid ""
"The %(name)s \"%(obj)s\" was added successfully. You may add another "
"%(name)s below."
msgstr ""
-#: options.py:853
+#: options.py:906
#, python-format
msgid "The %(name)s \"%(obj)s\" was added successfully."
msgstr ""
-#: options.py:867
+#: options.py:920
#, python-format
msgid ""
"The %(name)s \"%(obj)s\" was changed successfully. You may edit it again "
"below."
msgstr ""
-#: options.py:881
+#: options.py:934
#, python-format
msgid ""
"The %(name)s \"%(obj)s\" was changed successfully. You may add another "
"%(name)s below."
msgstr ""
-#: options.py:887
+#: options.py:940
#, python-format
msgid "The %(name)s \"%(obj)s\" was changed successfully."
msgstr ""
-#: options.py:965 options.py:1225
+#: options.py:1018 options.py:1278
msgid ""
"Items must be selected in order to perform actions on them. No items have "
"been changed."
msgstr ""
-#: options.py:984
+#: options.py:1037
msgid "No action selected."
msgstr ""
-#: options.py:1064
+#: options.py:1117
#, python-format
msgid "Add %s"
msgstr ""
-#: options.py:1088 options.py:1333
+#: options.py:1141 options.py:1386
#, python-format
msgid "%(name)s object with primary key %(key)r does not exist."
msgstr ""
-#: options.py:1154
+#: options.py:1207
#, python-format
msgid "Change %s"
msgstr ""
-#: options.py:1204
+#: options.py:1257
msgid "Database error"
msgstr ""
-#: options.py:1267
+#: options.py:1320
#, python-format
msgid "%(count)s %(name)s was changed successfully."
msgid_plural "%(count)s %(name)s were changed successfully."
msgstr[0] ""
msgstr[1] ""
-#: options.py:1294
+#: options.py:1347
#, python-format
msgid "%(total_count)s selected"
msgid_plural "All %(total_count)s selected"
msgstr[0] ""
msgstr[1] ""
-#: options.py:1299
+#: options.py:1352
#, python-format
msgid "0 of %(cnt)s selected"
msgstr ""
-#: options.py:1350
+#: options.py:1403
#, python-format
msgid "The %(name)s \"%(obj)s\" was deleted successfully."
msgstr ""
-#: options.py:1406
+#: options.py:1459
#, python-format
msgid "Change history: %s"
msgstr ""
#. Translators: Model verbose name and instance representation, suitable to be an item in a list
-#: options.py:1508
+#: options.py:1564
#, python-format
msgid "%(class_name)s %(instance)s"
msgstr ""
-#: options.py:1515
+#: options.py:1571
#, python-format
msgid ""
"Deleting %(class_name)s %(instance)s would require deleting the following "
"protected related objects: %(related_objects)s"
msgstr ""
-#: sites.py:324 tests.py:71 templates/admin/login.html:48
+#: sites.py:318 tests.py:71 templates/admin/login.html:48
#: templates/registration/password_reset_complete.html:19
#: views/decorators.py:24
msgid "Log in"
msgstr ""
-#: sites.py:392
+#: sites.py:386
msgid "Site administration"
msgstr ""
-#: sites.py:446
+#: sites.py:440
#, python-format
msgid "%s administration"
msgstr ""
@@ -429,9 +429,14 @@ msgstr ""
#: templates/admin/auth/user/change_password.html:27
#: templates/registration/password_change_form.html:20
msgid "Please correct the error below."
-msgid_plural "Please correct the errors below."
-msgstr[0] ""
-msgstr[1] ""
+msgstr ""
+
+#: templates/admin/change_form.html:44 templates/admin/change_list.html:67
+#: templates/admin/login.html:17
+#: templates/admin/auth/user/change_password.html:27
+#: templates/registration/password_change_form.html:20
+msgid "Please correct the errors below."
+msgstr ""
#: templates/admin/change_list.html:58
#, python-format
@@ -811,16 +816,16 @@ msgstr ""
msgid "All dates"
msgstr ""
-#: views/main.py:37
+#: views/main.py:35
msgid "(None)"
msgstr ""
-#: views/main.py:86
+#: views/main.py:84
#, python-format
msgid "Select %s"
msgstr ""
-#: views/main.py:88
+#: views/main.py:86
#, python-format
msgid "Select %s to change"
msgstr ""
View
85 django/contrib/admin/options.py
@@ -1,5 +1,6 @@
import copy
-from functools import update_wrapper, partial
+import operator
+from functools import partial, reduce, update_wrapper
from django import forms
from django.conf import settings
@@ -9,7 +10,8 @@
from django.contrib.contenttypes.models import ContentType
from django.contrib.admin import widgets, helpers
from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects,
- model_format_dict, NestedObjects)
+ model_format_dict, NestedObjects, lookup_needs_distinct)
+from django.contrib.admin import validation
from django.contrib.admin.templatetags.admin_static import static
from django.contrib import messages
from django.views.decorators.csrf import csrf_protect
@@ -22,6 +24,7 @@
from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
from django.db.models.sql.constants import QUERY_TERMS
from django.http import Http404, HttpResponse, HttpResponseRedirect
+from django.http.response import HttpResponseBase
from django.shortcuts import get_object_or_404
from django.template.response import SimpleTemplateResponse, TemplateResponse
from django.utils.decorators import method_decorator
@@ -87,6 +90,14 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
readonly_fields = ()
ordering = None
+ # validation
+ validator_class = validation.BaseValidator
+
+ @classmethod
+ def validate(cls, model):
+ validator = cls.validator_class()
+ validator.validate(cls, model)
+
def __init__(self):
overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy()
overrides.update(self.formfield_overrides)
@@ -246,6 +257,34 @@ def get_prepopulated_fields(self, request, obj=None):
"""
return self.prepopulated_fields
+ def get_search_results(self, request, queryset, search_term):
+ # Apply keyword searches.
+ def construct_search(field_name):
+ if field_name.startswith('^'):
+ return "%s__istartswith" % field_name[1:]
+ elif field_name.startswith('='):
+ return "%s__iexact" % field_name[1:]
+ elif field_name.startswith('@'):
+ return "%s__search" % field_name[1:]
+ else:
+ return "%s__icontains" % field_name
+
+ use_distinct = False
+ if self.search_fields and search_term:
+ orm_lookups = [construct_search(str(search_field))
+ for search_field in self.search_fields]
+ for bit in search_term.split():
+ or_queries = [models.Q(**{orm_lookup: bit})
+ for orm_lookup in orm_lookups]
+ queryset = queryset.filter(reduce(operator.or_, or_queries))
+ if not use_distinct:
+ for search_spec in orm_lookups:
+ if lookup_needs_distinct(self.opts, search_spec):
+ use_distinct = True
+ break
+
+ return queryset, use_distinct
+
def get_queryset(self, request):
"""
Returns a QuerySet of all model instances that can be edited by the
@@ -371,6 +410,9 @@ class ModelAdmin(BaseModelAdmin):
actions_on_bottom = False
actions_selection_counter = True
+ # validation
+ validator_class = validation.ModelAdminValidator
+
def __init__(self, model, admin_site):
self.model = model
self.opts = model._meta
@@ -456,7 +498,7 @@ 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, obj)
+ form = self.get_form(request, obj, fields=None)
fields = list(form.base_fields) + list(self.get_readonly_fields(request, obj))
return [(None, {'fields': fields})]
@@ -465,10 +507,10 @@ def get_form(self, request, obj=None, **kwargs):
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)
+ if 'fields' in kwargs:
+ fields = kwargs.pop('fields')
else:
- fields = None
+ fields = flatten_fieldsets(self.get_fieldsets(request, obj))
if self.exclude is None:
exclude = []
else:
@@ -985,10 +1027,10 @@ def response_action(self, request, queryset):
response = func(self, request, queryset)
- # Actions may return an HttpResponse, which will be used as the
- # response from the POST. If not, we'll be a good little HTTP
- # citizen and redirect back to the changelist page.
- if isinstance(response, HttpResponse):
+ # Actions may return an HttpResponse-like object, which will be
+ # used as the response from the POST. If not, we'll be a good
+ # little HTTP citizen and redirect back to the changelist page.
+ if isinstance(response, HttpResponseBase):
return response
else:
return HttpResponseRedirect(request.get_full_path())
@@ -1447,6 +1489,9 @@ class InlineModelAdmin(BaseModelAdmin):
verbose_name_plural = None
can_delete = True
+ # validation
+ validator_class = validation.InlineValidator
+
def __init__(self, parent_model, admin_site):
self.admin_site = admin_site
self.parent_model = parent_model
@@ -1467,12 +1512,20 @@ def media(self):
js.extend(['SelectBox.js', 'SelectFilter2.js'])
return forms.Media(js=[static('admin/js/%s' % url) for url in js])
+ def get_extra(self, request, obj=None, **kwargs):
+ """Hook for customizing the number of extra inline forms."""
+ return self.extra
+
+ def get_max_num(self, request, obj=None, **kwargs):
+ """Hook for customizing the max number of extra inline forms."""
+ return self.max_num
+
def get_formset(self, request, obj=None, **kwargs):
"""Returns a BaseInlineFormSet class for use in admin add/change views."""
- if self.declared_fieldsets:
- fields = flatten_fieldsets(self.declared_fieldsets)
+ if 'fields' in kwargs:
+ fields = kwargs.pop('fields')
else:
- fields = None
+ fields = flatten_fieldsets(self.get_fieldsets(request, obj))
if self.exclude is None:
exclude = []
else:
@@ -1493,8 +1546,8 @@ def get_formset(self, request, obj=None, **kwargs):
"fields": fields,
"exclude": exclude,
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
- "extra": self.extra,
- "max_num": self.max_num,
+ "extra": self.get_extra(request, obj, **kwargs),
+ "max_num": self.get_max_num(request, obj, **kwargs),
"can_delete": can_delete,
}
@@ -1544,7 +1597,7 @@ def is_valid(self):
def get_fieldsets(self, request, obj=None):
if self.declared_fieldsets:
return self.declared_fieldsets
- form = self.get_formset(request, obj).form
+ form = self.get_formset(request, obj, fields=None).form
fields = list(form.base_fields) + list(self.get_readonly_fields(request, obj))
return [(None, {'fields': fields})]
View
10 django/contrib/admin/sites.py
@@ -66,12 +66,6 @@ def register(self, model_or_iterable, admin_class=None, **options):
if not admin_class:
admin_class = ModelAdmin
- # Don't import the humongous validation code unless required
- if admin_class and settings.DEBUG:
- from django.contrib.admin.validation import validate
- else:
- validate = lambda model, adminclass: None
-
if isinstance(model_or_iterable, ModelBase):
model_or_iterable = [model_or_iterable]
for model in model_or_iterable:
@@ -94,8 +88,8 @@ def register(self, model_or_iterable, admin_class=None, **options):
options['__module__'] = __name__
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
- # Validate (which might be a no-op)
- validate(admin_class, model)
+ if admin_class is not ModelAdmin and settings.DEBUG:
+ admin_class.validate(model)
# Instantiate the admin class to save in the registry
self._registry[model] = admin_class(model, self)
View
2  django/contrib/admin/templates/admin/auth/user/change_password.html
@@ -24,7 +24,7 @@
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
{% if form.errors %}
<p class="errornote">
- {% blocktrans count counter=form.errors.items|length %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
+ {% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
</p>
{% endif %}
View
2  django/contrib/admin/templates/admin/change_form.html
@@ -41,7 +41,7 @@
{% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %}
{% if errors %}
<p class="errornote">
- {% blocktrans count counter=errors|length %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
+ {% if errors|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
</p>
{{ adminform.form.non_field_errors }}
{% endif %}
View
2  django/contrib/admin/templates/admin/change_list.html
@@ -64,7 +64,7 @@
{% endblock %}
{% if cl.formset.errors %}
<p class="errornote">
- {% blocktrans count cl.formset.errors|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
+ {% if cl.formset.errors|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
</p>
{{ cl.formset.non_form_errors }}
{% endif %}
View
2  django/contrib/admin/templates/admin/includes/fieldset.html
@@ -7,7 +7,7 @@
<div class="form-row{% if line.fields|length_is:'1' and line.errors %} errors{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
{% if line.fields|length_is:'1' %}{{ line.errors }}{% endif %}
{% for field in line %}
- <div{% if not line.fields|length_is:'1' %} class="field-box{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}"{% endif %}>
+ <div{% if not line.fields|length_is:'1' %} class="field-box{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}"{% elif field.is_checkbox %} class="checkbox-row"{% endif %}>
{% if not line.fields|length_is:'1' and not field.is_readonly %}{{ field.errors }}{% endif %}
{% if field.is_checkbox %}
{{ field.field }}{{ field.label_tag }}
View
2  django/contrib/admin/templates/admin/login.html
@@ -14,7 +14,7 @@
{% block content %}
{% if form.errors and not form.non_field_errors and not form.this_is_the_login_form.errors %}
<p class="errornote">
-{% blocktrans count counter=form.errors.items|length %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
+{% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
</p>
{% endif %}
View
2  django/contrib/admin/templates/registration/password_change_form.html
@@ -17,7 +17,7 @@
<div>
{% if form.errors %}
<p class="errornote">
- {% blocktrans count counter=form.errors.items|length %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
+ {% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
</p>
{% endif %}
View
2  django/contrib/admin/templates/registration/password_reset_email.html
@@ -3,7 +3,7 @@
{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
-{{ protocol }}://{{ domain }}{% url 'django.contrib.auth.views.password_reset_confirm' uidb36=uid token=token %}
+{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb36=uid token=token %}
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
View
2  django/contrib/admin/templatetags/admin_list.py
@@ -62,7 +62,7 @@ def pagination(cl):
# ON_EACH_SIDE links at either end of the "current page" link.
page_range = []
if page_num > (ON_EACH_SIDE + ON_ENDS):
- page_range.extend(range(0, ON_EACH_SIDE - 1))
+ page_range.extend(range(0, ON_ENDS))
page_range.append(DOT)
page_range.extend(range(page_num - ON_EACH_SIDE, page_num + 1))
else:
View
2  django/contrib/admin/templatetags/log.py
@@ -53,4 +53,4 @@ def get_admin_log(parser, token):
if tokens[4] != 'for_user':
raise template.TemplateSyntaxError(
"Fourth argument to 'get_admin_log' must be 'for_user'")
- return AdminLogNode(limit=tokens[1], varname=tokens[3], user=(len(tokens) > 5 and tokens[5] or None))
+ return AdminLogNode(limit=tokens[1], varname=tokens[3], user=(tokens[5] if len(tokens) > 5 else None))
View
14 django/contrib/admin/util.py
@@ -37,9 +37,9 @@ def prepare_lookup_value(key, value):
# if key ends with __in, split parameter into separate values
if key.endswith('__in'):
value = value.split(',')
- # if key ends with __isnull, special case '' and false
+ # if key ends with __isnull, special case '' and the string literals 'false' and '0'
if key.endswith('__isnull'):
- if value.lower() in ('', 'false'):
+ if value.lower() in ('', 'false', '0'):
value = False
else:
value = True
@@ -269,8 +269,9 @@ def lookup_field(name, obj, model_admin=None):
def label_for_field(name, model, model_admin=None, return_attr=False):
"""
- Returns a sensible label for a field name. The name can be a callable or the
- name of an object attributes, as well as a genuine fields. If return_attr is
+ Returns a sensible label for a field name. The name can be a callable,
+ property (but not created with @property decorator) or the name of an
+ object's attribute, as well as a genuine fields. If return_attr is
True, the resolved attribute (which could be a callable) is also returned.
This will be None if (and only if) the name refers to a field.
"""
@@ -303,6 +304,10 @@ def label_for_field(name, model, model_admin=None, return_attr=False):
if hasattr(attr, "short_description"):
label = attr.short_description
+ elif (isinstance(attr, property) and
+ hasattr(attr, "fget") and
+ hasattr(attr.fget, "short_description")):
+ label = attr.fget.short_description
elif callable(attr):
if attr.__name__ == "<lambda>":
label = "--"
@@ -315,6 +320,7 @@ def label_for_field(name, model, model_admin=None, return_attr=False):
else:
return label
+
def help_text_for_field(name, model):
try:
help_text = model._meta.get_field_by_name(name)[0].help_text
View
759 django/contrib/admin/validation.py
@@ -3,358 +3,405 @@
from django.db.models.fields import FieldDoesNotExist
from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model,
_get_foreign_key)
-from django.contrib.admin import ListFilter, FieldListFilter
from django.contrib.admin.util import get_fields_from_path, NotRelationField
-from django.contrib.admin.options import (flatten_fieldsets, BaseModelAdmin,
- ModelAdmin, HORIZONTAL, VERTICAL)
-
-
-__all__ = ['validate']
-
-def validate(cls, model):
- """
- Does basic ModelAdmin option validation. Calls custom validation
- classmethod in the end if it is provided in cls. The signature of the
- custom validation classmethod should be: def validate(cls, model).
- """
- # Before we can introspect models, they need to be fully loaded so that
- # inter-relations are set up correctly. We force that here.
- models.get_apps()
-
- opts = model._meta
- validate_base(cls, model)
-
- # list_display
- if hasattr(cls, 'list_display'):
- check_isseq(cls, 'list_display', cls.list_display)
- for idx, field in enumerate(cls.list_display):
- if not callable(field):
- if not hasattr(cls, field):
- if not hasattr(model, field):
- try:
- opts.get_field(field)
- except models.FieldDoesNotExist:
- raise ImproperlyConfigured("%s.list_display[%d], %r is not a callable or an attribute of %r or found in the model %r."
- % (cls.__name__, idx, field, cls.__name__, model._meta.object_name))
- else:
- # getattr(model, field) could be an X_RelatedObjectsDescriptor
- f = fetch_attr(cls, model, opts, "list_display[%d]" % idx, field)
- if isinstance(f, models.ManyToManyField):
- raise ImproperlyConfigured("'%s.list_display[%d]', '%s' is a ManyToManyField which is not supported."
- % (cls.__name__, idx, field))
- # list_display_links
- if hasattr(cls, 'list_display_links'):
- check_isseq(cls, 'list_display_links', cls.list_display_links)
- for idx, field in enumerate(cls.list_display_links):
- if field not in cls.list_display:
- raise ImproperlyConfigured("'%s.list_display_links[%d]' "
- "refers to '%s' which is not defined in 'list_display'."
- % (cls.__name__, idx, field))
-
- # list_filter
- if hasattr(cls, 'list_filter'):
- check_isseq(cls, 'list_filter', cls.list_filter)
- for idx, item in enumerate(cls.list_filter):
- # There are three options for specifying a filter:
- # 1: 'field' - a basic field filter, possibly w/ relationships (eg, 'field__rel')
- # 2: ('field', SomeFieldListFilter) - a field-based list filter class
- # 3: SomeListFilter - a non-field list filter class
- if callable(item) and not isinstance(item, models.Field):
- # If item is option 3, it should be a ListFilter...
- if not issubclass(item, ListFilter):
- raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'"
- " which is not a descendant of ListFilter."
- % (cls.__name__, idx, item.__name__))
- # ... but not a FieldListFilter.
- if issubclass(item, FieldListFilter):
- raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'"
- " which is of type FieldListFilter but is not"
- " associated with a field name."
- % (cls.__name__, idx, item.__name__))
- else:
- if isinstance(item, (tuple, list)):
- # item is option #2
- field, list_filter_class = item
- if not issubclass(list_filter_class, FieldListFilter):
- raise ImproperlyConfigured("'%s.list_filter[%d][1]'"
- " is '%s' which is not of type FieldListFilter."
- % (cls.__name__, idx, list_filter_class.__name__))
- else:
- # item is option #1
- field = item
- # Validate the field string
+"""
+Does basic ModelAdmin option validation. Calls custom validation
+classmethod in the end if it is provided in cls. The signature of the
+custom validation classmethod should be: def validate(cls, model).
+"""
+
+__all__ = ['BaseValidator', 'InlineValidator']
+
+
+class BaseValidator(object):
+ def __init__(self):
+ # Before we can introspect models, they need to be fully loaded so that
+ # inter-relations are set up correctly. We force that here.
+ models.get_apps()
+
+ def validate(self, cls, model):
+ for m in dir(self):
+ if m.startswith('validate_'):
+ getattr(self, m)(cls, model)
+
+ def check_field_spec(self, cls, model, flds, label):
+ """
+ Validate the fields specification in `flds` from a ModelAdmin subclass
+ `cls` for the `model` model. Use `label` for reporting problems to the user.
+
+ The fields specification can be a ``fields`` option or a ``fields``
+ sub-option from a ``fieldsets`` option component.
+ """
+ for fields in flds:
+ # The entry in fields might be a tuple. If it is a standalone
+ # field, make it into a tuple to make processing easier.
+ if type(fields) != tuple:
+ fields = (fields,)
+ for field in fields:
+ if field in cls.readonly_fields:
+ # Stuff can be put in fields that isn't actually a
+ # model field if it's in readonly_fields,
+ # readonly_fields will handle the validation of such
+ # things.
+ continue
try:
- get_fields_from_path(model, field)
- except (NotRelationField, FieldDoesNotExist):
- raise ImproperlyConfigured("'%s.list_filter[%d]' refers to '%s'"
- " which does not refer to a Field."
+ f = model._meta.get_field(field)
+ except models.FieldDoesNotExist:
+ # If we can't find a field on the model that matches, it could be an
+ # extra field on the form; nothing to check so move on to the next field.
+ continue
+ if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created:
+ raise ImproperlyConfigured("'%s.%s' "
+ "can't include the ManyToManyField field '%s' because "
+ "'%s' manually specifies a 'through' model." % (
+ cls.__name__, label, field, field))
+
+ def validate_raw_id_fields(self, cls, model):
+ " Validate that raw_id_fields only contains field names that are listed on the model. "
+ if hasattr(cls, 'raw_id_fields'):
+ check_isseq(cls, 'raw_id_fields', cls.raw_id_fields)
+ for idx, field in enumerate(cls.raw_id_fields):
+ f = get_field(cls, model, 'raw_id_fields', field)
+ if not isinstance(f, (models.ForeignKey, models.ManyToManyField)):
+ raise ImproperlyConfigured("'%s.raw_id_fields[%d]', '%s' must "
+ "be either a ForeignKey or ManyToManyField."
+ % (cls.__name__, idx, field))
+
+ def validate_fields(self, cls, model):
+ " Validate that fields only refer to existing fields, doesn't contain duplicates. "
+ # fields
+ if cls.fields: # default value is None
+ check_isseq(cls, 'fields', cls.fields)
+ self.check_field_spec(cls, model, cls.fields, 'fields')
+ if cls.fieldsets:
+ raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
+ if len(cls.fields) > len(set(cls.fields)):
+ raise ImproperlyConfigured('There are duplicate field(s) in %s.fields' % cls.__name__)
+
+ def validate_fieldsets(self, cls, model):
+ " Validate that fieldsets is properly formatted and doesn't contain duplicates. "
+ from django.contrib.admin.options import flatten_fieldsets
+ if cls.fieldsets: # default value is None
+ check_isseq(cls, 'fieldsets', cls.fieldsets)
+ for idx, fieldset in enumerate(cls.fieldsets):
+ check_isseq(cls, 'fieldsets[%d]' % idx, fieldset)
+ if len(fieldset) != 2:
+ raise ImproperlyConfigured("'%s.fieldsets[%d]' does not "
+ "have exactly two elements." % (cls.__name__, idx))
+ check_isdict(cls, 'fieldsets[%d][1]' % idx, fieldset[1])
+ if 'fields' not in fieldset[1]:
+ raise ImproperlyConfigured("'fields' key is required in "
+ "%s.fieldsets[%d][1] field options dict."
+ % (cls.__name__, idx))
+ self.check_field_spec(cls, model, fieldset[1]['fields'], "fieldsets[%d][1]['fields']" % idx)
+ flattened_fieldsets = flatten_fieldsets(cls.fieldsets)
+ if len(flattened_fieldsets) > len(set(flattened_fieldsets)):
+ raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__)
+
+ def validate_exclude(self, cls, model):
+ " Validate that exclude is a sequence without duplicates. "
+ if cls.exclude: # default value is None
+ check_isseq(cls, 'exclude', cls.exclude)
+ if len(cls.exclude) > len(set(cls.exclude)):
+ raise ImproperlyConfigured('There are duplicate field(s) in %s.exclude' % cls.__name__)
+
+ def validate_form(self, cls, model):
+ " Validate that form subclasses BaseModelForm. "
+ if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm):
+ raise ImproperlyConfigured("%s.form does not inherit from "
+ "BaseModelForm." % cls.__name__)
+
+ def validate_filter_vertical(self, cls, model):
+ " Validate that filter_vertical is a sequence of field names. "
+ if hasattr(cls, 'filter_vertical'):
+ check_isseq(cls, 'filter_vertical', cls.filter_vertical)
+ for idx, field in enumerate(cls.filter_vertical):
+ f = get_field(cls, model, 'filter_vertical', field)