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

Conflicts:
	django/db/models/fields/related.py
This commit is contained in:
Andrew Godwin 2013-06-07 11:15:34 +01:00
commit 3c296382b8
383 changed files with 8084 additions and 3560 deletions

33
AUTHORS
View File

@ -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 File

@ -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 File

@ -127,7 +127,10 @@ class Settings(BaseSettings):
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 File

@ -131,7 +131,7 @@ LANGUAGES = (
)
# 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 File

@ -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 File

@ -5,8 +5,9 @@ from django.utils.importlib import import_module
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 File

@ -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 File

@ -216,7 +216,7 @@ class RelatedFieldListFilter(FieldListFilter):
}
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 File

@ -131,7 +131,7 @@ class AdminField(object):
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 @@ class AdminReadonlyField(object):
# {{ 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 File

@ -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 File

@ -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.forms.models import (modelform_factory, modelformset_factory,
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.related import RelatedObject
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 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
"""
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 @@ class ModelAdmin(BaseModelAdmin):
"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 @@ class ModelAdmin(BaseModelAdmin):
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 @@ class ModelAdmin(BaseModelAdmin):
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 @@ class InlineModelAdmin(BaseModelAdmin):
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 @@ class InlineModelAdmin(BaseModelAdmin):
"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 @@ class InlineModelAdmin(BaseModelAdmin):
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 File

@ -66,12 +66,6 @@ class AdminSite(object):
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 @@ class AdminSite(object):
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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -3,358 +3,405 @@ from django.db import models
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)
"""
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']
__all__ = ['validate']
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(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()
def validate(self, cls, model):
for m in dir(self):
if m.startswith('validate_'):
getattr(self, m)(cls, model)
opts = model._meta
validate_base(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.
# 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
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))
# list_per_page = 100
if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int):
raise ImproperlyConfigured("'%s.list_per_page' should be a integer."
% cls.__name__)
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__)
# list_max_show_all
if hasattr(cls, 'list_max_show_all') and not isinstance(cls.list_max_show_all, int):
raise ImproperlyConfigured("'%s.list_max_show_all' should be an integer."
% 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__)
# list_editable
if hasattr(cls, 'list_editable') and cls.list_editable:
check_isseq(cls, 'list_editable', cls.list_editable)
for idx, field_name in enumerate(cls.list_editable):
try:
field = opts.get_field_by_name(field_name)[0]
except models.FieldDoesNotExist:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
"field, '%s', not defined on %s.%s."
% (cls.__name__, idx, field_name, model._meta.app_label, model.__name__))
if field_name not in cls.list_display:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to "
"'%s' which is not defined in 'list_display'."
% (cls.__name__, idx, field_name))
if field_name in cls.list_display_links:
raise ImproperlyConfigured("'%s' cannot be in both '%s.list_editable'"
" and '%s.list_display_links'"
% (field_name, cls.__name__, cls.__name__))
if not cls.list_display_links and cls.list_display[0] in cls.list_editable:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to"
" the first field in list_display, '%s', which can't be"
" used unless list_display_links is set."
% (cls.__name__, idx, cls.list_display[0]))
if not field.editable:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
"field, '%s', which isn't editable through the admin."
% (cls.__name__, idx, field_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__)
# search_fields = ()
if hasattr(cls, 'search_fields'):
check_isseq(cls, 'search_fields', cls.search_fields)
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__)
# date_hierarchy = None
if cls.date_hierarchy:
f = get_field(cls, model, opts, 'date_hierarchy', cls.date_hierarchy)
if not isinstance(f, (models.DateField, models.DateTimeField)):
raise ImproperlyConfigured("'%s.date_hierarchy is "
"neither an instance of DateField nor DateTimeField."
% 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)
if not isinstance(f, models.ManyToManyField):
raise ImproperlyConfigured("'%s.filter_vertical[%d]' must be "
"a ManyToManyField." % (cls.__name__, idx))
# ordering = None
if cls.ordering:
check_isseq(cls, 'ordering', cls.ordering)
for idx, field in enumerate(cls.ordering):
if field == '?' and len(cls.ordering) != 1:
raise ImproperlyConfigured("'%s.ordering' has the random "
"ordering marker '?', but contains other fields as "
"well. Please either remove '?' or the other fields."
def validate_filter_horizontal(self, cls, model):
" Validate that filter_horizontal is a sequence of field names. "
if hasattr(cls, 'filter_horizontal'):
check_isseq(cls, 'filter_horizontal', cls.filter_horizontal)
for idx, field in enumerate(cls.filter_horizontal):
f = get_field(cls, model, 'filter_horizontal', field)
if not isinstance(f, models.ManyToManyField):
raise ImproperlyConfigured("'%s.filter_horizontal[%d]' must be "
"a ManyToManyField." % (cls.__name__, idx))
def validate_radio_fields(self, cls, model):
" Validate that radio_fields is a dictionary of choice or foreign key fields. "
from django.contrib.admin.options import HORIZONTAL, VERTICAL
if hasattr(cls, 'radio_fields'):
check_isdict(cls, 'radio_fields', cls.radio_fields)
for field, val in cls.radio_fields.items():
f = get_field(cls, model, 'radio_fields', field)
if not (isinstance(f, models.ForeignKey) or f.choices):
raise ImproperlyConfigured("'%s.radio_fields['%s']' "
"is neither an instance of ForeignKey nor does "
"have choices set." % (cls.__name__, field))
if not val in (HORIZONTAL, VERTICAL):
raise ImproperlyConfigured("'%s.radio_fields['%s']' "
"is neither admin.HORIZONTAL nor admin.VERTICAL."
% (cls.__name__, field))
def validate_prepopulated_fields(self, cls, model):
" Validate that prepopulated_fields if a dictionary containing allowed field types. "
# prepopulated_fields
if hasattr(cls, 'prepopulated_fields'):
check_isdict(cls, 'prepopulated_fields', cls.prepopulated_fields)
for field, val in cls.prepopulated_fields.items():
f = get_field(cls, model, 'prepopulated_fields', field)
if isinstance(f, (models.DateTimeField, models.ForeignKey,
models.ManyToManyField)):
raise ImproperlyConfigured("'%s.prepopulated_fields['%s']' "
"is either a DateTimeField, ForeignKey or "
"ManyToManyField. This isn't allowed."
% (cls.__name__, field))
check_isseq(cls, "prepopulated_fields['%s']" % field, val)
for idx, f in enumerate(val):
get_field(cls, model, "prepopulated_fields['%s'][%d]" % (field, idx), f)
def validate_ordering(self, cls, model):
" Validate that ordering refers to existing fields or is random. "
# ordering = None
if cls.ordering:
check_isseq(cls, 'ordering', cls.ordering)
for idx, field in enumerate(cls.ordering):
if field == '?' and len(cls.ordering) != 1:
raise ImproperlyConfigured("'%s.ordering' has the random "
"ordering marker '?', but contains other fields as "
"well. Please either remove '?' or the other fields."
% cls.__name__)
if field == '?':
continue
if field.startswith('-'):
field = field[1:]
# Skip ordering in the format field1__field2 (FIXME: checking
# this format would be nice, but it's a little fiddly).
if '__' in field:
continue
get_field(cls, model, 'ordering[%d]' % idx, field)
def validate_readonly_fields(self, cls, model):
" Validate that readonly_fields refers to proper attribute or field. "
if hasattr(cls, "readonly_fields"):
check_isseq(cls, "readonly_fields", cls.readonly_fields)
for idx, field in enumerate(cls.readonly_fields):
if not callable(field):
if not hasattr(cls, field):
if not hasattr(model, field):
try:
model._meta.get_field(field)
except models.FieldDoesNotExist:
raise ImproperlyConfigured("%s.readonly_fields[%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))
class ModelAdminValidator(BaseValidator):
def validate_save_as(self, cls, model):
" Validate save_as is a boolean. "
check_type(cls, 'save_as', bool)
def validate_save_on_top(self, cls, model):
" Validate save_on_top is a boolean. "
check_type(cls, 'save_on_top', bool)
def validate_inlines(self, cls, model):
" Validate inline model admin classes. "
from django.contrib.admin.options import BaseModelAdmin
if hasattr(cls, 'inlines'):
check_isseq(cls, 'inlines', cls.inlines)
for idx, inline in enumerate(cls.inlines):
if not issubclass(inline, BaseModelAdmin):
raise ImproperlyConfigured("'%s.inlines[%d]' does not inherit "
"from BaseModelAdmin." % (cls.__name__, idx))
if not inline.model:
raise ImproperlyConfigured("'model' is a required attribute "
"of '%s.inlines[%d]'." % (cls.__name__, idx))
if not issubclass(inline.model, models.Model):
raise ImproperlyConfigured("'%s.inlines[%d].model' does not "
"inherit from models.Model." % (cls.__name__, idx))
inline.validate(inline.model)
self.check_inline(inline, model)
def check_inline(self, cls, parent_model):
" Validate inline class's fk field is not excluded. "
fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name, can_fail=True)
if hasattr(cls, 'exclude') and cls.exclude:
if fk and fk.name in cls.exclude:
raise ImproperlyConfigured("%s cannot exclude the field "
"'%s' - this is the foreign key to the parent model "
"%s.%s." % (cls.__name__, fk.name, parent_model._meta.app_label, parent_model.__name__))
def validate_list_display(self, cls, model):
" Validate that list_display only contains fields or usable attributes. "
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:
model._meta.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, "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))
def validate_list_display_links(self, cls, model):
" Validate that list_display_links is a unique subset of list_display. "
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))
def validate_list_filter(self, cls, model):
"""
Validate that list_filter is a sequence of one of three options:
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
"""
from django.contrib.admin import ListFilter, FieldListFilter
if hasattr(cls, 'list_filter'):
check_isseq(cls, 'list_filter', cls.list_filter)
for idx, item in enumerate(cls.list_filter):
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
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."
% (cls.__name__, idx, field))
def validate_list_select_related(self, cls, model):
" Validate that list_select_related is a boolean, a list or a tuple. "
list_select_related = getattr(cls, 'list_select_related', None)
if list_select_related:
types = (bool, tuple, list)
if not isinstance(list_select_related, types):
raise ImproperlyConfigured("'%s.list_select_related' should be "
"either a bool, a tuple or a list" %
cls.__name__)
def validate_list_per_page(self, cls, model):
" Validate that list_per_page is an integer. "
check_type(cls, 'list_per_page', int)
def validate_list_max_show_all(self, cls, model):
" Validate that list_max_show_all is an integer. "
check_type(cls, 'list_max_show_all', int)
def validate_list_editable(self, cls, model):
"""
Validate that list_editable is a sequence of editable fields from
list_display without first element.
"""
if hasattr(cls, 'list_editable') and cls.list_editable:
check_isseq(cls, 'list_editable', cls.list_editable)
for idx, field_name in enumerate(cls.list_editable):
try:
field = model._meta.get_field_by_name(field_name)[0]
except models.FieldDoesNotExist:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
"field, '%s', not defined on %s.%s."
% (cls.__name__, idx, field_name, model._meta.app_label, model.__name__))
if field_name not in cls.list_display:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to "
"'%s' which is not defined in 'list_display'."
% (cls.__name__, idx, field_name))
if field_name in cls.list_display_links:
raise ImproperlyConfigured("'%s' cannot be in both '%s.list_editable'"
" and '%s.list_display_links'"
% (field_name, cls.__name__, cls.__name__))
if not cls.list_display_links and cls.list_display[0] in cls.list_editable:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to"
" the first field in list_display, '%s', which can't be"
" used unless list_display_links is set."
% (cls.__name__, idx, cls.list_display[0]))
if not field.editable:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
"field, '%s', which isn't editable through the admin."
% (cls.__name__, idx, field_name))
def validate_search_fields(self, cls, model):
" Validate search_fields is a sequence. "
if hasattr(cls, 'search_fields'):
check_isseq(cls, 'search_fields', cls.search_fields)
def validate_date_hierarchy(self, cls, model):
" Validate that date_hierarchy refers to DateField or DateTimeField. "
if cls.date_hierarchy:
f = get_field(cls, model, 'date_hierarchy', cls.date_hierarchy)
if not isinstance(f, (models.DateField, models.DateTimeField)):
raise ImproperlyConfigured("'%s.date_hierarchy is "
"neither an instance of DateField nor DateTimeField."
% cls.__name__)
if field == '?':
continue
if field.startswith('-'):
field = field[1:]
# Skip ordering in the format field1__field2 (FIXME: checking
# this format would be nice, but it's a little fiddly).
if '__' in field:
continue
get_field(cls, model, opts, 'ordering[%d]' % idx, field)
if hasattr(cls, "readonly_fields"):
check_readonly_fields(cls, model, opts)
# list_select_related = False
# save_as = False
# save_on_top = False
for attr in ('list_select_related', 'save_as', 'save_on_top'):
if not isinstance(getattr(cls, attr), bool):
raise ImproperlyConfigured("'%s.%s' should be a boolean."
% (cls.__name__, attr))
# inlines = []
if hasattr(cls, 'inlines'):
check_isseq(cls, 'inlines', cls.inlines)
for idx, inline in enumerate(cls.inlines):
if not issubclass(inline, BaseModelAdmin):
raise ImproperlyConfigured("'%s.inlines[%d]' does not inherit "
"from BaseModelAdmin." % (cls.__name__, idx))
if not inline.model:
raise ImproperlyConfigured("'model' is a required attribute "
"of '%s.inlines[%d]'." % (cls.__name__, idx))
if not issubclass(inline.model, models.Model):
raise ImproperlyConfigured("'%s.inlines[%d].model' does not "
"inherit from models.Model." % (cls.__name__, idx))
validate_base(inline, inline.model)
validate_inline(inline, cls, model)
class InlineValidator(BaseValidator):
def validate_fk_name(self, cls, model):
" Validate that fk_name refers to a ForeignKey. "
if cls.fk_name: # default value is None
f = get_field(cls, model, 'fk_name', cls.fk_name)
if not isinstance(f, models.ForeignKey):
raise ImproperlyConfigured("'%s.fk_name is not an instance of "
"models.ForeignKey." % cls.__name__)
def validate_inline(cls, parent, parent_model):
def validate_extra(self, cls, model):
" Validate that extra is an integer. "
check_type(cls, 'extra', int)
# model is already verified to exist and be a Model
if cls.fk_name: # default value is None
f = get_field(cls, cls.model, cls.model._meta, 'fk_name', cls.fk_name)
if not isinstance(f, models.ForeignKey):
raise ImproperlyConfigured("'%s.fk_name is not an instance of "
"models.ForeignKey." % cls.__name__)
def validate_max_num(self, cls, model):
" Validate that max_num is an integer. "
check_type(cls, 'max_num', int)
fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name, can_fail=True)
def validate_formset(self, cls, model):
" Validate formset is a subclass of BaseModelFormSet. "
if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet):
raise ImproperlyConfigured("'%s.formset' does not inherit from "
"BaseModelFormSet." % cls.__name__)
# extra = 3
if not isinstance(cls.extra, int):
raise ImproperlyConfigured("'%s.extra' should be a integer."
% cls.__name__)
# max_num = None
max_num = getattr(cls, 'max_num', None)
if max_num is not None and not isinstance(max_num, int):
raise ImproperlyConfigured("'%s.max_num' should be an integer or None (default)."
% cls.__name__)
# formset
if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet):
raise ImproperlyConfigured("'%s.formset' does not inherit from "
"BaseModelFormSet." % cls.__name__)
# exclude
if hasattr(cls, 'exclude') and cls.exclude:
if fk and fk.name in cls.exclude:
raise ImproperlyConfigured("%s cannot exclude the field "
"'%s' - this is the foreign key to the parent model "
"%s.%s." % (cls.__name__, fk.name, parent_model._meta.app_label, parent_model.__name__))
if hasattr(cls, "readonly_fields"):
check_readonly_fields(cls, cls.model, cls.model._meta)
def validate_fields_spec(cls, model, opts, flds, label):
"""
Validate the fields specification in `flds` from a ModelAdmin subclass
`cls` for the `model` model. `opts` is `model`'s Meta inner class.
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:
f = opts.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_base(cls, model):
opts = model._meta
# raw_id_fields
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, opts, '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))
# fields
if cls.fields: # default value is None
check_isseq(cls, 'fields', cls.fields)
validate_fields_spec(cls, model, opts, 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__)
# 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))
validate_fields_spec(cls, model, opts, 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__)
# exclude
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__)
# form
if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm):
raise ImproperlyConfigured("%s.form does not inherit from "
"BaseModelForm." % cls.__name__)
# filter_vertical
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, opts, 'filter_vertical', field)
if not isinstance(f, models.ManyToManyField):
raise ImproperlyConfigured("'%s.filter_vertical[%d]' must be "
"a ManyToManyField." % (cls.__name__, idx))
# filter_horizontal
if hasattr(cls, 'filter_horizontal'):
check_isseq(cls, 'filter_horizontal', cls.filter_horizontal)
for idx, field in enumerate(cls.filter_horizontal):
f = get_field(cls, model, opts, 'filter_horizontal', field)
if not isinstance(f, models.ManyToManyField):
raise ImproperlyConfigured("'%s.filter_horizontal[%d]' must be "
"a ManyToManyField." % (cls.__name__, idx))
# radio_fields
if hasattr(cls, 'radio_fields'):
check_isdict(cls, 'radio_fields', cls.radio_fields)
for field, val in cls.radio_fields.items():
f = get_field(cls, model, opts, 'radio_fields', field)
if not (isinstance(f, models.ForeignKey) or f.choices):
raise ImproperlyConfigured("'%s.radio_fields['%s']' "
"is neither an instance of ForeignKey nor does "
"have choices set." % (cls.__name__, field))
if not val in (HORIZONTAL, VERTICAL):
raise ImproperlyConfigured("'%s.radio_fields['%s']' "
"is neither admin.HORIZONTAL nor admin.VERTICAL."
% (cls.__name__, field))
# prepopulated_fields
if hasattr(cls, 'prepopulated_fields'):
check_isdict(cls, 'prepopulated_fields', cls.prepopulated_fields)
for field, val in cls.prepopulated_fields.items():
f = get_field(cls, model, opts, 'prepopulated_fields', field)
if isinstance(f, (models.DateTimeField, models.ForeignKey,
models.ManyToManyField)):
raise ImproperlyConfigured("'%s.prepopulated_fields['%s']' "
"is either a DateTimeField, ForeignKey or "
"ManyToManyField. This isn't allowed."
% (cls.__name__, field))
check_isseq(cls, "prepopulated_fields['%s']" % field, val)
for idx, f in enumerate(val):
get_field(cls, model, opts, "prepopulated_fields['%s'][%d]" % (field, idx), f)
def check_type(cls, attr, type_):
if getattr(cls, attr, None) is not None and not isinstance(getattr(cls, attr), type_):
raise ImproperlyConfigured("'%s.%s' should be a %s."
% (cls.__name__, attr, type_.__name__ ))
def check_isseq(cls, label, obj):
if not isinstance(obj, (list, tuple)):
@ -364,16 +411,16 @@ def check_isdict(cls, label, obj):
if not isinstance(obj, dict):
raise ImproperlyConfigured("'%s.%s' must be a dictionary." % (cls.__name__, label))
def get_field(cls, model, opts, label, field):
def get_field(cls, model, label, field):
try:
return opts.get_field(field)
return model._meta.get_field(field)
except models.FieldDoesNotExist:
raise ImproperlyConfigured("'%s.%s' refers to field '%s' that is missing from model '%s.%s'."
% (cls.__name__, label, field, model._meta.app_label, model.__name__))
def fetch_attr(cls, model, opts, label, field):
def fetch_attr(cls, model, label, field):
try:
return opts.get_field(field)
return model._meta.get_field(field)
except models.FieldDoesNotExist:
pass
try:
@ -381,15 +428,3 @@ def fetch_attr(cls, model, opts, label, field):
except AttributeError:
raise ImproperlyConfigured("'%s.%s' refers to '%s' that is neither a field, method or property of model '%s.%s'."
% (cls.__name__, label, field, model._meta.app_label, model.__name__))
def check_readonly_fields(cls, model, opts):
check_isseq(cls, "readonly_fields", cls.readonly_fields)
for idx, field in enumerate(cls.readonly_fields):
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.readonly_fields[%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))

View File

@ -1,7 +1,5 @@
import operator
import sys
import warnings
from functools import reduce
from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured
from django.core.paginator import InvalidPage
@ -16,6 +14,7 @@ from django.utils.translation import ugettext, ugettext_lazy
from django.utils.http import urlencode
from django.contrib.admin import FieldListFilter
from django.contrib.admin.exceptions import DisallowedModelAdminLookup
from django.contrib.admin.options import IncorrectLookupParameters
from django.contrib.admin.util import (quote, get_fields_from_path,
lookup_needs_distinct, prepare_lookup_value)
@ -130,7 +129,7 @@ class ChangeList(six.with_metaclass(RenameChangeListMethods)):
lookup_params[force_str(key)] = value
if not self.model_admin.lookup_allowed(key, value):
raise SuspiciousOperation("Filtering by %s not allowed" % key)
raise DisallowedModelAdminLookup("Filtering by %s not allowed" % key)
filter_specs = []
if self.list_filter:
@ -331,7 +330,7 @@ class ChangeList(six.with_metaclass(RenameChangeListMethods)):
def get_queryset(self, request):
# First, we collect all the declared list filters.
(self.filter_specs, self.has_filters, remaining_lookup_params,
use_distinct) = self.get_filters(request)
filters_use_distinct) = self.get_filters(request)
# Then, we let every list filter modify the queryset to its liking.
qs = self.root_queryset
@ -357,56 +356,46 @@ class ChangeList(six.with_metaclass(RenameChangeListMethods)):
# ValueError, ValidationError, or ?.
raise IncorrectLookupParameters(e)
# Use select_related() if one of the list_display options is a field
# with a relationship and the provided queryset doesn't already have
# select_related defined.
if not qs.query.select_related:
if self.list_select_related:
qs = qs.select_related()
else:
for field_name in self.list_display:
try:
field = self.lookup_opts.get_field(field_name)
except models.FieldDoesNotExist:
pass
else:
if isinstance(field.rel, models.ManyToOneRel):
qs = qs.select_related()
break
qs = self.apply_select_related(qs)
# Set ordering.
ordering = self.get_ordering(request, qs)
qs = qs.order_by(*ordering)
# 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
# Apply search results
qs, search_use_distinct = self.model_admin.get_search_results(
request, qs, self.query)
if self.search_fields and self.query:
orm_lookups = [construct_search(str(search_field))
for search_field in self.search_fields]
for bit in self.query.split():
or_queries = [models.Q(**{orm_lookup: bit})
for orm_lookup in orm_lookups]
qs = qs.filter(reduce(operator.or_, or_queries))
if not use_distinct:
for search_spec in orm_lookups:
if lookup_needs_distinct(self.lookup_opts, search_spec):
use_distinct = True
break
if use_distinct:
# Remove duplicates from results, if necessary
if filters_use_distinct | search_use_distinct:
return qs.distinct()
else:
return qs
def apply_select_related(self, qs):
if self.list_select_related is True:
return qs.select_related()
if self.list_select_related is False:
if self.has_related_field_in_list_display():
return qs.select_related()
if self.list_select_related:
return qs.select_related(*self.list_select_related)
return qs
def has_related_field_in_list_display(self):
for field_name in self.list_display:
try:
field = self.lookup_opts.get_field(field_name)
except models.FieldDoesNotExist:
pass
else:
if isinstance(field.rel, models.ManyToOneRel):
return True
return False
def url_for_result(self, result):
pk = getattr(result, self.pk_attname)
return reverse('admin:%s_%s_change' % (self.opts.app_label,

View File

@ -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-06-02 00:30-0400\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English <en@li.org>\n"
@ -60,12 +60,13 @@ msgstr ""
msgid "number of %s"
msgstr ""
#: views.py:270
#. Translators: %s is an object type name
#: views.py:271
#, python-format
msgid "Fields on %s objects"
msgid "Attributes on %s objects"
msgstr ""
#: views.py:362
#: views.py:363
#, python-format
msgid "%s does not appear to be a urlpattern object"
msgstr ""
@ -83,7 +84,9 @@ msgid "Home"
msgstr ""
#: templates/admin_doc/bookmarklets.html:7 templates/admin_doc/index.html:7
#: templates/admin_doc/index.html.py:10 templates/admin_doc/index.html:14
#: templates/admin_doc/missing_docutils.html:7
#: templates/admin_doc/missing_docutils.html:14
#: templates/admin_doc/model_detail.html:15
#: templates/admin_doc/model_index.html:9
#: templates/admin_doc/template_detail.html:7
@ -94,7 +97,7 @@ msgstr ""
msgid "Documentation"
msgstr ""
#: templates/admin_doc/bookmarklets.html:8
#: templates/admin_doc/bookmarklets.html:8 templates/admin_doc/index.html:29
msgid "Bookmarklets"
msgstr ""
@ -149,26 +152,202 @@ msgstr ""
msgid "As above, but opens the admin page in a new window."
msgstr ""
#: templates/admin_doc/model_detail.html:16
#: templates/admin_doc/index.html:17
#: templates/admin_doc/template_tag_index.html:9
msgid "Tags"
msgstr ""
#: templates/admin_doc/index.html:18
msgid "List of all the template tags and their functions."
msgstr ""
#: templates/admin_doc/index.html:20
#: templates/admin_doc/template_filter_index.html:9
msgid "Filters"
msgstr ""
#: templates/admin_doc/index.html:21
msgid ""
"Filters are actions which can be applied to variables in a template to alter "
"the output."
msgstr ""
#: templates/admin_doc/index.html:23 templates/admin_doc/model_detail.html:16
#: templates/admin_doc/model_index.html:10
#: templates/admin_doc/model_index.html:14
msgid "Models"
msgstr ""
#: templates/admin_doc/index.html:24
msgid ""
"Models are descriptions of all the objects in the system and their "
"associated fields. Each model has a list of fields which can be accessed as "
"template variables"
msgstr ""
#: templates/admin_doc/index.html:26 templates/admin_doc/view_detail.html:8
#: templates/admin_doc/view_index.html:9
#: templates/admin_doc/view_index.html:12
msgid "Views"
msgstr ""
#: templates/admin_doc/index.html:27
msgid ""
"Each page on the public site is generated by a view. The view defines which "
"template is used to generate the page and which objects are available to "
"that template."
msgstr ""
#: templates/admin_doc/index.html:30
msgid "Tools for your browser to quickly access admin functionality."
msgstr ""
#: templates/admin_doc/missing_docutils.html:10
msgid "Please install docutils"
msgstr ""
#: templates/admin_doc/missing_docutils.html:17
#, python-format
msgid ""
"The admin documentation system requires Python's <a href=\"%(link)s"
"\">docutils</a> library."
msgstr ""
#: templates/admin_doc/missing_docutils.html:19
#, python-format
msgid ""
"Please ask your administrators to install <a href=\"%(link)s\">docutils</a>."
msgstr ""
#: templates/admin_doc/model_detail.html:21
#, python-format
msgid "Model: %(name)s"
msgstr ""
#: templates/admin_doc/model_detail.html:35
msgid "Field"
msgstr ""
#: templates/admin_doc/model_detail.html:36
msgid "Type"
msgstr ""
#: templates/admin_doc/model_detail.html:37
msgid "Description"
msgstr ""
#: templates/admin_doc/model_detail.html:52
msgid "Back to Models Documentation"
msgstr ""
#: templates/admin_doc/model_index.html:18
msgid "Model documentation"
msgstr ""
#: templates/admin_doc/model_index.html:43
msgid "Model groups"
msgstr ""
#: templates/admin_doc/template_detail.html:8
msgid "Templates"
msgstr ""
#: templates/admin_doc/template_filter_index.html:9
msgid "Filters"
#: templates/admin_doc/template_detail.html:13
#, python-format
msgid "Template: %(name)s"
msgstr ""
#: templates/admin_doc/template_tag_index.html:9
msgid "Tags"
#: templates/admin_doc/template_detail.html:16
#, python-format
msgid "Template: \"%(name)s\""
msgstr ""
#: templates/admin_doc/view_detail.html:8
#: templates/admin_doc/view_index.html:9
msgid "Views"
#: templates/admin_doc/template_detail.html:20
#, python-format
msgid "Search path for template \"%(name)s\" on %(grouper)s:"
msgstr ""
#: templates/admin_doc/template_detail.html:23
msgid "(does not exist)"
msgstr ""
#: templates/admin_doc/template_detail.html:28
msgid "Back to Documentation"
msgstr ""
#: templates/admin_doc/template_filter_index.html:12
msgid "Template filters"
msgstr ""
#: templates/admin_doc/template_filter_index.html:16
msgid "Template filter documentation"
msgstr ""
#: templates/admin_doc/template_filter_index.html:22
#: templates/admin_doc/template_filter_index.html:43
msgid "Built-in filters"
msgstr ""
#: templates/admin_doc/template_filter_index.html:23
#, python-format
msgid ""
"To use these filters, put <code>%(code)s</code> in your template before "
"using the filter."
msgstr ""
#: templates/admin_doc/template_tag_index.html:12
msgid "Template tags"
msgstr ""
#: templates/admin_doc/template_tag_index.html:16
msgid "Template tag documentation"
msgstr ""
#: templates/admin_doc/template_tag_index.html:22
#: templates/admin_doc/template_tag_index.html:43
msgid "Built-in tags"
msgstr ""
#: templates/admin_doc/template_tag_index.html:23
#, python-format
msgid ""
"To use these tags, put <code>%(code)s</code> in your template before using "
"the tag."
msgstr ""
#: templates/admin_doc/view_detail.html:12
#, python-format
msgid "View: %(name)s"
msgstr ""
#: templates/admin_doc/view_detail.html:23
msgid "Context:"
msgstr ""
#: templates/admin_doc/view_detail.html:28
msgid "Templates:"
msgstr ""
#: templates/admin_doc/view_detail.html:32
msgid "Back to Views Documentation"
msgstr ""
#: templates/admin_doc/view_index.html:16
msgid "View documentation"
msgstr ""
#: templates/admin_doc/view_index.html:22
msgid "Jump to site"
msgstr ""
#: templates/admin_doc/view_index.html:35
#, python-format
msgid "Views by URL on %(name)s"
msgstr ""
#: templates/admin_doc/view_index.html:40
#, python-format
msgid "View function: %(name)s"
msgstr ""
#: tests/test_fields.py:29

View File

@ -0,0 +1,23 @@
from django.conf import settings
from django import http
class XViewMiddleware(object):
"""
Adds an X-View header to internal HEAD requests -- used by the documentation system.
"""
def process_view(self, request, view_func, view_args, view_kwargs):
"""
If the request method is HEAD and either the IP is internal or the
user is a logged-in staff member, quickly return with an x-header
indicating the view function. This is used by the documentation module
to lookup the view function for an arbitrary page.
"""
assert hasattr(request, 'user'), (
"The XView middleware requires authentication middleware to be "
"installed. Edit your MIDDLEWARE_CLASSES setting to insert "
"'django.contrib.auth.middleware.AuthenticationMiddleware'.")
if request.method == 'HEAD' and (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS or
(request.user.is_active and request.user.is_staff)):
response = http.HttpResponse()
response['X-View'] = "%s.%s" % (view_func.__module__, view_func.__name__)
return response

View File

@ -7,27 +7,27 @@
&rsaquo; {% trans 'Documentation' %}</a>
</div>
{% endblock %}
{% block title %}Documentation{% endblock %}
{% block title %}{% trans 'Documentation' %}{% endblock %}
{% block content %}
<h1>Documentation</h1>
<h1>{% trans 'Documentation' %}</h1>
<div id="content-main">
<h3><a href="tags/">Tags</a></h3>
<p>List of all the template tags and their functions.</p>
<h3><a href="tags/">{% trans 'Tags' %}</a></h3>
<p>{% trans 'List of all the template tags and their functions.' %}</p>
<h3><a href="filters/">Filters</a></h3>
<p>Filters are actions which can be applied to variables in a template to alter the output.</p>
<h3><a href="filters/">{% trans 'Filters' %}</a></h3>
<p>{% trans 'Filters are actions which can be applied to variables in a template to alter the output.' %}</p>
<h3><a href="models/">Models</a></h3>
<p>Models are descriptions of all the objects in the system and their associated fields. Each model has a list of fields which can be accessed as template variables.</p>
<h3><a href="models/">{% trans 'Models' %}</a></h3>
<p>{% trans 'Models are descriptions of all the objects in the system and their associated fields. Each model has a list of fields which can be accessed as template variables' %}.</p>
<h3><a href="views/">Views</a></h3>
<p>Each page on the public site is generated by a view. The view defines which template is used to generate the page and which objects are available to that template.</p>
<h3><a href="views/">{% trans 'Views' %}</a></h3>
<p>{% trans 'Each page on the public site is generated by a view. The view defines which template is used to generate the page and which objects are available to that template.' %}</p>
<h3><a href="bookmarklets/">Bookmarklets</a></h3>
<p>Tools for your browser to quickly access admin functionality.</p>
<h3><a href="bookmarklets/">{% trans 'Bookmarklets' %}</a></h3>
<p>{% trans 'Tools for your browser to quickly access admin functionality.' %}</p>
</div>
{% endblock %}

View File

@ -7,16 +7,16 @@
&rsaquo; {% trans 'Documentation' %}</a>
</div>
{% endblock %}
{% block title %}Please install docutils{% endblock %}
{% block title %}{% trans 'Please install docutils' %}{% endblock %}
{% block content %}
<h1>Documentation</h1>
<h1>{% trans 'Documentation' %}</h1>
<div id="content-main">
<h3>The admin documentation system requires Python's <a href="http://docutils.sf.net/">docutils</a> library.</h3>
<h3>{% blocktrans with "http://docutils.sf.net/" as link %}The admin documentation system requires Python's <a href="{{ link }}">docutils</a> library.{% endblocktrans %}</h3>
<p>Please ask your administrators to install <a href="http://docutils.sf.net/">docutils</a>.</p>
<p>{% blocktrans with "http://docutils.sf.net/" as link %}Please ask your administrators to install <a href="{{ link }}">docutils</a>.{% endblocktrans %}</p>
</div>
{% endblock %}

View File

@ -18,7 +18,7 @@
</div>
{% endblock %}
{% block title %}Model: {{ name }}{% endblock %}
{% block title %}{% blocktrans %}Model: {{ name }}{% endblocktrans %}{% endblock %}
{% block content %}
<div id="content-main">
@ -32,9 +32,9 @@
<table class="model">
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
<th>{% trans 'Field' %}</th>
<th>{% trans 'Type' %}</th>
<th>{% trans 'Description' %}</th>
</tr>
</thead>
<tbody>
@ -49,6 +49,6 @@
</table>
</div>
<p class="small"><a href="{% url 'django-admindocs-models-index' %}">&lsaquo; Back to Models Documentation</a></p>
<p class="small"><a href="{% url 'django-admindocs-models-index' %}">&lsaquo; {% trans 'Back to Models Documentation' %}</a></p>
</div>
{% endblock %}

View File

@ -11,11 +11,11 @@
</div>
{% endblock %}
{% block title %}Models{% endblock %}
{% block title %}{% trans 'Models' %}{% endblock %}
{% block content %}
<h1>Model documentation</h1>
<h1>{% trans 'Model documentation' %}</h1>
{% regroup models by app_label as grouped_models %}
@ -40,7 +40,7 @@
{% block sidebar %}
<div id="content-related" class="sidebar">
<div class="module">
<h2>Model groups</h2>
<h2>{% trans 'Model groups' %}</h2>
<ul>
{% regroup models by app_label as grouped_models %}
{% for group in grouped_models %}

View File

@ -10,20 +10,20 @@
</div>
{% endblock %}
{% block title %}Template: {{ name }}{% endblock %}
{% block title %}{% blocktrans %}Template: {{ name }}{% endblocktrans %}{% endblock %}
{% block content %}
<h1>Template: "{{ name }}"</h1>
<h1>{% blocktrans %}Template: "{{ name }}"{% endblocktrans %}</h1>
{% regroup templates|dictsort:"site_id" by site as templates_by_site %}
{% for group in templates_by_site %}
<h2>Search path for template "{{ name }}" on {{ group.grouper }}:</h2>
<h2>{% blocktrans with group.grouper as grouper %}Search path for template "{{ name }}" on {{ grouper }}:{% endblocktrans %}</h2>
<ol>
{% for template in group.list|dictsort:"order" %}
<li><code>{{ template.file }}</code>{% if not template.exists %} <em>(does not exist)</em>{% endif %}</li>
<li><code>{{ template.file }}</code>{% if not template.exists %} <em>{% trans '(does not exist)' %}</em>{% endif %}</li>
{% endfor %}
</ol>
{% endfor %}
<p class="small"><a href="{% url 'django-admindocs-docroot' %}">&lsaquo; Back to Documentation</a></p>
<p class="small"><a href="{% url 'django-admindocs-docroot' %}">&lsaquo; {% trans 'Back to Documentation' %}</a></p>
{% endblock %}

View File

@ -9,18 +9,18 @@
&rsaquo; {% trans 'Filters' %}
</div>
{% endblock %}
{% block title %}Template filters{% endblock %}
{% block title %}{% trans 'Template filters' %}{% endblock %}
{% block content %}
<h1>Template filter documentation</h1>
<h1>{% trans 'Template filter documentation' %}</h1>
<div id="content-main">
{% regroup filters|dictsort:"library" by library as filter_libraries %}
{% for library in filter_libraries %}
<div class="module">
<h2>{% firstof library.grouper "Built-in filters" %}</h2>
{% if library.grouper %}<p class="small quiet">To use these filters, put <code>{% templatetag openblock %} load {{ library.grouper }} {% templatetag closeblock %}</code> in your template before using the filter.</p><hr />{% endif %}
<h2>{% firstof library.grouper _("Built-in filters") %}</h2>
{% if library.grouper %}<p class="small quiet">{% blocktrans with code="{"|add:"% load "|add:library.grouper|add:" %"|add:"}" %}To use these filters, put <code>{{ code }}</code> in your template before using the filter.{% endblocktrans %}</p><hr />{% endif %}
{% for filter in library.list|dictsort:"name" %}
<h3 id="{{ library.grouper|default:"built_in" }}-{{ filter.name }}">{{ filter.name }}</h3>
{{ filter.title }}
@ -40,7 +40,7 @@
{% regroup filters|dictsort:"library" by library as filter_libraries %}
{% for library in filter_libraries %}
<div class="module">
<h2>{% firstof library.grouper "Built-in filters" %}</h2>
<h2>{% firstof library.grouper _("Built-in filters") %}</h2>
<ul>
{% for filter in library.list|dictsort:"name" %}
<li><a href="#{{ library.grouper|default:"built_in" }}-{{ filter.name }}">{{ filter.name }}</a></li>

View File

@ -9,18 +9,18 @@
&rsaquo; {% trans 'Tags' %}
</div>
{% endblock %}
{% block title %}Template tags{% endblock %}
{% block title %}{% trans 'Template tags' %}{% endblock %}
{% block content %}
<h1>Template tag documentation</h1>
<h1>{% trans 'Template tag documentation' %}</h1>
<div id="content-main">
{% regroup tags|dictsort:"library" by library as tag_libraries %}
{% for library in tag_libraries %}
<div class="module">
<h2>{% firstof library.grouper "Built-in tags" %}</h2>
{% if library.grouper %}<p class="small quiet">To use these tags, put <code>{% templatetag openblock %} load {{ library.grouper }} {% templatetag closeblock %}</code> in your template before using the tag.</p><hr />{% endif %}
<h2>{% firstof library.grouper _("Built-in tags") %}</h2>
{% if library.grouper %}<p class="small quiet">{% blocktrans with code="{"|add:"% load "|add:library.grouper|add:" %"|add:"}" %}To use these tags, put <code>{{ code }}</code> in your template before using the tag.{% endblocktrans %}</p><hr />{% endif %}
{% for tag in library.list|dictsort:"name" %}
<h3 id="{{ library.grouper|default:"built_in" }}-{{ tag.name }}">{{ tag.name }}</h3>
<h4>{{ tag.title|striptags }}</h4>
@ -40,7 +40,7 @@
{% regroup tags|dictsort:"library" by library as tag_libraries %}
{% for library in tag_libraries %}
<div class="module">
<h2>{% firstof library.grouper "Built-in tags" %}</h2>
<h2>{% firstof library.grouper _("Built-in tags") %}</h2>
<ul>
{% for tag in library.list|dictsort:"name" %}
<li><a href="#{{ library.grouper|default:"built_in" }}-{{ tag.name }}">{{ tag.name }}</a></li>

View File

@ -9,7 +9,7 @@
&rsaquo; {{ name }}
</div>
{% endblock %}
{% block title %}View: {{ name }}{% endblock %}
{% block title %}{% blocktrans %}View: {{ name }}{% endblocktrans %}{% endblock %}
{% block content %}
@ -20,14 +20,14 @@
{{ body }}
{% if meta.Context %}
<h3>Context:</h3>
<h3>{% trans 'Context:' %}</h3>
<p>{{ meta.Context }}</p>
{% endif %}
{% if meta.Templates %}
<h3>Templates:</h3>
<h3>{% trans 'Templates:' %}</h3>
<p>{{ meta.Templates }}</p>
{% endif %}
<p class="small"><a href="{% url 'django-admindocs-views-index' %}">&lsaquo; Back to Views Documentation</a></p>
<p class="small"><a href="{% url 'django-admindocs-views-index' %}">&lsaquo; {% trans 'Back to Views Documentation' %}</a></p>
{% endblock %}

View File

@ -9,17 +9,17 @@
&rsaquo; {% trans 'Views' %}
</div>
{% endblock %}
{% block title %}Views{% endblock %}
{% block title %}{% trans 'Views' %}{% endblock %}
{% block content %}
<h1>View documentation</h1>
<h1>{% trans 'View documentation' %}</h1>
{% regroup views|dictsort:"site_id" by site as views_by_site %}
<div id="content-related" class="sidebar">
<div class="module">
<h2>Jump to site</h2>
<h2>{% trans 'Jump to site' %}</h2>
<ul>
{% for site_views in views_by_site %}
<li><a href="#site{{ site_views.grouper.id }}">{{ site_views.grouper.name }}</a></li>
@ -32,12 +32,12 @@
{% for site_views in views_by_site %}
<div class="module">
<h2 id="site{{ site_views.grouper.id }}">Views by URL on {{ site_views.grouper.name }}</h2>
<h2 id="site{{ site_views.grouper.id }}">{% blocktrans with site_views.grouper.name as name %}Views by URL on {{ name }}{% endblocktrans %}</h2>
{% for view in site_views.list|dictsort:"url" %}
{% ifchanged %}
<h3><a href="{% url 'django-admindocs-views-detail' view=view.full_name %}">{{ view.url }}</a></h3>
<p class="small quiet">View function: {{ view.full_name }}</p>
<p class="small quiet">{% blocktrans with view.full_name as name %}View function: {{ name }}{% endblocktrans %}</p>
<p>{{ view.title }}</p>
<hr />
{% endifchanged %}
@ -46,5 +46,3 @@
{% endfor %}
</div>
{% endblock %}

View File

@ -39,7 +39,7 @@ def bookmarklets(request):
admin_root = urlresolvers.reverse('admin:index')
return render_to_response('admin_doc/bookmarklets.html', {
'root_path': admin_root,
'admin_url': "%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), admin_root),
'admin_url': "%s://%s%s" % ('https' if request.is_secure() else 'http', request.get_host(), admin_root),
}, context_instance=RequestContext(request))
@staff_member_required
@ -267,6 +267,7 @@ def model_detail(request, app_label, model_name):
return render_to_response('admin_doc/model_detail.html', {
'root_path': urlresolvers.reverse('admin:index'),
'name': '%s.%s' % (opts.app_label, opts.object_name),
# Translators: %s is an object type name
'summary': _("Attributes on %s objects") % opts.object_name,
'description': model.__doc__,
'fields': fields,
@ -286,7 +287,7 @@ def template_detail(request, template):
templates.append({
'file': template_file,
'exists': os.path.exists(template_file),
'contents': lambda: os.path.exists(template_file) and open(template_file).read() or '',
'contents': lambda: open(template_file).read() if os.path.exists(template_file) else '',
'site_id': settings_mod.SITE_ID,
'site': site_obj,
'order': list(settings_mod.TEMPLATE_DIRS).index(dir),

View File

@ -1,8 +1,11 @@
import re
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.utils.module_loading import import_by_path
from django.middleware.csrf import rotate_token
from .signals import user_logged_in, user_logged_out, user_login_failed
SESSION_KEY = '_auth_user_id'
BACKEND_SESSION_KEY = '_auth_user_backend'
@ -14,7 +17,6 @@ def load_backend(path):
def get_backends():
from django.conf import settings
backends = []
for backend_path in settings.AUTHENTICATION_BACKENDS:
backends.append(load_backend(backend_path))
@ -83,6 +85,7 @@ def login(request, user):
request.session[BACKEND_SESSION_KEY] = user.backend
if hasattr(request, 'user'):
request.user = user
rotate_token(request)
user_logged_in.send(sender=user.__class__, request=request, user=user)
@ -106,7 +109,6 @@ def logout(request):
def get_user_model():
"Return the User model that is active in this project"
from django.conf import settings
from django.db.models import get_model
try:
@ -120,12 +122,13 @@ def get_user_model():
def get_user(request):
from django.contrib.auth.models import AnonymousUser
from .models import AnonymousUser
try:
user_id = request.session[SESSION_KEY]
backend_path = request.session[BACKEND_SESSION_KEY]
assert backend_path in settings.AUTHENTICATION_BACKENDS
backend = load_backend(backend_path)
user = backend.get_user(user_id) or AnonymousUser()
except KeyError:
except (KeyError, AssertionError):
user = AnonymousUser()
return user

View File

@ -237,7 +237,7 @@ class PasswordResetForm(forms.Form):
'uid': int_to_base36(user.pk),
'user': user,
'token': token_generator.make_token(user),
'protocol': use_https and 'https' or 'http',
'protocol': 'https' if use_https else 'http',
}
subject = loader.render_to_string(subject_template_name, c)
# Email subject *must not* contain newlines

View File

@ -80,7 +80,7 @@ def create_permissions(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, **kw
for perm in _get_all_permissions(klass._meta, ctype):
searched_perms.append((ctype, perm))
# Find all the Permissions that have a context_type for a model we're
# Find all the Permissions that have a content_type for a model we're
# looking for. We don't need to check for codenames since we already have
# a list of the ones we're going to create.
all_perms = set(auth_app.Permission.objects.using(db).filter(

View File

@ -177,7 +177,7 @@ class UserManager(BaseUserManager):
now = timezone.now()
if not username:
raise ValueError('The given username must be set')
email = UserManager.normalize_email(email)
email = self.normalize_email(email)
user = self.model(username=username, email=email,
is_staff=False, is_active=True, is_superuser=False,
last_login=now, date_joined=now, **extra_fields)

View File

@ -2,12 +2,14 @@ from __future__ import unicode_literals
from datetime import date
from django.conf import settings
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User, Group, Permission, AnonymousUser
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.auth.tests.test_custom_user import ExtensionUser, CustomPermissionsUser, CustomUser
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.contrib.auth import authenticate
from django.contrib.auth import authenticate, get_user
from django.http import HttpRequest
from django.test import TestCase
from django.test.utils import override_settings
@ -402,3 +404,52 @@ class PermissionDeniedBackendTest(TestCase):
settings.AUTHENTICATION_BACKENDS) + (backend, ))
def test_authenticates(self):
self.assertEqual(authenticate(username='test', password='test'), self.user1)
class NewModelBackend(ModelBackend):
pass
@skipIfCustomUser
class ChangedBackendSettingsTest(TestCase):
"""
Tests for changes in the settings.AUTHENTICATION_BACKENDS
"""
backend = 'django.contrib.auth.tests.test_auth_backends.NewModelBackend'
TEST_USERNAME = 'test_user'
TEST_PASSWORD = 'test_password'
TEST_EMAIL = 'test@example.com'
def setUp(self):
User.objects.create_user(self.TEST_USERNAME,
self.TEST_EMAIL,
self.TEST_PASSWORD)
@override_settings(AUTHENTICATION_BACKENDS=(backend, ))
def test_changed_backend_settings(self):
"""
Tests that removing a backend configured in AUTHENTICATION_BACKENDS
make already logged-in users disconnect.
"""
# Get a session for the test user
self.assertTrue(self.client.login(
username=self.TEST_USERNAME,
password=self.TEST_PASSWORD)
)
# Prepare a request object
request = HttpRequest()
request.session = self.client.session
# Remove NewModelBackend
with self.settings(AUTHENTICATION_BACKENDS=(
'django.contrib.auth.backends.ModelBackend',)):
# Get the user from the request
user = get_user(request)
# Assert that the user retrieval is successful and the user is
# anonymous as the backend is not longer available.
self.assertIsNotNone(user)
self.assertTrue(user.is_anonymous())

View File

@ -21,7 +21,7 @@ class CustomUserManager(BaseUserManager):
raise ValueError('Users must have an email address')
user = self.model(
email=CustomUserManager.normalize_email(email),
email=self.normalize_email(email),
date_of_birth=date_of_birth,
)

View File

@ -7,6 +7,7 @@ from django.contrib.auth.management.commands import changepassword
from django.contrib.auth.models import User
from django.contrib.auth.tests.test_custom_user import CustomUser
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.contenttypes.models import ContentType
from django.core.management import call_command
from django.core.management.base import CommandError
from django.core.management.validation import get_validation_errors
@ -195,6 +196,7 @@ class PermissionDuplicationTestCase(TestCase):
def tearDown(self):
models.Permission._meta.permissions = self._original_permissions
ContentType.objects.clear_cache()
def test_duplicated_permissions(self):
"""

