1
0
mirror of https://github.com/django/django.git synced 2025-07-04 09:49:12 +00:00

gis: Merged revisions 5491-5539 via svnmerge from

http://code.djangoproject.com/svn/django/trunk


git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@5540 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jeremy Dunck 2007-06-25 19:33:37 +00:00
parent b0a56a9919
commit fc779fe55a
91 changed files with 10420 additions and 3955 deletions

View File

@ -100,11 +100,14 @@ answer newbie questions, and generally made Django that much better:
Marc Fargas <telenieko@telenieko.com> Marc Fargas <telenieko@telenieko.com>
favo@exoweb.net favo@exoweb.net
Bill Fenner <fenner@gmail.com> Bill Fenner <fenner@gmail.com>
Stefane Fermgier <sf@fermigier.com>
Afonso Fernández Nogueira <fonzzo.django@gmail.com>
Matthew Flanagan <http://wadofstuff.blogspot.com> Matthew Flanagan <http://wadofstuff.blogspot.com>
Eric Floehr <eric@intellovations.com> Eric Floehr <eric@intellovations.com>
Jorge Gajon <gajon@gajon.org> Jorge Gajon <gajon@gajon.org>
gandalf@owca.info gandalf@owca.info
Baishampayan Ghose Baishampayan Ghose
glin@seznam.cz
martin.glueck@gmail.com martin.glueck@gmail.com
GomoX <gomo@datafull.com> GomoX <gomo@datafull.com>
Simon Greenhill <dev@simon.net.nz> Simon Greenhill <dev@simon.net.nz>
@ -119,6 +122,7 @@ answer newbie questions, and generally made Django that much better:
Ian Holsman <http://feh.holsman.net/> Ian Holsman <http://feh.holsman.net/>
Kieran Holland <http://www.kieranholland.com> Kieran Holland <http://www.kieranholland.com>
Sung-Jin Hong <serialx.net@gmail.com> Sung-Jin Hong <serialx.net@gmail.com>
Richard House <Richard.House@i-logue.com>
Robert Rock Howard <http://djangomojo.com/> Robert Rock Howard <http://djangomojo.com/>
Jason Huggins <http://www.jrandolph.com/blog/> Jason Huggins <http://www.jrandolph.com/blog/>
Hyun Mi Ae Hyun Mi Ae
@ -170,6 +174,7 @@ answer newbie questions, and generally made Django that much better:
mikko@sorl.net mikko@sorl.net
mitakummaa@gmail.com mitakummaa@gmail.com
mmarshall mmarshall
Reza Mohammadi <reza@zeerak.ir>
Eric Moritz <http://eric.themoritzfamily.com/> Eric Moritz <http://eric.themoritzfamily.com/>
mrmachine <real.human@mrmachine.net> mrmachine <real.human@mrmachine.net>
Robin Munn <http://www.geekforgod.com/> Robin Munn <http://www.geekforgod.com/>
@ -183,6 +188,7 @@ answer newbie questions, and generally made Django that much better:
Jay Parlar <parlar@gmail.com> Jay Parlar <parlar@gmail.com>
pavithran s <pavithran.s@gmail.com> pavithran s <pavithran.s@gmail.com>
Barry Pederson <bp@barryp.org> Barry Pederson <bp@barryp.org>
petr.marhoun@gmail.com
pgross@thoughtworks.com pgross@thoughtworks.com
phaedo <http://phaedo.cx/> phaedo <http://phaedo.cx/>
phil@produxion.net phil@produxion.net
@ -233,6 +239,7 @@ answer newbie questions, and generally made Django that much better:
Amit Upadhyay Amit Upadhyay
Geert Vanderkelen Geert Vanderkelen
viestards.lists@gmail.com viestards.lists@gmail.com
Vlado <vlado@labath.org>
Milton Waddams Milton Waddams
wam-djangobug@wamber.net wam-djangobug@wamber.net
wangchun <yaohua2000@gmail.com> wangchun <yaohua2000@gmail.com>
@ -245,6 +252,7 @@ answer newbie questions, and generally made Django that much better:
wojtek wojtek
ye7cakf02@sneakemail.com ye7cakf02@sneakemail.com
ymasuda@ethercube.com ymasuda@ethercube.com
Jarek Zgoda <jarek.zgoda@gmail.com>
Cheng Zhang Cheng Zhang
A big THANK YOU goes to: A big THANK YOU goes to:

View File