View File

@ -1,25 +1,31 @@
import itertools
import os
import re
try:
from urllib.parse import urlparse, ParseResult
except ImportError: # Python 2
from urlparse import urlparse, ParseResult
from django.conf import global_settings, settings
from django.contrib.sites.models import Site, RequestSite
from django.contrib.auth.models import User
from django.core import mail
from django.core.exceptions import SuspiciousOperation
from django.core.urlresolvers import reverse, NoReverseMatch
from django.http import QueryDict
from django.http import QueryDict, HttpRequest
from django.utils.encoding import force_text
from django.utils.html import escape
from django.utils.http import urlquote
from django.utils._os import upath
from django.test import TestCase
from django.test.utils import override_settings
from django.test.utils import override_settings, patch_logger
from django.middleware.csrf import CsrfViewMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.contrib.auth import SESSION_KEY, REDIRECT_FIELD_NAME
from django.contrib.auth.forms import (AuthenticationForm, PasswordChangeForm,
SetPasswordForm, PasswordResetForm)
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.auth.views import login as login_view
@override_settings(
@ -46,15 +52,31 @@ class AuthViewsTestCase(TestCase):
'username': 'testclient',
'password': password,
})
self.assertEqual(response.status_code, 302)
self.assertTrue(response.url.endswith(settings.LOGIN_REDIRECT_URL))
self.assertTrue(SESSION_KEY in self.client.session)
return response
def assertFormError(self, response, error):
"""Assert that error is found in response.context['form'] errors"""
form_errors = list(itertools.chain(*response.context['form'].errors.values()))
self.assertIn(force_text(error), form_errors)
def assertURLEqual(self, url, expected, parse_qs=False):
"""
Given two URLs, make sure all their components (the ones given by
urlparse) are equal, only comparing components that are present in both
URLs.
If `parse_qs` is True, then the querystrings are parsed with QueryDict.
This is useful if you don't want the order of parameters to matter.
Otherwise, the query strings are compared as-is.
"""
fields = ParseResult._fields
for attr, x, y in zip(fields, urlparse(url), urlparse(expected)):
if parse_qs and attr == 'query':
x, y = QueryDict(x), QueryDict(y)
if x and y and x != y:
self.fail("%r != %r (%s doesn't match)" % (url, expected, attr))
@skipIfCustomUser
class AuthViewNamedURLTests(AuthViewsTestCase):
@ -132,28 +154,32 @@ class PasswordResetTest(AuthViewsTestCase):
# produce a meaningful reset URL, we need to be certain that the
# HTTP_HOST header isn't poisoned. This is done as a check when get_host()
# is invoked, but we check here as a practical consequence.
with self.assertRaises(SuspiciousOperation):
self.client.post('/password_reset/',
{'email': 'staffmember@example.com'},
HTTP_HOST='www.example:dr.frankenstein@evil.tld'
)
self.assertEqual(len(mail.outbox), 0)
with patch_logger('django.security.DisallowedHost', 'error') as logger_calls:
response = self.client.post('/password_reset/',
{'email': 'staffmember@example.com'},
HTTP_HOST='www.example:dr.frankenstein@evil.tld'
)
self.assertEqual(response.status_code, 400)
self.assertEqual(len(mail.outbox), 0)
self.assertEqual(len(logger_calls), 1)
# Skip any 500 handler action (like sending more mail...)
@override_settings(DEBUG_PROPAGATE_EXCEPTIONS=True)
def test_poisoned_http_host_admin_site(self):
"Poisoned HTTP_HOST headers can't be used for reset emails on admin views"
with self.assertRaises(SuspiciousOperation):
self.client.post('/admin_password_reset/',
{'email': 'staffmember@example.com'},
HTTP_HOST='www.example:dr.frankenstein@evil.tld'
)
self.assertEqual(len(mail.outbox), 0)
with patch_logger('django.security.DisallowedHost', 'error') as logger_calls:
response = self.client.post('/admin_password_reset/',
{'email': 'staffmember@example.com'},
HTTP_HOST='www.example:dr.frankenstein@evil.tld'
)
self.assertEqual(response.status_code, 400)
self.assertEqual(len(mail.outbox), 0)
self.assertEqual(len(logger_calls), 1)
def _test_confirm_start(self):
# Start by creating the email
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
self.assertEqual(response.status_code, 302)
self.assertEqual(len(mail.outbox), 1)
return self._read_signup_email(mail.outbox[0])
@ -205,8 +231,6 @@ class PasswordResetTest(AuthViewsTestCase):
url, path = self._test_confirm_start()
response = self.client.post(path, {'new_password1': 'anewpassword',
'new_password2': 'anewpassword'})
# It redirects us to a 'complete' page:
self.assertEqual(response.status_code, 302)
# Check the password has been changed
u = User.objects.get(email='staffmember@example.com')
self.assertTrue(u.check_password("anewpassword"))
@ -221,6 +245,47 @@ class PasswordResetTest(AuthViewsTestCase):
'new_password2': 'x'})
self.assertFormError(response, SetPasswordForm.error_messages['password_mismatch'])
def test_reset_redirect_default(self):
response = self.client.post('/password_reset/',
{'email': 'staffmember@example.com'})
self.assertEqual(response.status_code, 302)
self.assertURLEqual(response.url, '/password_reset/done/')
def test_reset_custom_redirect(self):
response = self.client.post('/password_reset/custom_redirect/',
{'email': 'staffmember@example.com'})
self.assertEqual(response.status_code, 302)
self.assertURLEqual(response.url, '/custom/')
def test_reset_custom_redirect_named(self):
response = self.client.post('/password_reset/custom_redirect/named/',
{'email': 'staffmember@example.com'})
self.assertEqual(response.status_code, 302)
self.assertURLEqual(response.url, '/password_reset/')
def test_confirm_redirect_default(self):
url, path = self._test_confirm_start()
response = self.client.post(path, {'new_password1': 'anewpassword',
'new_password2': 'anewpassword'})
self.assertEqual(response.status_code, 302)
self.assertURLEqual(response.url, '/reset/done/')
def test_confirm_redirect_custom(self):
url, path = self._test_confirm_start()
path = path.replace('/reset/', '/reset/custom/')
response = self.client.post(path, {'new_password1': 'anewpassword',
'new_password2': 'anewpassword'})
self.assertEqual(response.status_code, 302)
self.assertURLEqual(response.url, '/custom/')
def test_confirm_redirect_custom_named(self):
url, path = self._test_confirm_start()
path = path.replace('/reset/', '/reset/custom/named/')
response = self.client.post(path, {'new_password1': 'anewpassword',
'new_password2': 'anewpassword'})
self.assertEqual(response.status_code, 302)
self.assertURLEqual(response.url, '/password_reset/')
@override_settings(AUTH_USER_MODEL='auth.CustomUser')
class CustomUserPasswordResetTest(AuthViewsTestCase):
@ -285,8 +350,6 @@ class ChangePasswordTest(AuthViewsTestCase):
'new_password1': 'password1',
'new_password2': 'password1',
})
self.assertEqual(response.status_code, 302)
self.assertTrue(response.url.endswith('/password_change/done/'))
self.fail_login()
self.login(password='password1')
@ -298,20 +361,50 @@ class ChangePasswordTest(AuthViewsTestCase):
'new_password2': 'password1',
})
self.assertEqual(response.status_code, 302)
self.assertTrue(response.url.endswith('/password_change/done/'))
self.assertURLEqual(response.url, '/password_change/done/')
@override_settings(LOGIN_URL='/login/')
def test_password_change_done_fails(self):
with self.settings(LOGIN_URL='/login/'):
response = self.client.get('/password_change/done/')
self.assertEqual(response.status_code, 302)
self.assertTrue(response.url.endswith('/login/?next=/password_change/done/'))
response = self.client.get('/password_change/done/')
self.assertEqual(response.status_code, 302)
self.assertURLEqual(response.url, '/login/?next=/password_change/done/')
def test_password_change_redirect_default(self):
self.login()
response = self.client.post('/password_change/', {
'old_password': 'password',
'new_password1': 'password1',
'new_password2': 'password1',
})
self.assertEqual(response.status_code, 302)
self.assertURLEqual(response.url, '/password_change/done/')
def test_password_change_redirect_custom(self):
self.login()
response = self.client.post('/password_change/custom/', {
'old_password': 'password',
'new_password1': 'password1',
'new_password2': 'password1',
})
self.assertEqual(response.status_code, 302)
self.assertURLEqual(response.url, '/custom/')
def test_password_change_redirect_custom_named(self):
self.login()
response = self.client.post('/password_change/custom/named/', {
'old_password': 'password',
'new_password1': 'password1',
'new_password2': 'password1',
})
self.assertEqual(response.status_code, 302)
self.assertURLEqual(response.url, '/password_reset/')
@skipIfCustomUser
class LoginTest(AuthViewsTestCase):
def test_current_site_in_context_after_login(self):
response = self.client.get(reverse('django.contrib.auth.views.login'))
response = self.client.get(reverse('login'))
self.assertEqual(response.status_code, 200)
if Site._meta.installed:
site = Site.objects.get_current()
@ -323,7 +416,7 @@ class LoginTest(AuthViewsTestCase):
'Login form is not an AuthenticationForm')
def test_security_check(self, password='password'):
login_url = reverse('django.contrib.auth.views.login')
login_url = reverse('login')
# Those URLs should not pass the security check
for bad_url in ('http://example.com',
@ -374,63 +467,103 @@ class LoginTest(AuthViewsTestCase):
# the custom authentication form used by this login asserts
# that a request is passed to the form successfully.
def test_login_csrf_rotate(self, password='password'):
"""
Makes sure that a login rotates the currently-used CSRF token.
"""
# Do a GET to establish a CSRF token
# TestClient isn't used here as we're testing middleware, essentially.
req = HttpRequest()
CsrfViewMiddleware().process_view(req, login_view, (), {})
req.META["CSRF_COOKIE_USED"] = True
resp = login_view(req)
resp2 = CsrfViewMiddleware().process_response(req, resp)
csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, None)
token1 = csrf_cookie.coded_value
# Prepare the POST request
req = HttpRequest()
req.COOKIES[settings.CSRF_COOKIE_NAME] = token1
req.method = "POST"
req.POST = {'username': 'testclient', 'password': password, 'csrfmiddlewaretoken': token1}
req.REQUEST = req.POST
# Use POST request to log in
SessionMiddleware().process_request(req)
CsrfViewMiddleware().process_view(req, login_view, (), {})
req.META["SERVER_NAME"] = "testserver" # Required to have redirect work in login view
req.META["SERVER_PORT"] = 80
req.META["CSRF_COOKIE_USED"] = True
resp = login_view(req)
resp2 = CsrfViewMiddleware().process_response(req, resp)
csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, None)
token2 = csrf_cookie.coded_value
# Check the CSRF token switched
self.assertNotEqual(token1, token2)
@skipIfCustomUser
class LoginURLSettings(AuthViewsTestCase):
def setUp(self):
super(LoginURLSettings, self).setUp()
self.old_LOGIN_URL = settings.LOGIN_URL
def tearDown(self):
super(LoginURLSettings, self).tearDown()
settings.LOGIN_URL = self.old_LOGIN_URL
def get_login_required_url(self, login_url):
settings.LOGIN_URL = login_url
"""Tests for settings.LOGIN_URL."""
def assertLoginURLEquals(self, url, parse_qs=False):
response = self.client.get('/login_required/')
self.assertEqual(response.status_code, 302)
return response.url
self.assertURLEqual(response.url, url, parse_qs=parse_qs)
@override_settings(LOGIN_URL='/login/')
def test_standard_login_url(self):
login_url = '/login/'
login_required_url = self.get_login_required_url(login_url)
querystring = QueryDict('', mutable=True)
querystring['next'] = '/login_required/'
self.assertEqual(login_required_url, 'http://testserver%s?%s' %
(login_url, querystring.urlencode('/')))
self.assertLoginURLEquals('/login/?next=/login_required/')
@override_settings(LOGIN_URL='login')
def test_named_login_url(self):
self.assertLoginURLEquals('/login/?next=/login_required/')
@override_settings(LOGIN_URL='http://remote.example.com/login')
def test_remote_login_url(self):
login_url = 'http://remote.example.com/login'
login_required_url = self.get_login_required_url(login_url)
querystring = QueryDict('', mutable=True)
querystring['next'] = 'http://testserver/login_required/'
self.assertEqual(login_required_url,
'%s?%s' % (login_url, querystring.urlencode('/')))
quoted_next = urlquote('http://testserver/login_required/')
expected = 'http://remote.example.com/login?next=%s' % quoted_next
self.assertLoginURLEquals(expected)
@override_settings(LOGIN_URL='https:///login/')
def test_https_login_url(self):
login_url = 'https:///login/'
login_required_url = self.get_login_required_url(login_url)
querystring = QueryDict('', mutable=True)
querystring['next'] = 'http://testserver/login_required/'
self.assertEqual(login_required_url,
'%s?%s' % (login_url, querystring.urlencode('/')))
quoted_next = urlquote('http://testserver/login_required/')
expected = 'https:///login/?next=%s' % quoted_next
self.assertLoginURLEquals(expected)
@override_settings(LOGIN_URL='/login/?pretty=1')
def test_login_url_with_querystring(self):
login_url = '/login/?pretty=1'
login_required_url = self.get_login_required_url(login_url)
querystring = QueryDict('pretty=1', mutable=True)
querystring['next'] = '/login_required/'
self.assertEqual(login_required_url, 'http://testserver/login/?%s' %
querystring.urlencode('/'))
self.assertLoginURLEquals('/login/?pretty=1&next=/login_required/', parse_qs=True)
@override_settings(LOGIN_URL='http://remote.example.com/login/?next=/default/')
def test_remote_login_url_with_next_querystring(self):
login_url = 'http://remote.example.com/login/'
login_required_url = self.get_login_required_url('%s?next=/default/' %
login_url)
querystring = QueryDict('', mutable=True)
querystring['next'] = 'http://testserver/login_required/'
self.assertEqual(login_required_url, '%s?%s' % (login_url,
querystring.urlencode('/')))
quoted_next = urlquote('http://testserver/login_required/')
expected = 'http://remote.example.com/login/?next=%s' % quoted_next
self.assertLoginURLEquals(expected)
@skipIfCustomUser
class LoginRedirectUrlTest(AuthViewsTestCase):
"""Tests for settings.LOGIN_REDIRECT_URL."""
def assertLoginRedirectURLEqual(self, url):
response = self.login()
self.assertEqual(response.status_code, 302)
self.assertURLEqual(response.url, url)
def test_default(self):
self.assertLoginRedirectURLEqual('/accounts/profile/')
@override_settings(LOGIN_REDIRECT_URL='/custom/')
def test_custom(self):
self.assertLoginRedirectURLEqual('/custom/')
@override_settings(LOGIN_REDIRECT_URL='password_reset')
def test_named(self):
self.assertLoginRedirectURLEqual('/password_reset/')
@override_settings(LOGIN_REDIRECT_URL='http://remote.example.com/welcome/')
def test_remote(self):
self.assertLoginRedirectURLEqual('http://remote.example.com/welcome/')
@skipIfCustomUser
@ -457,11 +590,11 @@ class LogoutTest(AuthViewsTestCase):
self.login()
response = self.client.get('/logout/next_page/')
self.assertEqual(response.status_code, 302)
self.assertTrue(response.url.endswith('/somewhere/'))
self.assertURLEqual(response.url, '/somewhere/')
response = self.client.get('/logout/next_page/?next=/login/')
self.assertEqual(response.status_code, 302)
self.assertTrue(response.url.endswith('/login/'))
self.assertURLEqual(response.url, '/login/')
self.confirm_logged_out()
@ -470,7 +603,7 @@ class LogoutTest(AuthViewsTestCase):
self.login()
response = self.client.get('/logout/next_page/')
self.assertEqual(response.status_code, 302)
self.assertTrue(response.url.endswith('/somewhere/'))
self.assertURLEqual(response.url, '/somewhere/')
self.confirm_logged_out()
def test_logout_with_redirect_argument(self):
@ -478,7 +611,7 @@ class LogoutTest(AuthViewsTestCase):
self.login()
response = self.client.get('/logout/?next=/login/')
self.assertEqual(response.status_code, 302)
self.assertTrue(response.url.endswith('/login/'))
self.assertURLEqual(response.url, '/login/')
self.confirm_logged_out()
def test_logout_with_custom_redirect_argument(self):
@ -486,11 +619,19 @@ class LogoutTest(AuthViewsTestCase):
self.login()
response = self.client.get('/logout/custom_query/?follow=/somewhere/')
self.assertEqual(response.status_code, 302)
self.assertTrue(response.url.endswith('/somewhere/'))
self.assertURLEqual(response.url, '/somewhere/')
self.confirm_logged_out()
def test_logout_with_named_redirect(self):
"Logout resolves names or URLs passed as next_page."
self.login()
response = self.client.get('/logout/next_page/named/')
self.assertEqual(response.status_code, 302)
self.assertURLEqual(response.url, '/password_reset/')
self.confirm_logged_out()
def test_security_check(self, password='password'):
logout_url = reverse('django.contrib.auth.views.logout')
logout_url = reverse('logout')
# Those URLs should not pass the security check
for bad_url in ('http://example.com',
@ -541,5 +682,7 @@ class ChangelistTests(AuthViewsTestCase):
self.login()
# A lookup that tries to filter on password isn't OK
with self.assertRaises(SuspiciousOperation):
with patch_logger('django.security.DisallowedModelAdminLookup', 'error') as logger_calls:
response = self.client.get('/admin/auth/user/?password__startswith=sha1$')
self.assertEqual(response.status_code, 400)
self.assertEqual(len(logger_calls), 1)

View File

@ -62,8 +62,19 @@ def custom_request_auth_login(request):
urlpatterns = urlpatterns + patterns('',
(r'^logout/custom_query/$', 'django.contrib.auth.views.logout', dict(redirect_field_name='follow')),
(r'^logout/next_page/$', 'django.contrib.auth.views.logout', dict(next_page='/somewhere/')),
(r'^logout/next_page/named/$', 'django.contrib.auth.views.logout', dict(next_page='password_reset')),
(r'^remote_user/$', remote_user_auth_view),
(r'^password_reset_from_email/$', 'django.contrib.auth.views.password_reset', dict(from_email='staffmember@example.com')),
(r'^password_reset/custom_redirect/$', 'django.contrib.auth.views.password_reset', dict(post_reset_redirect='/custom/')),
(r'^password_reset/custom_redirect/named/$', 'django.contrib.auth.views.password_reset', dict(post_reset_redirect='password_reset')),
(r'^reset/custom/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
'django.contrib.auth.views.password_reset_confirm',
dict(post_reset_redirect='/custom/')),
(r'^reset/custom/named/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
'django.contrib.auth.views.password_reset_confirm',
dict(post_reset_redirect='password_reset')),
(r'^password_change/custom/$', 'django.contrib.auth.views.password_change', dict(post_change_redirect='/custom/')),
(r'^password_change/custom/named/$', 'django.contrib.auth.views.password_change', dict(post_change_redirect='password_reset')),
(r'^admin_password_reset/$', 'django.contrib.auth.views.password_reset', dict(is_admin_site=True)),
(r'^login_required/$', login_required(password_reset)),
(r'^login_required_login_url/$', login_required(password_reset, login_url='/somewhere/')),

View File

@ -72,6 +72,9 @@ def logout(request, next_page=None,
"""
auth_logout(request)
if next_page is not None:
next_page = resolve_url(next_page)
if redirect_field_name in request.REQUEST:
next_page = request.REQUEST[redirect_field_name]
# Security check -- don't allow redirection to a different host.
@ -139,7 +142,9 @@ def password_reset(request, is_admin_site=False,
current_app=None,
extra_context=None):
if post_reset_redirect is None:
post_reset_redirect = reverse('django.contrib.auth.views.password_reset_done')
post_reset_redirect = reverse('password_reset_done')
else:
post_reset_redirect = resolve_url(post_reset_redirect)
if request.method == "POST":
form = password_reset_form(request.POST)
if form.is_valid():
@ -192,7 +197,9 @@ def password_reset_confirm(request, uidb36=None, token=None,
UserModel = get_user_model()
assert uidb36 is not None and token is not None # checked by URLconf
if post_reset_redirect is None:
post_reset_redirect = reverse('django.contrib.auth.views.password_reset_complete')
post_reset_redirect = reverse('password_reset_complete')
else:
post_reset_redirect = resolve_url(post_reset_redirect)
try:
uid_int = base36_to_int(uidb36)
user = UserModel._default_manager.get(pk=uid_int)
@ -242,7 +249,9 @@ def password_change(request,
password_change_form=PasswordChangeForm,
current_app=None, extra_context=None):
if post_change_redirect is None:
post_change_redirect = reverse('django.contrib.auth.views.password_change_done')
post_change_redirect = reverse('password_change_done')
else:
post_change_redirect = resolve_url(post_change_redirect)
if request.method == "POST":
form = password_change_form(user=request.user, data=request.POST)
if form.is_valid():

View File

@ -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"
@ -269,9 +269,11 @@ msgstr ""
#: templates/comments/preview.html:11
msgid "Please correct the error below"
msgid_plural "Please correct the errors below"
msgstr[0] ""
msgstr[1] ""
msgstr ""
#: templates/comments/preview.html:11
msgid "Please correct the errors below"
msgstr ""
#: templates/comments/preview.html:16
msgid "Post your comment"

View File

@ -8,7 +8,7 @@
<form action="{% comment_form_target %}" method="post">{% csrf_token %}
{% if next %}<div><input type="hidden" name="next" value="{{ next }}" /></div>{% endif %}
{% if form.errors %}
<h1>{% blocktrans count counter=form.errors|length %}Please correct the error below{% plural %}Please correct the errors below{% endblocktrans %}</h1>
<h1>{% if form.errors|length == 1 %}{% trans "Please correct the error below" %}{% else %}{% trans "Please correct the errors below" %}{% endif %}</h1>
{% else %}
<h1>{% trans "Preview your comment" %}</h1>
<blockquote>{{ comment|linebreaks }}</blockquote>

View File

@ -35,9 +35,10 @@ class GenericForeignKey(six.with_metaclass(RenameGenericForeignKeyMethods)):
fields.
"""
def __init__(self, ct_field="content_type", fk_field="object_id"):
def __init__(self, ct_field="content_type", fk_field="object_id", for_concrete_model=True):
self.ct_field = ct_field
self.fk_field = fk_field
self.for_concrete_model = for_concrete_model
def contribute_to_class(self, cls, name):
self.name = name
@ -63,7 +64,8 @@ class GenericForeignKey(six.with_metaclass(RenameGenericForeignKeyMethods)):
def get_content_type(self, obj=None, id=None, using=None):
if obj is not None:
return ContentType.objects.db_manager(obj._state.db).get_for_model(obj)
return ContentType.objects.db_manager(obj._state.db).get_for_model(
obj, for_concrete_model=self.for_concrete_model)
elif id:
return ContentType.objects.db_manager(using).get_for_id(id)
else:
@ -160,6 +162,8 @@ class GenericRelation(ForeignObject):
self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
self.content_type_field_name = kwargs.pop("content_type_field", "content_type")
self.for_concrete_model = kwargs.pop("for_concrete_model", True)
kwargs['blank'] = True
kwargs['editable'] = False
kwargs['serialize'] = False
@ -201,7 +205,7 @@ class GenericRelation(ForeignObject):
# Save a reference to which model this class is on for future use
self.model = cls
# Add the descriptor for the relation
setattr(cls, self.name, ReverseGenericRelatedObjectsDescriptor(self))
setattr(cls, self.name, ReverseGenericRelatedObjectsDescriptor(self, self.for_concrete_model))
def contribute_to_related_class(self, cls, related):
pass
@ -216,7 +220,8 @@ class GenericRelation(ForeignObject):
"""
Returns the content type associated with this field's model.
"""
return ContentType.objects.get_for_model(self.model)
return ContentType.objects.get_for_model(self.model,
for_concrete_model=self.for_concrete_model)
def get_extra_restriction(self, where_class, alias, remote_alias):
field = self.rel.to._meta.get_field_by_name(self.content_type_field_name)[0]
@ -232,7 +237,8 @@ class GenericRelation(ForeignObject):
"""
return self.rel.to._base_manager.db_manager(using).filter(**{
"%s__pk" % self.content_type_field_name:
ContentType.objects.db_manager(using).get_for_model(self.model).pk,
ContentType.objects.db_manager(using).get_for_model(
self.model, for_concrete_model=self.for_concrete_model).pk,
"%s__in" % self.object_id_field_name:
[obj.pk for obj in objs]
})
@ -247,8 +253,9 @@ class ReverseGenericRelatedObjectsDescriptor(object):
"article.publications", the publications attribute is a
ReverseGenericRelatedObjectsDescriptor instance.
"""
def __init__(self, field):
def __init__(self, field, for_concrete_model=True):
self.field = field
self.for_concrete_model = for_concrete_model
def __get__(self, instance, instance_type=None):
if instance is None:
@ -261,7 +268,8 @@ class ReverseGenericRelatedObjectsDescriptor(object):
RelatedManager = create_generic_related_manager(superclass)
qn = connection.ops.quote_name
content_type = ContentType.objects.db_manager(instance._state.db).get_for_model(instance)
content_type = ContentType.objects.db_manager(instance._state.db).get_for_model(
instance, for_concrete_model=self.for_concrete_model)
join_cols = self.field.get_joining_columns(reverse_join=True)[0]
manager = RelatedManager(
@ -376,7 +384,7 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
"""
def __init__(self, data=None, files=None, instance=None, save_as_new=None,
prefix=None, queryset=None):
prefix=None, queryset=None, **kwargs):
opts = self.model._meta
self.instance = instance
self.rel_name = '-'.join((
@ -389,12 +397,14 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
if queryset is None:
queryset = self.model._default_manager
qs = queryset.filter(**{
self.ct_field.name: ContentType.objects.get_for_model(self.instance),
self.ct_field.name: ContentType.objects.get_for_model(
self.instance, for_concrete_model=self.for_concrete_model),
self.ct_fk_field.name: self.instance.pk,
})
super(BaseGenericInlineFormSet, self).__init__(
queryset=qs, data=data, files=files,
prefix=prefix
prefix=prefix,
**kwargs
)
@classmethod
@ -406,7 +416,8 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
def save_new(self, form, commit=True):
kwargs = {
self.ct_field.get_attname(): ContentType.objects.get_for_model(self.instance).pk,
self.ct_field.get_attname(): ContentType.objects.get_for_model(
self.instance, for_concrete_model=self.for_concrete_model).pk,
self.ct_fk_field.get_attname(): self.instance.pk,
}
new_obj = self.model(**kwargs)
@ -418,7 +429,8 @@ def generic_inlineformset_factory(model, form=ModelForm,
fields=None, exclude=None,
extra=3, can_order=False, can_delete=True,
max_num=None,
formfield_callback=None, validate_max=False):
formfield_callback=None, validate_max=False,
for_concrete_model=True):
"""
Returns a ``GenericInlineFormSet`` for the given kwargs.
@ -444,6 +456,7 @@ def generic_inlineformset_factory(model, form=ModelForm,
validate_max=validate_max)
FormSet.ct_field = ct_field
FormSet.ct_fk_field = fk_field
FormSet.for_concrete_model = for_concrete_model
return FormSet
class GenericInlineModelAdmin(InlineModelAdmin):

View File

@ -118,11 +118,13 @@ class ContentTypeManager(models.Manager):
def _add_to_cache(self, using, ct):
"""Insert a ContentType into the cache."""
model = ct.model_class()
key = (model._meta.app_label, model._meta.model_name)
# Note it's possible for ContentType objects to be stale; model_class() will return None.
# Hence, there is no reliance on model._meta.app_label here, just using the model fields instead.
key = (ct.app_label, ct.model)
self.__class__._cache.setdefault(using, {})[key] = ct
self.__class__._cache.setdefault(using, {})[ct.id] = ct
@python_2_unicode_compatible
class ContentType(models.Model):
name = models.CharField(max_length=100)
@ -153,7 +155,6 @@ class ContentType(models.Model):
def model_class(self):
"Returns the Python model class for this type of content."
from django.db import models
return models.get_model(self.app_label, self.model,
only_installed=False)

View File

@ -274,3 +274,10 @@ class ContentTypesTests(TestCase):
model = 'OldModel',
)
self.assertEqual(six.text_type(ct), 'Old model')
self.assertIsNone(ct.model_class())
# Make sure stale ContentTypes can be fetched like any other object.
# Before Django 1.6 this caused a NoneType error in the caching mechanism.
# Instead, just return the ContentType object and let the app detect stale states.
ct_fetched = ContentType.objects.get_for_id(ct.pk)
self.assertIsNone(ct_fetched.model_class())

View File

@ -1,7 +1,6 @@
from django.conf import settings
from django.contrib.flatpages.models import FlatPage
from django.contrib.sites.models import get_current_site
from django.core.xheaders import populate_xheaders
from django.http import Http404, HttpResponse, HttpResponsePermanentRedirect
from django.shortcuts import get_object_or_404
from django.template import loader, RequestContext
@ -70,5 +69,4 @@ def render_flatpage(request, f):
'flatpage': f,
})
response = HttpResponse(t.render(c))
populate_xheaders(request, response, FlatPage, f.id)
return response

View File

@ -0,0 +1,6 @@
from django.core.exceptions import SuspiciousOperation
class WizardViewCookieModified(SuspiciousOperation):
"""Signature of cookie modified"""
pass

View File

@ -1,8 +1,8 @@
import json
from django.core.exceptions import SuspiciousOperation
from django.core.signing import BadSignature
from django.contrib.formtools.exceptions import WizardViewCookieModified
from django.contrib.formtools.wizard import storage
@ -21,7 +21,7 @@ class CookieStorage(storage.BaseStorage):
except KeyError:
data = None
except BadSignature:
raise SuspiciousOperation('WizardView cookie manipulated')
raise WizardViewCookieModified('WizardView cookie manipulated')
if data is None:
return None
return json.loads(data, cls=json.JSONDecoder)

View File

@ -11,6 +11,10 @@ from django.core.exceptions import ImproperlyConfigured
from django.db.backends.postgresql_psycopg2.base import DatabaseOperations
from django.db.utils import DatabaseError
from django.utils import six
from django.utils.functional import cached_property
from .models import GeometryColumns, SpatialRefSys
#### Classes used in constructing PostGIS spatial SQL ####
class PostGISOperator(SpatialOperation):
@ -62,6 +66,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
compiler_module = 'django.contrib.gis.db.models.sql.compiler'
name = 'postgis'
postgis = True
geom_func_prefix = 'ST_'
version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
valid_aggregates = dict([(k, None) for k in
('Collect', 'Extent', 'Extent3D', 'MakeLine', 'Union')])
@ -72,45 +77,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
def __init__(self, connection):
super(PostGISOperations, self).__init__(connection)
# Trying to get the PostGIS version because the function
# signatures will depend on the version used. The cost
# here is a database query to determine the version, which
# can be mitigated by setting `POSTGIS_VERSION` with a 3-tuple
# comprising user-supplied values for the major, minor, and
# subminor revision of PostGIS.
try:
if hasattr(settings, 'POSTGIS_VERSION'):
vtup = settings.POSTGIS_VERSION
if len(vtup) == 3:
# The user-supplied PostGIS version.
version = vtup
else:
# This was the old documented way, but it's stupid to
# include the string.
version = vtup[1:4]
else:
vtup = self.postgis_version_tuple()
version = vtup[1:]
# Getting the prefix -- even though we don't officially support
# PostGIS 1.2 anymore, keeping it anyway in case a prefix change
# for something else is necessary.
if version >= (1, 2, 2):
prefix = 'ST_'
else:
prefix = ''
self.geom_func_prefix = prefix
self.spatial_version = version
except DatabaseError:
raise ImproperlyConfigured(
'Cannot determine PostGIS version for database "%s". '
'GeoDjango requires at least PostGIS version 1.3. '
'Was the database created from a spatial database '
'template?' % self.connection.settings_dict['NAME']
)
# TODO: Raise helpful exceptions as they become known.
prefix = self.geom_func_prefix
# PostGIS-specific operators. The commented descriptions of these
# operators come from Section 7.6 of the PostGIS 1.4 documentation.
self.geometry_operators = {
@ -188,13 +155,13 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
self.geometry_functions.update(self.distance_functions)
# Only PostGIS versions 1.3.4+ have GeoJSON serialization support.
if version < (1, 3, 4):
if self.spatial_version < (1, 3, 4):
GEOJSON = False
else:
GEOJSON = prefix + 'AsGeoJson'
# ST_ContainsProperly ST_MakeLine, and ST_GeoHash added in 1.4.
if version >= (1, 4, 0):
if self.spatial_version >= (1, 4, 0):
GEOHASH = 'ST_GeoHash'
BOUNDINGCIRCLE = 'ST_MinimumBoundingCircle'
self.geometry_functions['contains_properly'] = PostGISFunction(prefix, 'ContainsProperly')
@ -202,7 +169,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
GEOHASH, BOUNDINGCIRCLE = False, False
# Geography type support added in 1.5.
if version >= (1, 5, 0):
if self.spatial_version >= (1, 5, 0):
self.geography = True
# Only a subset of the operators and functions are available
# for the geography type.
@ -217,7 +184,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
}
# Native geometry type support added in PostGIS 2.0.
if version >= (2, 0, 0):
if self.spatial_version >= (2, 0, 0):
self.geometry = True
# Creating a dictionary lookup of all GIS terms for PostGIS.
@ -260,7 +227,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
self.union = prefix + 'Union'
self.unionagg = prefix + 'Union'
if version >= (2, 0, 0):
if self.spatial_version >= (2, 0, 0):
self.extent3d = prefix + '3DExtent'
self.length3d = prefix + '3DLength'
self.perimeter3d = prefix + '3DPerimeter'
@ -269,6 +236,30 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
self.length3d = prefix + 'Length3D'
self.perimeter3d = prefix + 'Perimeter3D'
@cached_property
def spatial_version(self):
"""Determine the version of the PostGIS library."""
# Trying to get the PostGIS version because the function
# signatures will depend on the version used. The cost
# here is a database query to determine the version, which
# can be mitigated by setting `POSTGIS_VERSION` with a 3-tuple
# comprising user-supplied values for the major, minor, and
# subminor revision of PostGIS.
if hasattr(settings, 'POSTGIS_VERSION'):
version = settings.POSTGIS_VERSION
else:
try:
vtup = self.postgis_version_tuple()
except DatabaseError:
raise ImproperlyConfigured(
'Cannot determine PostGIS version for database "%s". '
'GeoDjango requires at least PostGIS version 1.3. '
'Was the database created from a spatial database '
'template?' % self.connection.settings_dict['NAME']
)
version = vtup[1:]
return version
def check_aggregate_support(self, aggregate):
"""
Checks if the given aggregate name is supported (that is, if it's
@ -572,9 +563,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
# Routines for getting the OGC-compliant models.
def geometry_columns(self):
from django.contrib.gis.db.backends.postgis.models import GeometryColumns
return GeometryColumns
def spatial_ref_sys(self):
from django.contrib.gis.db.backends.postgis.models import SpatialRefSys
return SpatialRefSys

View File

@ -384,7 +384,7 @@ class GEOSGeometry(GEOSBase, ListMixin):
@property
def wkt(self):
"Returns the WKT (Well-Known Text) representation of this Geometry."
return wkt_w(self.hasz and 3 or 2).write(self).decode()
return wkt_w(3 if self.hasz else 2).write(self).decode()
@property
def hex(self):
@ -395,7 +395,7 @@ class GEOSGeometry(GEOSBase, ListMixin):
"""
# A possible faster, all-python, implementation:
# str(self.wkb).encode('hex')
return wkb_w(self.hasz and 3 or 2).write_hex(self)
return wkb_w(3 if self.hasz else 2).write_hex(self)
@property
def hexewkb(self):
@ -407,7 +407,7 @@ class GEOSGeometry(GEOSBase, ListMixin):
if self.hasz and not GEOS_PREPARE:
# See: http://trac.osgeo.org/geos/ticket/216
raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D HEXEWKB.')
return ewkb_w(self.hasz and 3 or 2).write_hex(self)
return ewkb_w(3 if self.hasz else 2).write_hex(self)
@property
def json(self):
@ -427,7 +427,7 @@ class GEOSGeometry(GEOSBase, ListMixin):
as a Python buffer. SRID and Z values are not included, use the
`ewkb` property instead.
"""
return wkb_w(self.hasz and 3 or 2).write(self)
return wkb_w(3 if self.hasz else 2).write(self)
@property
def ewkb(self):
@ -439,7 +439,7 @@ class GEOSGeometry(GEOSBase, ListMixin):
if self.hasz and not GEOS_PREPARE:
# See: http://trac.osgeo.org/geos/ticket/216
raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D EWKB.')
return ewkb_w(self.hasz and 3 or 2).write(self)
return ewkb_w(3 if self.hasz else 2).write(self)
@property
def kml(self):

View File

@ -546,7 +546,7 @@ class LayerMapping(object):
# Attempting to save.
m.save(using=self.using)
num_saved += 1
if verbose: stream.write('%s: %s\n' % (is_update and 'Updated' or 'Saved', m))
if verbose: stream.write('%s: %s\n' % ('Updated' if is_update else 'Saved', m))
except SystemExit:
raise
except Exception as msg:

View File

@ -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-18 23:10+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English <en@li.org>\n"
@ -237,54 +237,60 @@ msgctxt "naturaltime"
msgid "%(delta)s ago"
msgstr ""
#: templatetags/humanize.py:194 templatetags/humanize.py:216
#: templatetags/humanize.py:194 templatetags/humanize.py:219
msgid "now"
msgstr ""
#: templatetags/humanize.py:197
#. Translators: \\u00a0 is non-breaking space
#: templatetags/humanize.py:198
#, python-format
msgid "a second ago"
msgid_plural "%(count)s seconds ago"
msgid_plural "%(count)s\\u00a0seconds ago"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:202
#. Translators: \\u00a0 is non-breaking space
#: templatetags/humanize.py:204
#, python-format
msgid "a minute ago"
msgid_plural "%(count)s minutes ago"
msgid_plural "%(count)s\\u00a0minutes ago"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:207
#. Translators: \\u00a0 is non-breaking space
#: templatetags/humanize.py:210
#, python-format
msgid "an hour ago"
msgid_plural "%(count)s hours ago"
msgid_plural "%(count)s\\u00a0hours ago"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:213
#: templatetags/humanize.py:216
#, python-format
msgctxt "naturaltime"
msgid "%(delta)s from now"
msgstr ""
#: templatetags/humanize.py:219
#. Translators: \\u00a0 is non-breaking space
#: templatetags/humanize.py:223
#, python-format
msgid "a second from now"
msgid_plural "%(count)s seconds from now"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:224
#, python-format
msgid "a minute from now"
msgid_plural "%(count)s minutes from now"
msgid_plural "%(count)s\\u00a0seconds from now"
msgstr[0] ""
msgstr[1] ""
#. Translators: \\u00a0 is non-breaking space
#: templatetags/humanize.py:229
#, python-format
msgid "an hour from now"
msgid_plural "%(count)s hours from now"
msgid "a minute from now"
msgid_plural "%(count)s\\u00a0minutes from now"
msgstr[0] ""
msgstr[1] ""
#. Translators: \\u00a0 is non-breaking space
#: templatetags/humanize.py:235
#, python-format
msgid "an hour from now"
msgid_plural "%(count)s\\u00a0hours from now"
msgstr[0] ""
msgstr[1] ""

View File

@ -194,17 +194,20 @@ def naturaltime(value):
return _('now')
elif delta.seconds < 60:
return ungettext(
'a second ago', '%(count)s seconds ago', delta.seconds
# Translators: \\u00a0 is non-breaking space
'a second ago', '%(count)s\u00a0seconds ago', delta.seconds
) % {'count': delta.seconds}
elif delta.seconds // 60 < 60:
count = delta.seconds // 60
return ungettext(
'a minute ago', '%(count)s minutes ago', count
# Translators: \\u00a0 is non-breaking space
'a minute ago', '%(count)s\u00a0minutes ago', count
) % {'count': count}
else:
count = delta.seconds // 60 // 60
return ungettext(
'an hour ago', '%(count)s hours ago', count
# Translators: \\u00a0 is non-breaking space
'an hour ago', '%(count)s\u00a0hours ago', count
) % {'count': count}
else:
delta = value - now
@ -216,15 +219,18 @@ def naturaltime(value):
return _('now')
elif delta.seconds < 60:
return ungettext(
'a second from now', '%(count)s seconds from now', delta.seconds
# Translators: \\u00a0 is non-breaking space
'a second from now', '%(count)s\u00a0seconds from now', delta.seconds
) % {'count': delta.seconds}
elif delta.seconds // 60 < 60:
count = delta.seconds // 60
return ungettext(
'a minute from now', '%(count)s minutes from now', count
# Translators: \\u00a0 is non-breaking space
'a minute from now', '%(count)s\u00a0minutes from now', count
) % {'count': count}
else:
count = delta.seconds // 60 // 60
return ungettext(
'an hour from now', '%(count)s hours from now', count
# Translators: \\u00a0 is non-breaking space
'an hour from now', '%(count)s\u00a0hours from now', count
) % {'count': count}

View File

@ -19,6 +19,8 @@ from django.utils.translation import ugettext as _
from django.utils import tzinfo
from django.utils.unittest import skipIf
from i18n import TransRealMixin
# Mock out datetime in some tests so they don't fail occasionally when they
# run too slow. Use a fixed datetime for datetime.now(). DST change in
@ -36,7 +38,7 @@ class MockDateTime(datetime.datetime):
return now.replace(tzinfo=tz) + tz.utcoffset(now)
class HumanizeTests(TestCase):
class HumanizeTests(TransRealMixin, TestCase):
def humanize_tester(self, test_list, result_list, method):
for test_content, result in zip(test_list, result_list):
@ -195,22 +197,22 @@ class HumanizeTests(TestCase):
result_list = [
'now',
'a second ago',
'30 seconds ago',
'30\xa0seconds ago',
'a minute ago',
'2 minutes ago',
'2\xa0minutes ago',
'an hour ago',
'23 hours ago',
'1 day ago',
'1 year, 4 months ago',
'23\xa0hours ago',
'1\xa0day ago',
'1\xa0year, 4\xa0months ago',
'a second from now',
'30 seconds from now',
'30\xa0seconds from now',
'a minute from now',
'2 minutes from now',
'2\xa0minutes from now',
'an hour from now',
'23 hours from now',
'1 day from now',
'2 days, 6 hours from now',
'1 year, 4 months from now',
'23\xa0hours from now',
'1\xa0day from now',
'2\xa0days, 6\xa0hours from now',
'1\xa0year, 4\xa0months from now',
'now',
'now',
]
@ -218,8 +220,8 @@ class HumanizeTests(TestCase):
# date in naive arithmetic is only 2 days and 5 hours after in
# aware arithmetic.
result_list_with_tz_support = result_list[:]
assert result_list_with_tz_support[-4] == '2 days, 6 hours from now'
result_list_with_tz_support[-4] == '2 days, 5 hours from now'
assert result_list_with_tz_support[-4] == '2\xa0days, 6\xa0hours from now'
result_list_with_tz_support[-4] == '2\xa0days, 5\xa0hours from now'
orig_humanize_datetime, humanize.datetime = humanize.datetime, MockDateTime
try:

View File

@ -2,6 +2,8 @@ from __future__ import unicode_literals
import base64
from datetime import datetime, timedelta
import logging
try:
from django.utils.six.moves import cPickle as pickle
except ImportError:
@ -14,7 +16,9 @@ from django.utils.crypto import constant_time_compare
from django.utils.crypto import get_random_string
from django.utils.crypto import salted_hmac
from django.utils import timezone
from django.utils.encoding import force_bytes
from django.utils.encoding import force_bytes, force_text
from django.contrib.sessions.exceptions import SuspiciousSession
# session_key should not be case sensitive because some backends can store it
# on case insensitive file systems.
@ -94,12 +98,16 @@ class SessionBase(object):
hash, pickled = encoded_data.split(b':', 1)
expected_hash = self._hash(pickled)
if not constant_time_compare(hash.decode(), expected_hash):
raise SuspiciousOperation("Session data corrupted")
raise SuspiciousSession("Session data corrupted")
else:
return pickle.loads(pickled)
except Exception:
except Exception as e:
# ValueError, SuspiciousOperation, unpickling exceptions. If any of
# these happen, just return an empty dictionary (an empty session).
if isinstance(e, SuspiciousOperation):
logger = logging.getLogger('django.security.%s' %
e.__class__.__name__)
logger.warning(force_text(e))
return {}
def update(self, dict_):

View File

@ -2,10 +2,13 @@
Cached, database-backed sessions.
"""
import logging
from django.contrib.sessions.backends.db import SessionStore as DBStore
from django.core.cache import cache
from django.core.exceptions import SuspiciousOperation
from django.utils import timezone
from django.utils.encoding import force_text
KEY_PREFIX = "django.contrib.sessions.cached_db"
@ -41,7 +44,11 @@ class SessionStore(DBStore):
data = self.decode(s.session_data)
cache.set(self.cache_key, data,
self.get_expiry_age(expiry=s.expire_date))
except (Session.DoesNotExist, SuspiciousOperation):
except (Session.DoesNotExist, SuspiciousOperation) as e:
if isinstance(e, SuspiciousOperation):
logger = logging.getLogger('django.security.%s' %
e.__class__.__name__)
logger.warning(force_text(e))
self.create()
data = {}
return data

View File

@ -1,8 +1,10 @@
import logging
from django.contrib.sessions.backends.base import SessionBase, CreateError
from django.core.exceptions import SuspiciousOperation
from django.db import IntegrityError, transaction, router
from django.utils import timezone
from django.utils.encoding import force_text
class SessionStore(SessionBase):
"""
@ -18,7 +20,11 @@ class SessionStore(SessionBase):
expire_date__gt=timezone.now()
)
return self.decode(s.session_data)
except (Session.DoesNotExist, SuspiciousOperation):
except (Session.DoesNotExist, SuspiciousOperation) as e:
if isinstance(e, SuspiciousOperation):
logger = logging.getLogger('django.security.%s' %
e.__class__.__name__)
logger.warning(force_text(e))
self.create()
return {}

View File

@ -1,5 +1,6 @@
import datetime
import errno
import logging
import os
import shutil
import tempfile
@ -8,6 +9,9 @@ from django.conf import settings
from django.contrib.sessions.backends.base import SessionBase, CreateError, VALID_KEY_CHARS
from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured
from django.utils import timezone
from django.utils.encoding import force_text
from django.contrib.sessions.exceptions import InvalidSessionKey
class SessionStore(SessionBase):
"""
@ -48,7 +52,7 @@ class SessionStore(SessionBase):
# should always be md5s, so they should never contain directory
# components.
if not set(session_key).issubset(set(VALID_KEY_CHARS)):
raise SuspiciousOperation(
raise InvalidSessionKey(
"Invalid characters in session key")
return os.path.join(self.storage_path, self.file_prefix + session_key)
@ -75,7 +79,11 @@ class SessionStore(SessionBase):
if file_data:
try:
session_data = self.decode(file_data)
except (EOFError, SuspiciousOperation):
except (EOFError, SuspiciousOperation) as e:
if isinstance(e, SuspiciousOperation):
logger = logging.getLogger('django.security.%s' %
e.__class__.__name__)
logger.warning(force_text(e))
self.create()
# Remove expired sessions.
@ -86,7 +94,7 @@ class SessionStore(SessionBase):
session_data = {}
self.delete()
self.create()
except IOError:
except (IOError, SuspiciousOperation):
self.create()
return session_data

View File

@ -0,0 +1,11 @@
from django.core.exceptions import SuspiciousOperation
class InvalidSessionKey(SuspiciousOperation):
"""Invalid characters in session key"""
pass
class SuspiciousSession(SuspiciousOperation):
"""The session may be tampered with"""
pass

View File

@ -1,3 +1,4 @@
import base64
from datetime import timedelta
import os
import shutil
@ -15,14 +16,16 @@ from django.contrib.sessions.models import Session
from django.contrib.sessions.middleware import SessionMiddleware
from django.core.cache import get_cache
from django.core import management
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponse
from django.test import TestCase, RequestFactory
from django.test.utils import override_settings
from django.test.utils import override_settings, patch_logger
from django.utils import six
from django.utils import timezone
from django.utils import unittest
from django.contrib.sessions.exceptions import InvalidSessionKey
class SessionTestsMixin(object):
# This does not inherit from TestCase to avoid any tests being run with this
@ -272,6 +275,15 @@ class SessionTestsMixin(object):
encoded = self.session.encode(data)
self.assertEqual(self.session.decode(encoded), data)
def test_decode_failure_logged_to_security(self):
bad_encode = base64.b64encode(b'flaskdj:alkdjf')
with patch_logger('django.security.SuspiciousSession', 'warning') as calls:
self.assertEqual({}, self.session.decode(bad_encode))
# check that the failed decode is logged
self.assertEqual(len(calls), 1)
self.assertTrue('corrupted' in calls[0])
def test_actual_expiry(self):
# Regression test for #19200
old_session_key = None
@ -403,14 +415,21 @@ class FileSessionTests(SessionTestsMixin, unittest.TestCase):
self.assertRaises(ImproperlyConfigured, self.backend)
def test_invalid_key_backslash(self):
# Ensure we don't allow directory-traversal
self.assertRaises(SuspiciousOperation,
self.backend("a\\b\\c").load)
# This key should be refused and a new session should be created
self.assertTrue(self.backend("a\\b\\c").load())
def test_invalid_key_backslash(self):
# Ensure we don't allow directory-traversal.
# This is tested directly on _key_to_file, as load() will swallow
# a SuspiciousOperation in the same way as an IOError - by creating
# a new session, making it unclear whether the slashes were detected.
self.assertRaises(InvalidSessionKey,
self.backend()._key_to_file, "a\\b\\c")
def test_invalid_key_forwardslash(self):
# Ensure we don't allow directory-traversal
self.assertRaises(SuspiciousOperation,
self.backend("a/b/c").load)
self.assertRaises(InvalidSessionKey,
self.backend()._key_to_file, "a/b/c")
@override_settings(SESSION_ENGINE="django.contrib.sessions.backends.file")
def test_clearsessions_command(self):

View File

@ -94,7 +94,7 @@ class Sitemap(object):
'location': loc,
'lastmod': self.__get('lastmod', item, None),
'changefreq': self.__get('changefreq', item, None),
'priority': str(priority is not None and priority or ''),
'priority': str(priority if priority is not None else ''),
}
urls.append(url_info)
return urls

View File

@ -245,7 +245,7 @@ def find(path, all=False):
if matches:
return matches
# No match.
return all and [] or None
return [] if all else None
def get_finders():

View File

@ -175,11 +175,9 @@ Type 'yes' to continue, or 'no' to cancel: """
summary = template % {
'modified_count': modified_count,
'identifier': 'static file' + ('' if modified_count == 1 else 's'),
'action': self.symlink and 'symlinked' or 'copied',
'destination': (destination_path and " to '%s'"
% destination_path or ''),
'unmodified': (collected['unmodified'] and ', %s unmodified'
% unmodified_count or ''),
'action': 'symlinked' if self.symlink else 'copied',
'destination': (" to '%s'" % destination_path if destination_path else ''),
'unmodified': (', %s unmodified' % unmodified_count if collected['unmodified'] else ''),
'post_processed': (collected['post_processed'] and
', %s post-processed'
% post_processed_count or ''),

View File

@ -15,6 +15,10 @@ class CacheKeyWarning(DjangoRuntimeWarning):
pass
# Stub class to ensure not passing in a `timeout` argument results in
# the default timeout
DEFAULT_TIMEOUT = object()
# Memcached does not accept keys longer than this.
MEMCACHE_MAX_KEY_LENGTH = 250
@ -84,7 +88,7 @@ class BaseCache(object):
new_key = self.key_func(key, self.key_prefix, version)
return new_key
def add(self, key, value, timeout=None, version=None):
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
"""
Set a value in the cache if the key does not already exist. If
timeout is given, that timeout will be used for the key; otherwise
@ -101,7 +105,7 @@ class BaseCache(object):
"""
raise NotImplementedError
def set(self, key, value, timeout=None, version=None):
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
"""
Set a value in the cache. If timeout is given, that timeout will be
used for the key; otherwise the default cache timeout will be used.
@ -163,7 +167,7 @@ class BaseCache(object):
# if a subclass overrides it.
return self.has_key(key)
def set_many(self, data, timeout=None, version=None):
def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
"""
Set a bunch of values in the cache at once from a dict of key/value
pairs. For certain backends (memcached), this is much more efficient

View File

@ -9,7 +9,7 @@ except ImportError:
import pickle
from django.conf import settings
from django.core.cache.backends.base import BaseCache
from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
from django.db import connections, transaction, router, DatabaseError
from django.utils import timezone, six
from django.utils.encoding import force_bytes
@ -65,6 +65,7 @@ class DatabaseCache(BaseDatabaseCache):
if row is None:
return default
now = timezone.now()
if row[2] < now:
db = router.db_for_write(self.cache_model_class)
cursor = connections[db].cursor()
@ -74,18 +75,18 @@ class DatabaseCache(BaseDatabaseCache):
value = connections[db].ops.process_clob(row[1])
return pickle.loads(base64.b64decode(force_bytes(value)))
def set(self, key, value, timeout=None, version=None):
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
self._base_set('set', key, value, timeout)
def add(self, key, value, timeout=None, version=None):
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
return self._base_set('add', key, value, timeout)
def _base_set(self, mode, key, value, timeout=None):
if timeout is None:
def _base_set(self, mode, key, value, timeout=DEFAULT_TIMEOUT):
if timeout == DEFAULT_TIMEOUT:
timeout = self.default_timeout
db = router.db_for_write(self.cache_model_class)
table = connections[db].ops.quote_name(self._table)
@ -95,7 +96,9 @@ class DatabaseCache(BaseDatabaseCache):
num = cursor.fetchone()[0]
now = timezone.now()
now = now.replace(microsecond=0)
if settings.USE_TZ:
if timeout is None:
exp = datetime.max
elif settings.USE_TZ:
exp = datetime.utcfromtimestamp(time.time() + timeout)
else:
exp = datetime.fromtimestamp(time.time() + timeout)

View File

@ -1,12 +1,12 @@
"Dummy cache backend"
from django.core.cache.backends.base import BaseCache
from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
class DummyCache(BaseCache):
def __init__(self, host, *args, **kwargs):
BaseCache.__init__(self, *args, **kwargs)
def add(self, key, value, timeout=None, version=None):
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
return True
@ -16,7 +16,7 @@ class DummyCache(BaseCache):
self.validate_key(key)
return default
def set(self, key, value, timeout=None, version=None):
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
@ -32,7 +32,7 @@ class DummyCache(BaseCache):
self.validate_key(key)
return False
def set_many(self, data, timeout=0, version=None):
def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
pass
def delete_many(self, keys, version=None):

View File

@ -9,9 +9,10 @@ try:
except ImportError:
import pickle
from django.core.cache.backends.base import BaseCache
from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
from django.utils.encoding import force_bytes
class FileBasedCache(BaseCache):
def __init__(self, dir, params):
BaseCache.__init__(self, params)
@ -19,7 +20,7 @@ class FileBasedCache(BaseCache):
if not os.path.exists(self._dir):
self._createdir()
def add(self, key, value, timeout=None, version=None):
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
if self.has_key(key, version=version):
return False
@ -35,7 +36,7 @@ class FileBasedCache(BaseCache):
with open(fname, 'rb') as f:
exp = pickle.load(f)
now = time.time()
if exp < now:
if exp is not None and exp < now:
self._delete(fname)
else:
return pickle.load(f)
@ -43,14 +44,14 @@ class FileBasedCache(BaseCache):
pass
return default
def set(self, key, value, timeout=None, version=None):
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
fname = self._key_to_file(key)
dirname = os.path.dirname(fname)
if timeout is None:
if timeout == DEFAULT_TIMEOUT:
timeout = self.default_timeout
self._cull()
@ -60,8 +61,8 @@ class FileBasedCache(BaseCache):
os.makedirs(dirname)
with open(fname, 'wb') as f:
now = time.time()
pickle.dump(now + timeout, f, pickle.HIGHEST_PROTOCOL)
expiry = None if timeout is None else time.time() + timeout
pickle.dump(expiry, f, pickle.HIGHEST_PROTOCOL)
pickle.dump(value, f, pickle.HIGHEST_PROTOCOL)
except (IOError, OSError):
pass

View File

@ -6,7 +6,7 @@ try:
except ImportError:
import pickle
from django.core.cache.backends.base import BaseCache
from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
from django.utils.synch import RWLock
# Global in-memory store of cache data. Keyed by name, to provide
@ -23,7 +23,7 @@ class LocMemCache(BaseCache):
self._expire_info = _expire_info.setdefault(name, {})
self._lock = _locks.setdefault(name, RWLock())
def add(self, key, value, timeout=None, version=None):
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
with self._lock.writer():
@ -41,10 +41,8 @@ class LocMemCache(BaseCache):
key = self.make_key(key, version=version)
self.validate_key(key)
with self._lock.reader():
exp = self._expire_info.get(key)
if exp is None:
return default
elif exp > time.time():
exp = self._expire_info.get(key, 0)
if exp is None or exp > time.time():
try:
pickled = self._cache[key]
return pickle.loads(pickled)
@ -58,15 +56,16 @@ class LocMemCache(BaseCache):
pass
return default
def _set(self, key, value, timeout=None):
def _set(self, key, value, timeout=DEFAULT_TIMEOUT):
if len(self._cache) >= self._max_entries:
self._cull()
if timeout is None:
if timeout == DEFAULT_TIMEOUT:
timeout = self.default_timeout
expiry = None if timeout is None else time.time() + timeout
self._cache[key] = value
self._expire_info[key] = time.time() + timeout
self._expire_info[key] = expiry
def set(self, key, value, timeout=None, version=None):
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
with self._lock.writer():

View File

@ -4,7 +4,7 @@ import time
import pickle
from threading import local
from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError
from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
from django.utils import six
from django.utils.encoding import force_str
@ -36,12 +36,22 @@ class BaseMemcachedCache(BaseCache):
return self._client
def _get_memcache_timeout(self, timeout):
def _get_memcache_timeout(self, timeout=DEFAULT_TIMEOUT):
"""
Memcached deals with long (> 30 days) timeouts in a special
way. Call this function to obtain a safe value for your timeout.
"""
timeout = timeout or self.default_timeout
if timeout == DEFAULT_TIMEOUT:
return self.default_timeout
if timeout is None:
# Using 0 in memcache sets a non-expiring timeout.
return 0
elif int(timeout) == 0:
# Other cache backends treat 0 as set-and-expire. To achieve this
# in memcache backends, a negative timeout must be passed.
timeout = -1
if timeout > 2592000: # 60*60*24*30, 30 days
# See http://code.google.com/p/memcached/wiki/FAQ
# "You can set expire times up to 30 days in the future. After that
@ -56,7 +66,7 @@ class BaseMemcachedCache(BaseCache):
# Python 2 memcache requires the key to be a byte string.
return force_str(super(BaseMemcachedCache, self).make_key(key, version))
def add(self, key, value, timeout=0, version=None):
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
return self._cache.add(key, value, self._get_memcache_timeout(timeout))
@ -67,7 +77,7 @@ class BaseMemcachedCache(BaseCache):
return default
return val
def set(self, key, value, timeout=0, version=None):
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self._cache.set(key, value, self._get_memcache_timeout(timeout))
@ -125,7 +135,7 @@ class BaseMemcachedCache(BaseCache):
raise ValueError("Key '%s' not found" % key)
return val
def set_many(self, data, timeout=0, version=None):
def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
safe_data = {}
for key, value in data.items():
key = self.make_key(key, version=version)

View File

@ -1,6 +1,7 @@
"""
Global Django exception and warning classes.
"""
import logging
from functools import reduce
@ -9,37 +10,56 @@ class DjangoRuntimeWarning(RuntimeWarning):
class ObjectDoesNotExist(Exception):
"The requested object does not exist"
"""The requested object does not exist"""
silent_variable_failure = True
class MultipleObjectsReturned(Exception):
"The query returned multiple objects when only one was expected."
"""The query returned multiple objects when only one was expected."""
pass
class SuspiciousOperation(Exception):
"The user did something suspicious"
"""The user did something suspicious"""
class SuspiciousMultipartForm(SuspiciousOperation):
"""Suspect MIME request in multipart form data"""
pass
class SuspiciousFileOperation(SuspiciousOperation):
"""A Suspicious filesystem operation was attempted"""
pass
class DisallowedHost(SuspiciousOperation):
"""HTTP_HOST header contains invalid value"""
pass
class DisallowedRedirect(SuspiciousOperation):
"""Redirect to scheme not in allowed list"""
pass
class PermissionDenied(Exception):
"The user did not have permission to do that"
"""The user did not have permission to do that"""
pass
class ViewDoesNotExist(Exception):
"The requested view does not exist"
"""The requested view does not exist"""
pass
class MiddlewareNotUsed(Exception):
"This middleware is not used in this server configuration"
"""This middleware is not used in this server configuration"""
pass
class ImproperlyConfigured(Exception):
"Django is somehow improperly configured"
"""Django is somehow improperly configured"""
pass

View File

@ -41,7 +41,7 @@ except (ImportError, AttributeError):
def fd(f):
"""Get a filedescriptor from something which could be a file or an fd."""
return hasattr(f, 'fileno') and f.fileno() or f
return f.fileno() if hasattr(f, 'fileno') else f
if system_type == 'nt':
def lock(file, flags):

View File

@ -62,7 +62,7 @@ def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_ove
with open(old_file_name, 'rb') as old_file:
# now open the new file, not forgetting allow_overwrite
fd = os.open(new_file_name, os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) |
(not allow_overwrite and os.O_EXCL or 0))
(os.O_EXCL if not allow_overwrite else 0))
try:
locks.lock(fd, locks.LOCK_EX)
current_chunk = None
@ -77,8 +77,8 @@ def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_ove
try:
os.remove(old_file_name)
except OSError as e:
# Certain operating systems (Cygwin and Windows)
# fail when deleting opened files, ignore it. (For the
# Certain operating systems (Cygwin and Windows)
# fail when deleting opened files, ignore it. (For the
# systems where this happens, temporary files will be auto-deleted
# on close anyway.)
if getattr(e, 'winerror', 0) != 32 and getattr(e, 'errno', 0) != 13:

View File

@ -8,7 +8,7 @@ import itertools
from datetime import datetime
from django.conf import settings
from django.core.exceptions import SuspiciousOperation
from django.core.exceptions import SuspiciousFileOperation
from django.core.files import locks, File
from django.core.files.move import file_move_safe
from django.utils.encoding import force_text, filepath_to_uri
@ -260,7 +260,7 @@ class FileSystemStorage(Storage):
try:
path = safe_join(self.location, name)
except ValueError:
raise SuspiciousOperation("Attempted access to '%s' denied." % name)
raise SuspiciousFileOperation("Attempted access to '%s' denied." % name)
return os.path.normpath(path)
def size(self, name):

View File

@ -8,7 +8,7 @@ from django import http
from django.conf import settings
from django.core import urlresolvers
from django.core import signals
from django.core.exceptions import MiddlewareNotUsed, PermissionDenied
from django.core.exceptions import MiddlewareNotUsed, PermissionDenied, SuspiciousOperation
from django.db import connections, transaction
from django.utils.encoding import force_text
from django.utils.module_loading import import_by_path
@ -66,10 +66,11 @@ class BaseHandler(object):
self._request_middleware = request_middleware
def make_view_atomic(self, view):
if getattr(view, 'transactions_per_request', True):
for db in connections.all():
if db.settings_dict['ATOMIC_REQUESTS']:
view = transaction.atomic(using=db.alias)(view)
non_atomic_requests = getattr(view, '_non_atomic_requests', set())
for db in connections.all():
if (db.settings_dict['ATOMIC_REQUESTS']
and db.alias not in non_atomic_requests):
view = transaction.atomic(using=db.alias)(view)
return view
def get_response(self, request):
@ -169,11 +170,27 @@ class BaseHandler(object):
response = self.handle_uncaught_exception(request,
resolver, sys.exc_info())
except SuspiciousOperation as e:
# The request logger receives events for any problematic request
# The security logger receives events for all SuspiciousOperations
security_logger = logging.getLogger('django.security.%s' %
e.__class__.__name__)
security_logger.error(force_text(e))
try:
callback, param_dict = resolver.resolve400()
response = callback(request, **param_dict)
except:
signals.got_request_exception.send(
sender=self.__class__, request=request)
response = self.handle_uncaught_exception(request,
resolver, sys.exc_info())
except SystemExit:
# Allow sys.exit() to actually exit. See tickets #1023 and #4701
raise
except: # Handle everything else, including SuspiciousOperation, etc.
except: # Handle everything else.
# Get the exception info now, in case another exception is thrown later.
signals.got_request_exception.send(sender=self.__class__, request=request)
response = self.handle_uncaught_exception(request, resolver, sys.exc_info())

View File

@ -13,67 +13,12 @@ from django.core.urlresolvers import set_script_prefix
from django.utils import datastructures
from django.utils.encoding import force_str, force_text, iri_to_uri
# For backwards compatibility -- lots of code uses this in the wild!
from django.http.response import REASON_PHRASES as STATUS_CODE_TEXT
logger = logging.getLogger('django.request')
# See http://www.iana.org/assignments/http-status-codes
STATUS_CODE_TEXT = {
100: 'CONTINUE',
101: 'SWITCHING PROTOCOLS',
102: 'PROCESSING',
200: 'OK',
201: 'CREATED',
202: 'ACCEPTED',
203: 'NON-AUTHORITATIVE INFORMATION',
204: 'NO CONTENT',
205: 'RESET CONTENT',
206: 'PARTIAL CONTENT',
207: 'MULTI-STATUS',
208: 'ALREADY REPORTED',
226: 'IM USED',
300: 'MULTIPLE CHOICES',
301: 'MOVED PERMANENTLY',
302: 'FOUND',
303: 'SEE OTHER',
304: 'NOT MODIFIED',
305: 'USE PROXY',
306: 'RESERVED',
307: 'TEMPORARY REDIRECT',
400: 'BAD REQUEST',
401: 'UNAUTHORIZED',
402: 'PAYMENT REQUIRED',
403: 'FORBIDDEN',
404: 'NOT FOUND',
405: 'METHOD NOT ALLOWED',
406: 'NOT ACCEPTABLE',
407: 'PROXY AUTHENTICATION REQUIRED',
408: 'REQUEST TIMEOUT',
409: 'CONFLICT',
410: 'GONE',
411: 'LENGTH REQUIRED',
412: 'PRECONDITION FAILED',
413: 'REQUEST ENTITY TOO LARGE',
414: 'REQUEST-URI TOO LONG',
415: 'UNSUPPORTED MEDIA TYPE',
416: 'REQUESTED RANGE NOT SATISFIABLE',
417: 'EXPECTATION FAILED',
418: "I'M A TEAPOT",
422: 'UNPROCESSABLE ENTITY',
423: 'LOCKED',
424: 'FAILED DEPENDENCY',
426: 'UPGRADE REQUIRED',
500: 'INTERNAL SERVER ERROR',
501: 'NOT IMPLEMENTED',
502: 'BAD GATEWAY',
503: 'SERVICE UNAVAILABLE',
504: 'GATEWAY TIMEOUT',
505: 'HTTP VERSION NOT SUPPORTED',
506: 'VARIANT ALSO NEGOTIATES',
507: 'INSUFFICIENT STORAGE',
508: 'LOOP DETECTED',
510: 'NOT EXTENDED',
}
class LimitedStream(object):
'''
LimitedStream wraps another stream in order to not allow reading from it
@ -254,11 +199,7 @@ class WSGIHandler(base.BaseHandler):
response._handler_class = self.__class__
try:
status_text = STATUS_CODE_TEXT[response.status_code]
except KeyError:
status_text = 'UNKNOWN STATUS CODE'
status = '%s %s' % (response.status_code, status_text)
status = '%s %s' % (response.status_code, response.reason_phrase)
response_headers = [(str(k), str(v)) for k, v in response.items()]
for c in response.cookies.values():
response_headers.append((str('Set-Cookie'), str(c.output(header=''))))

View File

@ -7,7 +7,6 @@ import os
import sys
from optparse import make_option, OptionParser
import traceback
import django
from django.core.exceptions import ImproperlyConfigured
@ -171,7 +170,7 @@ class BaseCommand(object):
make_option('--pythonpath',
help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".'),
make_option('--traceback', action='store_true',
help='Print traceback on exception'),
help='Raise on exception'),
)
help = ''
args = ''
@ -231,7 +230,8 @@ class BaseCommand(object):
Set up any environment changes requested (e.g., Python path
and Django settings), then run this command. If the
command raises a ``CommandError``, intercept it and print it sensibly
to stderr.
to stderr. If the ``--traceback`` option is present or the raised
``Exception`` is not ``CommandError``, raise it.
"""
parser = self.create_parser(argv[0], argv[1])
options, args = parser.parse_args(argv[2:])
@ -239,12 +239,12 @@ class BaseCommand(object):
try:
self.execute(*args, **options.__dict__)
except Exception as e:
if options.traceback or not isinstance(e, CommandError):
raise
# self.stderr is not guaranteed to be set here
stderr = getattr(self, 'stderr', OutputWrapper(sys.stderr, self.style.ERROR))
if options.traceback or not isinstance(e, CommandError):
stderr.write(traceback.format_exc())
else:
stderr.write('%s: %s' % (e.__class__.__name__, e))
stderr.write('%s: %s' % (e.__class__.__name__, e))
sys.exit(1)
def execute(self, *args, **options):

View File

@ -38,7 +38,7 @@ class Command(LabelCommand):
qn = connection.ops.quote_name
for f in fields:
field_output = [qn(f.name), f.db_type(connection=connection)]
field_output.append("%sNULL" % (not f.null and "NOT " or ""))
field_output.append("%sNULL" % ("NOT " if not f.null else ""))
if f.primary_key:
field_output.append("PRIMARY KEY")
elif f.unique:
@ -51,7 +51,7 @@ class Command(LabelCommand):
table_output.append(" ".join(field_output))
full_statement = ["CREATE TABLE %s (" % qn(tablename)]
for i, line in enumerate(table_output):
full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or ''))
full_statement.append(' %s%s' % (line, ',' if i < len(table_output)-1 else ''))
full_statement.append(');')
with transaction.commit_on_success_unless_managed():
curs = connection.cursor()

View File

@ -21,6 +21,9 @@ class Command(BaseCommand):
help='Use natural keys if they are available.'),
make_option('-a', '--all', action='store_true', dest='use_base_manager', default=False,
help="Use Django's base manager to dump all models stored in the database, including those that would otherwise be filtered or modified by a custom manager."),
make_option('--pks', dest='primary_keys', help="Only dump objects with "
"given primary keys. Accepts a comma seperated list of keys. "
"This option will only work when you specify one model."),
)
help = ("Output the contents of the database as a fixture of the given "
"format (using each model's default manager unless --all is "
@ -37,6 +40,12 @@ class Command(BaseCommand):
show_traceback = options.get('traceback')
use_natural_keys = options.get('use_natural_keys')
use_base_manager = options.get('use_base_manager')
pks = options.get('primary_keys')
if pks:
primary_keys = pks.split(',')
else:
primary_keys = []
excluded_apps = set()
excluded_models = set()
@ -55,8 +64,12 @@ class Command(BaseCommand):
raise CommandError('Unknown app in excludes: %s' % exclude)
if len(app_labels) == 0:
if primary_keys:
raise CommandError("You can only use --pks option with one model")
app_list = SortedDict((app, None) for app in get_apps() if app not in excluded_apps)
else:
if len(app_labels) > 1 and primary_keys:
raise CommandError("You can only use --pks option with one model")
app_list = SortedDict()
for label in app_labels:
try:
@ -77,6 +90,8 @@ class Command(BaseCommand):
else:
app_list[app] = [model]
except ValueError:
if primary_keys:
raise CommandError("You can only use --pks option with one model")
# This is just an app - no model qualifier
app_label = label
try:
@ -107,8 +122,11 @@ class Command(BaseCommand):
objects = model._base_manager
else:
objects = model._default_manager
for obj in objects.using(using).\
order_by(model._meta.pk.name).iterator():
queryset = objects.using(using).order_by(model._meta.pk.name)
if primary_keys:
queryset = queryset.filter(pk__in=primary_keys)
for obj in queryset.iterator():
yield obj
try:

View File

@ -20,7 +20,7 @@ class Command(NoArgsCommand):
default=DEFAULT_DB_ALIAS, help='Nominates a database to flush. '
'Defaults to the "default" database.'),
make_option('--no-initial-data', action='store_false', dest='load_initial_data', default=True,
help='Tells Django not to load any initial data after database synchronization.'),
help='Tells Django not to load any initial data after database synchronization.'),
)
help = ('Returns the database to the state it was in immediately after '
'syncdb was executed. This means that all data will be removed '

View File

@ -1,9 +1,11 @@
from __future__ import unicode_literals
import os
import glob
import gzip
import os
import zipfile
from optparse import make_option
import warnings
from django.conf import settings
from django.core import serializers
@ -11,8 +13,9 @@ from django.core.management.base import BaseCommand, CommandError
from django.core.management.color import no_style
from django.db import (connections, router, transaction, DEFAULT_DB_ALIAS,
IntegrityError, DatabaseError)
from django.db.models import get_apps
from django.db.models import get_app_paths
from django.utils.encoding import force_text
from django.utils.functional import cached_property, memoize
from django.utils._os import upath
from itertools import product
@ -43,9 +46,8 @@ class Command(BaseCommand):
if not len(fixture_labels):
raise CommandError(
"No database fixture specified. Please provide the path of at "
"least one fixture in the command line."
)
"No database fixture specified. Please provide the path "
"of at least one fixture in the command line.")
self.verbosity = int(options.get('verbosity'))
@ -68,37 +70,18 @@ class Command(BaseCommand):
self.fixture_object_count = 0
self.models = set()
class SingleZipReader(zipfile.ZipFile):
def __init__(self, *args, **kwargs):
zipfile.ZipFile.__init__(self, *args, **kwargs)
if settings.DEBUG:
assert len(self.namelist()) == 1, "Zip-compressed fixtures must contain only one file."
def read(self):
return zipfile.ZipFile.read(self, self.namelist()[0])
self.compression_types = {
self.serialization_formats = serializers.get_public_serializer_formats()
self.compression_formats = {
None: open,
'gz': gzip.GzipFile,
'zip': SingleZipReader
}
if has_bz2:
self.compression_types['bz2'] = bz2.BZ2File
app_module_paths = []
for app in get_apps():
if hasattr(app, '__path__'):
# It's a 'models/' subpackage
for path in app.__path__:
app_module_paths.append(upath(path))
else:
# It's a models.py module
app_module_paths.append(upath(app.__file__))
app_fixtures = [os.path.join(os.path.dirname(path), 'fixtures') for path in app_module_paths]
self.compression_formats['bz2'] = bz2.BZ2File
with connection.constraint_checks_disabled():
for fixture_label in fixture_labels:
self.load_label(fixture_label, app_fixtures)
self.load_label(fixture_label)
# Since we disabled constraint checks, we must manually check for
# any invalid keys that might have been added
@ -123,122 +106,174 @@ class Command(BaseCommand):
if self.verbosity >= 1:
if self.fixture_object_count == self.loaded_object_count:
self.stdout.write("Installed %d object(s) from %d fixture(s)" % (
self.loaded_object_count, self.fixture_count))
self.stdout.write("Installed %d object(s) from %d fixture(s)" %
(self.loaded_object_count, self.fixture_count))
else:
self.stdout.write("Installed %d object(s) (of %d) from %d fixture(s)" % (
self.loaded_object_count, self.fixture_object_count, self.fixture_count))
self.stdout.write("Installed %d object(s) (of %d) from %d fixture(s)" %
(self.loaded_object_count, self.fixture_object_count, self.fixture_count))
def load_label(self, fixture_label, app_fixtures):
def load_label(self, fixture_label):
"""
Loads fixtures files for a given label.
"""
for fixture_file, fixture_dir, fixture_name in self.find_fixtures(fixture_label):
_, ser_fmt, cmp_fmt = self.parse_name(os.path.basename(fixture_file))
open_method = self.compression_formats[cmp_fmt]
fixture = open_method(fixture_file, 'r')
try:
self.fixture_count += 1
objects_in_fixture = 0
loaded_objects_in_fixture = 0
if self.verbosity >= 2:
self.stdout.write("Installing %s fixture '%s' from %s." %
(ser_fmt, fixture_name, humanize(fixture_dir)))
parts = fixture_label.split('.')
objects = serializers.deserialize(ser_fmt, fixture,
using=self.using, ignorenonexistent=self.ignore)
if len(parts) > 1 and parts[-1] in self.compression_types:
compression_formats = [parts[-1]]
parts = parts[:-1]
else:
compression_formats = self.compression_types.keys()
for obj in objects:
objects_in_fixture += 1
if router.allow_syncdb(self.using, obj.object.__class__):
loaded_objects_in_fixture += 1
self.models.add(obj.object.__class__)
try:
obj.save(using=self.using)
except (DatabaseError, IntegrityError) as e:
e.args = ("Could not load %(app_label)s.%(object_name)s(pk=%(pk)s): %(error_msg)s" % {
'app_label': obj.object._meta.app_label,
'object_name': obj.object._meta.object_name,
'pk': obj.object.pk,
'error_msg': force_text(e)
},)
raise
if len(parts) == 1:
fixture_name = parts[0]
formats = serializers.get_public_serializer_formats()
else:
fixture_name, format = '.'.join(parts[:-1]), parts[-1]
if format in serializers.get_public_serializer_formats():
formats = [format]
else:
formats = []
self.loaded_object_count += loaded_objects_in_fixture
self.fixture_object_count += objects_in_fixture
except Exception as e:
if not isinstance(e, CommandError):
e.args = ("Problem installing fixture '%s': %s" % (fixture_file, e),)
raise
finally:
fixture.close()
if formats:
if self.verbosity >= 2:
self.stdout.write("Loading '%s' fixtures..." % fixture_name)
else:
# If the fixture we loaded contains 0 objects, assume that an
# error was encountered during fixture loading.
if objects_in_fixture == 0:
raise CommandError(
"No fixture data found for '%s'. "
"(File format may be invalid.)" % fixture_name)
def _find_fixtures(self, fixture_label):
"""
Finds fixture files for a given label.
"""
fixture_name, ser_fmt, cmp_fmt = self.parse_name(fixture_label)
databases = [self.using, None]
cmp_fmts = list(self.compression_formats.keys()) if cmp_fmt is None else [cmp_fmt]
ser_fmts = serializers.get_public_serializer_formats() if ser_fmt is None else [ser_fmt]
# Check kept for backwards-compatibility; it doesn't look very useful.
if '.' in os.path.basename(fixture_name):
raise CommandError(
"Problem installing fixture '%s': %s is not a known serialization format." %
(fixture_name, format))
if os.path.isabs(fixture_name):
fixture_dirs = [fixture_name]
else:
fixture_dirs = app_fixtures + list(settings.FIXTURE_DIRS) + ['']
for fixture_dir in fixture_dirs:
self.process_dir(fixture_dir, fixture_name, compression_formats,
formats)
def process_dir(self, fixture_dir, fixture_name, compression_formats,
serialization_formats):
humanize = lambda dirname: "'%s'" % dirname if dirname else 'absolute path'
"Problem installing fixture '%s': %s is not a known "
"serialization format." % tuple(fixture_name.rsplit('.')))
if self.verbosity >= 2:
self.stdout.write("Checking %s for fixtures..." % humanize(fixture_dir))
self.stdout.write("Loading '%s' fixtures..." % fixture_name)
label_found = False
for combo in product([self.using, None], serialization_formats, compression_formats):
database, format, compression_format = combo
file_name = '.'.join(
p for p in [
fixture_name, database, format, compression_format
]
if p
)
if os.path.isabs(fixture_name):
fixture_dirs = [os.path.dirname(fixture_name)]
fixture_name = os.path.basename(fixture_name)
else:
fixture_dirs = self.fixture_dirs
if self.verbosity >= 3:
self.stdout.write("Trying %s for %s fixture '%s'..." % \
(humanize(fixture_dir), file_name, fixture_name))
full_path = os.path.join(fixture_dir, file_name)
open_method = self.compression_types[compression_format]
try:
fixture = open_method(full_path, 'r')
except IOError:
if self.verbosity >= 2:
self.stdout.write("No %s fixture '%s' in %s." % \
(format, fixture_name, humanize(fixture_dir)))
else:
try:
if label_found:
raise CommandError("Multiple fixtures named '%s' in %s. Aborting." %
(fixture_name, humanize(fixture_dir)))
suffixes = ('.'.join(ext for ext in combo if ext)
for combo in product(databases, ser_fmts, cmp_fmts))
targets = set('.'.join((fixture_name, suffix)) for suffix in suffixes)
self.fixture_count += 1
objects_in_fixture = 0
loaded_objects_in_fixture = 0
if self.verbosity >= 2:
self.stdout.write("Installing %s fixture '%s' from %s." % \
(format, fixture_name, humanize(fixture_dir)))
fixture_files = []
for fixture_dir in fixture_dirs:
if self.verbosity >= 2:
self.stdout.write("Checking %s for fixtures..." % humanize(fixture_dir))
fixture_files_in_dir = []
for candidate in glob.iglob(os.path.join(fixture_dir, fixture_name + '*')):
if os.path.basename(candidate) in targets:
# Save the fixture_dir and fixture_name for future error messages.
fixture_files_in_dir.append((candidate, fixture_dir, fixture_name))
objects = serializers.deserialize(format, fixture, using=self.using, ignorenonexistent=self.ignore)
if self.verbosity >= 2 and not fixture_files_in_dir:
self.stdout.write("No fixture '%s' in %s." %
(fixture_name, humanize(fixture_dir)))
for obj in objects:
objects_in_fixture += 1
if router.allow_syncdb(self.using, obj.object.__class__):
loaded_objects_in_fixture += 1
self.models.add(obj.object.__class__)
try:
obj.save(using=self.using)
except (DatabaseError, IntegrityError) as e:
e.args = ("Could not load %(app_label)s.%(object_name)s(pk=%(pk)s): %(error_msg)s" % {
'app_label': obj.object._meta.app_label,
'object_name': obj.object._meta.object_name,
'pk': obj.object.pk,
'error_msg': force_text(e)
},)
raise
# Check kept for backwards-compatibility; it isn't clear why
# duplicates are only allowed in different directories.
if len(fixture_files_in_dir) > 1:
raise CommandError(
"Multiple fixtures named '%s' in %s. Aborting." %
(fixture_name, humanize(fixture_dir)))
fixture_files.extend(fixture_files_in_dir)
self.loaded_object_count += loaded_objects_in_fixture
self.fixture_object_count += objects_in_fixture
label_found = True
except Exception as e:
if not isinstance(e, CommandError):
e.args = ("Problem installing fixture '%s': %s" % (full_path, e),)
raise
finally:
fixture.close()
if fixture_name != 'initial_data' and not fixture_files:
# Warning kept for backwards-compatibility; why not an exception?
warnings.warn("No fixture named '%s' found." % fixture_name)
# If the fixture we loaded contains 0 objects, assume that an
# error was encountered during fixture loading.
if objects_in_fixture == 0:
raise CommandError(
"No fixture data found for '%s'. (File format may be invalid.)" %
(fixture_name))
return fixture_files
_label_to_fixtures_cache = {}
find_fixtures = memoize(_find_fixtures, _label_to_fixtures_cache, 2)
@cached_property
def fixture_dirs(self):
"""
Return a list of fixture directories.
The list contains the 'fixtures' subdirectory of each installed
application, if it exists, the directories in FIXTURE_DIRS, and the
current directory.
"""
dirs = []
for path in get_app_paths():
d = os.path.join(os.path.dirname(path), 'fixtures')
if os.path.isdir(d):
dirs.append(d)
dirs.extend(list(settings.FIXTURE_DIRS))
dirs.append('')
dirs = [upath(os.path.abspath(os.path.realpath(d))) for d in dirs]
return dirs
def parse_name(self, fixture_name):
"""
Splits fixture name in name, serialization format, compression format.
"""
parts = fixture_name.rsplit('.', 2)
if len(parts) > 1 and parts[-1] in self.compression_formats:
cmp_fmt = parts[-1]
parts = parts[:-1]
else:
cmp_fmt = None
if len(parts) > 1 and parts[-1] in self.serialization_formats:
ser_fmt = parts[-1]
parts = parts[:-1]
else:
ser_fmt = None
name = '.'.join(parts)
return name, ser_fmt, cmp_fmt
class SingleZipReader(zipfile.ZipFile):
def __init__(self, *args, **kwargs):
zipfile.ZipFile.__init__(self, *args, **kwargs)
if len(self.namelist()) != 1:
raise ValueError("Zip-compressed fixtures must contain one file.")
def read(self):
return zipfile.ZipFile.read(self, self.namelist()[0])
def humanize(dirname):
return "'%s'" % dirname if dirname else 'absolute path'