@ -48,6 +48,7 @@ LANGUAGES = (
('en', gettext_noop('English')), ('en', gettext_noop('English')),
('es', gettext_noop('Spanish')), ('es', gettext_noop('Spanish')),
('es_AR', gettext_noop('Argentinean Spanish')), ('es_AR', gettext_noop('Argentinean Spanish')),
('fa', gettext_noop('Persian')),
('fi', gettext_noop('Finnish')), ('fi', gettext_noop('Finnish')),
('fr', gettext_noop('French')), ('fr', gettext_noop('French')),
('gl', gettext_noop('Galician')), ('gl', gettext_noop('Galician')),

View File

@ -1,20 +1,18 @@
# translation of django.po to # translation of django.po to
# This file is distributed under the same license as the PACKAGE package. # This file is distributed under the same license as the Django package.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: django\n" "Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2007-05-25 13:04+0200\n" "POT-Creation-Date: 2007-06-25 17:31+0200\n"
"PO-Revision-Date: 2007-05-25 13:04+0200\n" "PO-Revision-Date: 2007-06-25 17:47+0200\n"
"Last-Translator: Marc Fargas <marc@fargas.com>\n" "Last-Translator: Marc Fargas <telenieko@telenieko.com>\n"
"Language-Team: <es@li.org>\n" "Language-Team: <es@li.org>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Generator: VIM 7.0\n" "Plural-Forms: nplurals=2; plural=(n != 1);"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: template/defaultfilters.py:491 #: template/defaultfilters.py:491
msgid "yes,no,maybe" msgid "yes,no,maybe"
@ -42,38 +40,38 @@ msgstr "%.1f MB"
msgid "%.1f GB" msgid "%.1f GB"
msgstr "%.1f GB" msgstr "%.1f GB"
#: newforms/models.py:173 newforms/fields.py:432 #: newforms/models.py:172 newforms/fields.py:432
msgid "Select a valid choice. That choice is not one of the available choices." msgid "Select a valid choice. That choice is not one of the available choices."
msgstr "" msgstr ""
"Esculli una opció vàlida; Aquesta opció no és una de les opcions disponibles." "Esculli una opció vàlida; Aquesta opció no és una de les opcions disponibles."
#: newforms/models.py:186 newforms/fields.py:87 newforms/fields.py:444 #: newforms/models.py:185 newforms/fields.py:87 newforms/fields.py:444
#: newforms/fields.py:520 newforms/fields.py:531 oldforms/__init__.py:357 #: newforms/fields.py:522 newforms/fields.py:533 oldforms/__init__.py:357
#: db/models/fields/__init__.py:121 db/models/fields/__init__.py:278 #: db/models/fields/__init__.py:126 db/models/fields/__init__.py:283
#: db/models/fields/__init__.py:675 db/models/fields/__init__.py:686 #: db/models/fields/__init__.py:680 db/models/fields/__init__.py:691
msgid "This field is required." msgid "This field is required."
msgstr "Aquest camp és obligatori." msgstr "Aquest camp és obligatori."
#: newforms/models.py:190 newforms/fields.py:448 newforms/fields.py:524 #: newforms/models.py:189 newforms/fields.py:448 newforms/fields.py:526
msgid "Enter a list of values." msgid "Enter a list of values."
msgstr "Introdueixi una llista de valors." msgstr "Introdueixi una llista de valors."
#: newforms/models.py:196 newforms/fields.py:457 #: newforms/models.py:195 newforms/fields.py:457
#, python-format #, python-format
msgid "Select a valid choice. %s is not one of the available choices." msgid "Select a valid choice. %s is not one of the available choices."
msgstr "Esculli una opció vàlida; %s' no és una de les opcions vàlides." msgstr "Esculli una opció vàlida; %s' no és una de les opcions vàlides."
#: newforms/widgets.py:182 contrib/admin/filterspecs.py:150 #: newforms/widgets.py:181 contrib/admin/filterspecs.py:150
#: oldforms/__init__.py:577 #: oldforms/__init__.py:577
msgid "Unknown" msgid "Unknown"
msgstr "Desconegut" msgstr "Desconegut"
#: newforms/widgets.py:182 contrib/admin/filterspecs.py:143 #: newforms/widgets.py:181 contrib/admin/filterspecs.py:143
#: oldforms/__init__.py:577 #: oldforms/__init__.py:577
msgid "Yes" msgid "Yes"
msgstr "Si" msgstr "Si"
#: newforms/widgets.py:182 contrib/admin/filterspecs.py:143 #: newforms/widgets.py:181 contrib/admin/filterspecs.py:143
#: oldforms/__init__.py:577 #: oldforms/__init__.py:577
msgid "No" msgid "No"
msgstr "No" msgstr "No"
@ -121,11 +119,11 @@ msgstr "Asseguris de que no hi ha més de %s decimals."
msgid "Ensure that there are no more than %s digits before the decimal point." msgid "Ensure that there are no more than %s digits before the decimal point."
msgstr "Asseguris de que no hia ha més de %s dígits decimals." msgstr "Asseguris de que no hia ha més de %s dígits decimals."
#: newforms/fields.py:233 #: newforms/fields.py:233 newforms/fields.py:566
msgid "Enter a valid date." msgid "Enter a valid date."
msgstr "Introdueixi una data vàlida." msgstr "Introdueixi una data vàlida."
#: newforms/fields.py:260 #: newforms/fields.py:260 newforms/fields.py:568
msgid "Enter a valid time." msgid "Enter a valid time."
msgstr "Introdueixi una hora vàlida." msgstr "Introdueixi una hora vàlida."
@ -198,118 +196,122 @@ msgid "Argentinean Spanish"
msgstr "Castellà Argentí" msgstr "Castellà Argentí"
#: conf/global_settings.py:51 #: conf/global_settings.py:51
msgid "Persian"
msgstr "Persa"
#: conf/global_settings.py:52
msgid "Finnish" msgid "Finnish"
msgstr "Finlandès" msgstr "Finlandès"
#: conf/global_settings.py:52 #: conf/global_settings.py:53
msgid "French" msgid "French"
msgstr "Francès" msgstr "Francès"
#: conf/global_settings.py:53 #: conf/global_settings.py:54
msgid "Galician" msgid "Galician"
msgstr "Galleg" msgstr "Galleg"
#: conf/global_settings.py:54 #: conf/global_settings.py:55
msgid "Hungarian" msgid "Hungarian"
msgstr "Húngar" msgstr "Húngar"
#: conf/global_settings.py:55 #: conf/global_settings.py:56
msgid "Hebrew" msgid "Hebrew"
msgstr "Hebreu" msgstr "Hebreu"
#: conf/global_settings.py:56 #: conf/global_settings.py:57
msgid "Icelandic" msgid "Icelandic"
msgstr "Islandès" msgstr "Islandès"
#: conf/global_settings.py:57 #: conf/global_settings.py:58
msgid "Italian" msgid "Italian"
msgstr "Italià" msgstr "Italià"
#: conf/global_settings.py:58 #: conf/global_settings.py:59
msgid "Japanese" msgid "Japanese"
msgstr "Japonès" msgstr "Japonès"
#: conf/global_settings.py:59 #: conf/global_settings.py:60
msgid "Korean" msgid "Korean"
msgstr "Coreà" msgstr "Coreà"
#: conf/global_settings.py:60 #: conf/global_settings.py:61
msgid "Kannada" msgid "Kannada"
msgstr "" msgstr ""
#: conf/global_settings.py:61
msgid "Latvian"
msgstr ""
#: conf/global_settings.py:62 #: conf/global_settings.py:62
msgid "Latvian"
msgstr "Letó"
#: conf/global_settings.py:63
msgid "Macedonian" msgid "Macedonian"
msgstr "Macedoni" msgstr "Macedoni"
#: conf/global_settings.py:63 #: conf/global_settings.py:64
msgid "Dutch" msgid "Dutch"
msgstr "Holandès" msgstr "Holandès"
#: conf/global_settings.py:64 #: conf/global_settings.py:65
msgid "Norwegian" msgid "Norwegian"
msgstr "Norueg" msgstr "Norueg"
#: conf/global_settings.py:65 #: conf/global_settings.py:66
msgid "Polish" msgid "Polish"
msgstr "Polac" msgstr "Polac"
#: conf/global_settings.py:66 #: conf/global_settings.py:67
msgid "Portugese" msgid "Portugese"
msgstr "Portuguès" msgstr "Portuguès"
#: conf/global_settings.py:67 #: conf/global_settings.py:68
msgid "Brazilian" msgid "Brazilian"
msgstr "Brasileny" msgstr "Brasileny"
#: conf/global_settings.py:68 #: conf/global_settings.py:69
msgid "Romanian" msgid "Romanian"
msgstr "Rumanès" msgstr "Rumanès"
#: conf/global_settings.py:69 #: conf/global_settings.py:70
msgid "Russian" msgid "Russian"
msgstr "Rús" msgstr "Rús"
#: conf/global_settings.py:70 #: conf/global_settings.py:71
msgid "Slovak" msgid "Slovak"
msgstr "Eslovac" msgstr "Eslovac"
#: conf/global_settings.py:71 #: conf/global_settings.py:72
msgid "Slovenian" msgid "Slovenian"
msgstr "Esloveni" msgstr "Esloveni"
#: conf/global_settings.py:72 #: conf/global_settings.py:73
msgid "Serbian" msgid "Serbian"
msgstr "Serbi" msgstr "Serbi"
#: conf/global_settings.py:73 #: conf/global_settings.py:74
msgid "Swedish" msgid "Swedish"
msgstr "Suec" msgstr "Suec"
#: conf/global_settings.py:74
msgid "Tamil"
msgstr ""
#: conf/global_settings.py:75 #: conf/global_settings.py:75
msgid "Telugu" msgid "Tamil"
msgstr "" msgstr "Tàmil"
#: conf/global_settings.py:76 #: conf/global_settings.py:76
msgid "Telugu"
msgstr "Telugu"
#: conf/global_settings.py:77
msgid "Turkish" msgid "Turkish"
msgstr "Turc" msgstr "Turc"
#: conf/global_settings.py:77 #: conf/global_settings.py:78
msgid "Ukrainian" msgid "Ukrainian"
msgstr "Ucranià" msgstr "Ucranià"
#: conf/global_settings.py:78 #: conf/global_settings.py:79
msgid "Simplified Chinese" msgid "Simplified Chinese"
msgstr "Xinés simplificat" msgstr "Xinés simplificat"
#: conf/global_settings.py:79 #: conf/global_settings.py:80
msgid "Traditional Chinese" msgid "Traditional Chinese"
msgstr "Xinés tradicional" msgstr "Xinés tradicional"
@ -576,17 +578,15 @@ msgid ""
"comment:\n" "comment:\n"
"\n" "\n"
"%(text)s" "%(text)s"
msgid_plural ""
"This comment was posted by a user who has posted fewer than %(count)s " "This comment was posted by a user who has posted fewer than %(count)s "
"comments:\n" "comments:\n"
"\n" "\n"
"%(text)s" "%(text)s"
msgstr[0] "" msgstr ""
"Aquest comentari el va enviar un usuari que ha enviat menys de %(count)s " "Aquest comentari el va enviar un usuari que ha enviat menys de %(count)s "
"comentari:\n" "comentari:\n"
"\n" "\n"
"%(text)s" "%(text)s"
msgstr[1] ""
"Aquest comentari el va enviar un usuari que ha enviat menys de %(count)s " "Aquest comentari el va enviar un usuari que ha enviat menys de %(count)s "
"comentaris:\n" "comentaris:\n"
"\n" "\n"
@ -1218,13 +1218,13 @@ msgstr "Editar aquest objecte (nova finestra)"
msgid "As above, but opens the admin page in a new window." msgid "As above, but opens the admin page in a new window."
msgstr "Com abans, però obre la pàgina d'administració en una nova finestra." msgstr "Com abans, però obre la pàgina d'administració en una nova finestra."
#: contrib/admin/views/auth.py:19 contrib/admin/views/main.py:257 #: contrib/admin/views/auth.py:19 contrib/admin/views/main.py:262
#, python-format #, python-format
msgid "The %(name)s \"%(obj)s\" was added successfully." msgid "The %(name)s \"%(obj)s\" was added successfully."
msgstr "El/la %(name)s \"%(obj)s\".ha estat agregat/da amb èxit." msgstr "El/la %(name)s \"%(obj)s\".ha estat agregat/da amb èxit."
#: contrib/admin/views/auth.py:24 contrib/admin/views/main.py:261 #: contrib/admin/views/auth.py:24 contrib/admin/views/main.py:266
#: contrib/admin/views/main.py:347 #: contrib/admin/views/main.py:352
msgid "You may edit it again below." msgid "You may edit it again below."
msgstr "Pot editar-lo de nou abaix." msgstr "Pot editar-lo de nou abaix."
@ -1241,96 +1241,96 @@ msgstr "Canvi de clau exitós"
msgid "Change password: %s" msgid "Change password: %s"
msgstr "Canviar clau: %s" msgstr "Canviar clau: %s"
#: contrib/admin/views/main.py:223 #: contrib/admin/views/main.py:228
msgid "Site administration" msgid "Site administration"
msgstr "Lloc administratiu" msgstr "Lloc administratiu"
#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356 #: contrib/admin/views/main.py:276 contrib/admin/views/main.py:361
#, python-format #, python-format
msgid "You may add another %s below." msgid "You may add another %s below."
msgstr "Pot agregar un altre %s abaix." msgstr "Pot agregar un altre %s abaix."
#: contrib/admin/views/main.py:289 #: contrib/admin/views/main.py:294
#, python-format #, python-format
msgid "Add %s" msgid "Add %s"
msgstr "Agregar %s" msgstr "Agregar %s"
#: contrib/admin/views/main.py:335 #: contrib/admin/views/main.py:340
#, python-format #, python-format
msgid "Added %s." msgid "Added %s."
msgstr "Agregat %s." msgstr "Agregat %s."
#: contrib/admin/views/main.py:335 contrib/admin/views/main.py:337 #: contrib/admin/views/main.py:340 contrib/admin/views/main.py:342
#: contrib/admin/views/main.py:339 db/models/manipulators.py:308 #: contrib/admin/views/main.py:344 db/models/manipulators.py:308
msgid "and" msgid "and"
msgstr "i" msgstr "i"
#: contrib/admin/views/main.py:337 #: contrib/admin/views/main.py:342
#, python-format #, python-format
msgid "Changed %s." msgid "Changed %s."
msgstr "Modificat %s." msgstr "Modificat %s."
#: contrib/admin/views/main.py:339 #: contrib/admin/views/main.py:344
#, python-format #, python-format
msgid "Deleted %s." msgid "Deleted %s."
msgstr "Eliminat %s." msgstr "Eliminat %s."
#: contrib/admin/views/main.py:342 #: contrib/admin/views/main.py:347
msgid "No fields changed." msgid "No fields changed."
msgstr "Cap camp canviat." msgstr "Cap camp canviat."
#: contrib/admin/views/main.py:345 #: contrib/admin/views/main.py:350
#, python-format #, python-format
msgid "The %(name)s \"%(obj)s\" was changed successfully." msgid "The %(name)s \"%(obj)s\" was changed successfully."
msgstr "S'ha modificat amb èxist el/la %(name)s \"%(obj)s." msgstr "S'ha modificat amb èxist el/la %(name)s \"%(obj)s."
#: contrib/admin/views/main.py:353 #: contrib/admin/views/main.py:358
#, python-format #, python-format
msgid "" msgid ""
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
msgstr "" msgstr ""
"S'ha agregat amb èxit el/la %(name)s \"%(obj)s\". Pot editar-lo de nou abaix." "S'ha agregat amb èxit el/la %(name)s \"%(obj)s\". Pot editar-lo de nou abaix."
#: contrib/admin/views/main.py:391 #: contrib/admin/views/main.py:396
#, python-format #, python-format
msgid "Change %s" msgid "Change %s"
msgstr "Modificar %s" msgstr "Modificar %s"
#: contrib/admin/views/main.py:476 #: contrib/admin/views/main.py:481
#, python-format #, python-format
msgid "One or more %(fieldname)s in %(name)s: %(obj)s" msgid "One or more %(fieldname)s in %(name)s: %(obj)s"
msgstr "Un o més %(fieldname)s en %(name)s: %(obj)s" msgstr "Un o més %(fieldname)s en %(name)s: %(obj)s"
#: contrib/admin/views/main.py:481 #: contrib/admin/views/main.py:486
#, python-format #, python-format
msgid "One or more %(fieldname)s in %(name)s:" msgid "One or more %(fieldname)s in %(name)s:"
msgstr "Un o més %(fieldname)s en %(name)s:" msgstr "Un o més %(fieldname)s en %(name)s:"
#: contrib/admin/views/main.py:514 #: contrib/admin/views/main.py:518
#, python-format #, python-format
msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgid "The %(name)s \"%(obj)s\" was deleted successfully."
msgstr "El/la %(name)s \"%(obj)s\".ha estat eliminat amb èxit." msgstr "El/la %(name)s \"%(obj)s\".ha estat eliminat amb èxit."
#: contrib/admin/views/main.py:517 #: contrib/admin/views/main.py:521
msgid "Are you sure?" msgid "Are you sure?"
msgstr "Està segur?" msgstr "Està segur?"
#: contrib/admin/views/main.py:539 #: contrib/admin/views/main.py:543
#, python-format #, python-format
msgid "Change history: %s" msgid "Change history: %s"
msgstr "Modificar històric: %s" msgstr "Modificar històric: %s"
#: contrib/admin/views/main.py:573 #: contrib/admin/views/main.py:577
#, python-format #, python-format
msgid "Select %s" msgid "Select %s"
msgstr "Seleccioni %s" msgstr "Seleccioni %s"
#: contrib/admin/views/main.py:573 #: contrib/admin/views/main.py:577
#, python-format #, python-format
msgid "Select %s to change" msgid "Select %s to change"
msgstr "Seleccioni %s per modificar" msgstr "Seleccioni %s per modificar"
#: contrib/admin/views/main.py:768 #: contrib/admin/views/main.py:772
msgid "Database error" msgid "Database error"
msgstr "Error de/en la base de dades" msgstr "Error de/en la base de dades"
@ -1697,35 +1697,35 @@ msgstr "llocs"
msgid "Logged out" msgid "Logged out"
msgstr "Sessió finalitzada" msgstr "Sessió finalitzada"
#: contrib/auth/models.py:44 contrib/auth/models.py:64 #: contrib/auth/models.py:49 contrib/auth/models.py:69
msgid "name" msgid "name"
msgstr "nom" msgstr "nom"
#: contrib/auth/models.py:46 #: contrib/auth/models.py:51
msgid "codename" msgid "codename"
msgstr "nom en clau" msgstr "nom en clau"
#: contrib/auth/models.py:49 #: contrib/auth/models.py:54
msgid "permission" msgid "permission"
msgstr "permís" msgstr "permís"
#: contrib/auth/models.py:50 contrib/auth/models.py:65 #: contrib/auth/models.py:55 contrib/auth/models.py:70
msgid "permissions" msgid "permissions"
msgstr "permissos" msgstr "permissos"
#: contrib/auth/models.py:68 #: contrib/auth/models.py:73
msgid "group" msgid "group"
msgstr "grup" msgstr "grup"
#: contrib/auth/models.py:69 contrib/auth/models.py:109 #: contrib/auth/models.py:74 contrib/auth/models.py:114
msgid "groups" msgid "groups"
msgstr "grups" msgstr "grups"
#: contrib/auth/models.py:99 #: contrib/auth/models.py:104
msgid "username" msgid "username"
msgstr "nom d'usuari" msgstr "nom d'usuari"
#: contrib/auth/models.py:99 #: contrib/auth/models.py:104
msgid "" msgid ""
"Required. 30 characters or fewer. Alphanumeric characters only (letters, " "Required. 30 characters or fewer. Alphanumeric characters only (letters, "
"digits and underscores)." "digits and underscores)."
@ -1733,23 +1733,23 @@ msgstr ""
"Requerit. 30 o menys caracters. Només caracters alfanumèrics (lletres, " "Requerit. 30 o menys caracters. Només caracters alfanumèrics (lletres, "
"dígits i guions baixos)." "dígits i guions baixos)."
#: contrib/auth/models.py:100 #: contrib/auth/models.py:105
msgid "first name" msgid "first name"
msgstr "nom propi" msgstr "nom propi"
#: contrib/auth/models.py:101 #: contrib/auth/models.py:106
msgid "last name" msgid "last name"
msgstr "cognoms" msgstr "cognoms"
#: contrib/auth/models.py:102 #: contrib/auth/models.py:107
msgid "e-mail address" msgid "e-mail address"
msgstr "adreça de correu electrònic" msgstr "adreça de correu electrònic"
#: contrib/auth/models.py:103 #: contrib/auth/models.py:108
msgid "password" msgid "password"
msgstr "contrasenya" msgstr "contrasenya"
#: contrib/auth/models.py:103 #: contrib/auth/models.py:108
msgid "" msgid ""
"Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change " "Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change "
"password form</a>." "password form</a>."
@ -1757,19 +1757,19 @@ msgstr ""
"Utilitzi '[algo]$[salt]$[hexdigest]' o el <a href=\"password/\">formulari de " "Utilitzi '[algo]$[salt]$[hexdigest]' o el <a href=\"password/\">formulari de "
"canvi de contrasenya</a>." "canvi de contrasenya</a>."
#: contrib/auth/models.py:104 #: contrib/auth/models.py:109
msgid "staff status" msgid "staff status"
msgstr "és membre del personal" msgstr "és membre del personal"
#: contrib/auth/models.py:104 #: contrib/auth/models.py:109
msgid "Designates whether the user can log into this admin site." msgid "Designates whether the user can log into this admin site."
msgstr "Indica si l'usuari pot entrar en el lloc administratiu." msgstr "Indica si l'usuari pot entrar en el lloc administratiu."
#: contrib/auth/models.py:105 #: contrib/auth/models.py:110
msgid "active" msgid "active"
msgstr "actiu" msgstr "actiu"
#: contrib/auth/models.py:105 #: contrib/auth/models.py:110
msgid "" msgid ""
"Designates whether this user can log into the Django admin. Unselect this " "Designates whether this user can log into the Django admin. Unselect this "
"instead of deleting accounts." "instead of deleting accounts."
@ -1777,11 +1777,11 @@ msgstr ""
"Designa si aquest usuari pot iniciar sessió a la interfície administrativa " "Designa si aquest usuari pot iniciar sessió a la interfície administrativa "
"Djano. Deselecciona-ho enlloc de esborrar comptes d'usuari." "Djano. Deselecciona-ho enlloc de esborrar comptes d'usuari."
#: contrib/auth/models.py:106 #: contrib/auth/models.py:111
msgid "superuser status" msgid "superuser status"
msgstr "estat de superusuari" msgstr "estat de superusuari"
#: contrib/auth/models.py:106 #: contrib/auth/models.py:111
msgid "" msgid ""
"Designates that this user has all permissions without explicitly assigning " "Designates that this user has all permissions without explicitly assigning "
"them." "them."
@ -1789,15 +1789,15 @@ msgstr ""
"Designa que aquest usuari té tots els permisos sense assignar-los " "Designa que aquest usuari té tots els permisos sense assignar-los "
"explícitament." "explícitament."
#: contrib/auth/models.py:107 #: contrib/auth/models.py:112
msgid "last login" msgid "last login"
msgstr "últim inici de sessió" msgstr "últim inici de sessió"
#: contrib/auth/models.py:108 #: contrib/auth/models.py:113
msgid "date joined" msgid "date joined"
msgstr "data de creació" msgstr "data de creació"
#: contrib/auth/models.py:110 #: contrib/auth/models.py:115
msgid "" msgid ""
"In addition to the permissions manually assigned, this user will also get " "In addition to the permissions manually assigned, this user will also get "
"all permissions granted to each group he/she is in." "all permissions granted to each group he/she is in."
@ -1805,39 +1805,39 @@ msgstr ""
"Junt amb els permissos asignats manualment, aquest usuari tindrà, també, els " "Junt amb els permissos asignats manualment, aquest usuari tindrà, també, els "
"permissos dels grups dels que sigui membre." "permissos dels grups dels que sigui membre."
#: contrib/auth/models.py:111 #: contrib/auth/models.py:116
msgid "user permissions" msgid "user permissions"
msgstr "permissos de l'usuari" msgstr "permissos de l'usuari"
#: contrib/auth/models.py:115 #: contrib/auth/models.py:120
msgid "user" msgid "user"
msgstr "usuari" msgstr "usuari"
#: contrib/auth/models.py:116 #: contrib/auth/models.py:121
msgid "users" msgid "users"
msgstr "usuaris" msgstr "usuaris"
#: contrib/auth/models.py:122 #: contrib/auth/models.py:127
msgid "Personal info" msgid "Personal info"
msgstr "Informaciò personal" msgstr "Informaciò personal"
#: contrib/auth/models.py:123 #: contrib/auth/models.py:128
msgid "Permissions" msgid "Permissions"
msgstr "permissos" msgstr "permissos"
#: contrib/auth/models.py:124 #: contrib/auth/models.py:129
msgid "Important dates" msgid "Important dates"
msgstr "Dates importants" msgstr "Dates importants"
#: contrib/auth/models.py:125 #: contrib/auth/models.py:130
msgid "Groups" msgid "Groups"
msgstr "Grups" msgstr "Grups"
#: contrib/auth/models.py:269 #: contrib/auth/models.py:273
msgid "message" msgid "message"
msgstr "missatge" msgstr "missatge"
#: contrib/auth/models.py:282 #: contrib/auth/models.py:286
msgid "AnonymousUser" msgid "AnonymousUser"
msgstr "AnonymousUser" msgstr "AnonymousUser"
@ -1861,7 +1861,7 @@ msgstr ""
msgid "This account is inactive." msgid "This account is inactive."
msgstr "Aquest compte està inactiu" msgstr "Aquest compte està inactiu"
#: contrib/auth/forms.py:85 #: contrib/auth/forms.py:84
msgid "" msgid ""
"That e-mail address doesn't have an associated user account. Are you sure " "That e-mail address doesn't have an associated user account. Are you sure "
"you've registered?" "you've registered?"
@ -2423,7 +2423,7 @@ msgstr "L'any ha de ser posterior al 1900"
msgid "Invalid date: %s" msgid "Invalid date: %s"
msgstr "Data invàlida: %s" msgstr "Data invàlida: %s"
#: core/validators.py:149 db/models/fields/__init__.py:463 #: core/validators.py:149 db/models/fields/__init__.py:468
msgid "Enter a valid date in YYYY-MM-DD format." msgid "Enter a valid date in YYYY-MM-DD format."
msgstr "Introdueixi una data vàlida en el forma AAAA-MM-DD." msgstr "Introdueixi una data vàlida en el forma AAAA-MM-DD."
@ -2431,7 +2431,7 @@ msgstr "Introdueixi una data vàlida en el forma AAAA-MM-DD."
msgid "Enter a valid time in HH:MM format." msgid "Enter a valid time in HH:MM format."
msgstr "Introdueixi una hora vàlida en el format HH:MM." msgstr "Introdueixi una hora vàlida en el format HH:MM."
#: core/validators.py:158 db/models/fields/__init__.py:532 #: core/validators.py:158 db/models/fields/__init__.py:537
msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format."
msgstr "Introdueixi un data/hora vàlida en format YYYY-MM-DD HH:MM." msgstr "Introdueixi un data/hora vàlida en format YYYY-MM-DD HH:MM."
@ -2558,35 +2558,29 @@ msgstr "Si us plau, introdueixi un número decimal vàlid."
#: core/validators.py:423 #: core/validators.py:423
#, python-format #, python-format
msgid "Please enter a valid decimal number with at most %s total digit." msgid "Please enter a valid decimal number with at most %s total digit."
msgid_plural ""
"Please enter a valid decimal number with at most %s total digits." "Please enter a valid decimal number with at most %s total digits."
msgstr[0] "" msgstr ""
"Si us plau, introdueixi un número decimal vàlid amb no més de %s digit." "Si us plau, introdueixi un número decimal vàlid amb no més de %s digit."
msgstr[1] ""
"Si us plau, introdueixi un número decimal vàlid amb no més de %s digits." "Si us plau, introdueixi un número decimal vàlid amb no més de %s digits."
#: core/validators.py:426 #: core/validators.py:426
#, python-format #, python-format
msgid "" msgid ""
"Please enter a valid decimal number with a whole part of at most %s digit." "Please enter a valid decimal number with a whole part of at most %s digit."
msgid_plural ""
"Please enter a valid decimal number with a whole part of at most %s digits." "Please enter a valid decimal number with a whole part of at most %s digits."
msgstr[0] "" msgstr ""
"Si us plau, introdueixi un número decimal vàlid amb la part entera amb com a " "Si us plau, introdueixi un número decimal vàlid amb la part entera amb com a "
"màxim %s dígit." "màxim %s dígit."
msgstr[1] ""
"Si us plau, introdueixi un número decimal vàlid amb la part entera amb com a " "Si us plau, introdueixi un número decimal vàlid amb la part entera amb com a "
"màxim %s dígits." "màxim %s dígits."
#: core/validators.py:429 #: core/validators.py:429
#, python-format #, python-format
msgid "Please enter a valid decimal number with at most %s decimal place." msgid "Please enter a valid decimal number with at most %s decimal place."
msgid_plural ""
"Please enter a valid decimal number with at most %s decimal places." "Please enter a valid decimal number with at most %s decimal places."
msgstr[0] "" msgstr ""
"Si us plau, introdueixi un número decimal vàlid amb no més de %s dígit en la " "Si us plau, introdueixi un número decimal vàlid amb no més de %s dígit en la "
"part decimal." "part decimal."
msgstr[1] ""
"Si us plau, introdueixi un número decimal vàlid amb no més de %s dígits en " "Si us plau, introdueixi un número decimal vàlid amb no més de %s dígits en "
"la part decimal." "la part decimal."
@ -2983,52 +2977,51 @@ msgstr "Ja existeix un %(object)s del tipus %(type)s amb aquest %(field)s."
msgid "%(optname)s with this %(fieldname)s already exists." msgid "%(optname)s with this %(fieldname)s already exists."
msgstr "Ja existeix %(optname)s amb auqest %(fieldname)s." msgstr "Ja existeix %(optname)s amb auqest %(fieldname)s."
#: db/models/fields/__init__.py:373 #: db/models/fields/__init__.py:378
msgid "This value must be an integer." msgid "This value must be an integer."
msgstr "Aquest valor ha de ser un enter." msgstr "Aquest valor ha de ser un enter."
#: db/models/fields/__init__.py:408 #: db/models/fields/__init__.py:413
msgid "This value must be either True or False." msgid "This value must be either True or False."
msgstr "Aquest valor ha de ser True (Veritat) o False (Fals)" msgstr "Aquest valor ha de ser True (Veritat) o False (Fals)"
#: db/models/fields/__init__.py:429 #: db/models/fields/__init__.py:434
msgid "This field cannot be null." msgid "This field cannot be null."
msgstr "Aquest camp no pot ser null (estar buit)." msgstr "Aquest camp no pot ser null (estar buit)."
#: db/models/fields/__init__.py:592 #: db/models/fields/__init__.py:597
msgid "This value must be a decimal number." msgid "This value must be a decimal number."
msgstr "Aquest valor ha de ser un número decimal." msgstr "Aquest valor ha de ser un número decimal."
#: db/models/fields/__init__.py:695 #: db/models/fields/__init__.py:700
msgid "Enter a valid filename." msgid "Enter a valid filename."
msgstr "Introdueixi un nom de fitxer vàlid." msgstr "Introdueixi un nom de fitxer vàlid."
#: db/models/fields/__init__.py:818 #: db/models/fields/__init__.py:824
msgid "This value must be either None, True or False." msgid "This value must be either None, True or False."
msgstr "Aquest valor ha de ser None (Cap), True (Veritat) o False (Fals)" msgstr "Aquest valor ha de ser None (Cap), True (Veritat) o False (Fals)"
#: db/models/fields/related.py:53 #: db/models/fields/related.py:54
#, python-format #, python-format
msgid "Please enter a valid %s." msgid "Please enter a valid %s."
msgstr "Si us plau, introdueixi un %s vàlid." msgstr "Si us plau, introdueixi un %s vàlid."
#: db/models/fields/related.py:642 #: db/models/fields/related.py:640
msgid "Separate multiple IDs with commas." msgid "Separate multiple IDs with commas."
msgstr "Separi múltiples IDs amb comes." msgstr "Separi múltiples IDs amb comes."
#: db/models/fields/related.py:644 #: db/models/fields/related.py:642
msgid "" msgid ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." "Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr "Premi \"Control\" o \"Command\" en un Mac per escollir més d'un." msgstr "Premi \"Control\" o \"Command\" en un Mac per escollir més d'un."
#: db/models/fields/related.py:691 #: db/models/fields/related.py:689
#, python-format #, python-format
msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid."
msgid_plural ""
"Please enter valid %(self)s IDs. The values %(value)r are invalid." "Please enter valid %(self)s IDs. The values %(value)r are invalid."
msgstr[0] "" msgstr ""
"Si us plau, introdueixi IDs de %(self)s vàlids. El valor %(value)r és " "Si us plau, introdueixi IDs de %(self)s vàlids. El valor %(value)r és "
"invàlid." "invàlid."
msgstr[1] ""
"Si us plau, introdueixi IDs de %(self)s vàlids. Els valors %(value)r són " "Si us plau, introdueixi IDs de %(self)s vàlids. Els valors %(value)r són "
"invàlids." "invàlids."

View File

@ -1,15 +1,14 @@
# translation of djangojs.po to # translation of djangojs.po to
# Spanish translation for the django-admin JS files. # Catalan translation for the django-admin JS files.
# Copyright (C) # This file is distributed under the same license as the Django package.
# This file is distributed under the same license as the PACKAGE package.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: djangojs\n" "Project-Id-Version: djangojs\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2007-05-20 18:25+0200\n" "POT-Creation-Date: 2007-05-20 18:25+0200\n"
"PO-Revision-Date: 2007-05-20 18:24+0200\n" "PO-Revision-Date: 2007-06-25 17:47+0200\n"
"Last-Translator: Marc Fargas <marc@fargas.com>\n" "Last-Translator: Marc Fargas <telenieko@telenieko.com>\n"
"Language-Team: <es@li.org>\n" "Language-Team: <es@li.org>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"

File diff suppressed because it is too large Load Diff

View File

@ -7,25 +7,34 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Django JavaScript Czech translation\n" "Project-Id-Version: Django JavaScript Czech translation\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2005-12-17 22:26+0100\n" "POT-Creation-Date: 2007-06-18 11:26+0200\n"
"PO-Revision-Date: 2006-05-03 12:04+0100\n"
"Last-Translator: \n"
"Language-Team: Czech\n" "Language-Team: Czech\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n>1 && n<5 ? 1 : 2;\n"
"X-Poedit-Language: Czech\n"
"X-Poedit-Country: CZECH REPUBLIC\n" #: contrib/admin/media/js/dateparse.js:32
#: contrib/admin/media/js/calendar.js:24
msgid ""
"January February March April May June July August September October November "
"December"
msgstr ""
"Leden Únor Březen Duben Květen Červen Červenec Srpen Září Říjen Listopad "
"Prosinec"
#: contrib/admin/media/js/dateparse.js:33
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
msgstr "Neděle Pondělí Úterý Středa Čtvrtek Pátek Sobota"
#: contrib/admin/media/js/SelectFilter2.js:33 #: contrib/admin/media/js/SelectFilter2.js:33
#, perl-format #, perl-format
msgid "Available %s" msgid "Available %s"
msgstr "K dispozici %s" msgstr "Dostupná %s"
#: contrib/admin/media/js/SelectFilter2.js:41 #: contrib/admin/media/js/SelectFilter2.js:41
msgid "Choose all" msgid "Choose all"
msgstr "Vybrat vše" msgstr "Vybrat vše"
#: contrib/admin/media/js/SelectFilter2.js:46 #: contrib/admin/media/js/SelectFilter2.js:46
msgid "Add" msgid "Add"
@ -38,75 +47,72 @@ msgstr "Odebrat"
#: contrib/admin/media/js/SelectFilter2.js:53 #: contrib/admin/media/js/SelectFilter2.js:53
#, perl-format #, perl-format
msgid "Chosen %s" msgid "Chosen %s"
msgstr "Vybraný %s" msgstr "Vybraná %s"
#: contrib/admin/media/js/SelectFilter2.js:54 #: contrib/admin/media/js/SelectFilter2.js:54
msgid "Select your choice(s) and click " msgid "Select your choice(s) and click "
msgstr "Vyberte si a klikněte" msgstr "Vyberte si a klikněte "
#: contrib/admin/media/js/SelectFilter2.js:59 #: contrib/admin/media/js/SelectFilter2.js:59
msgid "Clear all" msgid "Clear all"
msgstr "Vše vymazat" msgstr "Vymazat vše"
#: contrib/admin/media/js/dateparse.js:26
#: contrib/admin/media/js/calendar.js:24
msgid "January February March April May June July August September October November December"
msgstr "Leden Únor Březen Duben Květen Červen Červenec Srpen Září Říjen Listopad Prosinec"
#: contrib/admin/media/js/dateparse.js:27
#, fuzzy
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
msgstr "Neděle Pondělí Úterý Středa Čtvrtek Pátek Sobota"
#: contrib/admin/media/js/calendar.js:25 #: contrib/admin/media/js/calendar.js:25
#, fuzzy
msgid "S M T W T F S" msgid "S M T W T F S"
msgstr "N P U S C P S" msgstr "N P U S C P S"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 #: contrib/admin/media/js/admin/CollapsedFieldsets.js:34
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 #: contrib/admin/media/js/admin/CollapsedFieldsets.js:72
msgid "Show"
msgstr "Ukázat"
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63
msgid "Hide"
msgstr "Skrýt"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:47
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
msgid "Now" msgid "Now"
msgstr "Nyní" msgstr "Nyní"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:51
msgid "Clock" msgid "Clock"
msgstr "Hodiny" msgstr "Hodiny"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:78
msgid "Choose a time" msgid "Choose a time"
msgstr "Vyberte čas" msgstr "Vyberte čas"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
msgid "Midnight" msgid "Midnight"
msgstr "Půlnoc" msgstr "Půlnoc"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
msgid "6 a.m." msgid "6 a.m."
msgstr "6 ráno" msgstr "6 ráno"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:84
msgid "Noon" msgid "Noon"
msgstr "Poledne" msgstr "Poledne"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:88
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:183
msgid "Cancel" msgid "Cancel"
msgstr "Storno" msgstr "Storno"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:128
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:177
msgid "Today" msgid "Today"
msgstr "Dnes" msgstr "Dnes"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:132
msgid "Calendar" msgid "Calendar"
msgstr "Kalendář" msgstr "Kalendář"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:175
msgid "Yesterday" msgid "Yesterday"
msgstr "Včera" msgstr "Včera"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:179
msgid "Tomorrow" msgid "Tomorrow"
msgstr "Zítra" msgstr "Zítra"

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,118 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2007-06-24 22:09+1000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: contrib/admin/media/js/SelectFilter2.js:33
#, perl-format
msgid "Available %s"
msgstr ""
#: contrib/admin/media/js/SelectFilter2.js:41
msgid "Choose all"
msgstr ""
#: contrib/admin/media/js/SelectFilter2.js:46
msgid "Add"
msgstr ""
#: contrib/admin/media/js/SelectFilter2.js:48
msgid "Remove"
msgstr ""
#: contrib/admin/media/js/SelectFilter2.js:53
#, perl-format
msgid "Chosen %s"
msgstr ""
#: contrib/admin/media/js/SelectFilter2.js:54
msgid "Select your choice(s) and click "
msgstr ""
#: contrib/admin/media/js/SelectFilter2.js:59
msgid "Clear all"
msgstr ""
#: contrib/admin/media/js/dateparse.js:32
#: contrib/admin/media/js/calendar.js:24
msgid ""
"January February March April May June July August September October November "
"December"
msgstr ""
#: contrib/admin/media/js/dateparse.js:33
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
msgstr ""
#: contrib/admin/media/js/calendar.js:25
msgid "S M T W T F S"
msgstr ""
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:47
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
msgid "Now"
msgstr ""
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:51
msgid "Clock"
msgstr ""
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:78
msgid "Choose a time"
msgstr ""
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
msgid "Midnight"
msgstr ""
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
msgid "6 a.m."
msgstr ""
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84
msgid "Noon"
msgstr ""
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183
msgid "Cancel"
msgstr ""
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:128
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:177
msgid "Today"
msgstr ""
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:132
msgid "Calendar"
msgstr ""
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:175
msgid "Yesterday"
msgstr ""
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179
msgid "Tomorrow"
msgstr ""
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:34
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:72
msgid "Show"
msgstr ""
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63
msgid "Hide"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,8 @@ msgstr "To pole jest wymagane."
msgid "Ensure your text is less than %s character." msgid "Ensure your text is less than %s character."
msgid_plural "Ensure your text is less than %s characters." msgid_plural "Ensure your text is less than %s characters."
msgstr[0] "Upewnij się, że tekst ma mniej niż %s znak." msgstr[0] "Upewnij się, że tekst ma mniej niż %s znak."
msgstr[1] "Upewnij się, że tekst ma mniej niż %s znaków." msgstr[1] "Upewnij się, że tekst ma mniej niż %s znaki."
msgstr[2] "Upewnij się, że tekst ma mniej niż %s znaków."
#: oldforms/__init__.py:397 #: oldforms/__init__.py:397
msgid "Line breaks are not allowed here." msgid "Line breaks are not allowed here."
@ -75,7 +76,7 @@ msgid "Enter a whole number between 0 and 32,767."
msgstr "Proszę wpisać liczbę całkowitą z zakresu od 0 do 32 767" msgstr "Proszę wpisać liczbę całkowitą z zakresu od 0 do 32 767"
#: db/models/manipulators.py:307 #: db/models/manipulators.py:307
#, fuzzy, python-format #, python-format
msgid "%(object)s with this %(type)s already exists for the given %(field)s." msgid "%(object)s with this %(type)s already exists for the given %(field)s."
msgstr "%(object)s z %(type)s już istnieje dla %(field)s." msgstr "%(object)s z %(type)s już istnieje dla %(field)s."
@ -85,7 +86,7 @@ msgid "and"
msgstr "i" msgstr "i"
#: db/models/fields/__init__.py:42 #: db/models/fields/__init__.py:42
#, fuzzy, python-format #, python-format
msgid "%(optname)s with this %(fieldname)s already exists." msgid "%(optname)s with this %(fieldname)s already exists."
msgstr "Już istnieje %(optname)s z %(fieldname)s." msgstr "Już istnieje %(optname)s z %(fieldname)s."
@ -142,6 +143,9 @@ msgstr[0] ""
msgstr[1] "" msgstr[1] ""
"Proszę podać poprawne identyfikatory %(self)s. Wartości %(value)r są " "Proszę podać poprawne identyfikatory %(self)s. Wartości %(value)r są "
"niepoprawne." "niepoprawne."
msgstr[2] ""
"Proszę podać poprawne identyfikatory %(self)s. Wartości %(value)r są "
"niepoprawne."
#: conf/global_settings.py:39 #: conf/global_settings.py:39
msgid "Arabic" msgid "Arabic"
@ -370,7 +374,7 @@ msgstr "Niepoprawna data: %s"
#: core/validators.py:153 #: core/validators.py:153
msgid "Enter a valid time in HH:MM format." msgid "Enter a valid time in HH:MM format."
msgstr "Proszę wpisać poprawną godzinę w formacie GG:MM." msgstr "Proszę wpisać poprawną godzinę w formacie HH:MM."
#: core/validators.py:162 newforms/fields.py:271 #: core/validators.py:162 newforms/fields.py:271
msgid "Enter a valid e-mail address." msgid "Enter a valid e-mail address."
@ -439,6 +443,7 @@ msgid "Watch your mouth! The word %s is not allowed here."
msgid_plural "Watch your mouth! The words %s are not allowed here." msgid_plural "Watch your mouth! The words %s are not allowed here."
msgstr[0] "Nie wolno przeklinać! Słowo %s nie jest dozwolone." msgstr[0] "Nie wolno przeklinać! Słowo %s nie jest dozwolone."
msgstr[1] "Nie wolno przeklinać! Słowa %s nie są dozwolone." msgstr[1] "Nie wolno przeklinać! Słowa %s nie są dozwolone."
msgstr[2] "Nie wolno przeklinać! Słowa %s nie są dozwolone."
#: core/validators.py:273 #: core/validators.py:273
#, python-format #, python-format
@ -497,13 +502,15 @@ msgid "Please enter a valid decimal number with at most %s total digit."
msgid_plural "Please enter a valid decimal number with at most %s total digits." msgid_plural "Please enter a valid decimal number with at most %s total digits."
msgstr[0] "Proszę wpisać poprawną liczbę dziesiętną o nie więcej niż %s cyfrze." msgstr[0] "Proszę wpisać poprawną liczbę dziesiętną o nie więcej niż %s cyfrze."
msgstr[1] "Proszę wpisać poprawną liczbę dziesiętną o nie więcej niż %s cyfrach." msgstr[1] "Proszę wpisać poprawną liczbę dziesiętną o nie więcej niż %s cyfrach."
msgstr[2] "Proszę wpisać poprawną liczbę dziesiętną o nie więcej niż %s cyfrach."
#: core/validators.py:425 #: core/validators.py:425
#, python-format #, python-format
msgid "Please enter a valid decimal number with a whole part of at most %s digit." msgid "Please enter a valid decimal number with a whole part of at most %s digit."
msgid_plural "Please enter a valid decimal number with a whole part of at most %s digits." msgid_plural "Please enter a valid decimal number with a whole part of at most %s digits."
msgstr[0] "Proszę wpisać poprawną liczbę dziesiętną zawierającą nie więcej niż %s cyfry." msgstr[0] "Proszę wpisać poprawną liczbę dziesiętną zawierającą nie więcej niż %s cyfrę."
msgstr[1] "Proszę wpisać poprawną liczbę dziesiętną zawierającą nie więcej niż %s cyfr." msgstr[1] "Proszę wpisać poprawną liczbę dziesiętną zawierającą nie więcej niż %s cyfry."
msgstr[2] "Proszę wpisać poprawną liczbę dziesiętną zawierającą nie więcej niż %s cyfr."
#: core/validators.py:428 #: core/validators.py:428
#, python-format #, python-format
@ -515,6 +522,9 @@ msgstr[0] ""
msgstr[1] "" msgstr[1] ""
"Proszę wpisać poprawną liczbę dziesiętną z dokładnością do %s miejsc po " "Proszę wpisać poprawną liczbę dziesiętną z dokładnością do %s miejsc po "
"przecinku." "przecinku."
msgstr[2] ""
"Proszę wpisać poprawną liczbę dziesiętną z dokładnością do %s miejsc po "
"przecinku."
#: core/validators.py:438 #: core/validators.py:438
#, python-format #, python-format
@ -668,7 +678,7 @@ msgstr "Wpisz poprawny URL."
#: newforms/fields.py:313 #: newforms/fields.py:313
msgid "This URL appears to be a broken link." msgid "This URL appears to be a broken link."
msgstr "Odnośnik %s jest nieprawidłowy." msgstr "Ten odnośnik jest nieprawidłowy."
#: contrib/humanize/templatetags/humanize.py:17 #: contrib/humanize/templatetags/humanize.py:17
msgid "th" msgid "th"
@ -691,21 +701,24 @@ msgstr "-ci"
msgid "%(value).1f million" msgid "%(value).1f million"
msgid_plural "%(value).1f million" msgid_plural "%(value).1f million"
msgstr[0] "%(value).1f milion" msgstr[0] "%(value).1f milion"
msgstr[1] "%(value).1f milionów" msgstr[1] "%(value).1f miliony"
msgstr[2] "%(value).1f milionów"
#: contrib/humanize/templatetags/humanize.py:50 #: contrib/humanize/templatetags/humanize.py:50
#, python-format #, python-format
msgid "%(value).1f billion" msgid "%(value).1f billion"
msgid_plural "%(value).1f billion" msgid_plural "%(value).1f billion"
msgstr[0] "%(value).1f miliard" msgstr[0] "%(value).1f miliard"
msgstr[1] "%(value).1f miliardów" msgstr[1] "%(value).1f miliardy"
msgstr[2] "%(value).1f miliardów"
#: contrib/humanize/templatetags/humanize.py:53 #: contrib/humanize/templatetags/humanize.py:53
#, python-format #, python-format
msgid "%(value).1f trillion" msgid "%(value).1f trillion"
msgid_plural "%(value).1f trillion" msgid_plural "%(value).1f trillion"
msgstr[0] "%(value).1f bilion" msgstr[0] "%(value).1f bilion"
msgstr[1] "%(value).1f bilionów" msgstr[1] "%(value).1f biliony"
msgstr[2] "%(value).1f bilionów"
#: contrib/humanize/templatetags/humanize.py:68 #: contrib/humanize/templatetags/humanize.py:68
msgid "one" msgid "one"
@ -987,6 +1000,15 @@ msgstr[0] ""
"\n" "\n"
"%(text)s" "%(text)s"
msgstr[1] "" msgstr[1] ""
"Ten komentarz został wysłany przez użytkownika, który wysłał mniej niż %"
"(count)s komentarze:\n"
"\n"
"%(text)s"
msgstr[2] ""
"Ten komentarz został wysłany przez użytkownika, który wysłał mniej niż %"
"(count)s komentarzy:\n"
"\n"
"%(text)s"
#: contrib/comments/views/comments.py:116 #: contrib/comments/views/comments.py:116
#, python-format #, python-format
@ -1574,7 +1596,8 @@ msgstr "Szukaj"
msgid "1 result" msgid "1 result"
msgid_plural "%(counter)s results" msgid_plural "%(counter)s results"
msgstr[0] "1 wynik" msgstr[0] "1 wynik"
msgstr[1] "%(counter)s wyników" msgstr[1] "%(counter)s wyniki"
msgstr[2] "%(counter)s wyników"
#: contrib/admin/templates/admin/search_form.html:10 #: contrib/admin/templates/admin/search_form.html:10
#, python-format #, python-format
@ -1681,6 +1704,7 @@ msgid "Please correct the error below."
msgid_plural "Please correct the errors below." msgid_plural "Please correct the errors below."
msgstr[0] "Proszę popraw poniższy błąd" msgstr[0] "Proszę popraw poniższy błąd"
msgstr[1] "Proszę popraw poniższe błędy" msgstr[1] "Proszę popraw poniższe błędy"
msgstr[2] "Proszę popraw poniższe błędy"
#: contrib/admin/templates/admin/change_form.html:50 #: contrib/admin/templates/admin/change_form.html:50
msgid "Ordering" msgid "Ordering"
@ -2077,7 +2101,6 @@ msgstr ""
msgid "user permissions" msgid "user permissions"
msgstr "uprawnienia użytkownika" msgstr "uprawnienia użytkownika"
# kurwa
#: contrib/auth/models.py:115 #: contrib/auth/models.py:115
msgid "user" msgid "user"
msgstr "użytkownik" msgstr "użytkownik"
@ -2831,37 +2854,43 @@ msgstr "Gru."
msgid "year" msgid "year"
msgid_plural "years" msgid_plural "years"
msgstr[0] "rok" msgstr[0] "rok"
msgstr[1] "lat" msgstr[1] "lata"
msgstr[2] "lat"
#: utils/timesince.py:13 #: utils/timesince.py:13
msgid "month" msgid "month"
msgid_plural "months" msgid_plural "months"
msgstr[0] "miesiąc" msgstr[0] "miesiąc"
msgstr[1] "miesięcy" msgstr[1] "miesięce"
msgstr[2] "miesięcy"
#: utils/timesince.py:14 #: utils/timesince.py:14
msgid "week" msgid "week"
msgid_plural "weeks" msgid_plural "weeks"
msgstr[0] "tydzień" msgstr[0] "tydzień"
msgstr[1] "tygodni" msgstr[1] "tygodnie"
msgstr[2] "tygodni"
#: utils/timesince.py:15 #: utils/timesince.py:15
msgid "day" msgid "day"
msgid_plural "days" msgid_plural "days"
msgstr[0] "dzień" msgstr[0] "dzień"
msgstr[1] "dni" msgstr[1] "dni"
msgstr[2] "dni"
#: utils/timesince.py:16 #: utils/timesince.py:16
msgid "hour" msgid "hour"
msgid_plural "hours" msgid_plural "hours"
msgstr[0] "godzina" msgstr[0] "godzina"
msgstr[1] "godzin" msgstr[1] "godziny"
msgstr[2] "godzin"
#: utils/timesince.py:17 #: utils/timesince.py:17
msgid "minute" msgid "minute"
msgid_plural "minutes" msgid_plural "minutes"
msgstr[0] "minuta" msgstr[0] "minuta"
msgstr[1] "minut" msgstr[1] "minuty"
msgstr[2] "minut"
#: utils/timesince.py:40 #: utils/timesince.py:40
#, python-format #, python-format
@ -2880,7 +2909,7 @@ msgstr ", %(number)d %(type)s"
#: utils/dateformat.py:40 #: utils/dateformat.py:40
msgid "p.m." msgid "p.m."
msgstr "popołudniu" msgstr "po południu"
#: utils/dateformat.py:41 #: utils/dateformat.py:41
msgid "a.m." msgid "a.m."
@ -2888,7 +2917,7 @@ msgstr "rano"
#: utils/dateformat.py:46 #: utils/dateformat.py:46
msgid "PM" msgid "PM"
msgstr "popołudniu" msgstr "po południu"
#: utils/dateformat.py:47 #: utils/dateformat.py:47
msgid "AM" msgid "AM"
@ -2931,13 +2960,13 @@ msgstr "tak,nie,może"
msgid "%(size)d byte" msgid "%(size)d byte"
msgid_plural "%(size)d bytes" msgid_plural "%(size)d bytes"
msgstr[0] "%(size)d bajt" msgstr[0] "%(size)d bajt"
msgstr[1] "%(size)d bajtów" msgstr[1] "%(size)d bajty"
msgstr[2] "" msgstr[2] "%(size)d bajtów"
#: template/defaultfilters.py:522 #: template/defaultfilters.py:522
#, python-format #, python-format
msgid "%.1f KB" msgid "%.1f KB"
msgstr "%.1f kB" msgstr "%.1f KB"
#: template/defaultfilters.py:524 #: template/defaultfilters.py:524
#, python-format #, python-format

View File

@ -40,7 +40,6 @@ msgid "Chosen %s"
msgstr "Wybrano %s" msgstr "Wybrano %s"
#: contrib/admin/media/js/SelectFilter2.js:54 #: contrib/admin/media/js/SelectFilter2.js:54
#, fuzzy
msgid "Select your choice(s) and click " msgid "Select your choice(s) and click "
msgstr "Zaznacz swój wybór i kliknij " msgstr "Zaznacz swój wybór i kliknij "

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,12 @@ function dismissRelatedLookupPopup(win, chosenId) {
function showAddAnotherPopup(triggeringLink) { function showAddAnotherPopup(triggeringLink) {
var name = triggeringLink.id.replace(/^add_/, ''); var name = triggeringLink.id.replace(/^add_/, '');
name = name.replace(/\./g, '___'); name = name.replace(/\./g, '___');
var win = window.open(triggeringLink.href + '?_popup=1', name, 'height=500,width=800,resizable=yes,scrollbars=yes'); href = triggeringLink.href
if (href.indexOf('?') == -1)
href += '?_popup=1';
else
href += '&_popup=1';
var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
win.focus(); win.focus();
return false; return false;
} }

View File

@ -9,7 +9,7 @@ DELETION = 3
class LogEntryManager(models.Manager): class LogEntryManager(models.Manager):
def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''): def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
e = self.model(None, None, user_id, content_type_id, object_id, object_repr[:200], action_flag, change_message) e = self.model(None, None, user_id, content_type_id, str(object_id), object_repr[:200], action_flag, change_message)
e.save() e.save()
class LogEntry(models.Model): class LogEntry(models.Model):

View File

@ -94,15 +94,15 @@ class FieldWidgetNode(template.Node):
return cls.nodelists[klass] return cls.nodelists[klass]
get_nodelist = classmethod(get_nodelist) get_nodelist = classmethod(get_nodelist)
def iter_render(self, context): def render(self, context):
bound_field = template.resolve_variable(self.bound_field_var, context) bound_field = template.resolve_variable(self.bound_field_var, context)
context.push() context.push()
context['bound_field'] = bound_field context['bound_field'] = bound_field
for chunk in self.get_nodelist(bound_field.field.__class__).iter_render(context): output = self.get_nodelist(bound_field.field.__class__).render(context)
yield chunk
context.pop() context.pop()
return output
class FieldWrapper(object): class FieldWrapper(object):
def __init__(self, field ): def __init__(self, field ):
@ -157,7 +157,7 @@ class EditInlineNode(template.Node):
def __init__(self, rel_var): def __init__(self, rel_var):
self.rel_var = rel_var self.rel_var = rel_var
def iter_render(self, context): def render(self, context):
relation = template.resolve_variable(self.rel_var, context) relation = template.resolve_variable(self.rel_var, context)
context.push() context.push()
if relation.field.rel.edit_inline == models.TABULAR: if relation.field.rel.edit_inline == models.TABULAR:
@ -169,9 +169,10 @@ class EditInlineNode(template.Node):
original = context.get('original', None) original = context.get('original', None)
bound_related_object = relation.bind(context['form'], original, bound_related_object_class) bound_related_object = relation.bind(context['form'], original, bound_related_object_class)
context['bound_related_object'] = bound_related_object context['bound_related_object'] = bound_related_object
for chunk in loader.get_template(bound_related_object.template_name()).iter_render(context): t = loader.get_template(bound_related_object.template_name())
yield chunk output = t.render(context)
context.pop() context.pop()
return output
def output_all(form_fields): def output_all(form_fields):
return ''.join([str(f) for f in form_fields]) return ''.join([str(f) for f in form_fields])

View File

@ -7,7 +7,7 @@ class AdminApplistNode(template.Node):
def __init__(self, varname): def __init__(self, varname):
self.varname = varname self.varname = varname
def iter_render(self, context): def render(self, context):
from django.db import models from django.db import models
from django.utils.text import capfirst from django.utils.text import capfirst
app_list = [] app_list = []
@ -54,7 +54,7 @@ class AdminApplistNode(template.Node):
'models': model_list, 'models': model_list,
}) })
context[self.varname] = app_list context[self.varname] = app_list
return () return ''
def get_admin_app_list(parser, token): def get_admin_app_list(parser, token):
""" """