View File

@ -250,18 +250,6 @@ class Command(NoArgsCommand):
"if you want to enable i18n for your project or application.")
check_programs('xgettext')
# We require gettext version 0.15 or newer.
output, errors, status = popen_wrapper(['xgettext', '--version'])
if status != STATUS_OK:
raise CommandError("Error running xgettext. Note that Django "
"internationalization requires GNU gettext 0.15 or newer.")
match = re.search(r'(?P<major>\d+)\.(?P<minor>\d+)', output)
if match:
xversion = (int(match.group('major')), int(match.group('minor')))
if xversion < (0, 15):
raise CommandError("Django internationalization requires GNU "
"gettext 0.15 or newer. You are using version %s, please "
"upgrade your gettext toolset." % match.group())
potfile = self.build_pot_file(localedir)
@ -309,10 +297,9 @@ class Command(NoArgsCommand):
"""
Check if the given path should be ignored or not.
"""
for pattern in ignore_patterns:
if fnmatch.fnmatchcase(path, pattern):
return True
return False
filename = os.path.basename(path)
ignore = lambda pattern: fnmatch.fnmatchcase(filename, pattern)
return any(ignore(pattern) for pattern in ignore_patterns)
dir_suffix = '%s*' % os.sep
norm_patterns = [p[:-len(dir_suffix)] if p.endswith(dir_suffix) else p for p in self.ignore_patterns]

View File

@ -99,7 +99,7 @@ class Command(BaseCommand):
"started_at": datetime.now().strftime('%B %d, %Y - %X'),
"version": self.get_version(),
"settings": settings.SETTINGS_MODULE,
"addr": self._raw_ipv6 and '[%s]' % self.addr or self.addr,
"addr": '[%s]' % self.addr if self._raw_ipv6 else self.addr,
"port": self.port,
"quit_command": quit_command,
})

View File

@ -1,11 +1,12 @@
from optparse import make_option
import itertools
import traceback
from django.conf import settings
from django.core.management import call_command
from django.core.management.base import NoArgsCommand
from django.core.management.color import no_style
from django.core.management.sql import custom_sql_for_model, emit_post_sync_signal
from django.core.management.sql import custom_sql_for_model, emit_post_sync_signal, emit_pre_sync_signal
from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS
from django.utils.datastructures import SortedDict
from django.utils.importlib import import_module
@ -82,6 +83,9 @@ class Command(NoArgsCommand):
for app_name, model_list in all_models
)
create_models = set([x for x in itertools.chain(*manifest.values())])
emit_pre_sync_signal(create_models, verbosity, interactive, db)
# Create the tables for each model
if verbosity >= 1:
self.stdout.write("Creating tables ...\n")