View File

@ -10,14 +10,14 @@ class AdminLogNode(template.Node):
def __repr__(self): def __repr__(self):
return "<GetAdminLog Node>" return "<GetAdminLog Node>"
def iter_render(self, context): def render(self, context):
if self.user is None: if self.user is None:
context[self.varname] = LogEntry.objects.all().select_related()[:self.limit] context[self.varname] = LogEntry.objects.all().select_related()[:self.limit]
else: else:
if not self.user.isdigit(): if not self.user.isdigit():
self.user = context[self.user].id self.user = context[self.user].id
context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit] context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit]
return () return ''
class DoGetAdminLog: class DoGetAdminLog:
""" """

View File

@ -14,6 +14,11 @@ from django.utils.html import escape
from django.utils.text import capfirst, get_text_list from django.utils.text import capfirst, get_text_list
import operator import operator
try:
set
except NameError:
from sets import Set as set # Python 2.3 fallback
from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION
if not LogEntry._meta.installed: if not LogEntry._meta.installed:
raise ImproperlyConfigured, "You'll need to put 'django.contrib.admin' in your INSTALLED_APPS setting before you can use the admin application." raise ImproperlyConfigured, "You'll need to put 'django.contrib.admin' in your INSTALLED_APPS setting before you can use the admin application."
@ -489,7 +494,6 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
perms_needed.add(related.opts.verbose_name) perms_needed.add(related.opts.verbose_name)
def delete_stage(request, app_label, model_name, object_id): def delete_stage(request, app_label, model_name, object_id):
import sets
model = models.get_model(app_label, model_name) model = models.get_model(app_label, model_name)
object_id = unquote(object_id) object_id = unquote(object_id)
if model is None: if model is None:
@ -502,7 +506,7 @@ def delete_stage(request, app_label, model_name, object_id):
# Populate deleted_objects, a data structure of all related objects that # Populate deleted_objects, a data structure of all related objects that
# will also be deleted. # will also be deleted.
deleted_objects = ['%s: <a href="../../%s/">%s</a>' % (capfirst(opts.verbose_name), object_id, escape(str(obj))), []] deleted_objects = ['%s: <a href="../../%s/">%s</a>' % (capfirst(opts.verbose_name), object_id, escape(str(obj))), []]
perms_needed = sets.Set() perms_needed = set()
_get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1) _get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1)
if request.POST: # The user has already confirmed the deletion. if request.POST: # The user has already confirmed the deletion.

View File

@ -79,32 +79,32 @@ class PasswordResetForm(oldforms.Manipulator):
def isValidUserEmail(self, new_data, all_data): def isValidUserEmail(self, new_data, all_data):
"Validates that a user exists with the given e-mail address" "Validates that a user exists with the given e-mail address"
try: self.users_cache = list(User.objects.filter(email__iexact=new_data))
self.user_cache = User.objects.get(email__iexact=new_data) if len(self.users_cache) == 0:
except User.DoesNotExist:
raise validators.ValidationError, _("That e-mail address doesn't have an associated user account. Are you sure you've registered?") raise validators.ValidationError, _("That e-mail address doesn't have an associated user account. Are you sure you've registered?")
def save(self, domain_override=None, email_template_name='registration/password_reset_email.html'): def save(self, domain_override=None, email_template_name='registration/password_reset_email.html'):
"Calculates a new password randomly and sends it to the user" "Calculates a new password randomly and sends it to the user"
from django.core.mail import send_mail from django.core.mail import send_mail
new_pass = User.objects.make_random_password() for user in self.users_cache:
self.user_cache.set_password(new_pass) new_pass = User.objects.make_random_password()
self.user_cache.save() user.set_password(new_pass)
if not domain_override: user.save()
current_site = Site.objects.get_current() if not domain_override:
site_name = current_site.name current_site = Site.objects.get_current()
domain = current_site.domain site_name = current_site.name
else: domain = current_site.domain
site_name = domain = domain_override else:
t = loader.get_template(email_template_name) site_name = domain = domain_override
c = { t = loader.get_template(email_template_name)
'new_password': new_pass, c = {
'email': self.user_cache.email, 'new_password': new_pass,
'domain': domain, 'email': user.email,
'site_name': site_name, 'domain': domain,
'user': self.user_cache, 'site_name': site_name,
} 'user': user,
send_mail('Password reset on %s' % site_name, t.render(Context(c)), None, [self.user_cache.email]) }
send_mail('Password reset on %s' % site_name, t.render(Context(c)), None, [user.email])
class PasswordChangeForm(oldforms.Manipulator): class PasswordChangeForm(oldforms.Manipulator):
"A form that lets a user change his password." "A form that lets a user change his password."

View File

@ -5,6 +5,11 @@ from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import datetime import datetime
try:
set
except NameError:
from sets import Set as set # Python 2.3 fallback
def check_password(raw_password, enc_password): def check_password(raw_password, enc_password):
""" """
Returns a boolean of whether the raw_password was correct. Handles Returns a boolean of whether the raw_password was correct. Handles
@ -175,7 +180,6 @@ class User(models.Model):
def get_group_permissions(self): def get_group_permissions(self):
"Returns a list of permission strings that this user has through his/her groups." "Returns a list of permission strings that this user has through his/her groups."
if not hasattr(self, '_group_perm_cache'): if not hasattr(self, '_group_perm_cache'):
import sets
cursor = connection.cursor() cursor = connection.cursor()
# The SQL below works out to the following, after DB quoting: # The SQL below works out to the following, after DB quoting:
# cursor.execute(""" # cursor.execute("""
@ -200,13 +204,12 @@ class User(models.Model):
backend.quote_name('id'), backend.quote_name('content_type_id'), backend.quote_name('id'), backend.quote_name('content_type_id'),
backend.quote_name('user_id'),) backend.quote_name('user_id'),)
cursor.execute(sql, [self.id]) cursor.execute(sql, [self.id])
self._group_perm_cache = sets.Set(["%s.%s" % (row[0], row[1]) for row in cursor.fetchall()]) self._group_perm_cache = set(["%s.%s" % (row[0], row[1]) for row in cursor.fetchall()])
return self._group_perm_cache return self._group_perm_cache
def get_all_permissions(self): def get_all_permissions(self):
if not hasattr(self, '_perm_cache'): if not hasattr(self, '_perm_cache'):
import sets self._perm_cache = set(["%s.%s" % (p.content_type.app_label, p.codename) for p in self.user_permissions.select_related()])
self._perm_cache = sets.Set(["%s.%s" % (p.content_type.app_label, p.codename) for p in self.user_permissions.select_related()])
self._perm_cache.update(self.get_group_permissions()) self._perm_cache.update(self.get_group_permissions())
return self._perm_cache return self._perm_cache

View File

@ -24,7 +24,7 @@ class CommentFormNode(template.Node):
self.photo_options, self.rating_options = photo_options, rating_options self.photo_options, self.rating_options = photo_options, rating_options
self.is_public = is_public self.is_public = is_public
def iter_render(self, context): def render(self, context):
from django.conf import settings from django.conf import settings
from django.utils.text import normalize_newlines from django.utils.text import normalize_newlines
import base64 import base64
@ -33,7 +33,7 @@ class CommentFormNode(template.Node):
try: try:
self.obj_id = template.resolve_variable(self.obj_id_lookup_var, context) self.obj_id = template.resolve_variable(self.obj_id_lookup_var, context)
except template.VariableDoesNotExist: except template.VariableDoesNotExist:
return return ''
# Validate that this object ID is valid for this content-type. # Validate that this object ID is valid for this content-type.
# We only have to do this validation if obj_id_lookup_var is provided, # We only have to do this validation if obj_id_lookup_var is provided,
# because do_comment_form() validates hard-coded object IDs. # because do_comment_form() validates hard-coded object IDs.
@ -67,9 +67,9 @@ class CommentFormNode(template.Node):
context['hash'] = Comment.objects.get_security_hash(context['options'], context['photo_options'], context['rating_options'], context['target']) context['hash'] = Comment.objects.get_security_hash(context['options'], context['photo_options'], context['rating_options'], context['target'])
context['logout_url'] = settings.LOGOUT_URL context['logout_url'] = settings.LOGOUT_URL
default_form = loader.get_template(COMMENT_FORM) default_form = loader.get_template(COMMENT_FORM)
for chunk in default_form.iter_render(context): output = default_form.render(context)
yield chunk
context.pop() context.pop()
return output
class CommentCountNode(template.Node): class CommentCountNode(template.Node):
def __init__(self, package, module, context_var_name, obj_id, var_name, free): def __init__(self, package, module, context_var_name, obj_id, var_name, free):
@ -77,7 +77,7 @@ class CommentCountNode(template.Node):
self.context_var_name, self.obj_id = context_var_name, obj_id self.context_var_name, self.obj_id = context_var_name, obj_id
self.var_name, self.free = var_name, free self.var_name, self.free = var_name, free
def iter_render(self, context): def render(self, context):
from django.conf import settings from django.conf import settings
manager = self.free and FreeComment.objects or Comment.objects manager = self.free and FreeComment.objects or Comment.objects
if self.context_var_name is not None: if self.context_var_name is not None:
@ -86,7 +86,7 @@ class CommentCountNode(template.Node):
content_type__app_label__exact=self.package, content_type__app_label__exact=self.package,
content_type__model__exact=self.module, site__id__exact=settings.SITE_ID).count() content_type__model__exact=self.module, site__id__exact=settings.SITE_ID).count()
context[self.var_name] = comment_count context[self.var_name] = comment_count
return () return ''
class CommentListNode(template.Node): class CommentListNode(template.Node):
def __init__(self, package, module, context_var_name, obj_id, var_name, free, ordering, extra_kwargs=None): def __init__(self, package, module, context_var_name, obj_id, var_name, free, ordering, extra_kwargs=None):
@ -96,14 +96,14 @@ class CommentListNode(template.Node):
self.ordering = ordering self.ordering = ordering
self.extra_kwargs = extra_kwargs or {} self.extra_kwargs = extra_kwargs or {}
def iter_render(self, context): def render(self, context):
from django.conf import settings from django.conf import settings
get_list_function = self.free and FreeComment.objects.filter or Comment.objects.get_list_with_karma get_list_function = self.free and FreeComment.objects.filter or Comment.objects.get_list_with_karma
if self.context_var_name is not None: if self.context_var_name is not None:
try: try:
self.obj_id = template.resolve_variable(self.context_var_name, context) self.obj_id = template.resolve_variable(self.context_var_name, context)
except template.VariableDoesNotExist: except template.VariableDoesNotExist:
return () return ''
kwargs = { kwargs = {
'object_id__exact': self.obj_id, 'object_id__exact': self.obj_id,
'content_type__app_label__exact': self.package, 'content_type__app_label__exact': self.package,
@ -127,7 +127,7 @@ class CommentListNode(template.Node):
comment_list = [c for c in comment_list if not c.is_hidden or (user_id == c.user_id)] comment_list = [c for c in comment_list if not c.is_hidden or (user_id == c.user_id)]
context[self.var_name] = comment_list context[self.var_name] = comment_list
return () return ''
class DoCommentForm: class DoCommentForm:
""" """

View File

@ -32,10 +32,10 @@
<li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object }}</a></li> <li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div>
{% else %} {% else %}
<p class="quiet">(None)</p> <p class="quiet">(None)</p>
{% endif %} {% endif %}
</div>
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}

View File

@ -19,7 +19,7 @@ class RedirectFallbackMiddleware(object):
except Redirect.DoesNotExist: except Redirect.DoesNotExist:
pass pass
if r is not None: if r is not None:
if r == '': if r.new_path == '':
return http.HttpResponseGone() return http.HttpResponseGone()
return http.HttpResponsePermanentRedirect(r.new_path) return http.HttpResponsePermanentRedirect(r.new_path)

View File

@ -7,9 +7,10 @@ from optparse import OptionParser
from django.utils import termcolors from django.utils import termcolors
import os, re, shutil, sys, textwrap import os, re, shutil, sys, textwrap
# For Python 2.3 try:
if not hasattr(__builtins__, 'set'): set
from sets import Set as set except NameError:
from sets import Set as set # Python 2.3 fallback
# For backwards compatibility: get_version() used to be in this module. # For backwards compatibility: get_version() used to be in this module.
get_version = django.get_version get_version = django.get_version
@ -58,12 +59,16 @@ def _is_valid_dir_name(s):
def _get_installed_models(table_list): def _get_installed_models(table_list):
"Gets a set of all models that are installed, given a list of existing tables" "Gets a set of all models that are installed, given a list of existing tables"
from django.db import models from django.db import backend, models
all_models = [] all_models = []
for app in models.get_apps(): for app in models.get_apps():
for model in models.get_models(app): for model in models.get_models(app):
all_models.append(model) all_models.append(model)
return set([m for m in all_models if m._meta.db_table in table_list]) if backend.uses_case_insensitive_names:
converter = str.upper
else:
converter = lambda x: x
return set([m for m in all_models if converter(m._meta.db_table) in map(converter, table_list)])
def _get_table_list(): def _get_table_list():
"Gets a list of all db tables that are physically installed." "Gets a list of all db tables that are physically installed."
@ -99,6 +104,7 @@ get_rel_data_type = lambda f: (f.get_internal_type() in ('AutoField', 'PositiveI
def get_sql_create(app): def get_sql_create(app):
"Returns a list of the CREATE TABLE SQL statements for the given app." "Returns a list of the CREATE TABLE SQL statements for the given app."
from django.db import get_creation_module, models from django.db import get_creation_module, models
data_types = get_creation_module().DATA_TYPES data_types = get_creation_module().DATA_TYPES
if not data_types: if not data_types:
@ -170,15 +176,20 @@ def _get_sql_model_create(model, known_models=set()):
rel_field = f rel_field = f
data_type = f.get_internal_type() data_type = f.get_internal_type()
col_type = data_types[data_type] col_type = data_types[data_type]
tablespace = f.db_tablespace or opts.db_tablespace
if col_type is not None: if col_type is not None:
# Make the definition (e.g. 'foo VARCHAR(30)') for this field. # Make the definition (e.g. 'foo VARCHAR(30)') for this field.
field_output = [style.SQL_FIELD(backend.quote_name(f.column)), field_output = [style.SQL_FIELD(backend.quote_name(f.column)),
style.SQL_COLTYPE(col_type % rel_field.__dict__)] style.SQL_COLTYPE(col_type % rel_field.__dict__)]
field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or ''))) field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')))
if f.unique: if f.unique and (not f.primary_key or backend.allows_unique_and_pk):
field_output.append(style.SQL_KEYWORD('UNIQUE')) field_output.append(style.SQL_KEYWORD('UNIQUE'))
if f.primary_key: if f.primary_key:
field_output.append(style.SQL_KEYWORD('PRIMARY KEY')) field_output.append(style.SQL_KEYWORD('PRIMARY KEY'))
if tablespace and backend.supports_tablespaces and (f.unique or f.primary_key) and backend.autoindexes_primary_keys:
# We must specify the index tablespace inline, because we
# won't be generating a CREATE INDEX statement for this field.
field_output.append(backend.get_tablespace_sql(tablespace, inline=True))
if f.rel: if f.rel:
if f.rel.to in known_models: if f.rel.to in known_models:
field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \ field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \
@ -202,9 +213,19 @@ def _get_sql_model_create(model, known_models=set()):
full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(backend.quote_name(opts.db_table)) + ' ('] full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(backend.quote_name(opts.db_table)) + ' (']
for i, line in enumerate(table_output): # Combine and add commas. for i, line in enumerate(table_output): # Combine and add commas.
full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or ''))
full_statement.append(');') full_statement.append(')')
if opts.db_tablespace and backend.supports_tablespaces:
full_statement.append(backend.get_tablespace_sql(opts.db_tablespace))
full_statement.append(';')
final_output.append('\n'.join(full_statement)) final_output.append('\n'.join(full_statement))
if opts.has_auto_field and hasattr(backend, 'get_autoinc_sql'):
# Add any extra SQL needed to support auto-incrementing primary keys
autoinc_sql = backend.get_autoinc_sql(opts.db_table)
if autoinc_sql:
for stmt in autoinc_sql:
final_output.append(stmt)
return final_output, pending_references return final_output, pending_references
def _get_sql_for_pending_references(model, pending_references): def _get_sql_for_pending_references(model, pending_references):
@ -212,6 +233,7 @@ def _get_sql_for_pending_references(model, pending_references):
Get any ALTER TABLE statements to add constraints after the fact. Get any ALTER TABLE statements to add constraints after the fact.
""" """
from django.db import backend, get_creation_module from django.db import backend, get_creation_module
from django.db.backends.util import truncate_name
data_types = get_creation_module().DATA_TYPES data_types = get_creation_module().DATA_TYPES
final_output = [] final_output = []
@ -228,7 +250,7 @@ def _get_sql_for_pending_references(model, pending_references):
# So we are careful with character usage here. # So we are careful with character usage here.
r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table)))) r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \ final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
(backend.quote_name(r_table), r_name, (backend.quote_name(r_table), truncate_name(r_name, backend.get_max_name_length()),
backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col), backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col),
backend.get_deferrable_sql())) backend.get_deferrable_sql()))
del pending_references[model] del pending_references[model]
@ -244,12 +266,18 @@ def _get_many_to_many_sql_for_model(model):
final_output = [] final_output = []
for f in opts.many_to_many: for f in opts.many_to_many:
if not isinstance(f.rel, generic.GenericRel): if not isinstance(f.rel, generic.GenericRel):
tablespace = f.db_tablespace or opts.db_tablespace
if tablespace and backend.supports_tablespaces and backend.autoindexes_primary_keys:
tablespace_sql = ' ' + backend.get_tablespace_sql(tablespace, inline=True)
else:
tablespace_sql = ''
table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \ table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
style.SQL_TABLE(backend.quote_name(f.m2m_db_table())) + ' ('] style.SQL_TABLE(backend.quote_name(f.m2m_db_table())) + ' (']
table_output.append(' %s %s %s,' % \ table_output.append(' %s %s %s%s,' % \
(style.SQL_FIELD(backend.quote_name('id')), (style.SQL_FIELD(backend.quote_name('id')),
style.SQL_COLTYPE(data_types['AutoField']), style.SQL_COLTYPE(data_types['AutoField']),
style.SQL_KEYWORD('NOT NULL PRIMARY KEY'))) style.SQL_KEYWORD('NOT NULL PRIMARY KEY'),
tablespace_sql))
table_output.append(' %s %s %s %s (%s)%s,' % \ table_output.append(' %s %s %s %s (%s)%s,' % \
(style.SQL_FIELD(backend.quote_name(f.m2m_column_name())), (style.SQL_FIELD(backend.quote_name(f.m2m_column_name())),
style.SQL_COLTYPE(data_types[get_rel_data_type(opts.pk)] % opts.pk.__dict__), style.SQL_COLTYPE(data_types[get_rel_data_type(opts.pk)] % opts.pk.__dict__),
@ -264,17 +292,30 @@ def _get_many_to_many_sql_for_model(model):
style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)), style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)),
style.SQL_FIELD(backend.quote_name(f.rel.to._meta.pk.column)), style.SQL_FIELD(backend.quote_name(f.rel.to._meta.pk.column)),
backend.get_deferrable_sql())) backend.get_deferrable_sql()))
table_output.append(' %s (%s, %s)' % \ table_output.append(' %s (%s, %s)%s' % \
(style.SQL_KEYWORD('UNIQUE'), (style.SQL_KEYWORD('UNIQUE'),
style.SQL_FIELD(backend.quote_name(f.m2m_column_name())), style.SQL_FIELD(backend.quote_name(f.m2m_column_name())),
style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name())))) style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name())),
table_output.append(');') tablespace_sql))
table_output.append(')')
if opts.db_tablespace and backend.supports_tablespaces:
# f.db_tablespace is only for indices, so ignore its value here.
table_output.append(backend.get_tablespace_sql(opts.db_tablespace))
table_output.append(';')
final_output.append('\n'.join(table_output)) final_output.append('\n'.join(table_output))
# Add any extra SQL needed to support auto-incrementing PKs
autoinc_sql = backend.get_autoinc_sql(f.m2m_db_table())
if autoinc_sql:
for stmt in autoinc_sql:
final_output.append(stmt)
return final_output return final_output
def get_sql_delete(app): def get_sql_delete(app):
"Returns a list of the DROP TABLE SQL statements for the given app." "Returns a list of the DROP TABLE SQL statements for the given app."
from django.db import backend, connection, models, get_introspection_module from django.db import backend, connection, models, get_introspection_module
from django.db.backends.util import truncate_name
introspection = get_introspection_module() introspection = get_introspection_module()
# This should work even if a connection isn't available # This should work even if a connection isn't available
@ -288,6 +329,10 @@ def get_sql_delete(app):
table_names = introspection.get_table_list(cursor) table_names = introspection.get_table_list(cursor)
else: else:
table_names = [] table_names = []
if backend.uses_case_insensitive_names:
table_name_converter = str.upper
else:
table_name_converter = lambda x: x
output = [] output = []
@ -297,7 +342,7 @@ def get_sql_delete(app):
references_to_delete = {} references_to_delete = {}
app_models = models.get_models(app) app_models = models.get_models(app)
for model in app_models: for model in app_models:
if cursor and model._meta.db_table in table_names: if cursor and table_name_converter(model._meta.db_table) in table_names:
# The table exists, so it needs to be dropped # The table exists, so it needs to be dropped
opts = model._meta opts = model._meta
for f in opts.fields: for f in opts.fields:
@ -307,7 +352,7 @@ def get_sql_delete(app):
to_delete.add(model) to_delete.add(model)
for model in app_models: for model in app_models:
if cursor and model._meta.db_table in table_names: if cursor and table_name_converter(model._meta.db_table) in table_names:
# Drop the table now # Drop the table now
output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'), output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
style.SQL_TABLE(backend.quote_name(model._meta.db_table)))) style.SQL_TABLE(backend.quote_name(model._meta.db_table))))
@ -317,20 +362,26 @@ def get_sql_delete(app):
col = f.column col = f.column
r_table = model._meta.db_table r_table = model._meta.db_table
r_col = model._meta.get_field(f.rel.field_name).column r_col = model._meta.get_field(f.rel.field_name).column
r_name = '%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table))))
output.append('%s %s %s %s;' % \ output.append('%s %s %s %s;' % \
(style.SQL_KEYWORD('ALTER TABLE'), (style.SQL_KEYWORD('ALTER TABLE'),
style.SQL_TABLE(backend.quote_name(table)), style.SQL_TABLE(backend.quote_name(table)),
style.SQL_KEYWORD(backend.get_drop_foreignkey_sql()), style.SQL_KEYWORD(backend.get_drop_foreignkey_sql()),
style.SQL_FIELD(backend.quote_name('%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table)))))))) style.SQL_FIELD(truncate_name(r_name, backend.get_max_name_length()))))
del references_to_delete[model] del references_to_delete[model]
if model._meta.has_auto_field and hasattr(backend, 'get_drop_sequence'):
output.append(backend.get_drop_sequence(model._meta.db_table))
# Output DROP TABLE statements for many-to-many tables. # Output DROP TABLE statements for many-to-many tables.
for model in app_models: for model in app_models:
opts = model._meta opts = model._meta
for f in opts.many_to_many: for f in opts.many_to_many:
if cursor and f.m2m_db_table() in table_names: if cursor and table_name_converter(f.m2m_db_table()) in table_names:
output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'), output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
style.SQL_TABLE(backend.quote_name(f.m2m_db_table())))) style.SQL_TABLE(backend.quote_name(f.m2m_db_table()))))
if hasattr(backend, 'get_drop_sequence'):
output.append(backend.get_drop_sequence("%s_%s" % (model._meta.db_table, f.column)))
app_label = app_models[0]._meta.app_label app_label = app_models[0]._meta.app_label
@ -433,14 +484,20 @@ def get_sql_indexes_for_model(model):
output = [] output = []
for f in model._meta.fields: for f in model._meta.fields:
if f.db_index: if f.db_index and not ((f.primary_key or f.unique) and backend.autoindexes_primary_keys):
unique = f.unique and 'UNIQUE ' or '' unique = f.unique and 'UNIQUE ' or ''
tablespace = f.db_tablespace or model._meta.db_tablespace
if tablespace and backend.supports_tablespaces:
tablespace_sql = ' ' + backend.get_tablespace_sql(tablespace)
else:
tablespace_sql = ''
output.append( output.append(
style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \ style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \
style.SQL_TABLE(backend.quote_name('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \ style.SQL_TABLE(backend.quote_name('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \
style.SQL_KEYWORD('ON') + ' ' + \ style.SQL_KEYWORD('ON') + ' ' + \
style.SQL_TABLE(backend.quote_name(model._meta.db_table)) + ' ' + \ style.SQL_TABLE(backend.quote_name(model._meta.db_table)) + ' ' + \
"(%s);" % style.SQL_FIELD(backend.quote_name(f.column)) "(%s)" % style.SQL_FIELD(backend.quote_name(f.column)) + \
"%s;" % tablespace_sql
) )
return output return output
@ -464,7 +521,7 @@ def _emit_post_sync_signal(created_models, verbosity, interactive):
def syncdb(verbosity=1, interactive=True): def syncdb(verbosity=1, interactive=True):
"Creates the database tables for all apps in INSTALLED_APPS whose tables haven't already been created." "Creates the database tables for all apps in INSTALLED_APPS whose tables haven't already been created."
from django.db import connection, transaction, models, get_creation_module from django.db import backend, connection, transaction, models, get_creation_module
from django.conf import settings from django.conf import settings
disable_termcolors() disable_termcolors()
@ -487,6 +544,10 @@ def syncdb(verbosity=1, interactive=True):
# Get a list of all existing database tables, # Get a list of all existing database tables,
# so we know what needs to be added. # so we know what needs to be added.
table_list = _get_table_list() table_list = _get_table_list()
if backend.uses_case_insensitive_names:
table_name_converter = str.upper
else:
table_name_converter = lambda x: x
# Get a list of already installed *models* so that references work right. # Get a list of already installed *models* so that references work right.
seen_models = _get_installed_models(table_list) seen_models = _get_installed_models(table_list)
@ -501,7 +562,7 @@ def syncdb(verbosity=1, interactive=True):
# Create the model's database table, if it doesn't already exist. # Create the model's database table, if it doesn't already exist.
if verbosity >= 2: if verbosity >= 2:
print "Processing %s.%s model" % (app_name, model._meta.object_name) print "Processing %s.%s model" % (app_name, model._meta.object_name)
if model._meta.db_table in table_list: if table_name_converter(model._meta.db_table) in table_list:
continue continue
sql, references = _get_sql_model_create(model, seen_models) sql, references = _get_sql_model_create(model, seen_models)
seen_models.add(model) seen_models.add(model)
@ -513,7 +574,7 @@ def syncdb(verbosity=1, interactive=True):
print "Creating table %s" % model._meta.db_table print "Creating table %s" % model._meta.db_table
for statement in sql: for statement in sql:
cursor.execute(statement) cursor.execute(statement)
table_list.append(model._meta.db_table) table_list.append(table_name_converter(model._meta.db_table))
# Create the m2m tables. This must be done after all tables have been created # Create the m2m tables. This must be done after all tables have been created
# to ensure that all referred tables will exist. # to ensure that all referred tables will exist.
@ -832,7 +893,7 @@ def inspectdb():
except NotImplementedError: except NotImplementedError:
indexes = {} indexes = {}
for i, row in enumerate(introspection_module.get_table_description(cursor, table_name)): for i, row in enumerate(introspection_module.get_table_description(cursor, table_name)):
att_name = row[0] att_name = row[0].lower()
comment_notes = [] # Holds Field notes, to be displayed in a Python comment. comment_notes = [] # Holds Field notes, to be displayed in a Python comment.
extra_params = {} # Holds Field parameters such as 'db_column'. extra_params = {} # Holds Field parameters such as 'db_column'.
@ -1325,7 +1386,7 @@ def load_data(fixture_labels, verbosity=1):
# Keep a count of the installed objects and fixtures # Keep a count of the installed objects and fixtures
count = [0,0] count = [0,0]
models = set() models = set()
humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path' humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path'
# Get a cursor (even though we don't need one yet). This has # Get a cursor (even though we don't need one yet). This has
@ -1403,7 +1464,7 @@ def load_data(fixture_labels, verbosity=1):
if verbosity > 1: if verbosity > 1:
print "No %s fixture '%s' in %s." % \ print "No %s fixture '%s' in %s." % \
(format, fixture_name, humanize(fixture_dir)) (format, fixture_name, humanize(fixture_dir))
if count[0] > 0: if count[0] > 0:
sequence_sql = backend.get_sql_sequence_reset(style, models) sequence_sql = backend.get_sql_sequence_reset(style, models)
if sequence_sql: if sequence_sql:
@ -1411,10 +1472,10 @@ def load_data(fixture_labels, verbosity=1):
print "Resetting sequences" print "Resetting sequences"
for line in sequence_sql: for line in sequence_sql:
cursor.execute(line) cursor.execute(line)
transaction.commit() transaction.commit()
transaction.leave_transaction_management() transaction.leave_transaction_management()
if count[0] == 0: if count[0] == 0:
if verbosity > 0: if verbosity > 0:
print "No fixtures found." print "No fixtures found."
@ -1629,7 +1690,9 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING, argv=None):
if not mod_list: if not mod_list:
parser.print_usage_and_exit() parser.print_usage_and_exit()
if action not in NO_SQL_TRANSACTION: if action not in NO_SQL_TRANSACTION:
print style.SQL_KEYWORD("BEGIN;") from django.db import backend
if backend.get_start_transaction_sql():
print style.SQL_KEYWORD(backend.get_start_transaction_sql())
for mod in mod_list: for mod in mod_list:
if action == 'reset': if action == 'reset':
output = action_mapping[action](mod, options.interactive) output = action_mapping[action](mod, options.interactive)

View File

@ -9,7 +9,7 @@ been reviewed for security issues. Don't use it for production use.
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from types import ListType, StringType from types import ListType, StringType
import os, re, sys, time, urllib import os, re, sys, time, urllib, mimetypes
__version__ = "0.1" __version__ = "0.1"
__all__ = ['WSGIServer','WSGIRequestHandler','demo_app'] __all__ = ['WSGIServer','WSGIRequestHandler','demo_app']
@ -309,7 +309,7 @@ class ServerHandler(object):
""" """
if not self.result_is_file() and not self.sendfile(): if not self.result_is_file() and not self.sendfile():
for data in self.result: for data in self.result:
self.write(data, False) self.write(data)
self.finish_content() self.finish_content()
self.close() self.close()
@ -377,7 +377,7 @@ class ServerHandler(object):
else: else:
self._write('Status: %s\r\n' % self.status) self._write('Status: %s\r\n' % self.status)
def write(self, data, flush=True): def write(self, data):
"""'write()' callable as specified by PEP 333""" """'write()' callable as specified by PEP 333"""
assert type(data) is StringType,"write() argument must be string" assert type(data) is StringType,"write() argument must be string"
@ -394,8 +394,7 @@ class ServerHandler(object):
# XXX check Content-Length and truncate if too many bytes written? # XXX check Content-Length and truncate if too many bytes written?
self._write(data) self._write(data)
if flush: self._flush()
self._flush()
def sendfile(self): def sendfile(self):
"""Platform-specific file transmission """Platform-specific file transmission
@ -422,6 +421,8 @@ class ServerHandler(object):
if not self.headers_sent: if not self.headers_sent:
self.headers['Content-Length'] = "0" self.headers['Content-Length'] = "0"
self.send_headers() self.send_headers()
else:
pass # XXX check if content-length was too short?
def close(self): def close(self):
try: try:
@ -629,6 +630,9 @@ class AdminMediaHandler(object):
else: else:
status = '200 OK' status = '200 OK'
headers = {} headers = {}
mime_type = mimetypes.guess_type(file_path)[0]
if mime_type:
headers['Content-Type'] = mime_type
output = [fp.read()] output = [fp.read()]
fp.close() fp.close()
start_response(status, headers.items()) start_response(status, headers.items())

View File

@ -9,8 +9,17 @@ a string) and returns a tuple in this format:
from django.http import Http404 from django.http import Http404
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
from django.utils.functional import memoize
import re import re
try:
reversed
except NameError:
from django.utils.itercompat import reversed # Python 2.3 fallback
_resolver_cache = {} # Maps urlconf modules to RegexURLResolver instances.
_callable_cache = {} # Maps view and url pattern names to their view functions.
class Resolver404(Http404): class Resolver404(Http404):
pass pass
@ -18,6 +27,34 @@ class NoReverseMatch(Exception):
# Don't make this raise an error when used in a template. # Don't make this raise an error when used in a template.
silent_variable_failure = True silent_variable_failure = True
def get_callable(lookup_view, can_fail=False):
"""
Convert a string version of a function name to the callable object.
If the lookup_view is not an import path, it is assumed to be a URL pattern
label and the original string is returned.
If can_fail is True, lookup_view might be a URL pattern label, so errors
during the import fail and the string is returned.
"""
if not callable(lookup_view):
mod_name, func_name = get_mod_func(lookup_view)
try:
if func_name != '':
lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
except (ImportError, AttributeError):
if not can_fail:
raise
return lookup_view
get_callable = memoize(get_callable, _callable_cache)
def get_resolver(urlconf):
if urlconf is None:
from django.conf import settings
urlconf = settings.ROOT_URLCONF
return RegexURLResolver(r'^/', urlconf)
get_resolver = memoize(get_resolver, _resolver_cache)
def get_mod_func(callback): def get_mod_func(callback):
# Converts 'django.views.news.stories.story_detail' to # Converts 'django.views.news.stories.story_detail' to
# ['django.views.news.stories', 'story_detail'] # ['django.views.news.stories', 'story_detail']
@ -129,12 +166,13 @@ class RegexURLPattern(object):
def _get_callback(self): def _get_callback(self):
if self._callback is not None: if self._callback is not None:
return self._callback return self._callback
mod_name, func_name = get_mod_func(self._callback_str)
try: try:
self._callback = getattr(__import__(mod_name, {}, {}, ['']), func_name) self._callback = get_callable(self._callback_str)
except ImportError, e: except ImportError, e:
mod_name, _ = get_mod_func(self._callback_str)
raise ViewDoesNotExist, "Could not import %s. Error was: %s" % (mod_name, str(e)) raise ViewDoesNotExist, "Could not import %s. Error was: %s" % (mod_name, str(e))
except AttributeError, e: except AttributeError, e:
mod_name, func_name = get_mod_func(self._callback_str)
raise ViewDoesNotExist, "Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e)) raise ViewDoesNotExist, "Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e))
return self._callback return self._callback
callback = property(_get_callback) callback = property(_get_callback)
@ -160,6 +198,19 @@ class RegexURLResolver(object):
self.urlconf_name = urlconf_name self.urlconf_name = urlconf_name
self.callback = None self.callback = None
self.default_kwargs = default_kwargs or {} self.default_kwargs = default_kwargs or {}
self._reverse_dict = {}
def _get_reverse_dict(self):
if not self._reverse_dict and hasattr(self.urlconf_module, 'urlpatterns'):
for pattern in reversed(self.urlconf_module.urlpatterns):
if isinstance(pattern, RegexURLResolver):
for key, value in pattern.reverse_dict.iteritems():
self._reverse_dict[key] = (pattern,) + value
else:
self._reverse_dict[pattern.callback] = (pattern,)
self._reverse_dict[pattern.name] = (pattern,)
return self._reverse_dict
reverse_dict = property(_get_reverse_dict)
def resolve(self, path): def resolve(self, path):
tried = [] tried = []
@ -209,24 +260,12 @@ class RegexURLResolver(object):
return self._resolve_special('500') return self._resolve_special('500')
def reverse(self, lookup_view, *args, **kwargs): def reverse(self, lookup_view, *args, **kwargs):
if not callable(lookup_view): try:
mod_name, func_name = get_mod_func(lookup_view) lookup_view = get_callable(lookup_view, True)
try: except (ImportError, AttributeError):
lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name) raise NoReverseMatch
except (ImportError, AttributeError): if lookup_view in self.reverse_dict:
if func_name != '': return ''.join([reverse_helper(part.regex, *args, **kwargs) for part in self.reverse_dict[lookup_view]])
raise NoReverseMatch
for pattern in self.urlconf_module.urlpatterns:
if isinstance(pattern, RegexURLResolver):
try:
return pattern.reverse_helper(lookup_view, *args, **kwargs)
except NoReverseMatch:
continue
elif pattern.callback == lookup_view or pattern.name == lookup_view:
try:
return pattern.reverse_helper(*args, **kwargs)
except NoReverseMatch:
continue
raise NoReverseMatch raise NoReverseMatch
def reverse_helper(self, lookup_view, *args, **kwargs): def reverse_helper(self, lookup_view, *args, **kwargs):
@ -235,17 +274,10 @@ class RegexURLResolver(object):
return result + sub_match return result + sub_match
def resolve(path, urlconf=None): def resolve(path, urlconf=None):
if urlconf is None: return get_resolver(urlconf).resolve(path)
from django.conf import settings
urlconf = settings.ROOT_URLCONF
resolver = RegexURLResolver(r'^/', urlconf)
return resolver.resolve(path)
def reverse(viewname, urlconf=None, args=None, kwargs=None): def reverse(viewname, urlconf=None, args=None, kwargs=None):
args = args or [] args = args or []
kwargs = kwargs or {} kwargs = kwargs or {}
if urlconf is None: return '/' + get_resolver(urlconf).reverse(viewname, *args, **kwargs)
from django.conf import settings
urlconf = settings.ROOT_URLCONF
resolver = RegexURLResolver(r'^/', urlconf)
return '/' + resolver.reverse(viewname, *args, **kwargs)

View File

@ -89,7 +89,14 @@ class DatabaseWrapper(local):
self.connection.close() self.connection.close()
self.connection = None self.connection = None
allows_group_by_ordinal = True
allows_unique_and_pk = True
autoindexes_primary_keys = True
needs_datetime_string_cast = True
needs_upper_for_iops = False
supports_constraints = True supports_constraints = True
supports_tablespaces = True
uses_case_insensitive_names = False
def quote_name(name): def quote_name(name):
if name.startswith('[') and name.endswith(']'): if name.startswith('[') and name.endswith(']'):
@ -117,6 +124,9 @@ def get_date_trunc_sql(lookup_type, field_name):
if lookup_type=='day': if lookup_type=='day':
return "Convert(datetime, Convert(varchar(12), %s))" % field_name return "Convert(datetime, Convert(varchar(12), %s))" % field_name
def get_datetime_cast_sql():
return None
def get_limit_offset_sql(limit, offset=None): def get_limit_offset_sql(limit, offset=None):
# TODO: This is a guess. Make sure this is correct. # TODO: This is a guess. Make sure this is correct.
sql = "LIMIT %s" % limit sql = "LIMIT %s" % limit
@ -139,6 +149,18 @@ def get_drop_foreignkey_sql():
def get_pk_default_value(): def get_pk_default_value():
return "DEFAULT" return "DEFAULT"
def get_max_name_length():
return None
def get_start_transaction_sql():
return "BEGIN;"
def get_tablespace_sql(tablespace, inline=False):
return "ON %s" % quote_name(tablespace)
def get_autoinc_sql(table):
return None
def get_sql_flush(style, tables, sequences): def get_sql_flush(style, tables, sequences):
"""Return a list of SQL statements required to remove all data from """Return a list of SQL statements required to remove all data from
all tables in the database (without actually removing the tables all tables in the database (without actually removing the tables

View File

@ -33,6 +33,7 @@ class DatabaseWrapper:
pass # close() pass # close()
supports_constraints = False supports_constraints = False
supports_tablespaces = False
quote_name = complain quote_name = complain
dictfetchone = complain dictfetchone = complain
dictfetchmany = complain dictfetchmany = complain

View File

@ -134,7 +134,14 @@ class DatabaseWrapper(local):
self.server_version = tuple([int(x) for x in m.groups()]) self.server_version = tuple([int(x) for x in m.groups()])
return self.server_version return self.server_version
allows_group_by_ordinal = True
allows_unique_and_pk = True
autoindexes_primary_keys = False
needs_datetime_string_cast = True # MySQLdb requires a typecast for dates
needs_upper_for_iops = False
supports_constraints = True supports_constraints = True
supports_tablespaces = False
uses_case_insensitive_names = False
def quote_name(name): def quote_name(name):
if name.startswith("`") and name.endswith("`"): if name.startswith("`") and name.endswith("`"):
@ -167,6 +174,9 @@ def get_date_trunc_sql(lookup_type, field_name):
sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str) sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
return sql return sql
def get_datetime_cast_sql():
return None
def get_limit_offset_sql(limit, offset=None): def get_limit_offset_sql(limit, offset=None):
sql = "LIMIT " sql = "LIMIT "
if offset and offset != 0: if offset and offset != 0:
@ -188,11 +198,20 @@ def get_drop_foreignkey_sql():
def get_pk_default_value(): def get_pk_default_value():
return "DEFAULT" return "DEFAULT"
def get_max_name_length():
return None;
def get_start_transaction_sql():
return "BEGIN;"
def get_autoinc_sql(table):
return None
def get_sql_flush(style, tables, sequences): def get_sql_flush(style, tables, sequences):
"""Return a list of SQL statements required to remove all data from """Return a list of SQL statements required to remove all data from
all tables in the database (without actually removing the tables all tables in the database (without actually removing the tables
themselves) and put the database in an empty 'initial' state themselves) and put the database in an empty 'initial' state
""" """
# NB: The generated SQL below is specific to MySQL # NB: The generated SQL below is specific to MySQL
# 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
@ -204,7 +223,7 @@ def get_sql_flush(style, tables, sequences):
style.SQL_FIELD(quote_name(table)) style.SQL_FIELD(quote_name(table))
) for table in tables] + \ ) for table in tables] + \
['SET FOREIGN_KEY_CHECKS = 1;'] ['SET FOREIGN_KEY_CHECKS = 1;']
# 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
# to reset sequence indices # to reset sequence indices
sql.extend(["%s %s %s %s %s;" % \ sql.extend(["%s %s %s %s %s;" % \

View File

@ -135,7 +135,14 @@ class DatabaseWrapper(local):
self.server_version = tuple([int(x) for x in m.groups()]) self.server_version = tuple([int(x) for x in m.groups()])
return self.server_version return self.server_version
allows_group_by_ordinal = True
allows_unique_and_pk = True
autoindexes_primary_keys = False
needs_datetime_string_cast = True # MySQLdb requires a typecast for dates
needs_upper_for_iops = False
supports_constraints = True supports_constraints = True
supports_tablespaces = False
uses_case_insensitive_names = False
def quote_name(name): def quote_name(name):
if name.startswith("`") and name.endswith("`"): if name.startswith("`") and name.endswith("`"):
@ -168,6 +175,9 @@ def get_date_trunc_sql(lookup_type, field_name):
sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str) sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
return sql return sql
def get_datetime_cast_sql():
return None
def get_limit_offset_sql(limit, offset=None): def get_limit_offset_sql(limit, offset=None):
sql = "LIMIT " sql = "LIMIT "
if offset and offset != 0: if offset and offset != 0:
@ -189,11 +199,20 @@ def get_drop_foreignkey_sql():
def get_pk_default_value(): def get_pk_default_value():
return "DEFAULT" return "DEFAULT"
def get_max_name_length():
return None;
def get_start_transaction_sql():
return "BEGIN;"
def get_autoinc_sql(table):
return None
def get_sql_flush(style, tables, sequences): def get_sql_flush(style, tables, sequences):
"""Return a list of SQL statements required to remove all data from """Return a list of SQL statements required to remove all data from
all tables in the database (without actually removing the tables all tables in the database (without actually removing the tables
themselves) and put the database in an empty 'initial' state themselves) and put the database in an empty 'initial' state
""" """
# NB: The generated SQL below is specific to MySQL # NB: The generated SQL below is specific to MySQL
# 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
@ -205,7 +224,7 @@ def get_sql_flush(style, tables, sequences):
style.SQL_FIELD(quote_name(table)) style.SQL_FIELD(quote_name(table))
) for table in tables] + \ ) for table in tables] + \
['SET FOREIGN_KEY_CHECKS = 1;'] ['SET FOREIGN_KEY_CHECKS = 1;']
# 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
# to reset sequence indices # to reset sequence indices
sql.extend(["%s %s %s %s %s;" % \ sql.extend(["%s %s %s %s %s;" % \

View File

@ -4,12 +4,16 @@ Oracle database backend for Django.
Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/ Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/
""" """
from django.conf import settings
from django.db.backends import util from django.db.backends import util
try: try:
import cx_Oracle as Database import cx_Oracle as Database
except ImportError, e: except ImportError, e:
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured, "Error loading cx_Oracle module: %s" % e raise ImproperlyConfigured, "Error loading cx_Oracle module: %s" % e
import datetime
from django.utils.datastructures import SortedDict
DatabaseError = Database.Error DatabaseError = Database.Error
IntegrityError = Database.IntegrityError IntegrityError = Database.IntegrityError
@ -31,7 +35,6 @@ class DatabaseWrapper(local):
return self.connection is not None return self.connection is not None
def cursor(self): def cursor(self):
from django.conf import settings
if not self._valid_connection(): if not self._valid_connection():
if len(settings.DATABASE_HOST.strip()) == 0: if len(settings.DATABASE_HOST.strip()) == 0:
settings.DATABASE_HOST = 'localhost' settings.DATABASE_HOST = 'localhost'
@ -41,25 +44,37 @@ class DatabaseWrapper(local):
else: else:
conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME) conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME)
self.connection = Database.connect(conn_string, **self.options) self.connection = Database.connect(conn_string, **self.options)
return FormatStylePlaceholderCursor(self.connection) cursor = FormatStylePlaceholderCursor(self.connection)
# default arraysize of 1 is highly sub-optimal
cursor.arraysize = 100
# set oracle date to ansi date format
cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'")
cursor.execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'")
if settings.DEBUG:
return util.CursorDebugWrapper(cursor, self)
return cursor
def _commit(self): def _commit(self):
if self.connection is not None: if self.connection is not None:
self.connection.commit() return self.connection.commit()
def _rollback(self): def _rollback(self):
if self.connection is not None: if self.connection is not None:
try: return self.connection.rollback()
self.connection.rollback()
except Database.NotSupportedError:
pass
def close(self): def close(self):
if self.connection is not None: if self.connection is not None:
self.connection.close() self.connection.close()
self.connection = None self.connection = None
allows_group_by_ordinal = False
allows_unique_and_pk = False # Suppress UNIQUE/PK for Oracle (ORA-02259)
autoindexes_primary_keys = True
needs_datetime_string_cast = False
needs_upper_for_iops = True
supports_constraints = True supports_constraints = True
supports_tablespaces = True
uses_case_insensitive_names = True
class FormatStylePlaceholderCursor(Database.Cursor): class FormatStylePlaceholderCursor(Database.Cursor):
""" """
@ -67,45 +82,75 @@ class FormatStylePlaceholderCursor(Database.Cursor):
This fixes it -- but note that if you want to use a literal "%s" in a query, This fixes it -- but note that if you want to use a literal "%s" in a query,
you'll need to use "%%s". you'll need to use "%%s".
""" """
def _rewrite_args(self, query, params=None):
if params is None:
params = []
else:
# cx_Oracle can't handle unicode parameters, so cast to str for now
for i, param in enumerate(params):
if type(param) == unicode:
try:
params[i] = param.encode('utf-8')
except UnicodeError:
params[i] = str(param)
args = [(':arg%d' % i) for i in range(len(params))]
query = query % tuple(args)
# cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
# it does want a trailing ';' but not a trailing '/'. However, these
# characters must be included in the original query in case the query
# is being passed to SQL*Plus.
if query.endswith(';') or query.endswith('/'):
query = query[:-1]
return query, params
def execute(self, query, params=None): def execute(self, query, params=None):
if params is None: params = [] query, params = self._rewrite_args(query, params)
query = self.convert_arguments(query, len(params))
return Database.Cursor.execute(self, query, params) return Database.Cursor.execute(self, query, params)
def executemany(self, query, params=None): def executemany(self, query, params=None):
if params is None: params = [] query, params = self._rewrite_args(query, params)
query = self.convert_arguments(query, len(params[0]))
return Database.Cursor.executemany(self, query, params) return Database.Cursor.executemany(self, query, params)
def convert_arguments(self, query, num_params):
# replace occurances of "%s" with ":arg" - Oracle requires colons for parameter placeholders.
args = [':arg' for i in range(num_params)]
return query % tuple(args)
def quote_name(name): def quote_name(name):
return name # SQL92 requires delimited (quoted) names to be case-sensitive. When
# not quoted, Oracle has case-insensitive behavior for identifiers, but
# always defaults to uppercase.
# We simplify things by making Oracle identifiers always uppercase.
if not name.startswith('"') and not name.endswith('"'):
name = '"%s"' % util.truncate_name(name.upper(), get_max_name_length())
return name.upper()
dictfetchone = util.dictfetchone dictfetchone = util.dictfetchone
dictfetchmany = util.dictfetchmany dictfetchmany = util.dictfetchmany
dictfetchall = util.dictfetchall dictfetchall = util.dictfetchall
def get_last_insert_id(cursor, table_name, pk_name): def get_last_insert_id(cursor, table_name, pk_name):
query = "SELECT %s_sq.currval from dual" % table_name sq_name = util.truncate_name(table_name, get_max_name_length()-3)
cursor.execute(query) cursor.execute('SELECT %s_sq.currval FROM dual' % sq_name)
return cursor.fetchone()[0] return cursor.fetchone()[0]
def get_date_extract_sql(lookup_type, table_name): def get_date_extract_sql(lookup_type, table_name):
# lookup_type is 'year', 'month', 'day' # lookup_type is 'year', 'month', 'day'
# http://www.psoug.org/reference/date_func.html # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions42a.htm#1017163
return "EXTRACT(%s FROM %s)" % (lookup_type, table_name) return "EXTRACT(%s FROM %s)" % (lookup_type, table_name)
def get_date_trunc_sql(lookup_type, field_name): def get_date_trunc_sql(lookup_type, field_name):
return "EXTRACT(%s FROM TRUNC(%s))" % (lookup_type, field_name) # lookup_type is 'year', 'month', 'day'
# Oracle uses TRUNC() for both dates and numbers.
# http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions155a.htm#SQLRF06151
if lookup_type == 'day':
sql = 'TRUNC(%s)' % (field_name,)
else:
sql = "TRUNC(%s, '%s')" % (field_name, lookup_type)
return sql
def get_datetime_cast_sql():
return "TO_TIMESTAMP(%s, 'YYYY-MM-DD HH24:MI:SS.FF')"
def get_limit_offset_sql(limit, offset=None): def get_limit_offset_sql(limit, offset=None):
# Limits and offset are too complicated to be handled here. # Limits and offset are too complicated to be handled here.
# Instead, they are handled in django/db/query.py. # Instead, they are handled in django/db/backends/oracle/query.py.
pass return ""
def get_random_function_sql(): def get_random_function_sql():
return "DBMS_RANDOM.RANDOM" return "DBMS_RANDOM.RANDOM"
@ -117,40 +162,363 @@ def get_fulltext_search_sql(field_name):
raise NotImplementedError raise NotImplementedError
def get_drop_foreignkey_sql(): def get_drop_foreignkey_sql():
return "DROP FOREIGN KEY" return "DROP CONSTRAINT"
def get_pk_default_value(): def get_pk_default_value():
return "DEFAULT" return "DEFAULT"
def get_max_name_length():
return 30
def get_start_transaction_sql():
return None
def get_tablespace_sql(tablespace, inline=False):
return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""), quote_name(tablespace))
def get_autoinc_sql(table):
# To simulate auto-incrementing primary keys in Oracle, we have to
# create a sequence and a trigger.
sq_name = get_sequence_name(table)
tr_name = get_trigger_name(table)
sequence_sql = 'CREATE SEQUENCE %s;' % sq_name
trigger_sql = """CREATE OR REPLACE TRIGGER %s
BEFORE INSERT ON %s
FOR EACH ROW
WHEN (new.id IS NULL)
BEGIN
SELECT %s.nextval INTO :new.id FROM dual;
END;
/""" % (tr_name, quote_name(table), sq_name)
return sequence_sql, trigger_sql
def get_drop_sequence(table):
return "DROP SEQUENCE %s;" % quote_name(get_sequence_name(table))
def _get_sequence_reset_sql():
# TODO: colorize this SQL code with style.SQL_KEYWORD(), etc.
return """
DECLARE
startvalue integer;
cval integer;
BEGIN
LOCK TABLE %(table)s IN SHARE MODE;
SELECT NVL(MAX(id), 0) INTO startvalue FROM %(table)s;
SELECT %(sequence)s.nextval INTO cval FROM dual;
cval := startvalue - cval;
IF cval != 0 THEN
EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s MINVALUE 0 INCREMENT BY '||cval;
SELECT %(sequence)s.nextval INTO cval FROM dual;
EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s INCREMENT BY 1';
END IF;
COMMIT;
END;
/"""
def get_sql_flush(style, tables, sequences): def get_sql_flush(style, tables, sequences):
"""Return a list of SQL statements required to remove all data from """Return a list of SQL statements required to remove all data from
all tables in the database (without actually removing the tables all tables in the database (without actually removing the tables
themselves) and put the database in an empty 'initial' state themselves) and put the database in an empty 'initial' state
""" """
# Return a list of 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements # Return a list of 'TRUNCATE x;', 'TRUNCATE y;',
# TODO - SQL not actually tested against Oracle yet! # 'TRUNCATE z;'... style SQL statements
# TODO - autoincrement indices reset required? See other get_sql_flush() implementations if tables:
sql = ['%s %s;' % \ # Oracle does support TRUNCATE, but it seems to get us into
(style.SQL_KEYWORD('TRUNCATE'), # FK referential trouble, whereas DELETE FROM table works.
style.SQL_FIELD(quote_name(table)) sql = ['%s %s %s;' % \
) for table in tables] (style.SQL_KEYWORD('DELETE'),
style.SQL_KEYWORD('FROM'),
style.SQL_FIELD(quote_name(table))
) for table in tables]
# Since we've just deleted all the rows, running our sequence
# ALTER code will reset the sequence to 0.
for sequence_info in sequences:
table_name = sequence_info['table']
seq_name = get_sequence_name(table_name)
query = _get_sequence_reset_sql() % {'sequence':seq_name,
'table':quote_name(table_name)}
sql.append(query)
return sql
else:
return []
def get_sequence_name(table):
name_length = get_max_name_length() - 3
return '%s_SQ' % util.truncate_name(table, name_length).upper()
def get_sql_sequence_reset(style, model_list): def get_sql_sequence_reset(style, model_list):
"Returns a list of the SQL statements to reset sequences for the given models." "Returns a list of the SQL statements to reset sequences for the given models."
# No sequence reset required from django.db import models
return [] output = []
query = _get_sequence_reset_sql()
for model in model_list:
for f in model._meta.fields:
if isinstance(f, models.AutoField):
sequence_name = get_sequence_name(model._meta.db_table)
output.append(query % {'sequence':sequence_name,
'table':model._meta.db_table})
break # Only one AutoField is allowed per model, so don't bother continuing.
for f in model._meta.many_to_many:
sequence_name = get_sequence_name(f.m2m_db_table())
output.append(query % {'sequence':sequence_name,
'table':f.m2m_db_table()})
return output
def get_trigger_name(table):
name_length = get_max_name_length() - 3
return '%s_TR' % util.truncate_name(table, name_length).upper()
def get_query_set_class(DefaultQuerySet):
"Create a custom QuerySet class for Oracle."
from django.db import backend, connection
from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE, quote_only_if_word
class OracleQuerySet(DefaultQuerySet):
def iterator(self):
"Performs the SELECT database lookup of this QuerySet."
from django.db.models.query import get_cached_row
# self._select is a dictionary, and dictionaries' key order is
# undefined, so we convert it to a list of tuples.
extra_select = self._select.items()
full_query = None
try:
try:
select, sql, params, full_query = self._get_sql_clause(get_full_query=True)
except TypeError:
select, sql, params = self._get_sql_clause()
except EmptyResultSet:
raise StopIteration
if not full_query:
full_query = "SELECT %s%s\n%s" % \
((self._distinct and "DISTINCT " or ""),
', '.join(select), sql)
cursor = connection.cursor()
cursor.execute(full_query, params)
fill_cache = self._select_related
fields = self.model._meta.fields
index_end = len(fields)
# so here's the logic;
# 1. retrieve each row in turn
# 2. convert NCLOBs
while 1:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
if not rows:
raise StopIteration
for row in rows:
row = self.resolve_columns(row, fields)
if fill_cache:
obj, index_end = get_cached_row(klass=self.model, row=row,
index_start=0, max_depth=self._max_related_depth)
else:
obj = self.model(*row[:index_end])
for i, k in enumerate(extra_select):
setattr(obj, k[0], row[index_end+i])
yield obj
def _get_sql_clause(self, get_full_query=False):
from django.db.models.query import fill_table_cache, \
handle_legacy_orderlist, orderfield2column
opts = self.model._meta
# Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
select = ["%s.%s" % (backend.quote_name(opts.db_table), backend.quote_name(f.column)) for f in opts.fields]
tables = [quote_only_if_word(t) for t in self._tables]
joins = SortedDict()
where = self._where[:]
params = self._params[:]
# Convert self._filters into SQL.
joins2, where2, params2 = self._filters.get_sql(opts)
joins.update(joins2)
where.extend(where2)
params.extend(params2)
# Add additional tables and WHERE clauses based on select_related.
if self._select_related:
fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table])
# Add any additional SELECTs.
if self._select:
select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()])
# Start composing the body of the SQL statement.
sql = [" FROM", backend.quote_name(opts.db_table)]
# Compose the join dictionary into SQL describing the joins.
if joins:
sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition)
for (alias, (table, join_type, condition)) in joins.items()]))
# Compose the tables clause into SQL.
if tables:
sql.append(", " + ", ".join(tables))
# Compose the where clause into SQL.
if where:
sql.append(where and "WHERE " + " AND ".join(where))
# ORDER BY clause
order_by = []
if self._order_by is not None:
ordering_to_use = self._order_by
else:
ordering_to_use = opts.ordering
for f in handle_legacy_orderlist(ordering_to_use):
if f == '?': # Special case.
order_by.append(backend.get_random_function_sql())
else:
if f.startswith('-'):
col_name = f[1:]
order = "DESC"
else:
col_name = f
order = "ASC"
if "." in col_name:
table_prefix, col_name = col_name.split('.', 1)
table_prefix = backend.quote_name(table_prefix) + '.'
else:
# Use the database table as a column prefix if it wasn't given,
# and if the requested column isn't a custom SELECT.
if "." not in col_name and col_name not in (self._select or ()):
table_prefix = backend.quote_name(opts.db_table) + '.'
else:
table_prefix = ''
order_by.append('%s%s %s' % (table_prefix, backend.quote_name(orderfield2column(col_name, opts)), order))
if order_by:
sql.append("ORDER BY " + ", ".join(order_by))
# Look for column name collisions in the select elements
# and fix them with an AS alias. This allows us to do a
# SELECT * later in the paging query.
cols = [clause.split('.')[-1] for clause in select]
for index, col in enumerate(cols):
if cols.count(col) > 1:
col = '%s%d' % (col.replace('"', ''), index)
cols[index] = col
select[index] = '%s AS %s' % (select[index], col)
# LIMIT and OFFSET clauses
# To support limits and offsets, Oracle requires some funky rewriting of an otherwise normal looking query.
select_clause = ",".join(select)
distinct = (self._distinct and "DISTINCT " or "")
if order_by:
order_by_clause = " OVER (ORDER BY %s )" % (", ".join(order_by))
else:
#Oracle's row_number() function always requires an order-by clause.
#So we need to define a default order-by, since none was provided.
order_by_clause = " OVER (ORDER BY %s.%s)" % \
(backend.quote_name(opts.db_table),
backend.quote_name(opts.fields[0].db_column or opts.fields[0].column))
# limit_and_offset_clause
if self._limit is None:
assert self._offset is None, "'offset' is not allowed without 'limit'"
if self._offset is not None:
offset = int(self._offset)
else:
offset = 0
if self._limit is not None:
limit = int(self._limit)
else:
limit = None
limit_and_offset_clause = ''
if limit is not None:
limit_and_offset_clause = "WHERE rn > %s AND rn <= %s" % (offset, limit+offset)
elif offset:
limit_and_offset_clause = "WHERE rn > %s" % (offset)
if len(limit_and_offset_clause) > 0:
fmt = \
"""SELECT * FROM
(SELECT %s%s,
ROW_NUMBER()%s AS rn
%s)
%s"""
full_query = fmt % (distinct, select_clause,
order_by_clause, ' '.join(sql).strip(),
limit_and_offset_clause)
else:
full_query = None
if get_full_query:
return select, " ".join(sql), params, full_query
else:
return select, " ".join(sql), params
def resolve_columns(self, row, fields=()):
from django.db.models.fields import DateField, DateTimeField, \
TimeField, BooleanField, NullBooleanField, DecimalField, Field
values = []
for value, field in map(None, row, fields):
if isinstance(value, Database.LOB):
value = value.read()
# Oracle stores empty strings as null. We need to undo this in
# order to adhere to the Django convention of using the empty
# string instead of null, but only if the field accepts the
# empty string.
if value is None and isinstance(field, Field) and field.empty_strings_allowed:
value = ''
# Convert 1 or 0 to True or False
elif value in (1, 0) and isinstance(field, (BooleanField, NullBooleanField)):
value = bool(value)
# Convert floats to decimals
elif value is not None and isinstance(field, DecimalField):
value = util.typecast_decimal(field.format_number(value))
# cx_Oracle always returns datetime.datetime objects for
# DATE and TIMESTAMP columns, but Django wants to see a
# python datetime.date, .time, or .datetime. We use the type
# of the Field to determine which to cast to, but it's not
# always available.
# As a workaround, we cast to date if all the time-related
# values are 0, or to time if the date is 1/1/1900.
# This could be cleaned a bit by adding a method to the Field
# classes to normalize values from the database (the to_python
# method is used for validation and isn't what we want here).
elif isinstance(value, Database.Timestamp):
# In Python 2.3, the cx_Oracle driver returns its own
# Timestamp object that we must convert to a datetime class.
if not isinstance(value, datetime.datetime):
value = datetime.datetime(value.year, value.month, value.day, value.hour,
value.minute, value.second, value.fsecond)
if isinstance(field, DateTimeField):
pass # DateTimeField subclasses DateField so must be checked first.
elif isinstance(field, DateField):
value = value.date()
elif isinstance(field, TimeField) or (value.year == 1900 and value.month == value.day == 1):
value = value.time()
elif value.hour == value.minute == value.second == value.microsecond == 0:
value = value.date()
values.append(value)
return values
return OracleQuerySet
OPERATOR_MAPPING = { OPERATOR_MAPPING = {
'exact': '= %s', 'exact': '= %s',
'iexact': 'LIKE %s', 'iexact': '= UPPER(%s)',
'contains': 'LIKE %s', 'contains': "LIKE %s ESCAPE '\\'",
'icontains': 'LIKE %s', 'icontains': "LIKE UPPER(%s) ESCAPE '\\'",
'gt': '> %s', 'gt': '> %s',
'gte': '>= %s', 'gte': '>= %s',
'lt': '< %s', 'lt': '< %s',
'lte': '<= %s', 'lte': '<= %s',
'startswith': 'LIKE %s', 'startswith': "LIKE %s ESCAPE '\\'",
'endswith': 'LIKE %s', 'endswith': "LIKE %s ESCAPE '\\'",
'istartswith': 'LIKE %s', 'istartswith': "LIKE UPPER(%s) ESCAPE '\\'",
'iendswith': 'LIKE %s', 'iendswith': "LIKE UPPER(%s) ESCAPE '\\'",
} }