View File

@ -29,7 +29,7 @@ class Command(BaseCommand):
)
help = ('Runs the test suite for the specified applications, or the '
'entire site if no apps are specified.')
args = '[appname ...]'
args = '[appname|appname.tests.TestCase|appname.tests.TestCase.test_method]...'
requires_model_validation = False

View File

@ -133,14 +133,15 @@ def sql_custom(app, style, connection):
def sql_indexes(app, style, connection):
"Returns a list of the CREATE INDEX SQL statements for all models in the given app."
output = []
for model in models.get_models(app):
for model in models.get_models(app, include_auto_created=True):
output.extend(connection.creation.sql_indexes_for_model(model, style))
return output
def sql_destroy_indexes(app, style, connection):
"Returns a list of the DROP INDEX SQL statements for all models in the given app."
output = []
for model in models.get_models(app):
for model in models.get_models(app, include_auto_created=True):
output.extend(connection.creation.sql_destroy_indexes_for_model(model, style))
return output
@ -191,6 +192,19 @@ def custom_sql_for_model(model, style, connection):
return output
def emit_pre_sync_signal(create_models, verbosity, interactive, db):
# Emit the pre_sync signal for every application.
for app in models.get_apps():
app_name = app.__name__.split('.')[-2]
if verbosity >= 2:
print("Running pre-sync handlers for application %s" % app_name)
models.signals.pre_syncdb.send(sender=app, app=app,
create_models=create_models,
verbosity=verbosity,
interactive=interactive,
db=db)
def emit_post_sync_signal(created_models, verbosity, interactive, db):
# Emit the post_sync signal for every application.
for app in models.get_apps():

View File

@ -113,13 +113,15 @@ def get_validation_errors(outfile, app=None):
e.add(opts, '"%s": BooleanFields do not accept null values. Use a NullBooleanField instead.' % f.name)
if isinstance(f, models.FilePathField) and not (f.allow_files or f.allow_folders):
e.add(opts, '"%s": FilePathFields must have either allow_files or allow_folders set to True.' % f.name)
if isinstance(f, models.GenericIPAddressField) and not getattr(f, 'null', False) and getattr(f, 'blank', False):
e.add(opts, '"%s": GenericIPAddressField can not accept blank values if null values are not allowed, as blank values are stored as null.' % f.name)
if f.choices:
if isinstance(f.choices, six.string_types) or not is_iterable(f.choices):
e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)
else:
for c in f.choices:
if not isinstance(c, (list, tuple)) or len(c) != 2:
e.add(opts, '"%s": "choices" should be a sequence of two-tuples.' % f.name)
if isinstance(c, six.string_types) or not is_iterable(c) or len(c) != 2:
e.add(opts, '"%s": "choices" should be a sequence of two-item iterables (e.g. list of 2 item tuples).' % f.name)
if f.db_index not in (None, True, False):
e.add(opts, '"%s": "db_index" should be either None, True or False.' % f.name)