View File

@ -2,9 +2,10 @@ from django.conf import settings
import os import os
def runshell(): def runshell():
args = '' dsn = settings.DATABASE_USER
args += settings.DATABASE_USER
if settings.DATABASE_PASSWORD: if settings.DATABASE_PASSWORD:
args += "/%s" % settings.DATABASE_PASSWORD dsn += "/%s" % settings.DATABASE_PASSWORD
args += "@%s" % settings.DATABASE_NAME if settings.DATABASE_NAME:
os.execvp('sqlplus', args) dsn += "@%s" % settings.DATABASE_NAME
args = ["sqlplus", "-L", dsn]
os.execvp("sqlplus", args)

View File

@ -1,26 +1,304 @@
import sys, time
from django.core import management
# This dictionary maps Field objects to their associated Oracle column
# types, as strings. Column-type strings can contain format strings; they'll
# be interpolated against the values of Field.__dict__ before being output.
# If a column type is set to None, it won't be included in the output.
DATA_TYPES = { DATA_TYPES = {
'AutoField': 'number(38)', 'AutoField': 'NUMBER(11)',
'BooleanField': 'number(1)', 'BooleanField': 'NUMBER(1) CHECK (%(column)s IN (0,1))',
'CharField': 'varchar2(%(maxlength)s)', 'CharField': 'VARCHAR2(%(maxlength)s)',
'CommaSeparatedIntegerField': 'varchar2(%(maxlength)s)', 'CommaSeparatedIntegerField': 'VARCHAR2(%(maxlength)s)',
'DateField': 'date', 'DateField': 'DATE',
'DateTimeField': 'date', 'DateTimeField': 'TIMESTAMP',
'DecimalField': 'number(%(max_digits)s, %(decimal_places)s)', 'DecimalField': 'NUMBER(%(max_digits)s, %(decimal_places)s)',
'FileField': 'varchar2(100)', 'FileField': 'VARCHAR2(100)',
'FilePathField': 'varchar2(100)', 'FilePathField': 'VARCHAR2(100)',
'FloatField': 'double precision', 'FloatField': 'DOUBLE PRECISION',
'ImageField': 'varchar2(100)', 'ImageField': 'VARCHAR2(100)',
'IntegerField': 'integer', 'IntegerField': 'NUMBER(11)',
'IPAddressField': 'char(15)', 'IPAddressField': 'VARCHAR2(15)',
'NullBooleanField': 'integer', 'NullBooleanField': 'NUMBER(1) CHECK ((%(column)s IN (0,1)) OR (%(column)s IS NULL))',
'OneToOneField': 'integer', 'OneToOneField': 'NUMBER(11)',
'PhoneNumberField': 'varchar(20)', 'PhoneNumberField': 'VARCHAR2(20)',
'PositiveIntegerField': 'integer', 'PositiveIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)',
'PositiveSmallIntegerField': 'smallint', 'PositiveSmallIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)',
'SlugField': 'varchar(50)', 'SlugField': 'VARCHAR2(50)',
'SmallIntegerField': 'smallint', 'SmallIntegerField': 'NUMBER(11)',
'TextField': 'long', 'TextField': 'NCLOB',
'TimeField': 'timestamp', 'TimeField': 'TIMESTAMP',
'USStateField': 'varchar(2)', 'URLField': 'VARCHAR2(200)',
'USStateField': 'CHAR(2)',
'NoField': None, 'NoField': None,
} }
TEST_DATABASE_PREFIX = 'test_'
PASSWORD = 'Im_a_lumberjack'
REMEMBER = {}
def create_test_db(settings, connection, backend, verbosity=1, autoclobber=False):
TEST_DATABASE_NAME = _test_database_name(settings)
TEST_DATABASE_USER = _test_database_user(settings)
TEST_DATABASE_PASSWD = _test_database_passwd(settings)
TEST_DATABASE_TBLSPACE = _test_database_tblspace(settings)
TEST_DATABASE_TBLSPACE_TMP = _test_database_tblspace_tmp(settings)
parameters = {
'dbname': TEST_DATABASE_NAME,
'user': TEST_DATABASE_USER,
'password': TEST_DATABASE_PASSWD,
'tblspace': TEST_DATABASE_TBLSPACE,
'tblspace_temp': TEST_DATABASE_TBLSPACE_TMP,
}
REMEMBER['user'] = settings.DATABASE_USER
REMEMBER['passwd'] = settings.DATABASE_PASSWORD
cursor = connection.cursor()
if _test_database_create(settings):
if verbosity >= 1:
print 'Creating test database...'
try:
_create_test_db(cursor, parameters, verbosity)
except Exception, e:
sys.stderr.write("Got an error creating the test database: %s\n" % e)
if not autoclobber:
confirm = raw_input("It appears the test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_NAME)
if autoclobber or confirm == 'yes':
try:
if verbosity >= 1:
print "Destroying old test database..."
_destroy_test_db(cursor, parameters, verbosity)
if verbosity >= 1:
print "Creating test database..."
_create_test_db(cursor, parameters, verbosity)
except Exception, e:
sys.stderr.write("Got an error recreating the test database: %s\n" % e)
sys.exit(2)
else:
print "Tests cancelled."
sys.exit(1)
if _test_user_create(settings):
if verbosity >= 1:
print "Creating test user..."
try:
_create_test_user(cursor, parameters, verbosity)
except Exception, e:
sys.stderr.write("Got an error creating the test user: %s\n" % e)
if not autoclobber:
confirm = raw_input("It appears the test user, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_USER)
if autoclobber or confirm == 'yes':
try:
if verbosity >= 1:
print "Destroying old test user..."
_destroy_test_user(cursor, parameters, verbosity)
if verbosity >= 1:
print "Creating test user..."
_create_test_user(cursor, parameters, verbosity)
except Exception, e:
sys.stderr.write("Got an error recreating the test user: %s\n" % e)
sys.exit(2)
else:
print "Tests cancelled."
sys.exit(1)
connection.close()
settings.DATABASE_USER = TEST_DATABASE_USER
settings.DATABASE_PASSWORD = TEST_DATABASE_PASSWD
management.syncdb(verbosity, interactive=False)
# Get a cursor (even though we don't need one yet). This has
# the side effect of initializing the test database.
cursor = connection.cursor()
def destroy_test_db(settings, connection, backend, old_database_name, verbosity=1):
connection.close()
TEST_DATABASE_NAME = _test_database_name(settings)
TEST_DATABASE_USER = _test_database_user(settings)
TEST_DATABASE_PASSWD = _test_database_passwd(settings)
TEST_DATABASE_TBLSPACE = _test_database_tblspace(settings)
TEST_DATABASE_TBLSPACE_TMP = _test_database_tblspace_tmp(settings)
settings.DATABASE_NAME = old_database_name
settings.DATABASE_USER = REMEMBER['user']
settings.DATABASE_PASSWORD = REMEMBER['passwd']
parameters = {
'dbname': TEST_DATABASE_NAME,
'user': TEST_DATABASE_USER,
'password': TEST_DATABASE_PASSWD,
'tblspace': TEST_DATABASE_TBLSPACE,
'tblspace_temp': TEST_DATABASE_TBLSPACE_TMP,
}
REMEMBER['user'] = settings.DATABASE_USER
REMEMBER['passwd'] = settings.DATABASE_PASSWORD
cursor = connection.cursor()
time.sleep(1) # To avoid "database is being accessed by other users" errors.
if _test_user_create(settings):
if verbosity >= 1:
print 'Destroying test user...'
_destroy_test_user(cursor, parameters, verbosity)
if _test_database_create(settings):
if verbosity >= 1:
print 'Destroying test database...'
_destroy_test_db(cursor, parameters, verbosity)
connection.close()
def _create_test_db(cursor, parameters, verbosity):
if verbosity >= 2:
print "_create_test_db(): dbname = %s" % parameters['dbname']
statements = [
"""CREATE TABLESPACE %(tblspace)s
DATAFILE '%(tblspace)s.dbf' SIZE 20M
REUSE AUTOEXTEND ON NEXT 10M MAXSIZE 100M
""",
"""CREATE TEMPORARY TABLESPACE %(tblspace_temp)s
TEMPFILE '%(tblspace_temp)s.dbf' SIZE 20M
REUSE AUTOEXTEND ON NEXT 10M MAXSIZE 100M
""",
]
_execute_statements(cursor, statements, parameters, verbosity)
def _create_test_user(cursor, parameters, verbosity):
if verbosity >= 2:
print "_create_test_user(): username = %s" % parameters['user']
statements = [
"""CREATE USER %(user)s
IDENTIFIED BY %(password)s
DEFAULT TABLESPACE %(tblspace)s
TEMPORARY TABLESPACE %(tblspace_temp)s
""",
"""GRANT CONNECT, RESOURCE TO %(user)s""",
]
_execute_statements(cursor, statements, parameters, verbosity)
def _destroy_test_db(cursor, parameters, verbosity):
if verbosity >= 2:
print "_destroy_test_db(): dbname=%s" % parameters['dbname']
statements = [
'DROP TABLESPACE %(tblspace)s INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS',
'DROP TABLESPACE %(tblspace_temp)s INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS',
]
_execute_statements(cursor, statements, parameters, verbosity)
def _destroy_test_user(cursor, parameters, verbosity):
if verbosity >= 2:
print "_destroy_test_user(): user=%s" % parameters['user']
print "Be patient. This can take some time..."
statements = [
'DROP USER %(user)s CASCADE',
]
_execute_statements(cursor, statements, parameters, verbosity)
def _execute_statements(cursor, statements, parameters, verbosity):
for template in statements:
stmt = template % parameters
if verbosity >= 2:
print stmt
try:
cursor.execute(stmt)
except Exception, err:
sys.stderr.write("Failed (%s)\n" % (err))
raise
def _test_database_name(settings):
name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
try:
if settings.TEST_DATABASE_NAME:
name = settings.TEST_DATABASE_NAME
except AttributeError:
pass
except:
raise
return name
def _test_database_create(settings):
name = True
try:
if settings.TEST_DATABASE_CREATE:
name = True
else:
name = False
except AttributeError:
pass
except:
raise
return name
def _test_user_create(settings):
name = True
try:
if settings.TEST_USER_CREATE:
name = True
else:
name = False
except AttributeError:
pass
except:
raise
return name
def _test_database_user(settings):
name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
try:
if settings.TEST_DATABASE_USER:
name = settings.TEST_DATABASE_USER
except AttributeError:
pass
except:
raise
return name
def _test_database_passwd(settings):
name = PASSWORD
try:
if settings.TEST_DATABASE_PASSWD:
name = settings.TEST_DATABASE_PASSWD
except AttributeError:
pass
except:
raise
return name
def _test_database_tblspace(settings):
name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
try:
if settings.TEST_DATABASE_TBLSPACE:
name = settings.TEST_DATABASE_TBLSPACE
except AttributeError:
pass
except:
raise
return name
def _test_database_tblspace_tmp(settings):
name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME + '_temp'
try:
if settings.TEST_DATABASE_TBLSPACE_TMP:
name = settings.TEST_DATABASE_TBLSPACE_TMP
except AttributeError:
pass
except:
raise
return name

View File

@ -1,14 +1,19 @@
from django.db.backends.oracle.base import quote_name
import re import re
import cx_Oracle
foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)") foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
def get_table_list(cursor): def get_table_list(cursor):
"Returns a list of table names in the current database." "Returns a list of table names in the current database."
cursor.execute("SELECT TABLE_NAME FROM USER_TABLES") cursor.execute("SELECT TABLE_NAME FROM USER_TABLES")
return [row[0] for row in cursor.fetchall()] return [row[0].upper() for row in cursor.fetchall()]
def get_table_description(cursor, table_name): def get_table_description(cursor, table_name):
return table_name "Returns a description of the table, with the DB-API cursor.description interface."
cursor.execute("SELECT * FROM %s WHERE ROWNUM < 2" % quote_name(table_name))
return cursor.description
def _name_to_index(cursor, table_name): def _name_to_index(cursor, table_name):
""" """
@ -22,7 +27,24 @@ def get_relations(cursor, table_name):
Returns a dictionary of {field_index: (field_index_other_table, other_table)} Returns a dictionary of {field_index: (field_index_other_table, other_table)}
representing all relationships to the given table. Indexes are 0-based. representing all relationships to the given table. Indexes are 0-based.
""" """
raise NotImplementedError cursor.execute("""
SELECT ta.column_id - 1, tb.table_name, tb.column_id - 1
FROM user_constraints, USER_CONS_COLUMNS ca, USER_CONS_COLUMNS cb,
user_tab_cols ta, user_tab_cols tb
WHERE user_constraints.table_name = %s AND
ta.table_name = %s AND
ta.column_name = ca.column_name AND
ca.table_name = %s AND
user_constraints.constraint_name = ca.constraint_name AND
user_constraints.r_constraint_name = cb.constraint_name AND
cb.table_name = tb.table_name AND
cb.column_name = tb.column_name AND
ca.position = cb.position""", [table_name, table_name, table_name])
relations = {}
for row in cursor.fetchall():
relations[row[0]] = (row[2], row[1])
return relations
def get_indexes(cursor, table_name): def get_indexes(cursor, table_name):
""" """
@ -31,20 +53,46 @@ def get_indexes(cursor, table_name):
{'primary_key': boolean representing whether it's the primary key, {'primary_key': boolean representing whether it's the primary key,
'unique': boolean representing whether it's a unique index} 'unique': boolean representing whether it's a unique index}
""" """
raise NotImplementedError # This query retrieves each index on the given table, including the
# first associated field name
# "We were in the nick of time; you were in great peril!"
sql = """
WITH primarycols AS (
SELECT user_cons_columns.table_name, user_cons_columns.column_name, 1 AS PRIMARYCOL
FROM user_cons_columns, user_constraints
WHERE user_cons_columns.constraint_name = user_constraints.constraint_name AND
user_constraints.constraint_type = 'P' AND
user_cons_columns.table_name = %s),
uniquecols AS (
SELECT user_ind_columns.table_name, user_ind_columns.column_name, 1 AS UNIQUECOL
FROM user_indexes, user_ind_columns
WHERE uniqueness = 'UNIQUE' AND
user_indexes.index_name = user_ind_columns.index_name AND
user_ind_columns.table_name = %s)
SELECT allcols.column_name, primarycols.primarycol, uniquecols.UNIQUECOL
FROM (SELECT column_name FROM primarycols UNION SELECT column_name FROM
uniquecols) allcols,
primarycols, uniquecols
WHERE allcols.column_name = primarycols.column_name (+) AND
allcols.column_name = uniquecols.column_name (+)
"""
cursor.execute(sql, [table_name, table_name])
indexes = {}
for row in cursor.fetchall():
# row[1] (idx.indkey) is stored in the DB as an array. It comes out as
# a string of space-separated integers. This designates the field
# indexes (1-based) of the fields that have indexes on the table.
# Here, we skip any indexes across multiple fields.
indexes[row[0]] = {'primary_key': row[1], 'unique': row[2]}
return indexes
# Maps type codes to Django Field types. # Maps type objects to Django Field types.
DATA_TYPES_REVERSE = { DATA_TYPES_REVERSE = {
16: 'BooleanField', cx_Oracle.CLOB: 'TextField',
21: 'SmallIntegerField', cx_Oracle.DATETIME: 'DateTimeField',
23: 'IntegerField', cx_Oracle.FIXED_CHAR: 'CharField',
25: 'TextField', cx_Oracle.NCLOB: 'TextField',
869: 'IPAddressField', cx_Oracle.NUMBER: 'DecimalField',
1043: 'CharField', cx_Oracle.STRING: 'CharField',
1082: 'DateField', cx_Oracle.TIMESTAMP: 'DateTimeField',
1083: 'TimeField',
1114: 'DateTimeField',
1184: 'DateTimeField',
1266: 'TimeField',
1700: 'DecimalField',
} }

View File

@ -87,7 +87,7 @@ class DatabaseWrapper(local):
global postgres_version global postgres_version
if not postgres_version: if not postgres_version:
cursor.execute("SELECT version()") cursor.execute("SELECT version()")
postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')] postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')]
if settings.DEBUG: if settings.DEBUG:
return util.CursorDebugWrapper(cursor, self) return util.CursorDebugWrapper(cursor, self)
return cursor return cursor
@ -105,7 +105,14 @@ class DatabaseWrapper(local):
self.connection.close() self.connection.close()
self.connection = None self.connection = None
allows_group_by_ordinal = True
allows_unique_and_pk = True
autoindexes_primary_keys = True
needs_datetime_string_cast = True
needs_upper_for_iops = False
supports_constraints = True supports_constraints = True
supports_tablespaces = False
uses_case_insensitive_names = False
def quote_name(name): def quote_name(name):
if name.startswith('"') and name.endswith('"'): if name.startswith('"') and name.endswith('"'):
@ -138,6 +145,9 @@ def get_date_trunc_sql(lookup_type, field_name):
# http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name) return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
def get_datetime_cast_sql():
return None
def get_limit_offset_sql(limit, offset=None): def get_limit_offset_sql(limit, offset=None):
sql = "LIMIT %s" % limit sql = "LIMIT %s" % limit
if offset and offset != 0: if offset and offset != 0:
@ -149,7 +159,7 @@ def get_random_function_sql():
def get_deferrable_sql(): def get_deferrable_sql():
return " DEFERRABLE INITIALLY DEFERRED" return " DEFERRABLE INITIALLY DEFERRED"
def get_fulltext_search_sql(field_name): def get_fulltext_search_sql(field_name):
raise NotImplementedError raise NotImplementedError
@ -159,12 +169,21 @@ def get_drop_foreignkey_sql():
def get_pk_default_value(): def get_pk_default_value():
return "DEFAULT" return "DEFAULT"
def get_max_name_length():
return None
def get_start_transaction_sql():
return "BEGIN;"
def get_autoinc_sql(table):
return None
def get_sql_flush(style, tables, sequences): def get_sql_flush(style, tables, sequences):
"""Return a list of SQL statements required to remove all data from """Return a list of SQL statements required to remove all data from
all tables in the database (without actually removing the tables all tables in the database (without actually removing the tables
themselves) and put the database in an empty 'initial' state themselves) and put the database in an empty 'initial' state
""" """
if tables: if tables:
if postgres_version[0] >= 8 and postgres_version[1] >= 1: if postgres_version[0] >= 8 and postgres_version[1] >= 1:
# Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to* in order to be able to # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to* in order to be able to
@ -175,7 +194,7 @@ def get_sql_flush(style, tables, sequences):
style.SQL_FIELD(', '.join([quote_name(table) for table in tables])) style.SQL_FIELD(', '.join([quote_name(table) for table in tables]))
)] )]
else: else:
# Older versions of Postgres can't do TRUNCATE in a single call, so they must use # Older versions of Postgres can't do TRUNCATE in a single call, so they must use
# a simple delete. # a simple delete.
sql = ['%s %s %s;' % \ sql = ['%s %s %s;' % \
(style.SQL_KEYWORD('DELETE'), (style.SQL_KEYWORD('DELETE'),
@ -243,7 +262,7 @@ def get_sql_sequence_reset(style, model_list):
style.SQL_KEYWORD('FROM'), style.SQL_KEYWORD('FROM'),
style.SQL_TABLE(f.m2m_db_table()))) style.SQL_TABLE(f.m2m_db_table())))
return output return output
# Register these custom typecasts, because Django expects dates/times to be # Register these custom typecasts, because Django expects dates/times to be
# in Python's native (standard-library) datetime/time format, whereas psycopg # in Python's native (standard-library) datetime/time format, whereas psycopg
# use mx.DateTime by default. # use mx.DateTime by default.

View File

@ -55,7 +55,7 @@ class DatabaseWrapper(local):
global postgres_version global postgres_version
if not postgres_version: if not postgres_version:
cursor.execute("SELECT version()") cursor.execute("SELECT version()")
postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')] postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')]
if settings.DEBUG: if settings.DEBUG:
return util.CursorDebugWrapper(cursor, self) return util.CursorDebugWrapper(cursor, self)
return cursor return cursor
@ -73,7 +73,14 @@ class DatabaseWrapper(local):
self.connection.close() self.connection.close()
self.connection = None self.connection = None
allows_group_by_ordinal = True
allows_unique_and_pk = True
autoindexes_primary_keys = True
needs_datetime_string_cast = False
needs_upper_for_iops = False
supports_constraints = True supports_constraints = True
supports_tablespaces = False
uses_case_insensitive_names = False
def quote_name(name): def quote_name(name):
if name.startswith('"') and name.endswith('"'): if name.startswith('"') and name.endswith('"'):
@ -98,6 +105,9 @@ def get_date_trunc_sql(lookup_type, field_name):
# http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name) return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
def get_datetime_cast_sql():
return None
def get_limit_offset_sql(limit, offset=None): def get_limit_offset_sql(limit, offset=None):
sql = "LIMIT %s" % limit sql = "LIMIT %s" % limit
if offset and offset != 0: if offset and offset != 0:
@ -119,6 +129,15 @@ def get_drop_foreignkey_sql():
def get_pk_default_value(): def get_pk_default_value():
return "DEFAULT" return "DEFAULT"
def get_max_name_length():
return None
def get_start_transaction_sql():
return "BEGIN;"
def get_autoinc_sql(table):
return None
def get_sql_flush(style, tables, sequences): def get_sql_flush(style, tables, sequences):
"""Return a list of SQL statements required to remove all data from """Return a list of SQL statements required to remove all data from
all tables in the database (without actually removing the tables all tables in the database (without actually removing the tables
@ -139,7 +158,7 @@ def get_sql_flush(style, tables, sequences):
style.SQL_KEYWORD('FROM'), style.SQL_KEYWORD('FROM'),
style.SQL_FIELD(quote_name(table)) style.SQL_FIELD(quote_name(table))
) for table in tables] ) for table in tables]
# 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements
# to reset sequence indices # to reset sequence indices
for sequence in sequences: for sequence in sequences:
@ -200,7 +219,7 @@ def get_sql_sequence_reset(style, model_list):
style.SQL_KEYWORD('FROM'), style.SQL_KEYWORD('FROM'),
style.SQL_TABLE(f.m2m_db_table()))) style.SQL_TABLE(f.m2m_db_table())))
return output return output
OPERATOR_MAPPING = { OPERATOR_MAPPING = {
'exact': '= %s', 'exact': '= %s',
'iexact': 'ILIKE %s', 'iexact': 'ILIKE %s',

View File

@ -107,7 +107,14 @@ class SQLiteCursorWrapper(Database.Cursor):
def convert_query(self, query, num_params): def convert_query(self, query, num_params):
return query % tuple("?" * num_params) return query % tuple("?" * num_params)
allows_group_by_ordinal = True
allows_unique_and_pk = True
autoindexes_primary_keys = True
needs_datetime_string_cast = True
needs_upper_for_iops = False
supports_constraints = False supports_constraints = False
supports_tablespaces = False
uses_case_insensitive_names = False
def quote_name(name): def quote_name(name):
if name.startswith('"') and name.endswith('"'): if name.startswith('"') and name.endswith('"'):
@ -139,6 +146,9 @@ def get_date_trunc_sql(lookup_type, field_name):
# sqlite doesn't support DATE_TRUNC, so we fake it as above. # sqlite doesn't support DATE_TRUNC, so we fake it as above.
return 'django_date_trunc("%s", %s)' % (lookup_type.lower(), field_name) return 'django_date_trunc("%s", %s)' % (lookup_type.lower(), field_name)
def get_datetime_cast_sql():
return None
def get_limit_offset_sql(limit, offset=None): def get_limit_offset_sql(limit, offset=None):
sql = "LIMIT %s" % limit sql = "LIMIT %s" % limit
if offset and offset != 0: if offset and offset != 0:
@ -160,11 +170,20 @@ def get_drop_foreignkey_sql():
def get_pk_default_value(): def get_pk_default_value():
return "NULL" return "NULL"
def get_max_name_length():
return None
def get_start_transaction_sql():
return "BEGIN;"
def get_autoinc_sql(table):
return None
def get_sql_flush(style, tables, sequences): def get_sql_flush(style, tables, sequences):
"""Return a list of SQL statements required to remove all data from """Return a list of SQL statements required to remove all data from
all tables in the database (without actually removing the tables all tables in the database (without actually removing the tables
themselves) and put the database in an empty 'initial' state themselves) and put the database in an empty 'initial' state
""" """
# NB: The generated SQL below is specific to SQLite # NB: The generated SQL below is specific to SQLite
# Note: The DELETE FROM... SQL generated below works for SQLite databases # Note: The DELETE FROM... SQL generated below works for SQLite databases
@ -182,7 +201,7 @@ def get_sql_sequence_reset(style, model_list):
"Returns a list of the SQL statements to reset sequences for the given models." "Returns a list of the SQL statements to reset sequences for the given models."
# No sequence reset required # No sequence reset required
return [] return []
def _sqlite_date_trunc(lookup_type, dt): def _sqlite_date_trunc(lookup_type, dt):
try: try:
dt = util.typecast_timestamp(dt) dt = util.typecast_timestamp(dt)

View File

@ -1,4 +1,5 @@
import datetime import datetime
import md5
from time import time from time import time
try: try:
@ -107,6 +108,16 @@ def rev_typecast_decimal(d):
return None return None
return str(d) return str(d)
def truncate_name(name, length=None):
"""Shortens a string to a repeatable mangled version with the given length.
"""
if length is None or len(name) <= length:
return name
hash = md5.md5(name).hexdigest()[:4]
return '%s%s' % (name[:length-4], hash)
################################################################################## ##################################################################################
# Helper functions for dictfetch* for databases that don't natively support them # # Helper functions for dictfetch* for databases that don't natively support them #
################################################################################## ##################################################################################

View File

@ -96,9 +96,9 @@ class Model(object):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
dispatcher.send(signal=signals.pre_init, sender=self.__class__, args=args, kwargs=kwargs) dispatcher.send(signal=signals.pre_init, sender=self.__class__, args=args, kwargs=kwargs)
# There is a rather weird disparity here; if kwargs, it's set, then args # There is a rather weird disparity here; if kwargs, it's set, then args
# overrides it. It should be one or the other; don't duplicate the work # overrides it. It should be one or the other; don't duplicate the work
# The reason for the kwargs check is that standard iterator passes in by # The reason for the kwargs check is that standard iterator passes in by
# args, and nstantiation for iteration is 33% faster. # args, and nstantiation for iteration is 33% faster.
args_len = len(args) args_len = len(args)
@ -122,10 +122,10 @@ class Model(object):
# Maintain compatibility with existing calls. # Maintain compatibility with existing calls.
if isinstance(field.rel, ManyToOneRel): if isinstance(field.rel, ManyToOneRel):
kwargs.pop(field.attname, None) kwargs.pop(field.attname, None)
# Now we're left with the unprocessed fields that *must* come from # Now we're left with the unprocessed fields that *must* come from
# keywords, or default. # keywords, or default.
for field in fields_iter: for field in fields_iter:
if kwargs: if kwargs:
if isinstance(field.rel, ManyToOneRel): if isinstance(field.rel, ManyToOneRel):
@ -147,7 +147,7 @@ class Model(object):
try: try:
val = getattr(rel_obj, field.rel.get_related_field().attname) val = getattr(rel_obj, field.rel.get_related_field().attname)
except AttributeError: except AttributeError:
raise TypeError("Invalid value: %r should be a %s instance, not a %s" % raise TypeError("Invalid value: %r should be a %s instance, not a %s" %
(field.name, field.rel.to, type(rel_obj))) (field.name, field.rel.to, type(rel_obj)))
else: else:
val = kwargs.pop(field.attname, field.get_default()) val = kwargs.pop(field.attname, field.get_default())
@ -210,17 +210,18 @@ class Model(object):
record_exists = True record_exists = True
if pk_set: if pk_set:
# Determine whether a record with the primary key already exists. # Determine whether a record with the primary key already exists.
cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % \ cursor.execute("SELECT COUNT(*) FROM %s WHERE %s=%%s" % \
(backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)), [pk_val]) (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)),
self._meta.pk.get_db_prep_lookup('exact', pk_val))
# If it does already exist, do an UPDATE. # If it does already exist, do an UPDATE.
if cursor.fetchone(): if cursor.fetchone()[0] > 0:
db_values = [f.get_db_prep_save(f.pre_save(self, False)) for f in non_pks] db_values = [f.get_db_prep_save(f.pre_save(self, False)) for f in non_pks]
if db_values: if db_values:
cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \ cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \
(backend.quote_name(self._meta.db_table), (backend.quote_name(self._meta.db_table),
','.join(['%s=%%s' % backend.quote_name(f.column) for f in non_pks]), ','.join(['%s=%%s' % backend.quote_name(f.column) for f in non_pks]),
backend.quote_name(self._meta.pk.column)), backend.quote_name(self._meta.pk.column)),
db_values + [pk_val]) db_values + self._meta.pk.get_db_prep_lookup('exact', pk_val))
else: else:
record_exists = False record_exists = False
if not pk_set or not record_exists: if not pk_set or not record_exists:

View File

@ -74,12 +74,16 @@ class Field(object):
core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True, core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True,
prepopulate_from=None, unique_for_date=None, unique_for_month=None, prepopulate_from=None, unique_for_date=None, unique_for_month=None,
unique_for_year=None, validator_list=None, choices=None, radio_admin=None, unique_for_year=None, validator_list=None, choices=None, radio_admin=None,
help_text='', db_column=None): help_text='', db_column=None, db_tablespace=None):
self.name = name self.name = name
self.verbose_name = verbose_name self.verbose_name = verbose_name
self.primary_key = primary_key self.primary_key = primary_key
self.maxlength, self.unique = maxlength, unique self.maxlength, self.unique = maxlength, unique
self.blank, self.null = blank, null self.blank, self.null = blank, null
# Oracle treats the empty string ('') as null, so coerce the null
# option whenever '' is a possible value.
if self.empty_strings_allowed and settings.DATABASE_ENGINE == 'oracle':
self.null = True
self.core, self.rel, self.default = core, rel, default self.core, self.rel, self.default = core, rel, default
self.editable = editable self.editable = editable
self.serialize = serialize self.serialize = serialize
@ -91,6 +95,7 @@ class Field(object):
self.radio_admin = radio_admin self.radio_admin = radio_admin
self.help_text = help_text self.help_text = help_text
self.db_column = db_column self.db_column = db_column
self.db_tablespace = db_tablespace
# Set db_index to True if the field has a relationship and doesn't explicitly set db_index. # Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
self.db_index = db_index self.db_index = db_index
@ -201,7 +206,7 @@ class Field(object):
if callable(self.default): if callable(self.default):
return self.default() return self.default()
return self.default return self.default
if not self.empty_strings_allowed or self.null: if not self.empty_strings_allowed or (self.null and settings.DATABASE_ENGINE != 'oracle'):
return None return None
return "" return ""
@ -806,6 +811,7 @@ class IPAddressField(Field):
validators.isValidIPAddress4(field_data, None) validators.isValidIPAddress4(field_data, None)
class NullBooleanField(Field): class NullBooleanField(Field):
empty_strings_allowed = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['null'] = True kwargs['null'] = True
Field.__init__(self, *args, **kwargs) Field.__init__(self, *args, **kwargs)
@ -875,10 +881,18 @@ class TimeField(Field):
Field.__init__(self, verbose_name, name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value):
if lookup_type == 'range': if settings.DATABASE_ENGINE == 'oracle':
value = [str(v) for v in value] # Oracle requires a date in order to parse.
def prep(value):
if isinstance(value, datetime.time):
value = datetime.datetime.combine(datetime.date(1900, 1, 1), value)
return str(value)
else: else:
value = str(value) prep = str
if lookup_type == 'range':
value = [prep(v) for v in value]
else:
value = prep(value)
return Field.get_db_prep_lookup(self, lookup_type, value) return Field.get_db_prep_lookup(self, lookup_type, value)
def pre_save(self, model_instance, add): def pre_save(self, model_instance, add):
@ -896,7 +910,15 @@ class TimeField(Field):
# doesn't support microseconds. # doesn't support microseconds.
if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'): if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
value = value.replace(microsecond=0) value = value.replace(microsecond=0)
value = str(value) if settings.DATABASE_ENGINE == 'oracle':
# cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field.
if isinstance(value, datetime.time):
value = datetime.datetime(1900, 1, 1, value.hour, value.minute,
value.second, value.microsecond)
elif isinstance(value, basestring):
value = datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6]))
else:
value = str(value)
return Field.get_db_prep_save(self, value) return Field.get_db_prep_save(self, value)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):

View File

@ -10,9 +10,10 @@ from django import oldforms
from django import newforms as forms from django import newforms as forms
from django.dispatch import dispatcher from django.dispatch import dispatcher
# For Python 2.3 try:
if not hasattr(__builtins__, 'set'): set
from sets import Set as set except NameError:
from sets import Set as set # Python 2.3 fallback
# Values for Relation.edit_inline. # Values for Relation.edit_inline.
TABULAR, STACKED = 1, 2 TABULAR, STACKED = 1, 2
@ -335,10 +336,7 @@ def create_many_related_manager(superclass):
(target_col_name, self.join_table, source_col_name, (target_col_name, self.join_table, source_col_name,
target_col_name, ",".join(['%s'] * len(new_ids))), target_col_name, ",".join(['%s'] * len(new_ids))),
[self._pk_val] + list(new_ids)) [self._pk_val] + list(new_ids))
if cursor.rowcount is not None and cursor.rowcount != 0: existing_ids = set([row[0] for row in cursor.fetchall()])
existing_ids = set([row[0] for row in cursor.fetchmany(cursor.rowcount)])
else:
existing_ids = set()
# Add the ones that aren't there already # Add the ones that aren't there already
for obj_id in (new_ids - existing_ids): for obj_id in (new_ids - existing_ids):

View File

@ -13,7 +13,7 @@ get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|
DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering', DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
'unique_together', 'permissions', 'get_latest_by', 'unique_together', 'permissions', 'get_latest_by',
'order_with_respect_to', 'app_label') 'order_with_respect_to', 'app_label', 'db_tablespace')
class Options(object): class Options(object):
def __init__(self, meta): def __init__(self, meta):
@ -27,6 +27,7 @@ class Options(object):
self.object_name, self.app_label = None, None self.object_name, self.app_label = None, None
self.get_latest_by = None self.get_latest_by = None
self.order_with_respect_to = None self.order_with_respect_to = None
self.db_tablespace = None
self.admin = None self.admin = None
self.meta = meta self.meta = meta
self.pk = None self.pk = None
@ -59,6 +60,8 @@ class Options(object):
del self.meta del self.meta
def _prepare(self, model): def _prepare(self, model):
from django.db import backend
from django.db.backends.util import truncate_name
if self.order_with_respect_to: if self.order_with_respect_to:
self.order_with_respect_to = self.get_field(self.order_with_respect_to) self.order_with_respect_to = self.get_field(self.order_with_respect_to)
self.ordering = ('_order',) self.ordering = ('_order',)
@ -73,6 +76,8 @@ class Options(object):
# If the db_table wasn't provided, use the app_label + module_name. # If the db_table wasn't provided, use the app_label + module_name.
if not self.db_table: if not self.db_table:
self.db_table = "%s_%s" % (self.app_label, self.module_name) self.db_table = "%s_%s" % (self.app_label, self.module_name)
self.db_table = truncate_name(self.db_table,
backend.get_max_name_length())
def add_field(self, field): def add_field(self, field):
# Insert the given field in the order in which it was created, using # Insert the given field in the order in which it was created, using
@ -88,10 +93,10 @@ class Options(object):
def __repr__(self): def __repr__(self):
return '<Options for %s>' % self.object_name return '<Options for %s>' % self.object_name
def __str__(self): def __str__(self):
return "%s.%s" % (self.app_label, self.module_name) return "%s.%s" % (self.app_label, self.module_name)
def get_field(self, name, many_to_many=True): def get_field(self, name, many_to_many=True):
"Returns the requested field by name. Raises FieldDoesNotExist on error." "Returns the requested field by name. Raises FieldDoesNotExist on error."
to_search = many_to_many and (self.fields + self.many_to_many) or self.fields to_search = many_to_many and (self.fields + self.many_to_many) or self.fields

View File

@ -4,12 +4,14 @@ from django.db.models import signals, loading
from django.dispatch import dispatcher from django.dispatch import dispatcher
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from django.contrib.contenttypes import generic from django.contrib.contenttypes import generic
import datetime
import operator import operator
import re import re
# For Python 2.3 try:
if not hasattr(__builtins__, 'set'): set
from sets import Set as set except NameError:
from sets import Set as set # Python 2.3 fallback
# The string constant used to separate query parts # The string constant used to separate query parts
LOOKUP_SEPARATOR = '__' LOOKUP_SEPARATOR = '__'
@ -77,7 +79,7 @@ def quote_only_if_word(word):
else: else:
return backend.quote_name(word) return backend.quote_name(word)
class QuerySet(object): class _QuerySet(object):
"Represents a lazy database lookup for a set of objects" "Represents a lazy database lookup for a set of objects"
def __init__(self, model=None): def __init__(self, model=None):
self.model = model self.model = model
@ -181,13 +183,18 @@ class QuerySet(object):
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
fill_cache = self._select_related fill_cache = self._select_related
index_end = len(self.model._meta.fields) fields = self.model._meta.fields
index_end = len(fields)
has_resolve_columns = hasattr(self, 'resolve_columns')
while 1: while 1:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
if not rows: if not rows:
raise StopIteration raise StopIteration
for row in rows: for row in rows:
if has_resolve_columns:
row = self.resolve_columns(row, fields)
if fill_cache: if fill_cache:
obj, index_end = get_cached_row(klass=self.model, row=row, obj, index_end = get_cached_row(klass=self.model, row=row,
index_start=0, max_depth=self._max_related_depth) index_start=0, max_depth=self._max_related_depth)
@ -551,6 +558,12 @@ class QuerySet(object):
return select, " ".join(sql), params return select, " ".join(sql), params
# Use the backend's QuerySet class if it defines one, otherwise use _QuerySet.
if hasattr(backend, 'get_query_set_class'):
QuerySet = backend.get_query_set_class(_QuerySet)
else:
QuerySet = _QuerySet
class ValuesQuerySet(QuerySet): class ValuesQuerySet(QuerySet):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ValuesQuerySet, self).__init__(*args, **kwargs) super(ValuesQuerySet, self).__init__(*args, **kwargs)
@ -565,35 +578,38 @@ class ValuesQuerySet(QuerySet):
# self._fields is a list of field names to fetch. # self._fields is a list of field names to fetch.
if self._fields: if self._fields:
#columns = [self.model._meta.get_field(f, many_to_many=False).column for f in self._fields]
if not self._select: if not self._select:
columns = [self.model._meta.get_field(f, many_to_many=False).column for f in self._fields] fields = [self.model._meta.get_field(f, many_to_many=False) for f in self._fields]
else: else:
columns = [] fields = []
for f in self._fields: for f in self._fields:
if f in [field.name for field in self.model._meta.fields]: if f in [field.name for field in self.model._meta.fields]:
columns.append( self.model._meta.get_field(f, many_to_many=False).column ) fields.append(self.model._meta.get_field(f, many_to_many=False))
elif not self._select.has_key( f ): elif not self._select.has_key( f ):
raise FieldDoesNotExist, '%s has no field named %r' % ( self.model._meta.object_name, f ) raise FieldDoesNotExist, '%s has no field named %r' % ( self.model._meta.object_name, f )
field_names = self._fields field_names = self._fields
else: # Default to all fields. else: # Default to all fields.
columns = [f.column for f in self.model._meta.fields] fields = self.model._meta.fields
field_names = [f.attname for f in self.model._meta.fields] field_names = [f.attname for f in fields]
columns = [f.column for f in fields]
select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns] select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns]
# Add any additional SELECTs. # Add any additional SELECTs.
if self._select: if self._select:
select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()]) select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()])
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
has_resolve_columns = hasattr(self, 'resolve_columns')
while 1: while 1:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
if not rows: if not rows:
raise StopIteration raise StopIteration
for row in rows: for row in rows:
if has_resolve_columns:
row = self.resolve_columns(row, fields)
yield dict(zip(field_names, row)) yield dict(zip(field_names, row))
def _clone(self, klass=None, **kwargs): def _clone(self, klass=None, **kwargs):
@ -604,25 +620,49 @@ class ValuesQuerySet(QuerySet):
class DateQuerySet(QuerySet): class DateQuerySet(QuerySet):
def iterator(self): def iterator(self):
from django.db.backends.util import typecast_timestamp from django.db.backends.util import typecast_timestamp
from django.db.models.fields import DateTimeField
self._order_by = () # Clear this because it'll mess things up otherwise. self._order_by = () # Clear this because it'll mess things up otherwise.
if self._field.null: if self._field.null:
self._where.append('%s.%s IS NOT NULL' % \ self._where.append('%s.%s IS NOT NULL' % \
(backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column))) (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column)))
try: try:
select, sql, params = self._get_sql_clause() select, sql, params = self._get_sql_clause()
except EmptyResultSet: except EmptyResultSet:
raise StopIteration raise StopIteration
sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1 %s' % \ table_name = backend.quote_name(self.model._meta.db_table)
field_name = backend.quote_name(self._field.column)
if backend.allows_group_by_ordinal:
group_by = '1'
else:
group_by = backend.get_date_trunc_sql(self._kind,
'%s.%s' % (table_name, field_name))
sql = 'SELECT %s %s GROUP BY %s ORDER BY 1 %s' % \
(backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table), (backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table),
backend.quote_name(self._field.column))), sql, self._order) backend.quote_name(self._field.column))), sql, group_by, self._order)
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute(sql, params) cursor.execute(sql, params)
# We have to manually run typecast_timestamp(str()) on the results, because
# MySQL doesn't automatically cast the result of date functions as datetime has_resolve_columns = hasattr(self, 'resolve_columns')
# objects -- MySQL returns the values as strings, instead. needs_datetime_string_cast = backend.needs_datetime_string_cast
return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()] dates = []
# It would be better to use self._field here instead of DateTimeField(),
# but in Oracle that will result in a list of datetime.date instead of
# datetime.datetime.
fields = [DateTimeField()]
while 1:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
if not rows:
return dates
for row in rows:
date = row[0]
if has_resolve_columns:
date = self.resolve_columns([date], fields)[0]
elif needs_datetime_string_cast:
date = typecast_timestamp(str(date))
dates.append(date)
def _clone(self, klass=None, **kwargs): def _clone(self, klass=None, **kwargs):
c = super(DateQuerySet, self)._clone(klass, **kwargs) c = super(DateQuerySet, self)._clone(klass, **kwargs)
@ -730,8 +770,17 @@ def get_where_clause(lookup_type, table_prefix, field_name, value):
if table_prefix.endswith('.'): if table_prefix.endswith('.'):
table_prefix = backend.quote_name(table_prefix[:-1])+'.' table_prefix = backend.quote_name(table_prefix[:-1])+'.'
field_name = backend.quote_name(field_name) field_name = backend.quote_name(field_name)
if type(value) == datetime.datetime and backend.get_datetime_cast_sql():
cast_sql = backend.get_datetime_cast_sql()
else:
cast_sql = '%s'
if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith') and backend.needs_upper_for_iops:
format = 'UPPER(%s%s) %s'
else:
format = '%s%s %s'
try: try:
return '%s%s %s' % (table_prefix, field_name, (backend.OPERATOR_MAPPING[lookup_type] % '%s')) return format % (table_prefix, field_name,
backend.OPERATOR_MAPPING[lookup_type] % cast_sql)
except KeyError: except KeyError:
pass pass
if lookup_type == 'in': if lookup_type == 'in':

View File

@ -222,12 +222,6 @@ class HttpResponse(object):
content = ''.join(self._container) content = ''.join(self._container)
if isinstance(content, unicode): if isinstance(content, unicode):
content = content.encode(self._charset) content = content.encode(self._charset)
# If self._container was an iterator, we have just exhausted it, so we
# need to save the results for anything else that needs access
if not self._is_string:
self._container = [content]
self._is_string = True
return content return content
def _set_content(self, value): def _set_content(self, value):
@ -237,10 +231,14 @@ class HttpResponse(object):
content = property(_get_content, _set_content) content = property(_get_content, _set_content)
def __iter__(self): def __iter__(self):
for chunk in self._container: self._iterator = self._container.__iter__()
if isinstance(chunk, unicode): return self
chunk = chunk.encode(self._charset)
yield chunk def next(self):
chunk = self._iterator.next()
if isinstance(chunk, unicode):
chunk = chunk.encode(self._charset)
return chunk
def close(self): def close(self):
if hasattr(self._container, 'close'): if hasattr(self._container, 'close'):

View File

@ -27,9 +27,9 @@ __all__ = (
EMPTY_VALUES = (None, '') EMPTY_VALUES = (None, '')
try: try:
set # Only available in Python 2.4+ set
except NameError: except NameError:
from sets import Set as set # Python 2.3 fallback from sets import Set as set # Python 2.3 fallback
try: try:
from decimal import Decimal from decimal import Decimal
@ -516,11 +516,13 @@ class MultiValueField(Field):
""" """
clean_data = [] clean_data = []
errors = ErrorList() errors = ErrorList()
if self.required and not value: if not value or isinstance(value, (list, tuple)):
raise ValidationError(gettext(u'This field is required.')) if not value or not [v for v in value if v not in EMPTY_VALUES]:
elif not self.required and not value: if self.required:
return self.compress([]) raise ValidationError(gettext(u'This field is required.'))
if not isinstance(value, (list, tuple)): else:
return self.compress([])
else:
raise ValidationError(gettext(u'Enter a list of values.')) raise ValidationError(gettext(u'Enter a list of values.'))
for i, field in enumerate(self.fields): for i, field in enumerate(self.fields):
try: try:
@ -558,5 +560,11 @@ class SplitDateTimeField(MultiValueField):
def compress(self, data_list): def compress(self, data_list):
if data_list: if data_list:
# Raise a validation error if time or date is empty
# (possible if SplitDateTimeField has required=False).
if data_list[0] in EMPTY_VALUES:
raise ValidationError(gettext(u'Enter a valid date.'))
if data_list[1] in EMPTY_VALUES:
raise ValidationError(gettext(u'Enter a valid time.'))
return datetime.datetime.combine(*data_list) return datetime.datetime.combine(*data_list)
return None return None

View File

@ -159,7 +159,7 @@ class BaseForm(StrAndUnicode):
def as_p(self): def as_p(self):
"Returns this form rendered as HTML <p>s." "Returns this form rendered as HTML <p>s."
return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'<p>%s</p>', '</p>', u' %s', True) return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True)
def non_field_errors(self): def non_field_errors(self):
""" """

View File

@ -3,16 +3,15 @@ HTML Widget classes
""" """
try: try:
set # Only available in Python 2.4+ set
except NameError: except NameError:
from sets import Set as set # Python 2.3 fallback from sets import Set as set # Python 2.3 fallback
from itertools import chain
from itertools import chain
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
from django.utils.html import escape from django.utils.html import escape
from django.utils.translation import gettext from django.utils.translation import gettext
from django.utils.encoding import StrAndUnicode, smart_unicode from django.utils.encoding import StrAndUnicode, smart_unicode
from util import flatatt from util import flatatt
__all__ = ( __all__ = (

View File

@ -309,10 +309,6 @@ class FormField(object):
return data return data
html2python = staticmethod(html2python) html2python = staticmethod(html2python)
def iter_render(self, data):
# this even needed?
return (self.render(data),)
def render(self, data): def render(self, data):
raise NotImplementedError raise NotImplementedError

View File

@ -7,7 +7,7 @@ from django.http import HttpResponse, Http404
from django.db.models.manager import Manager from django.db.models.manager import Manager
def render_to_response(*args, **kwargs): def render_to_response(*args, **kwargs):
return HttpResponse(loader.render_to_iter(*args, **kwargs)) return HttpResponse(loader.render_to_string(*args, **kwargs))
load_and_render = render_to_response # For backwards compatibility. load_and_render = render_to_response # For backwards compatibility.
def get_object_or_404(klass, *args, **kwargs): def get_object_or_404(klass, *args, **kwargs):

View File

@ -55,7 +55,6 @@ times with multiple contexts)
'\n<html>\n\n</html>\n' '\n<html>\n\n</html>\n'
""" """
import re import re
import types
from inspect import getargspec from inspect import getargspec
from django.conf import settings from django.conf import settings
from django.template.context import Context, RequestContext, ContextPopException from django.template.context import Context, RequestContext, ContextPopException
@ -168,12 +167,9 @@ class Template(object):
for subnode in node: for subnode in node:
yield subnode yield subnode
def iter_render(self, context):
"Display stage -- can be called many times"
return self.nodelist.iter_render(context)
def render(self, context): def render(self, context):
return ''.join(self.iter_render(context)) "Display stage -- can be called many times"
return self.nodelist.render(context)
def compile_string(template_string, origin): def compile_string(template_string, origin):
"Compiles template_string into NodeList ready for rendering" "Compiles template_string into NodeList ready for rendering"
@ -699,26 +695,10 @@ def resolve_variable(path, context):
del bits[0] del bits[0]
return current return current
class NodeBase(type):
def __new__(cls, name, bases, attrs):
"""
Ensures that either a 'render' or 'render_iter' method is defined on
any Node sub-class. This avoids potential infinite loops at runtime.
"""
if not (isinstance(attrs.get('render'), types.FunctionType) or
isinstance(attrs.get('iter_render'), types.FunctionType)):
raise TypeError('Unable to create Node subclass without either "render" or "iter_render" method.')
return type.__new__(cls, name, bases, attrs)
class Node(object): class Node(object):
__metaclass__ = NodeBase
def iter_render(self, context):
return (self.render(context),)
def render(self, context): def render(self, context):
"Return the node rendered as a string" "Return the node rendered as a string"
return ''.join(self.iter_render(context)) pass
def __iter__(self): def __iter__(self):
yield self yield self
@ -734,12 +714,13 @@ class Node(object):
class NodeList(list): class NodeList(list):
def render(self, context): def render(self, context):
return ''.join(self.iter_render(context)) bits = []
def iter_render(self, context):
for node in self: for node in self:
for chunk in node.iter_render(context): if isinstance(node, Node):
yield chunk bits.append(self.render_node(node, context))
else:
bits.append(node)
return ''.join(bits)
def get_nodes_by_type(self, nodetype): def get_nodes_by_type(self, nodetype):
"Return a list of all nodes of the given type" "Return a list of all nodes of the given type"
@ -748,25 +729,24 @@ class NodeList(list):
nodes.extend(node.get_nodes_by_type(nodetype)) nodes.extend(node.get_nodes_by_type(nodetype))
return nodes return nodes
def render_node(self, node, context):
return(node.render(context))
class DebugNodeList(NodeList): class DebugNodeList(NodeList):
def iter_render(self, context): def render_node(self, node, context):
for node in self: try:
if not isinstance(node, Node): result = node.render(context)
yield node except TemplateSyntaxError, e:
continue if not hasattr(e, 'source'):
try: e.source = node.source
for chunk in node.iter_render(context): raise
yield chunk except Exception, e:
except TemplateSyntaxError, e: from sys import exc_info
if not hasattr(e, 'source'): wrapped = TemplateSyntaxError('Caught an exception while rendering: %s' % e)
e.source = node.source wrapped.source = node.source
raise wrapped.exc_info = exc_info()
except Exception, e: raise wrapped
from sys import exc_info return result
wrapped = TemplateSyntaxError('Caught an exception while rendering: %s' % e)
wrapped.source = node.source
wrapped.exc_info = exc_info()
raise wrapped
class TextNode(Node): class TextNode(Node):
def __init__(self, s): def __init__(self, s):
@ -775,9 +755,6 @@ class TextNode(Node):
def __repr__(self): def __repr__(self):
return "<Text Node: '%s'>" % self.s[:25] return "<Text Node: '%s'>" % self.s[:25]
def iter_render(self, context):
return (self.s,)
def render(self, context): def render(self, context):
return self.s return self.s
@ -801,9 +778,6 @@ class VariableNode(Node):
else: else:
return output return output
def iter_render(self, context):
return (self.render(context),)
def render(self, context): def render(self, context):
output = self.filter_expression.resolve(context) output = self.filter_expression.resolve(context)
return self.encode_output(output) return self.encode_output(output)
@ -892,9 +866,6 @@ class Library(object):
def __init__(self, vars_to_resolve): def __init__(self, vars_to_resolve):
self.vars_to_resolve = vars_to_resolve self.vars_to_resolve = vars_to_resolve
#def iter_render(self, context):
# return (self.render(context),)
def render(self, context): def render(self, context):
resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve] resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
return func(*resolved_vars) return func(*resolved_vars)
@ -917,7 +888,7 @@ class Library(object):
def __init__(self, vars_to_resolve): def __init__(self, vars_to_resolve):
self.vars_to_resolve = vars_to_resolve self.vars_to_resolve = vars_to_resolve
def iter_render(self, context): def render(self, context):
resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve] resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
if takes_context: if takes_context:
args = [context] + resolved_vars args = [context] + resolved_vars
@ -933,7 +904,7 @@ class Library(object):
else: else:
t = get_template(file_name) t = get_template(file_name)
self.nodelist = t.nodelist self.nodelist = t.nodelist
return self.nodelist.iter_render(context_class(dict)) return self.nodelist.render(context_class(dict))
compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode) compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
compile_func.__doc__ = func.__doc__ compile_func.__doc__ = func.__doc__