View File

@ -121,7 +121,9 @@ class Page(collections.Sequence):
raise TypeError
# The object_list is converted to a list so that if it was a QuerySet
# it won't be a database hit per __getitem__.
return list(self.object_list)[index]
if not isinstance(self.object_list, list):
self.object_list = list(self.object_list)
return self.object_list[index]
def has_next(self):
return self.number < self.paginator.num_pages

View File

@ -75,8 +75,7 @@ class Resolver404(Http404):
pass
class NoReverseMatch(Exception):
# Don't make this raise an error when used in a template.
silent_variable_failure = True
pass
def get_callable(lookup_view, can_fail=False):
"""
@ -360,6 +359,9 @@ class RegexURLResolver(LocaleRegexProvider):
callback = getattr(urls, 'handler%s' % view_type)
return get_callable(callback), {}
def resolve400(self):
return self._resolve_special('400')
def resolve403(self):
return self._resolve_special('403')

View File

@ -1,24 +0,0 @@
"""
Pages in Django can are served up with custom HTTP headers containing useful
information about those pages -- namely, the content type and object ID.
This module contains utility functions for retrieving and doing interesting
things with these special "X-Headers" (so called because the HTTP spec demands
that custom headers are prefixed with "X-").
Next time you're at slashdot.org, watch out for X-Fry and X-Bender. :)
"""
def populate_xheaders(request, response, model, object_id):
"""
Adds the "X-Object-Type" and "X-Object-Id" headers to the given
HttpResponse according to the given model and object_id -- but only if the
given HttpRequest object has an IP address within the INTERNAL_IPS setting
or if the request is from a logged in staff member.
"""
from django.conf import settings
if (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS
or (hasattr(request, 'user') and request.user.is_active
and request.user.is_staff)):
response['X-Object-Type'] = "%s.%s" % (model._meta.app_label, model._meta.model_name)
response['X-Object-Id'] = str(object_id)

View File

@ -1,24 +1,19 @@
import warnings
from django.conf import settings
from django.core import signals
from django.core.exceptions import ImproperlyConfigured
from django.db.utils import (DEFAULT_DB_ALIAS,
DataError, OperationalError, IntegrityError, InternalError,
ProgrammingError, NotSupportedError, DatabaseError,
InterfaceError, Error,
load_backend, ConnectionHandler, ConnectionRouter)
from django.utils.functional import cached_property
__all__ = ('backend', 'connection', 'connections', 'router', 'DatabaseError',
'IntegrityError', 'DEFAULT_DB_ALIAS')
connections = ConnectionHandler()
if settings.DATABASES and DEFAULT_DB_ALIAS not in settings.DATABASES:
raise ImproperlyConfigured("You must define a '%s' database" % DEFAULT_DB_ALIAS)
connections = ConnectionHandler(settings.DATABASES)
router = ConnectionRouter(settings.DATABASE_ROUTERS)
router = ConnectionRouter()
# `connection`, `DatabaseError` and `IntegrityError` are convenient aliases
# for backend bits.
@ -45,7 +40,28 @@ class DefaultConnectionProxy(object):
return delattr(connections[DEFAULT_DB_ALIAS], name)
connection = DefaultConnectionProxy()
backend = load_backend(connection.settings_dict['ENGINE'])
class DefaultBackendProxy(object):
"""
Temporary proxy class used during deprecation period of the `backend` module
variable.
"""
@cached_property
def _backend(self):
warnings.warn("Accessing django.db.backend is deprecated.",
PendingDeprecationWarning, stacklevel=2)
return load_backend(connections[DEFAULT_DB_ALIAS].settings_dict['ENGINE'])
def __getattr__(self, item):
return getattr(self._backend, item)
def __setattr__(self, name, value):
return setattr(self._backend, name, value)
def __delattr__(self, name):
return delattr(self._backend, name)
backend = DefaultBackendProxy()
def close_connection(**kwargs):
warnings.warn(

View File

@ -390,9 +390,10 @@ class BaseDatabaseWrapper(object):
def disable_constraint_checking(self):
"""
Backends can implement as needed to temporarily disable foreign key
constraint checking.
constraint checking. Should return True if the constraints were
disabled and will need to be reenabled.
"""
pass
return False
def enable_constraint_checking(self):
"""
@ -784,12 +785,12 @@ class BaseDatabaseOperations(object):
"""
return cursor.fetchone()[0]
def field_cast_sql(self, db_type):
def field_cast_sql(self, db_type, internal_type):
"""
Given a column type (e.g. 'BLOB', 'VARCHAR'), returns the SQL necessary
to cast it before using it in a WHERE statement. Note that the
resulting string should contain a '%s' placeholder for the column being
searched against.
Given a column type (e.g. 'BLOB', 'VARCHAR'), and an internal type
(e.g. 'GenericIPAddressField'), returns the SQL necessary to cast it
before using it in a WHERE statement. Note that the resulting string
should contain a '%s' placeholder for the column being searched against.
"""
return '%s'

View File

@ -99,7 +99,7 @@ class BaseDatabaseCreation(object):
style.SQL_TABLE(qn(opts.db_table)) + ' (']
for i, line in enumerate(table_output): # Combine and add commas.
full_statement.append(
' %s%s' % (line, i < len(table_output) - 1 and ',' or ''))
' %s%s' % (line, ',' if i < len(table_output) - 1 else ''))
full_statement.append(')')
if opts.db_tablespace:
tablespace_sql = self.connection.ops.tablespace_sql(

View File

@ -17,8 +17,7 @@ class SQLCompiler(compiler.SQLCompiler):
values.append(value)
return row[:index_extra_select] + tuple(values)
def as_subquery_condition(self, alias, columns):
qn = self.quote_name_unless_alias
def as_subquery_condition(self, alias, columns, qn):
qn2 = self.connection.ops.quote_name
sql, params = self.as_sql()
return '(%s) IN (%s)' % (', '.join(['%s.%s' % (qn(alias), qn2(column)) for column in columns]), sql), params

View File

@ -44,6 +44,11 @@ except ImportError as e:
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured("Error loading cx_Oracle module: %s" % e)
try:
import pytz
except ImportError:
pytz = None
from django.db import utils
from django.db.backends import *
from django.db.backends.oracle.client import DatabaseClient
@ -78,6 +83,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_subqueries_in_group_by = False
supports_transactions = True
supports_timezones = False
has_zoneinfo_database = pytz is not None
supports_bitwise_or = False
can_defer_constraint_checks = True
ignores_nulls_in_unique_constraints = False
@ -243,9 +249,6 @@ WHEN (new.%(col_name)s IS NULL)
value = value.date()
return value
def datetime_cast_sql(self):
return "TO_TIMESTAMP(%s, 'YYYY-MM-DD HH24:MI:SS.FF')"
def deferrable_sql(self):
return " DEFERRABLE INITIALLY DEFERRED"
@ -255,7 +258,7 @@ WHEN (new.%(col_name)s IS NULL)
def fetch_returned_insert_id(self, cursor):
return int(cursor._insert_id_var.getvalue())
def field_cast_sql(self, db_type):
def field_cast_sql(self, db_type, internal_type):
if db_type and db_type.endswith('LOB'):
return "DBMS_LOB.SUBSTR(%s)"
else:
@ -434,6 +437,17 @@ WHEN (new.%(col_name)s IS NULL)
second = '%s-12-31'
return [first % value, second % value]
def year_lookup_bounds_for_datetime_field(self, value):
# The default implementation uses datetime objects for the bounds.
# This must be overridden here, to use a formatted date (string) as
# 'second' instead -- cx_Oracle chops the fraction-of-second part
# off of datetime objects, leaving almost an entire second out of
# the year under the default implementation.
bounds = super(DatabaseOperations, self).year_lookup_bounds_for_datetime_field(value)
if settings.USE_TZ:
bounds = [b.astimezone(timezone.utc).replace(tzinfo=None) for b in bounds]
return [b.isoformat(b' ') for b in bounds]
def combine_expression(self, connector, sub_expressions):
"Oracle requires special cases for %% and & operators in query expressions"
if connector == '%%':

Some files were not shown because too many files have changed in this diff Show More