View File

@ -8,18 +8,16 @@ from django.utils.itercompat import groupby
import sys import sys
import re import re
if not hasattr(__builtins__, 'reversed'): try:
# For Python 2.3. reversed
# From http://www.python.org/doc/current/tut/node11.html except NameError:
def reversed(data): from django.utils.itercompat import reversed # Python 2.3 fallback
for index in xrange(len(data)-1, -1, -1):
yield data[index]
register = Library() register = Library()
class CommentNode(Node): class CommentNode(Node):
def iter_render(self, context): def render(self, context):
return () return ''
class CycleNode(Node): class CycleNode(Node):
def __init__(self, cyclevars, variable_name=None): def __init__(self, cyclevars, variable_name=None):
@ -28,9 +26,6 @@ class CycleNode(Node):
self.counter = -1 self.counter = -1
self.variable_name = variable_name self.variable_name = variable_name
def iter_render(self, context):
return (self.render(context),)
def render(self, context): def render(self, context):
self.counter += 1 self.counter += 1
value = self.cyclevars[self.counter % self.cyclevars_len] value = self.cyclevars[self.counter % self.cyclevars_len]
@ -39,32 +34,29 @@ class CycleNode(Node):
return value return value
class DebugNode(Node): class DebugNode(Node):
def iter_render(self, context): def render(self, context):
from pprint import pformat from pprint import pformat
for val in context: output = [pformat(val) for val in context]
yield pformat(val) output.append('\n\n')
yield "\n\n" output.append(pformat(sys.modules))
yield pformat(sys.modules) return ''.join(output)
class FilterNode(Node): class FilterNode(Node):
def __init__(self, filter_expr, nodelist): def __init__(self, filter_expr, nodelist):
self.filter_expr, self.nodelist = filter_expr, nodelist self.filter_expr, self.nodelist = filter_expr, nodelist
def iter_render(self, context): def render(self, context):
output = self.nodelist.render(context) output = self.nodelist.render(context)
# apply filters # apply filters
context.update({'var': output}) context.update({'var': output})
filtered = self.filter_expr.resolve(context) filtered = self.filter_expr.resolve(context)
context.pop() context.pop()
return (filtered,) return filtered
class FirstOfNode(Node): class FirstOfNode(Node):
def __init__(self, vars): def __init__(self, vars):
self.vars = vars self.vars = vars
def iter_render(self, context):
return (self.render(context),)
def render(self, context): def render(self, context):
for var in self.vars: for var in self.vars:
try: try:
@ -100,7 +92,8 @@ class ForNode(Node):
nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype)) nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
return nodes return nodes
def iter_render(self, context): def render(self, context):
nodelist = NodeList()
if 'forloop' in context: if 'forloop' in context:
parentloop = context['forloop'] parentloop = context['forloop']
else: else:
@ -108,12 +101,12 @@ class ForNode(Node):
context.push() context.push()
try: try:
values = self.sequence.resolve(context, True) values = self.sequence.resolve(context, True)
if values is None:
values = ()
elif not hasattr(values, '__len__'):
values = list(values)
except VariableDoesNotExist: except VariableDoesNotExist:
values = () values = []
if values is None:
values = []
if not hasattr(values, '__len__'):
values = list(values)
len_values = len(values) len_values = len(values)
if self.reversed: if self.reversed:
values = reversed(values) values = reversed(values)
@ -132,17 +125,12 @@ class ForNode(Node):
'parentloop': parentloop, 'parentloop': parentloop,
} }
if unpack: if unpack:
# If there are multiple loop variables, unpack the item into # If there are multiple loop variables, unpack the item into them.
# them.
context.update(dict(zip(self.loopvars, item))) context.update(dict(zip(self.loopvars, item)))
else: else:
context[self.loopvars[0]] = item context[self.loopvars[0]] = item
# We inline this to avoid the overhead since ForNode is pretty
# common.
for node in self.nodelist_loop: for node in self.nodelist_loop:
for chunk in node.iter_render(context): nodelist.append(node.render(context))
yield chunk
if unpack: if unpack:
# The loop variables were pushed on to the context so pop them # The loop variables were pushed on to the context so pop them
# off again. This is necessary because the tag lets the length # off again. This is necessary because the tag lets the length
@ -151,6 +139,7 @@ class ForNode(Node):
# context. # context.
context.pop() context.pop()
context.pop() context.pop()
return nodelist.render(context)
class IfChangedNode(Node): class IfChangedNode(Node):
def __init__(self, nodelist, *varlist): def __init__(self, nodelist, *varlist):
@ -158,7 +147,7 @@ class IfChangedNode(Node):
self._last_seen = None self._last_seen = None
self._varlist = varlist self._varlist = varlist
def iter_render(self, context): def render(self, context):
if 'forloop' in context and context['forloop']['first']: if 'forloop' in context and context['forloop']['first']:
self._last_seen = None self._last_seen = None
try: try:
@ -176,9 +165,11 @@ class IfChangedNode(Node):
self._last_seen = compare_to self._last_seen = compare_to
context.push() context.push()
context['ifchanged'] = {'firstloop': firstloop} context['ifchanged'] = {'firstloop': firstloop}
for chunk in self.nodelist.iter_render(context): content = self.nodelist.render(context)
yield chunk
context.pop() context.pop()
return content
else:
return ''
class IfEqualNode(Node): class IfEqualNode(Node):
def __init__(self, var1, var2, nodelist_true, nodelist_false, negate): def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
@ -189,7 +180,7 @@ class IfEqualNode(Node):
def __repr__(self): def __repr__(self):
return "<IfEqualNode>" return "<IfEqualNode>"
def iter_render(self, context): def render(self, context):
try: try:
val1 = resolve_variable(self.var1, context) val1 = resolve_variable(self.var1, context)
except VariableDoesNotExist: except VariableDoesNotExist:
@ -199,8 +190,8 @@ class IfEqualNode(Node):
except VariableDoesNotExist: except VariableDoesNotExist:
val2 = None val2 = None
if (self.negate and val1 != val2) or (not self.negate and val1 == val2): if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
return self.nodelist_true.iter_render(context) return self.nodelist_true.render(context)
return self.nodelist_false.iter_render(context) return self.nodelist_false.render(context)
class IfNode(Node): class IfNode(Node):
def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type): def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
@ -225,7 +216,7 @@ class IfNode(Node):
nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype)) nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
return nodes return nodes
def iter_render(self, context): def render(self, context):
if self.link_type == IfNode.LinkTypes.or_: if self.link_type == IfNode.LinkTypes.or_:
for ifnot, bool_expr in self.bool_exprs: for ifnot, bool_expr in self.bool_exprs:
try: try:
@ -233,8 +224,8 @@ class IfNode(Node):
except VariableDoesNotExist: except VariableDoesNotExist:
value = None value = None
if (value and not ifnot) or (ifnot and not value): if (value and not ifnot) or (ifnot and not value):
return self.nodelist_true.iter_render(context) return self.nodelist_true.render(context)
return self.nodelist_false.iter_render(context) return self.nodelist_false.render(context)
else: else:
for ifnot, bool_expr in self.bool_exprs: for ifnot, bool_expr in self.bool_exprs:
try: try:
@ -242,8 +233,8 @@ class IfNode(Node):
except VariableDoesNotExist: except VariableDoesNotExist:
value = None value = None
if not ((value and not ifnot) or (ifnot and not value)): if not ((value and not ifnot) or (ifnot and not value)):
return self.nodelist_false.iter_render(context) return self.nodelist_false.render(context)
return self.nodelist_true.iter_render(context) return self.nodelist_true.render(context)
class LinkTypes: class LinkTypes:
and_ = 0, and_ = 0,
@ -254,16 +245,16 @@ class RegroupNode(Node):
self.target, self.expression = target, expression self.target, self.expression = target, expression
self.var_name = var_name self.var_name = var_name
def iter_render(self, context): def render(self, context):
obj_list = self.target.resolve(context, True) obj_list = self.target.resolve(context, True)
if obj_list == None: # target_var wasn't found in context; fail silently if obj_list == None: # target_var wasn't found in context; fail silently
context[self.var_name] = [] context[self.var_name] = []
return () return ''
# List of dictionaries in the format # List of dictionaries in the format
# {'grouper': 'key', 'list': [list of contents]}. # {'grouper': 'key', 'list': [list of contents]}.
context[self.var_name] = [{'grouper':key, 'list':list(val)} for key, val in context[self.var_name] = [{'grouper':key, 'list':list(val)} for key, val in
groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))] groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))]
return () return ''
def include_is_allowed(filepath): def include_is_allowed(filepath):
for root in settings.ALLOWED_INCLUDE_ROOTS: for root in settings.ALLOWED_INCLUDE_ROOTS:
@ -275,10 +266,10 @@ class SsiNode(Node):
def __init__(self, filepath, parsed): def __init__(self, filepath, parsed):
self.filepath, self.parsed = filepath, parsed self.filepath, self.parsed = filepath, parsed
def iter_render(self, context): def render(self, context):
if not include_is_allowed(self.filepath): if not include_is_allowed(self.filepath):
if settings.DEBUG: if settings.DEBUG:
return ("[Didn't have permission to include file]",) return "[Didn't have permission to include file]"
else: else:
return '' # Fail silently for invalid includes. return '' # Fail silently for invalid includes.
try: try:
@ -289,25 +280,23 @@ class SsiNode(Node):
output = '' output = ''
if self.parsed: if self.parsed:
try: try:
return Template(output, name=self.filepath).iter_render(context) t = Template(output, name=self.filepath)
return t.render(context)
except TemplateSyntaxError, e: except TemplateSyntaxError, e:
if settings.DEBUG: if settings.DEBUG:
return "[Included template had syntax error: %s]" % e return "[Included template had syntax error: %s]" % e
else: else:
return '' # Fail silently for invalid included templates. return '' # Fail silently for invalid included templates.
return (output,) return output
class LoadNode(Node): class LoadNode(Node):
def iter_render(self, context): def render(self, context):
return () return ''
class NowNode(Node): class NowNode(Node):
def __init__(self, format_string): def __init__(self, format_string):
self.format_string = format_string self.format_string = format_string
def iter_render(self, context):
return (self.render(context),)
def render(self, context): def render(self, context):
from datetime import datetime from datetime import datetime
from django.utils.dateformat import DateFormat from django.utils.dateformat import DateFormat
@ -336,9 +325,6 @@ class TemplateTagNode(Node):
def __init__(self, tagtype): def __init__(self, tagtype):
self.tagtype = tagtype self.tagtype = tagtype
def iter_render(self, context):
return (self.render(context),)
def render(self, context): def render(self, context):
return self.mapping.get(self.tagtype, '') return self.mapping.get(self.tagtype, '')
@ -348,18 +334,18 @@ class URLNode(Node):
self.args = args self.args = args
self.kwargs = kwargs self.kwargs = kwargs
def iter_render(self, context): def render(self, context):
from django.core.urlresolvers import reverse, NoReverseMatch from django.core.urlresolvers import reverse, NoReverseMatch
args = [arg.resolve(context) for arg in self.args] args = [arg.resolve(context) for arg in self.args]
kwargs = dict([(k, v.resolve(context)) for k, v in self.kwargs.items()]) kwargs = dict([(k, v.resolve(context)) for k, v in self.kwargs.items()])
try: try:
return (reverse(self.view_name, args=args, kwargs=kwargs),) return reverse(self.view_name, args=args, kwargs=kwargs)
except NoReverseMatch: except NoReverseMatch:
try: try:
project_name = settings.SETTINGS_MODULE.split('.')[0] project_name = settings.SETTINGS_MODULE.split('.')[0]
return reverse(project_name + '.' + self.view_name, args=args, kwargs=kwargs) return reverse(project_name + '.' + self.view_name, args=args, kwargs=kwargs)
except NoReverseMatch: except NoReverseMatch:
return () return ''
class WidthRatioNode(Node): class WidthRatioNode(Node):
def __init__(self, val_expr, max_expr, max_width): def __init__(self, val_expr, max_expr, max_width):
@ -367,9 +353,6 @@ class WidthRatioNode(Node):
self.max_expr = max_expr self.max_expr = max_expr
self.max_width = max_width self.max_width = max_width
def iter_render(self, context):
return (self.render(context),)
def render(self, context): def render(self, context):
try: try:
value = self.val_expr.resolve(context) value = self.val_expr.resolve(context)
@ -393,13 +376,13 @@ class WithNode(Node):
def __repr__(self): def __repr__(self):
return "<WithNode>" return "<WithNode>"
def iter_render(self, context): def render(self, context):
val = self.var.resolve(context) val = self.var.resolve(context)
context.push() context.push()
context[self.name] = val context[self.name] = val
for chunk in self.nodelist.iter_render(context): output = self.nodelist.render(context)
yield chunk
context.pop() context.pop()
return output
#@register.tag #@register.tag
def comment(parser, token): def comment(parser, token):

View File

@ -87,12 +87,14 @@ def get_template_from_string(source, origin=None, name=None):
""" """
return Template(source, origin, name) return Template(source, origin, name)
def _render_setup(template_name, dictionary=None, context_instance=None): def render_to_string(template_name, dictionary=None, context_instance=None):
""" """
Common setup code for render_to_string and render_to_iter. Loads the given template_name and renders it with the given dictionary as
context. The template_name may be a string to load a single template using
get_template, or it may be a tuple to use select_template to find one of
the templates in the list. Returns a string.
""" """
if dictionary is None: dictionary = dictionary or {}
dictionary = {}
if isinstance(template_name, (list, tuple)): if isinstance(template_name, (list, tuple)):
t = select_template(template_name) t = select_template(template_name)
else: else:
@ -101,28 +103,7 @@ def _render_setup(template_name, dictionary=None, context_instance=None):
context_instance.update(dictionary) context_instance.update(dictionary)
else: else:
context_instance = Context(dictionary) context_instance = Context(dictionary)
return t, context_instance return t.render(context_instance)
def render_to_string(template_name, dictionary=None, context_instance=None):
"""
Loads the given template_name and renders it with the given dictionary as
context. The template_name may be a string to load a single template using
get_template, or it may be a tuple to use select_template to find one of
the templates in the list. Returns a string.
"""
t, c = _render_setup(template_name, dictionary=dictionary, context_instance=context_instance)
return t.render(c)
def render_to_iter(template_name, dictionary=None, context_instance=None):
"""
Loads the given template_name and renders it with the given dictionary as
context. The template_name may be a string to load a single template using
get_template, or it may be a tuple to use select_template to find one of
the templates in the list. Returns a string.
"""
t, c = _render_setup(template_name, dictionary=dictionary, context_instance=context_instance)
return t.iter_render(c)
def select_template(template_name_list): def select_template(template_name_list):
"Given a list of template names, returns the first that can be loaded." "Given a list of template names, returns the first that can be loaded."

View File

@ -15,14 +15,14 @@ class BlockNode(Node):
def __repr__(self): def __repr__(self):
return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist) return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
def iter_render(self, context): def render(self, context):
context.push() context.push()
# Save context in case of block.super(). # Save context in case of block.super().
self.context = context self.context = context
context['block'] = self context['block'] = self
for chunk in self.nodelist.iter_render(context): result = self.nodelist.render(context)
yield chunk
context.pop() context.pop()
return result
def super(self): def super(self):
if self.parent: if self.parent:
@ -59,7 +59,7 @@ class ExtendsNode(Node):
else: else:
return get_template_from_string(source, origin, parent) return get_template_from_string(source, origin, parent)
def iter_render(self, context): def render(self, context):
compiled_parent = self.get_parent(context) compiled_parent = self.get_parent(context)
parent_is_child = isinstance(compiled_parent.nodelist[0], ExtendsNode) parent_is_child = isinstance(compiled_parent.nodelist[0], ExtendsNode)
parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)]) parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
@ -79,7 +79,7 @@ class ExtendsNode(Node):
parent_block.parent = block_node.parent parent_block.parent = block_node.parent
parent_block.add_parent(parent_block.nodelist) parent_block.add_parent(parent_block.nodelist)
parent_block.nodelist = block_node.nodelist parent_block.nodelist = block_node.nodelist
return compiled_parent.iter_render(context) return compiled_parent.render(context)
class ConstantIncludeNode(Node): class ConstantIncludeNode(Node):
def __init__(self, template_path): def __init__(self, template_path):
@ -91,26 +91,27 @@ class ConstantIncludeNode(Node):
raise raise
self.template = None self.template = None
def iter_render(self, context): def render(self, context):
if self.template: if self.template:
return self.template.iter_render(context) return self.template.render(context)
return () else:
return ''
class IncludeNode(Node): class IncludeNode(Node):
def __init__(self, template_name): def __init__(self, template_name):
self.template_name = template_name self.template_name = template_name
def iter_render(self, context): def render(self, context):
try: try:
template_name = resolve_variable(self.template_name, context) template_name = resolve_variable(self.template_name, context)
t = get_template(template_name) t = get_template(template_name)
return t.iter_render(context) return t.render(context)
except TemplateSyntaxError, e: except TemplateSyntaxError, e:
if settings.TEMPLATE_DEBUG: if settings.TEMPLATE_DEBUG:
raise raise
return () return ''
except: except:
return () # Fail silently for invalid included templates. return '' # Fail silently for invalid included templates.
def do_block(parser, token): def do_block(parser, token):
""" """

View File

@ -1,6 +1,7 @@
import sys, time import sys, time
from django.conf import settings from django.conf import settings
from django.db import connection, transaction, backend from django.db import connection, backend, get_creation_module
from django.core import management, mail
from django.core import management, mail from django.core import management, mail
from django.dispatch import dispatcher from django.dispatch import dispatcher
from django.test import signals from django.test import signals
@ -11,21 +12,12 @@ from django.template import Template
TEST_DATABASE_PREFIX = 'test_' TEST_DATABASE_PREFIX = 'test_'
def instrumented_test_render(self, context): def instrumented_test_render(self, context):
""" """An instrumented Template render method, providing a signal
An instrumented Template render method, providing a signal that can be that can be intercepted by the test system Client
intercepted by the test system Client.
""" """
dispatcher.send(signal=signals.template_rendered, sender=self, template=self, context=context) dispatcher.send(signal=signals.template_rendered, sender=self, template=self, context=context)
return self.nodelist.render(context) return self.nodelist.render(context)
def instrumented_test_iter_render(self, context):
"""
An instrumented Template iter_render method, providing a signal that can be
intercepted by the test system Client.
"""
for chunk in self.nodelist.iter_render(context):
yield chunk
dispatcher.send(signal=signals.template_rendered, sender=self, template=self, context=context)
class TestSMTPConnection(object): class TestSMTPConnection(object):
"""A substitute SMTP connection for use during test sessions. """A substitute SMTP connection for use during test sessions.
@ -53,9 +45,7 @@ def setup_test_environment():
""" """
Template.original_render = Template.render Template.original_render = Template.render
Template.original_iter_render = Template.iter_render
Template.render = instrumented_test_render Template.render = instrumented_test_render
Template.iter_render = instrumented_test_render
mail.original_SMTPConnection = mail.SMTPConnection mail.original_SMTPConnection = mail.SMTPConnection
mail.SMTPConnection = TestSMTPConnection mail.SMTPConnection = TestSMTPConnection
@ -70,8 +60,7 @@ def teardown_test_environment():
""" """
Template.render = Template.original_render Template.render = Template.original_render
Template.iter_render = Template.original_iter_render del Template.original_render
del Template.original_render, Template.original_iter_render
mail.SMTPConnection = mail.original_SMTPConnection mail.SMTPConnection = mail.original_SMTPConnection
del mail.original_SMTPConnection del mail.original_SMTPConnection
@ -100,6 +89,12 @@ def get_postgresql_create_suffix():
return '' return ''
def create_test_db(verbosity=1, autoclobber=False): def create_test_db(verbosity=1, autoclobber=False):
# If the database backend wants to create the test DB itself, let it
creation_module = get_creation_module()
if hasattr(creation_module, "create_test_db"):
creation_module.create_test_db(settings, connection, backend, verbosity, autoclobber)
return
if verbosity >= 1: if verbosity >= 1:
print "Creating test database..." print "Creating test database..."
# If we're using SQLite, it's more convenient to test against an # If we're using SQLite, it's more convenient to test against an
@ -154,6 +149,12 @@ def create_test_db(verbosity=1, autoclobber=False):
cursor = connection.cursor() cursor = connection.cursor()
def destroy_test_db(old_database_name, verbosity=1): def destroy_test_db(old_database_name, verbosity=1):
# If the database wants to drop the test DB itself, let it
creation_module = get_creation_module()
if hasattr(creation_module, "destroy_test_db"):
creation_module.destroy_test_db(settings, connection, backend, old_database_name, verbosity)
return
# Unless we're using SQLite, remove the test database to clean up after # Unless we're using SQLite, remove the test database to clean up after
# ourselves. Connect to the previous database (not the test database) # ourselves. Connect to the previous database (not the test database)
# to do so, because it's not allowed to delete a database while being # to do so, because it's not allowed to delete a database while being

View File

@ -3,6 +3,21 @@ def curry(_curried_func, *args, **kwargs):
return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs)) return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs))
return _curried return _curried
def memoize(func, cache):
"""
Wrap a function so that results for any argument tuple are stored in
'cache'. Note that the args to the function must be usable as dictionary
keys.
"""
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
class Promise: class Promise:
""" """
This is just a base class for the proxy class created in This is just a base class for the proxy class created in

View File

@ -53,16 +53,18 @@ def fix_ampersands(value):
def urlize(text, trim_url_limit=None, nofollow=False): def urlize(text, trim_url_limit=None, nofollow=False):
""" """
Converts any URLs in text into clickable links. Works on http://, https:// and Converts any URLs in text into clickable links. Works on http://, https://
www. links. Links can have trailing punctuation (periods, commas, close-parens) and www. links. Links can have trailing punctuation (periods, commas,
and leading punctuation (opening parens) and it'll still do the right thing. close-parens) and leading punctuation (opening parens) and it'll still do
the right thing.
If trim_url_limit is not None, the URLs in link text will be limited to If trim_url_limit is not None, the URLs in link text longer than this limit
trim_url_limit characters. will truncated to trim_url_limit-3 characters and appended with an elipsis.
If nofollow is True, the URLs in link text will get a rel="nofollow" attribute. If nofollow is True, the URLs in link text will get a rel="nofollow"
attribute.
""" """
trim_url = lambda x, limit=trim_url_limit: limit is not None and (x[:limit] + (len(x) >=limit and '...' or '')) or x trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
words = word_split_re.split(text) words = word_split_re.split(text)
nofollow_attr = nofollow and ' rel="nofollow"' or '' nofollow_attr = nofollow and ' rel="nofollow"' or ''
for i, word in enumerate(words): for i, word in enumerate(words):

View File

@ -34,7 +34,7 @@ def groupby(iterable, keyfunc=None):
keyfunc = lambda x:x keyfunc = lambda x:x
iterable = iter(iterable) iterable = iter(iterable)
l = [iterable.next()] l = [iterable.next()]
lastkey = keyfunc(l) lastkey = keyfunc(l[0])
for item in iterable: for item in iterable:
key = keyfunc(item) key = keyfunc(item)
if key != lastkey: if key != lastkey:
@ -45,6 +45,12 @@ def groupby(iterable, keyfunc=None):
l.append(item) l.append(item)
yield lastkey, l yield lastkey, l
# Not really in itertools, since it's a builtin in Python 2.4 and later, but it
# does operate as an iterator.
def reversed(data):
for index in xrange(len(data)-1, -1, -1):
yield data[index]
if hasattr(itertools, 'tee'): if hasattr(itertools, 'tee'):
tee = itertools.tee tee = itertools.tee
else: else:

View File

@ -137,7 +137,7 @@ def technical_500_response(request, exc_type, exc_value, tb):
'template_does_not_exist': template_does_not_exist, 'template_does_not_exist': template_does_not_exist,
'loader_debug_info': loader_debug_info, 'loader_debug_info': loader_debug_info,
}) })
return HttpResponseServerError(t.iter_render(c), mimetype='text/html') return HttpResponseServerError(t.render(c), mimetype='text/html')
def technical_404_response(request, exception): def technical_404_response(request, exception):
"Create a technical 404 error response. The exception should be the Http404." "Create a technical 404 error response. The exception should be the Http404."
@ -160,7 +160,7 @@ def technical_404_response(request, exception):
'request_protocol': request.is_secure() and "https" or "http", 'request_protocol': request.is_secure() and "https" or "http",
'settings': get_safe_settings(), 'settings': get_safe_settings(),
}) })
return HttpResponseNotFound(t.iter_render(c), mimetype='text/html') return HttpResponseNotFound(t.render(c), mimetype='text/html')
def empty_urlconf(request): def empty_urlconf(request):
"Create an empty URLconf 404 error response." "Create an empty URLconf 404 error response."
@ -168,7 +168,7 @@ def empty_urlconf(request):
c = Context({ c = Context({
'project_name': settings.SETTINGS_MODULE.split('.')[0] 'project_name': settings.SETTINGS_MODULE.split('.')[0]
}) })
return HttpResponseNotFound(t.iter_render(c), mimetype='text/html') return HttpResponseNotFound(t.render(c), mimetype='text/html')
def _get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None): def _get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None):
""" """

View File

@ -76,7 +76,7 @@ def page_not_found(request, template_name='404.html'):
The path of the requested URL (e.g., '/app/pages/bad_page/') The path of the requested URL (e.g., '/app/pages/bad_page/')
""" """
t = loader.get_template(template_name) # You need to create a 404.html template. t = loader.get_template(template_name) # You need to create a 404.html template.
return http.HttpResponseNotFound(t.iter_render(RequestContext(request, {'request_path': request.path}))) return http.HttpResponseNotFound(t.render(RequestContext(request, {'request_path': request.path})))
def server_error(request, template_name='500.html'): def server_error(request, template_name='500.html'):
""" """
@ -86,4 +86,4 @@ def server_error(request, template_name='500.html'):
Context: None Context: None
""" """
t = loader.get_template(template_name) # You need to create a 500.html template. t = loader.get_template(template_name) # You need to create a 500.html template.
return http.HttpResponseServerError(t.iter_render(Context({}))) return http.HttpResponseServerError(t.render(Context({})))

View File

@ -68,7 +68,7 @@ def create_object(request, model, template_name=None,
c[key] = value() c[key] = value()
else: else:
c[key] = value c[key] = value
return HttpResponse(t.iter_render(c)) return HttpResponse(t.render(c))
def update_object(request, model, object_id=None, slug=None, def update_object(request, model, object_id=None, slug=None,
slug_field=None, template_name=None, template_loader=loader, slug_field=None, template_name=None, template_loader=loader,
@ -141,7 +141,7 @@ def update_object(request, model, object_id=None, slug=None,
c[key] = value() c[key] = value()
else: else:
c[key] = value c[key] = value
response = HttpResponse(t.iter_render(c)) response = HttpResponse(t.render(c))
populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname)) populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname))
return response return response
@ -195,6 +195,6 @@ def delete_object(request, model, post_delete_redirect,
c[key] = value() c[key] = value()
else: else:
c[key] = value c[key] = value
response = HttpResponse(t.iter_render(c)) response = HttpResponse(t.render(c))
populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname)) populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname))
return response return response

View File

@ -44,7 +44,7 @@ def archive_index(request, queryset, date_field, num_latest=15,
c[key] = value() c[key] = value()
else: else:
c[key] = value c[key] = value
return HttpResponse(t.iter_render(c), mimetype=mimetype) return HttpResponse(t.render(c), mimetype=mimetype)
def archive_year(request, year, queryset, date_field, template_name=None, def archive_year(request, year, queryset, date_field, template_name=None,
template_loader=loader, extra_context=None, allow_empty=False, template_loader=loader, extra_context=None, allow_empty=False,
@ -92,7 +92,7 @@ def archive_year(request, year, queryset, date_field, template_name=None,
c[key] = value() c[key] = value()
else: else:
c[key] = value c[key] = value
return HttpResponse(t.iter_render(c), mimetype=mimetype) return HttpResponse(t.render(c), mimetype=mimetype)
def archive_month(request, year, month, queryset, date_field, def archive_month(request, year, month, queryset, date_field,
month_format='%b', template_name=None, template_loader=loader, month_format='%b', template_name=None, template_loader=loader,
@ -158,7 +158,7 @@ def archive_month(request, year, month, queryset, date_field,
c[key] = value() c[key] = value()
else: else:
c[key] = value c[key] = value
return HttpResponse(t.iter_render(c), mimetype=mimetype) return HttpResponse(t.render(c), mimetype=mimetype)
def archive_week(request, year, week, queryset, date_field, def archive_week(request, year, week, queryset, date_field,
template_name=None, template_loader=loader, template_name=None, template_loader=loader,
@ -206,7 +206,7 @@ def archive_week(request, year, week, queryset, date_field,
c[key] = value() c[key] = value()
else: else:
c[key] = value c[key] = value
return HttpResponse(t.iter_render(c), mimetype=mimetype) return HttpResponse(t.render(c), mimetype=mimetype)
def archive_day(request, year, month, day, queryset, date_field, def archive_day(request, year, month, day, queryset, date_field,
month_format='%b', day_format='%d', template_name=None, month_format='%b', day_format='%d', template_name=None,
@ -270,7 +270,7 @@ def archive_day(request, year, month, day, queryset, date_field,
c[key] = value() c[key] = value()
else: else:
c[key] = value c[key] = value
return HttpResponse(t.iter_render(c), mimetype=mimetype) return HttpResponse(t.render(c), mimetype=mimetype)
def archive_today(request, **kwargs): def archive_today(request, **kwargs):
""" """
@ -339,6 +339,6 @@ def object_detail(request, year, month, day, queryset, date_field,
c[key] = value() c[key] = value()
else: else:
c[key] = value c[key] = value
response = HttpResponse(t.iter_render(c), mimetype=mimetype) response = HttpResponse(t.render(c), mimetype=mimetype)
populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name)) populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
return response return response

View File

@ -84,7 +84,7 @@ def object_list(request, queryset, paginate_by=None, page=None,
model = queryset.model model = queryset.model
template_name = "%s/%s_list.html" % (model._meta.app_label, model._meta.object_name.lower()) template_name = "%s/%s_list.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name) t = template_loader.get_template(template_name)
return HttpResponse(t.iter_render(c), mimetype=mimetype) return HttpResponse(t.render(c), mimetype=mimetype)
def object_detail(request, queryset, object_id=None, slug=None, def object_detail(request, queryset, object_id=None, slug=None,
slug_field=None, template_name=None, template_name_field=None, slug_field=None, template_name=None, template_name_field=None,
@ -126,6 +126,6 @@ def object_detail(request, queryset, object_id=None, slug=None,
c[key] = value() c[key] = value()
else: else:
c[key] = value c[key] = value
response = HttpResponse(t.iter_render(c), mimetype=mimetype) response = HttpResponse(t.render(c), mimetype=mimetype)
populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name)) populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
return response return response

View File

@ -15,7 +15,7 @@ def direct_to_template(request, template, extra_context={}, mimetype=None, **kwa
dictionary[key] = value dictionary[key] = value
c = RequestContext(request, dictionary) c = RequestContext(request, dictionary)
t = loader.get_template(template) t = loader.get_template(template)
return HttpResponse(t.iter_render(c), mimetype=mimetype) return HttpResponse(t.render(c), mimetype=mimetype)
def redirect_to(request, url, **kwargs): def redirect_to(request, url, **kwargs):
""" """

View File

@ -92,7 +92,7 @@ def directory_index(path, fullpath):
'directory' : path + '/', 'directory' : path + '/',
'file_list' : files, 'file_list' : files,
}) })
return HttpResponse(t.iter_render(c)) return HttpResponse(t.render(c))
def was_modified_since(header=None, mtime=0, size=0): def was_modified_since(header=None, mtime=0, size=0):
""" """

View File

@ -325,7 +325,7 @@ Manually checking a user's password
If you'd like to manually authenticate a user by comparing a If you'd like to manually authenticate a user by comparing a
plain-text password to the hashed password in the database, use the plain-text password to the hashed password in the database, use the
convenience function `django.contrib.auth.models.check_password`. It convenience function ``django.contrib.auth.models.check_password``. It
takes two arguments: the plain-text password to check, and the full takes two arguments: the plain-text password to check, and the full
value of a user's ``password`` field in the database to check against, value of a user's ``password`` field in the database to check against,
and returns ``True`` if they match, ``False`` otherwise. and returns ``True`` if they match, ``False`` otherwise.

View File

@ -104,7 +104,7 @@ Lawrence, Kansas, USA.
`Wilson Miner`_ `Wilson Miner`_
Wilson's design-fu makes us all look like rock stars. By day, he's an Wilson's design-fu makes us all look like rock stars. By day, he's an
interactive designer for `Apple`. Don't ask him what he's working on, or interactive designer for `Apple`_. Don't ask him what he's working on, or
he'll have to kill you. He lives in San Francisco. he'll have to kill you. He lives in San Francisco.
On IRC, Wilson goes by ``wilsonian``. On IRC, Wilson goes by ``wilsonian``.
@ -301,7 +301,7 @@ means it can run on a variety of server platforms.
If you want to use Django with a database, which is probably the case, you'll If you want to use Django with a database, which is probably the case, you'll
also need a database engine. PostgreSQL_ is recommended, because we're also need a database engine. PostgreSQL_ is recommended, because we're
PostgreSQL fans, and MySQL_ and `SQLite 3`_ are also supported. PostgreSQL fans, and MySQL_, `SQLite 3`_, and Oracle_ are also supported.
.. _Python: http://www.python.org/ .. _Python: http://www.python.org/
.. _Apache 2: http://httpd.apache.org/ .. _Apache 2: http://httpd.apache.org/
@ -310,6 +310,7 @@ PostgreSQL fans, and MySQL_ and `SQLite 3`_ are also supported.
.. _PostgreSQL: http://www.postgresql.org/ .. _PostgreSQL: http://www.postgresql.org/
.. _MySQL: http://www.mysql.com/ .. _MySQL: http://www.mysql.com/
.. _`SQLite 3`: http://www.sqlite.org/ .. _`SQLite 3`: http://www.sqlite.org/
.. _Oracle: http://www.oracle.com/
Do I lose anything by using Python 2.3 versus newer Python versions, such as Python 2.5? Do I lose anything by using Python 2.3 versus newer Python versions, such as Python 2.5?
---------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------

View File

@ -754,10 +754,10 @@ If the results are paginated, the context will contain these extra variables:
* ``previous``: The previous page number, as an integer. This is 1-based. * ``previous``: The previous page number, as an integer. This is 1-based.
* `last_on_page`: The number of the * ``last_on_page``: The number of the
last result on the current page. This is 1-based. last result on the current page. This is 1-based.
* `first_on_page`: The number of the * ``first_on_page``: The number of the
first result on the current page. This is 1-based. first result on the current page. This is 1-based.
* ``pages``: The total number of pages, as an integer. * ``pages``: The total number of pages, as an integer.

View File

@ -17,8 +17,10 @@ probably already have it installed.
Install Apache and mod_python Install Apache and mod_python
============================= =============================
If you just want to experiment with Django, skip this step. Django comes with If you just want to experiment with Django, skip ahead to the next
its own Web server for development purposes. section; Django includes a lightweight web server you can use for
testing, so you won't need to set up Apache until you're ready to
deploy Django in production.
If you want to use Django on a production site, use Apache with `mod_python`_. If you want to use Django on a production site, use Apache with `mod_python`_.
mod_python is similar to mod_perl -- it embeds Python within Apache and loads mod_python is similar to mod_perl -- it embeds Python within Apache and loads
@ -62,6 +64,8 @@ installed.
* If you're using SQLite, you'll need pysqlite_. Use version 2.0.3 or higher. * If you're using SQLite, you'll need pysqlite_. Use version 2.0.3 or higher.
* If you're using Oracle, you'll need cx_Oracle_, version 4.3.1 or higher.
.. _PostgreSQL: http://www.postgresql.org/ .. _PostgreSQL: http://www.postgresql.org/
.. _MySQL: http://www.mysql.com/ .. _MySQL: http://www.mysql.com/
.. _Django's ticket system: http://code.djangoproject.com/report/1 .. _Django's ticket system: http://code.djangoproject.com/report/1
@ -71,6 +75,7 @@ installed.
.. _SQLite: http://www.sqlite.org/ .. _SQLite: http://www.sqlite.org/
.. _pysqlite: http://initd.org/tracker/pysqlite .. _pysqlite: http://initd.org/tracker/pysqlite
.. _MySQL backend: ../databases/ .. _MySQL backend: ../databases/
.. _cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/
Remove any old versions of Django Remove any old versions of Django
================================= =================================
@ -83,23 +88,20 @@ If you installed Django using ``setup.py install``, uninstalling
is as simple as deleting the ``django`` directory from your Python is as simple as deleting the ``django`` directory from your Python
``site-packages``. ``site-packages``.
If you installed Django from a Python Egg, remove the Django ``.egg`` file, If you installed Django from a Python egg, remove the Django ``.egg`` file,
and remove the reference to the egg in the file named ``easy-install.pth``. and remove the reference to the egg in the file named ``easy-install.pth``.
This file should also be located in your ``site-packages`` directory. This file should also be located in your ``site-packages`` directory.
.. admonition:: Where are my ``site-packages`` stored? .. admonition:: Where are my ``site-packages`` stored?
The location of the ``site-packages`` directory depends on the operating The location of the ``site-packages`` directory depends on the operating
system, and the location in which Python was installed. However, the system, and the location in which Python was installed. To find out your
following locations are common: system's ``site-packages`` location, execute the following::
* If you're using Linux: ``/usr/lib/python2.X/site-packages`` python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()"
* If you're using Windows: ``C:\Python2.X\lib\site-packages`` (Note that this should be run from a shell prompt, not a Python interactive
prompt.)
* If you're using MacOSX: ``/Library/Python2.X/site-packages`` or
``/Library/Frameworks/Python.framework/Versions/2.X/lib/python2.X/site-packages/``
(in later releases).
Install the Django code Install the Django code
======================= =======================
@ -138,12 +140,15 @@ latest bug fixes and improvements, follow these instructions:
1. Make sure you have Subversion_ installed. 1. Make sure you have Subversion_ installed.
2. Check out the Django code into your Python ``site-packages`` directory. 2. Check out the Django code into your Python ``site-packages`` directory.
On Linux / Mac OSX / Unix, do this:: On Linux / Mac OSX / Unix, do this::
svn co http://code.djangoproject.com/svn/django/trunk/ django_src svn co http://code.djangoproject.com/svn/django/trunk/ django_src
ln -s `pwd`/django_src/django /usr/lib/python2.3/site-packages/django ln -s `pwd`/django_src/django SITE-PACKAGES-DIR/django
(In the above line, change ``python2.3`` to match your current Python version.) (In the above line, change ``SITE-PACKAGES-DIR`` to match the location of
your system's ``site-packages`` directory, as explained in the
"Where are my ``site-packages`` stored?" section above.)
On Windows, do this:: On Windows, do this::

View File

@ -492,6 +492,11 @@ has ``null=True``, that means it has two possible values for "no data":
possible values for "no data;" Django convention is to use the empty possible values for "no data;" Django convention is to use the empty
string, not ``NULL``. string, not ``NULL``.
.. note::
Due to database limitations, when using the Oracle backend the
``null=True`` option will be coerced for string-based fields that can
blank, and the value ``NULL`` will be stored to denote the empty string.
``blank`` ``blank``
~~~~~~~~~ ~~~~~~~~~
@ -586,6 +591,13 @@ scenes.
If ``True``, ``django-admin.py sqlindexes`` will output a ``CREATE INDEX`` If ``True``, ``django-admin.py sqlindexes`` will output a ``CREATE INDEX``
statement for this field. statement for this field.
``db_tablespace``
~~~~~~~~~~~~~~~~~
If this field is indexed, the name of the database tablespace to use for the
index. The default is the ``db_tablespace`` of the model, if any. If the
backend doesn't support tablespaces, this option is ignored.
``default`` ``default``
~~~~~~~~~~~ ~~~~~~~~~~~
@ -996,6 +1008,12 @@ If your database table name is an SQL reserved word, or contains characters
that aren't allowed in Python variable names -- notably, the hyphen -- that aren't allowed in Python variable names -- notably, the hyphen --
that's OK. Django quotes column and table names behind the scenes. that's OK. Django quotes column and table names behind the scenes.
``db_tablespace``
-----------------
The name of the database tablespace to use for the model. If the backend
doesn't support tablespaces, this option is ignored.
``get_latest_by`` ``get_latest_by``
----------------- -----------------
@ -1876,11 +1894,11 @@ used by the SQLite Python bindings. This is for the sake of consistency and
sanity.) sanity.)
A final note: If all you want to do is a custom ``WHERE`` clause, you can just A final note: If all you want to do is a custom ``WHERE`` clause, you can just
just the ``where``, ``tables`` and ``params`` arguments to the standard lookup use the ``where``, ``tables`` and ``params`` arguments to the standard lookup
API. See `Other lookup options`_. API. See `Other lookup options`_.
.. _Python DB-API: http://www.python.org/peps/pep-0249.html .. _Python DB-API: http://www.python.org/peps/pep-0249.html
.. _Other lookup options: ../db-api/#extra-params-select-where-tables .. _Other lookup options: ../db-api/#extra-select-none-where-none-params-none-tables-none
.. _transaction handling: ../transactions/ .. _transaction handling: ../transactions/
Overriding default model methods Overriding default model methods

View File

@ -110,7 +110,7 @@ shortly.
Creating ``Form`` instances Creating ``Form`` instances
--------------------------- ---------------------------
A ``Form`` instance is either **bound** or **unbound** to a set of data. A ``Form`` instance is either **bound** to a set of data, or **unbound**.
* If it's **bound** to a set of data, it's capable of validating that data * If it's **bound** to a set of data, it's capable of validating that data
and rendering the form as HTML with the data displayed in the HTML. and rendering the form as HTML with the data displayed in the HTML.

View File

@ -244,9 +244,9 @@ DATABASE_ENGINE
Default: ``''`` (Empty string) Default: ``''`` (Empty string)
Which database backend to use. Either ``'postgresql_psycopg2'``, The database backend to use. Either ``'postgresql_psycopg2'``,
``'postgresql'``, ``'mysql'``, ``'mysql_old'``, ``'sqlite3'`` or ``'postgresql'``, ``'mysql'``, ``'mysql_old'``, ``'sqlite3'``,
``'ado_mssql'``. ``'oracle'``, or ``'ado_mssql'``.
DATABASE_HOST DATABASE_HOST
------------- -------------

View File

@ -1266,7 +1266,8 @@ Converts URLs in plain text into clickable links.
urlizetrunc urlizetrunc
~~~~~~~~~~~ ~~~~~~~~~~~
Converts URLs into clickable links, truncating URLs to the given character limit. Converts URLs into clickable links, truncating URLs longer than the given
character limit.
**Argument:** Length to truncate URLs to **Argument:** Length to truncate URLs to

View File

@ -219,13 +219,13 @@ be replaced with the name of the invalid variable.
While ``TEMPLATE_STRING_IF_INVALID`` can be a useful debugging tool, While ``TEMPLATE_STRING_IF_INVALID`` can be a useful debugging tool,
it is a bad idea to turn it on as a 'development default'. it is a bad idea to turn it on as a 'development default'.
Many templates, including those in the Admin site, rely upon the Many templates, including those in the Admin site, rely upon the
silence of the template system when a non-existent variable is silence of the template system when a non-existent variable is
encountered. If you assign a value other than ``''`` to encountered. If you assign a value other than ``''`` to
``TEMPLATE_STRING_IF_INVALID``, you will experience rendering ``TEMPLATE_STRING_IF_INVALID``, you will experience rendering
problems with these templates and sites. problems with these templates and sites.
Generally, ``TEMPLATE_STRING_IF_INVALID`` should only be enabled Generally, ``TEMPLATE_STRING_IF_INVALID`` should only be enabled
in order to debug a specific template problem, then cleared in order to debug a specific template problem, then cleared
once debugging is complete. once debugging is complete.
@ -342,7 +342,7 @@ If ``TEMPLATE_CONTEXT_PROCESSORS`` contains this processor, every
* ``user`` -- An ``auth.User`` instance representing the currently * ``user`` -- An ``auth.User`` instance representing the currently
logged-in user (or an ``AnonymousUser`` instance, if the client isn't logged-in user (or an ``AnonymousUser`` instance, if the client isn't
logged in). See the `user authentication docs`. logged in). See the `user authentication docs`_.
* ``messages`` -- A list of messages (as strings) for the currently * ``messages`` -- A list of messages (as strings) for the currently
logged-in user. Behind the scenes, this calls logged-in user. Behind the scenes, this calls
@ -693,15 +693,14 @@ how the compilation works and how the rendering works.
When Django compiles a template, it splits the raw template text into When Django compiles a template, it splits the raw template text into
''nodes''. Each node is an instance of ``django.template.Node`` and has ''nodes''. Each node is an instance of ``django.template.Node`` and has
either a ``render()`` or ``iter_render()`` method. A compiled template is, a ``render()`` method. A compiled template is, simply, a list of ``Node``
simply, a list of ``Node`` objects. When you call ``render()`` on a compiled objects. When you call ``render()`` on a compiled template object, the template
template object, the template calls ``render()`` on each ``Node`` in its node calls ``render()`` on each ``Node`` in its node list, with the given context.
list, with the given context. The results are all concatenated together to The results are all concatenated together to form the output of the template.
form the output of the template.
Thus, to define a custom template tag, you specify how the raw template tag is Thus, to define a custom template tag, you specify how the raw template tag is
converted into a ``Node`` (the compilation function), and what the node's converted into a ``Node`` (the compilation function), and what the node's
``render()`` or ``iter_render()`` method does. ``render()`` method does.
Writing the compilation function Writing the compilation function
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -771,8 +770,7 @@ Writing the renderer
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
The second step in writing custom tags is to define a ``Node`` subclass that The second step in writing custom tags is to define a ``Node`` subclass that
has a ``render()`` method (we will discuss the ``iter_render()`` alternative has a ``render()`` method.
in `Improving rendering speed`_, below).
Continuing the above example, we need to define ``CurrentTimeNode``:: Continuing the above example, we need to define ``CurrentTimeNode``::
@ -876,7 +874,7 @@ current context, available in the ``render`` method::
def __init__(self, date_to_be_formatted, format_string): def __init__(self, date_to_be_formatted, format_string):
self.date_to_be_formatted = date_to_be_formatted self.date_to_be_formatted = date_to_be_formatted
self.format_string = format_string self.format_string = format_string
def render(self, context): def render(self, context):
try: try:
actual_date = resolve_variable(self.date_to_be_formatted, context) actual_date = resolve_variable(self.date_to_be_formatted, context)
@ -1177,48 +1175,6 @@ For more examples of complex rendering, see the source code for ``{% if %}``,
.. _configuration: .. _configuration:
Improving rendering speed
~~~~~~~~~~~~~~~~~~~~~~~~~
For most practical purposes, the ``render()`` method on a ``Node`` will be
sufficient and the simplest way to implement a new tag. However, if your
template tag is expected to produce large strings via ``render()``, you can
speed up the rendering process (and reduce memory usage) using iterative
rendering via the ``iter_render()`` method.
The ``iter_render()`` method should either be an iterator that yields string
chunks, one at a time, or a method that returns a sequence of string chunks.
The template renderer will join the successive chunks together when creating
the final output. The improvement over the ``render()`` method here is that
you do not need to create one large string containing all the output of the
``Node``, instead you can produce the output in smaller chunks.
By way of example, here's a trivial ``Node`` subclass that simply returns the
contents of a file it is given::
class FileNode(Node):
def __init__(self, filename):
self.filename = filename
def iter_render(self):
for line in file(self.filename):
yield line
For very large files, the full file contents will never be read entirely into
memory when this tag is used, which is a useful optimisation.
If you define an ``iter_render()`` method on your ``Node`` subclass, you do
not need to define a ``render()`` method. The reverse is true as well: the
default ``Node.iter_render()`` method will call your ``render()`` method if
necessary. A useful side-effect of this is that you can develop a new tag
using ``render()`` and producing all the output at once, which is easy to
debug. Then you can rewrite the method as an iterator, rename it to
``iter_render()`` and everything will still work.
It is compulsory, however, to define *either* ``render()`` or ``iter_render()``
in your subclass. If you omit them both, a ``TypeError`` will be raised when
the code is imported.
Configuring the template system in standalone mode Configuring the template system in standalone mode
================================================== ==================================================
@ -1250,4 +1206,3 @@ is of obvious interest.
.. _settings file: ../settings/#using-settings-without-the-django-settings-module-environment-variable .. _settings file: ../settings/#using-settings-without-the-django-settings-module-environment-variable
.. _settings documentation: ../settings/ .. _settings documentation: ../settings/

View File

@ -10,7 +10,7 @@ poll application.
It'll consist of two parts: It'll consist of two parts:
* A public site that lets people view polls and vote in them. * A public site that lets people view polls and vote in them.
* An admin site that lets you add, change and delete poll. * An admin site that lets you add, change and delete polls.
We'll assume you have `Django installed`_ already. You can tell Django is We'll assume you have `Django installed`_ already. You can tell Django is
installed by running the Python interactive interpreter and typing installed by running the Python interactive interpreter and typing

View File

@ -0,0 +1,59 @@
"""
This is a basic model to test saving and loading boolean and date-related
types, which in the past were problematic for some database backends.
"""
from django.db import models
from django.conf import settings
class Donut(models.Model):
name = models.CharField(maxlength=100)
is_frosted = models.BooleanField(default=False)
has_sprinkles = models.NullBooleanField()
baked_date = models.DateField(null=True)
baked_time = models.TimeField(null=True)
consumed_at = models.DateTimeField(null=True)
class Meta:
ordering = ('consumed_at',)
def __str__(self):
return self.name
__test__ = {'API_TESTS': """
# No donuts are in the system yet.
>>> Donut.objects.all()
[]
>>> d = Donut(name='Apple Fritter')
# Ensure we're getting True and False, not 0 and 1
>>> d.is_frosted
False
>>> d.has_sprinkles
>>> d.has_sprinkles = True
>>> d.has_sprinkles == True
True
>>> d.save()
>>> d2 = Donut.objects.all()[0]
>>> d2
<Donut: Apple Fritter>
>>> d2.is_frosted == False
True
>>> d2.has_sprinkles == True
True
>>> import datetime
>>> d2.baked_date = datetime.date(year=1938, month=6, day=4)
>>> d2.baked_time = datetime.time(hour=5, minute=30)
>>> d2.consumed_at = datetime.datetime(year=2007, month=4, day=20, hour=16, minute=19, second=59)
>>> d2.save()
>>> d3 = Donut.objects.all()[0]
>>> d3.baked_date
datetime.date(1938, 6, 4)
>>> d3.baked_time
datetime.time(5, 30)
>>> d3.consumed_at
datetime.datetime(2007, 4, 20, 16, 19, 59)
"""}

View File

@ -121,7 +121,18 @@ u'\xcb'
'<a href="http://short.com/" rel="nofollow">http://short.com/</a>' '<a href="http://short.com/" rel="nofollow">http://short.com/</a>'
>>> urlizetrunc('http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=', 20) >>> urlizetrunc('http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=', 20)
'<a href="http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=" rel="nofollow">http://www.google.co...</a>' '<a href="http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=" rel="nofollow">http://www.google...</a>'
# Check truncating of URIs which are the exact length
>>> uri = 'http://31characteruri.com/test/'
>>> len(uri)
31
>>> urlizetrunc(uri, 31)
'<a href="http://31characteruri.com/test/" rel="nofollow">http://31characteruri.com/test/</a>'
>>> urlizetrunc(uri, 30)
'<a href="http://31characteruri.com/test/" rel="nofollow">http://31characteruri.com/t...</a>'
>>> urlizetrunc(uri, 2)
'<a href="http://31characteruri.com/test/" rel="nofollow">...</a>'
>>> wordcount('') >>> wordcount('')
0 0

View File

@ -1859,8 +1859,12 @@ ValidationError: [u'Enter a valid date.']
>>> f = SplitDateTimeField(required=False) >>> f = SplitDateTimeField(required=False)
>>> f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)]) >>> f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)])
datetime.datetime(2006, 1, 10, 7, 30) datetime.datetime(2006, 1, 10, 7, 30)
>>> f.clean(['2006-01-10', '07:30'])
datetime.datetime(2006, 1, 10, 7, 30)
>>> f.clean(None) >>> f.clean(None)
>>> f.clean('') >>> f.clean('')
>>> f.clean([''])
>>> f.clean(['', ''])
>>> f.clean('hello') >>> f.clean('hello')
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -1877,6 +1881,18 @@ ValidationError: [u'Enter a valid time.']
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid date.'] ValidationError: [u'Enter a valid date.']
>>> f.clean(['2006-01-10', ''])
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid time.']
>>> f.clean(['2006-01-10'])
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid time.']
>>> f.clean(['', '07:30'])
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid date.']
######### #########
# Forms # # Forms #
@ -1958,11 +1974,11 @@ AttributeError: 'Person' object has no attribute 'cleaned_data'
<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li> <li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li> <li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>
>>> print p.as_p() >>> print p.as_p()
<p><ul class="errorlist"><li>This field is required.</li></ul></p> <ul class="errorlist"><li>This field is required.</li></ul>
<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p> <p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
<p><ul class="errorlist"><li>This field is required.</li></ul></p> <ul class="errorlist"><li>This field is required.</li></ul>
<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p> <p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
<p><ul class="errorlist"><li>This field is required.</li></ul></p> <ul class="errorlist"><li>This field is required.</li></ul>
<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p> <p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
If you don't pass any values to the Form's __init__(), or if you pass None, If you don't pass any values to the Form's __init__(), or if you pass None,
@ -2668,7 +2684,7 @@ its field's order in the form.
<li>Last name: <input type="text" name="last_name" value="Lennon" /></li> <li>Last name: <input type="text" name="last_name" value="Lennon" /></li>
<li>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></li> <li>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></li>
>>> print p.as_p() >>> print p.as_p()
<p><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></p> <ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul>
<p>First name: <input type="text" name="first_name" value="John" /></p> <p>First name: <input type="text" name="first_name" value="John" /></p>
<p>Last name: <input type="text" name="last_name" value="Lennon" /></p> <p>Last name: <input type="text" name="last_name" value="Lennon" /></p>
<p>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></p> <p>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></p>

View File

@ -15,6 +15,7 @@ from django.utils.functional import curry
from django.core import serializers from django.core import serializers
from django.db import transaction from django.db import transaction
from django.core import management from django.core import management
from django.conf import settings
from models import * from models import *
try: try:
@ -116,10 +117,13 @@ test_data = [
(data_obj, 31, DateTimeData, None), (data_obj, 31, DateTimeData, None),
(data_obj, 40, EmailData, "hovercraft@example.com"), (data_obj, 40, EmailData, "hovercraft@example.com"),
(data_obj, 41, EmailData, None), (data_obj, 41, EmailData, None),
(data_obj, 42, EmailData, ""),
(data_obj, 50, FileData, 'file:///foo/bar/whiz.txt'), (data_obj, 50, FileData, 'file:///foo/bar/whiz.txt'),
(data_obj, 51, FileData, None), (data_obj, 51, FileData, None),
(data_obj, 52, FileData, ""),
(data_obj, 60, FilePathData, "/foo/bar/whiz.txt"), (data_obj, 60, FilePathData, "/foo/bar/whiz.txt"),
(data_obj, 61, FilePathData, None), (data_obj, 61, FilePathData, None),
(data_obj, 62, FilePathData, ""),
(data_obj, 70, DecimalData, decimal.Decimal('12.345')), (data_obj, 70, DecimalData, decimal.Decimal('12.345')),
(data_obj, 71, DecimalData, decimal.Decimal('-12.345')), (data_obj, 71, DecimalData, decimal.Decimal('-12.345')),
(data_obj, 72, DecimalData, decimal.Decimal('0.0')), (data_obj, 72, DecimalData, decimal.Decimal('0.0')),
@ -146,6 +150,7 @@ test_data = [
(data_obj, 131, PositiveSmallIntegerData, None), (data_obj, 131, PositiveSmallIntegerData, None),
(data_obj, 140, SlugData, "this-is-a-slug"), (data_obj, 140, SlugData, "this-is-a-slug"),
(data_obj, 141, SlugData, None), (data_obj, 141, SlugData, None),
(data_obj, 142, SlugData, ""),
(data_obj, 150, SmallData, 12), (data_obj, 150, SmallData, 12),
(data_obj, 151, SmallData, -12), (data_obj, 151, SmallData, -12),
(data_obj, 152, SmallData, 0), (data_obj, 152, SmallData, 0),
@ -160,8 +165,10 @@ The end."""),
(data_obj, 171, TimeData, None), (data_obj, 171, TimeData, None),
(data_obj, 180, USStateData, "MA"), (data_obj, 180, USStateData, "MA"),
(data_obj, 181, USStateData, None), (data_obj, 181, USStateData, None),
(data_obj, 182, USStateData, ""),
(data_obj, 190, XMLData, "<foo></foo>"), (data_obj, 190, XMLData, "<foo></foo>"),
(data_obj, 191, XMLData, None), (data_obj, 191, XMLData, None),
(data_obj, 192, XMLData, ""),
(generic_obj, 200, GenericData, ['Generic Object 1', 'tag1', 'tag2']), (generic_obj, 200, GenericData, ['Generic Object 1', 'tag1', 'tag2']),
(generic_obj, 201, GenericData, ['Generic Object 2', 'tag2', 'tag3']), (generic_obj, 201, GenericData, ['Generic Object 2', 'tag2', 'tag3']),
@ -241,6 +248,15 @@ The end."""),
# (pk_obj, 790, XMLPKData, "<foo></foo>"), # (pk_obj, 790, XMLPKData, "<foo></foo>"),
] ]
# Because Oracle treats the empty string as NULL, Oracle is expected to fail
# when field.empty_strings_allowed is True and the value is None; skip these
# tests.
if settings.DATABASE_ENGINE == 'oracle':
test_data = [data for data in test_data
if not (data[0] == data_obj and
data[2]._meta.get_field('data').empty_strings_allowed and
data[3] is None)]
# Dynamically create serializer tests to ensure that all # Dynamically create serializer tests to ensure that all
# registered serializers are automatically tested. # registered serializers are automatically tested.
class SerializerTests(unittest.TestCase): class SerializerTests(unittest.TestCase):

View File

@ -725,7 +725,7 @@ class Templates(unittest.TestCase):
'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'), 'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
'url02' : ('{% url regressiontests.templates.views.client_action client.id, action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'), 'url02' : ('{% url regressiontests.templates.views.client_action client.id, action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'url03' : ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'), 'url03' : ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'),
'url04' : ('{% url named-client client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'), 'url04' : ('{% url named.client client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'),
# Failures # Failures
'url-fail01' : ('{% url %}', {}, template.TemplateSyntaxError), 'url-fail01' : ('{% url %}', {}, template.TemplateSyntaxError),

View File

@ -7,5 +7,5 @@ urlpatterns = patterns('',
(r'^$', views.index), (r'^$', views.index),
(r'^client/(\d+)/$', views.client), (r'^client/(\d+)/$', views.client),
(r'^client/(\d+)/(?P<action>[^/]+)/$', views.client_action), (r'^client/(\d+)/(?P<action>[^/]+)/$', views.client_action),
url(r'^named-client/(\d+)/$', views.client, name="named-client"), url(r'^named-client/(\d+)/$', views.client, name="named.client"),
) )